diff --git a/stella/src/build/makefile b/stella/src/build/makefile index 92165fae5..863245ca4 100644 --- a/stella/src/build/makefile +++ b/stella/src/build/makefile @@ -13,51 +13,118 @@ ## See the file "license" for information on usage and redistribution of ## this file, and for a DISCLAIMER OF ALL WARRANTIES. ## -## $Id: makefile,v 1.2 2001-12-30 18:43:30 bwmott Exp $ +## $Id: makefile,v 1.3 2002-01-08 17:11:32 stephena Exp $ ##============================================================================ ##============================================================================ +## Development environment options ## -## The following options are used during compiling. You may need to -## define some of these on the CXXFLAGS line if you're running on an -## "unsupported" system -## -## -DBSPF_BOOL if your C++ compiler doesn't support the bool type -## -## -DSHOW_TIMING if your want some timing information displayed when -## you exit the program -## -## -DDEBUG if you want a 6507 trace written to stdout -## -## -DLINUX_JOYSTICK to include linux joystick driver support -## (requires you to install the joystick kernel module) -## +## The following options are used during compiling. +## Comment a line out to disable that option, remove comment to enable it. ##============================================================================ -CXX = g++ -LD = g++ +### for normal optimization, full warnings +OPTIMIZATIONS = -O -Wall +### for common optimizations, full warnings except unused vars +# OPTIMIZATIONS = -O2 -Wall -Wno-unused +### this should work with all compilers +# OPTIMIZATIONS = +### to get full optimization under gcc/x Intel based OS's.. +# OPTIMIZATIONS = -O3 -mcpu=pentiumpro -march=pentiumpro -Wall -Wno-unused \ +# -funroll-loops -fstrength-reduce -fomit-frame-pointer -ffast-math \ +# -malign-functions=2 -malign-jumps=2 -malign-loops=2 -LDFLAGS = -LDLIBS = -SRC = .. -CORE = $(SRC)/emucore -UI = $(SRC)/ui +### if your C++ compiler doesn't support the bool type +# BSPF_BOOL = 1 + +### if your want some timing information displayed when you exit the program +# SHOW_TIMING = 1 + +### you want a 6507 trace written to stdout +# DEBUG = 1 + +### to include linux joystick driver support in the X11 version +### (requires you to install the joystick kernel module) +# LINUX_JOYSTICK = 1 + +### to include support for saving snapshots in png format +### (X and SDL versions only) +### (requires you to install Imlib2) +# IMLIB2_SNAPSHOT = 1 + +##============================================================================ +## All done, type make to get a list of frontends +## No configurable options below this line ... +##============================================================================ + +CXX = g++ +LD = g++ + +LDFLAGS = +LDLIBS = + +OPTS.X11 = +LIBS.X11 = +CFLAGS.X11 = + +OPTS.SDL = +LIBS.SDL = +CFLAGS.SDL = + +OPTS.DOS = + +SRC = .. +CORE = $(SRC)/emucore +UI = $(SRC)/ui INCLUDES = -I. -I$(CORE) -I$(CORE)/m6502/src -I$(CORE)/m6502/src/bspf/src -CXXFLAGS = -O -Wall $(INCLUDES) $(SYS_INCLUDES) +CXXFLAGS = $(OPTIMIZATIONS) $(INCLUDES) $(SYS_INCLUDES) + +## set the user-defined options +ifdef BSPF_BOOL +OPTS.X11 += -DBSPF_BOOL +OPTS.DOS += -DBSPF_BOOL +endif + +ifdef SHOW_TIMING +OPTS.X11 += -DSHOW_TIMING +OPTS.SDL += -DSHOW_TIMING +OPTS.DOS += -DSHOW_TIMING +endif + +ifdef DEBUG +OPTS.X11 += -DDEBUG +OPTS.SDL += -DDEBUG +OPTS.DOS += -DDEBUG +endif + +ifdef LINUX_JOYSTICK +OPTS.X11 += -DLINUX_JOYSTICK +endif + +ifdef IMLIB2_SNAPSHOT +OPTS.X11 += -DIMLIB2_SNAPSHOT +LIBS.X11 += `imlib2-config --libs` +CFLAGS.X11 += `imlib2-config --cflags` +OPTS.SDL += -DIMLIB2_SNAPSHOT +LIBS.SDL += `imlib2-config --libs` +CFLAGS.SDL += `imlib2-config --cflags` +endif + default: @echo "" @echo "To build Stella type: 'make '" + @echo "You should edit the makefile for extra options" @echo "" - @echo "where is one of:" + @echo " is one of:" @echo "" @echo " dos DOS version using DJGPP" @echo " unix-x Generic Unix X windows version" @echo " linux-x Linux X windows version" - @echo " linux-x-joy Linux X windows version with joystick" + @echo " linux-sdl Linux SDL version" @echo " bsdi-x BSD/OS 4.0 X Windows version" @echo " solaris-x Solaris X windows version" @echo "" @@ -70,53 +137,69 @@ dos: CXX="gcc" \ INCLUDES="$(INCLUDES) -I$(UI)/dos -I$(UI)/sound" \ OPTIONS="-DBSPF_DOS" \ + OPTIONS+="$(OPTS.DOS)" \ LDFLAGS="" \ LDLIBS="" \ OBJS="mainDOS.o PCJoys.o SndDOS.o sbdrv.o TIASound.o" unix-x: - make xstella \ + make stella.x11 \ INCLUDES="$(INCLUDES) -I$(UI)/x11 -I$(UI)/sound" \ SYS_INCLUDES="" \ OPTIONS="-DBSPF_UNIX" \ + OPTIONS+="$(OPTS.X11)" \ LDFLAGS="-L/usr/X11R6/lib" \ + LDFLAGS+="$(CFLAGS.X11)" \ LDLIBS="-lX11 -lXext" \ + LDLIBS+="$(LIBS.X11)" \ OBJS="mainX11.o SndUnix.o" linux-x: - make xstella \ + make stella.x11 \ INCLUDES="$(INCLUDES) -I$(UI)/x11 -I$(UI)/sound" \ SYS_INCLUDES="" \ OPTIONS="-DBSPF_UNIX" \ + OPTIONS+="$(OPTS.X11)" \ LDFLAGS="-L/usr/X11R6/lib" \ + LDFLAGS+="$(CFLAGS.X11)" \ LDLIBS="-lX11 -lXext" \ + LDLIBS+="$(LIBS.X11)" \ OBJS="mainX11.o SndUnix.o" -linux-x-joy: - make xstella \ - INCLUDES="$(INCLUDES) -I$(UI)/x11 -I$(UI)/sound" \ +linux-sdl: + make stella.sdl \ + INCLUDES="$(INCLUDES) -I$(UI)/sdl -I$(UI)/sound" \ SYS_INCLUDES="" \ - OPTIONS="-DBSPF_UNIX -DLINUX_JOYSTICK" \ + OPTIONS="-DBSPF_UNIX" \ + OPTIONS+="$(OPTS.SDL)" \ LDFLAGS="-L/usr/X11R6/lib" \ + LDFLAGS+="$(CFLAGS.SDL)" \ LDLIBS="-lX11 -lXext" \ - OBJS="mainX11.o SndUnix.o" + LDLIBS+="$(LIBS.SDL)" \ + OBJS="mainSDL.o SndUnix.o" bsdi-x: - make xstella \ + make stella.x11 \ INCLUDES="$(INCLUDES) -I$(UI)/x11 -I$(UI)/sound" \ SYS_INCLUDES="-I/usr/X11R6/include" \ OPTIONS="-DBSPF_UNIX" \ + OPTIONS+="$(OPTS.X11)" \ LDFLAGS="-L/usr/X11R6/lib" \ + LDFLAGS+="$(CFLAGS.X11)" \ LDLIBS="-lX11 -lXext" \ + LDLIBS+="$(LIBS.X11)" \ OBJS="mainX11.o SndUnix.o" solaris-x: - make xstella \ + make stella.x11 \ INCLUDES="$(INCLUDES) -I$(UI)/x11 -I$(UI)/sound" \ SYS_INCLUDES="-I/usr/openwin/include" \ OPTIONS="-DBSPF_UNIX" \ + OPTIONS+="$(OPTS.X11)" \ LDFLAGS="-L/usr/openwin/lib" \ + LDFLAGS+="$(CFLAGS.X11)" \ LDLIBS="-lX11 -lXext" \ + LDLIBS+="$(LIBS.X11)" \ OBJS="mainX11.o SndUnix.o" ############################################################################### @@ -140,8 +223,11 @@ stella.exe: $(CORE_OBJS) $(OBJS) del a del a.exe -xstella: $(CORE_OBJS) $(OBJS) - $(LD) -o xstella $(CORE_OBJS) $(OBJS) $(LDFLAGS) $(LDLIBS) +stella.x11: $(CORE_OBJS) $(OBJS) + $(LD) -o stella.x11 $(CORE_OBJS) $(OBJS) $(LDFLAGS) $(LDLIBS) + +stella.sdl: $(CORE_OBJS) $(OBJS) + $(LD) -o stella.sdl $(CORE_OBJS) $(OBJS) $(LDFLAGS) $(LDLIBS) M6502Low.ins: $(CORE)/m6502/src/M6502Low.m4 $(CORE)/m6502/src/M6502.m4 m4 $(CORE)/m6502/src/M6502Low.m4 $(CORE)/m6502/src/M6502.m4 > M6502Low.ins @@ -164,7 +250,7 @@ cleandos: del M6502Hi.ins clean: - rm -f *.o stella xstella stella.exe core + rm -f *.o stella stella.x11 stella.sdl stella.exe core cleanall: clean rm -f DefProps.def M6502Low.ins M6502Hi.ins @@ -294,7 +380,10 @@ TermX11.o: $(UI)/x11/TermX11.cxx $(CXX) -c $(CXXFLAGS) $(OPTIONS) $(UI)/x11/TermX11.cxx mainX11.o: $(UI)/x11/mainX11.cxx - $(CXX) -c $(CXXFLAGS) $(OPTIONS) $(UI)/x11/mainX11.cxx + $(CXX) -c $(CXXFLAGS) $(OPTIONS) $(LDFLAGS) $(UI)/x11/mainX11.cxx + +mainSDL.o: $(UI)/sdl/mainSDL.cxx + $(CXX) -c $(CXXFLAGS) $(OPTIONS) $(LDFLAGS) $(UI)/sdl/mainSDL.cxx SndUnix.o: $(UI)/sound/SndUnix.cxx $(CXX) -c $(CXXFLAGS) $(OPTIONS) $(UI)/sound/SndUnix.cxx diff --git a/stella/src/emucore/Console.cxx b/stella/src/emucore/Console.cxx index a4be0ebb3..f5ecc1bfe 100644 --- a/stella/src/emucore/Console.cxx +++ b/stella/src/emucore/Console.cxx @@ -13,7 +13,7 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: Console.cxx,v 1.1.1.1 2001-12-27 19:54:21 bwmott Exp $ +// $Id: Console.cxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #include @@ -37,31 +37,7 @@ #include "System.hxx" #include "TIA.hxx" -/** - Compare the two strings s1 and s2 ignoring the case of the - characters. Answers true iff they are equal. - - @param s1 The first string to compare - @param s2 The second string to compare - @return true iff the two strings are equal -*/ -static bool compare(const string& s1, const string& s2) -{ - if(s1.length() != s2.length()) - { - return false; - } - - for(uInt32 i = 0; i < s1.length(); ++i) - { - if(tolower(s1[i]) != tolower(s2[i])) - { - return false; - } - } - - return true; -} +#include // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Console::Console(const uInt8* image, uInt32 size, const char* filename, @@ -78,34 +54,10 @@ Console::Console(const uInt8* image, uInt32 size, const char* filename, // Get the MD5 message-digest for the ROM image string md5 = MD5(image, size); - // Search through the properties set to see if some exist for this game - for(uInt32 i = 0; i < propertiesSet.size(); ++i) - { - const Properties& properties = propertiesSet.get(i); - - if(properties.get("Cartridge.MD5") == md5) - { - // We have a match so let's use those properties - myProperties = properties; - break; - } - } - - // If there was no MD5 match then let's search based on filename - if(md5 != myProperties.get("Cartridge.MD5")) - { - for(uInt32 i = 0; i < propertiesSet.size(); ++i) - { - const Properties& properties = propertiesSet.get(i); - - if(compare(properties.get("Cartridge.Filename"), filename)) - { - // We have a match so let's use those properties - myProperties = properties; - break; - } - } - } + // Search for the properties based on MD5 + const Properties* properties = propertiesSet.getMD5(md5); + if(properties) + myProperties = properties; // TODO: At some point I belive we'll need to set the properties' // MD5 value so the user will be able to edit it. @@ -258,4 +210,3 @@ const Properties& Console::defaultProperties() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Properties Console::ourDefaultProperties; - diff --git a/stella/src/emucore/Console.hxx b/stella/src/emucore/Console.hxx index 338f9d3f4..7c12ba1b1 100644 --- a/stella/src/emucore/Console.hxx +++ b/stella/src/emucore/Console.hxx @@ -13,7 +13,7 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: Console.hxx,v 1.1.1.1 2001-12-27 19:54:21 bwmott Exp $ +// $Id: Console.hxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #ifndef CONSOLE_HXX @@ -36,7 +36,7 @@ class System; This class represents the entire game console. @author Bradford W. Mott - @version $Id: Console.hxx,v 1.1.1.1 2001-12-27 19:54:21 bwmott Exp $ + @version $Id: Console.hxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ */ class Console { @@ -156,4 +156,3 @@ class Console static Properties ourDefaultProperties; }; #endif - diff --git a/stella/src/emucore/Props.cxx b/stella/src/emucore/Props.cxx index bec5bb838..fbe33f21a 100644 --- a/stella/src/emucore/Props.cxx +++ b/stella/src/emucore/Props.cxx @@ -13,7 +13,7 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: Props.cxx,v 1.1.1.1 2001-12-27 19:54:23 bwmott Exp $ +// $Id: Props.cxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #include "Props.hxx" @@ -108,32 +108,37 @@ void Properties::load(istream& in) // Empty my property array mySize = 0; + string line, key, value; + uInt32 one, two, three, four, garbage; + // Loop reading properties - for(;;) + while(getline(in, line)) { - // Get the key associated with this property - string key = readQuotedString(in); + // Strip all tabs from the line + while((garbage = line.find("\t")) != string::npos) + line.erase(garbage, 1); - // Make sure the stream is still okay - if(!in) - { - return; - } + // Ignore commented and empty lines + if((line.length() == 0) || (line[0] == ';')) + continue; - // A null key signifies the end of the property list - if(key == "") - { + // End of this record + if(line == "\"\"") break; - } - // Get the value associated with this property - string value = readQuotedString(in); + one = line.find("\"", 0); + two = line.find("\"", one + 1); + three = line.find("\"", two + 1); + four = line.find("\"", three + 1); - // Make sure the stream is still okay - if(!in) - { - return; - } + // Invalid line if it doesn't contain 4 quotes + if((one == string::npos) || (two == string::npos) || + (three == string::npos) || (four == string::npos)) + break; + + // Otherwise get the key and value + key = line.substr(one + 1, two - one - 1); + value = line.substr(three + 1, four - three - 1); // Set the property set(key, value); @@ -255,4 +260,3 @@ void Properties::copy(const Properties& properties) myProperties[i] = properties.myProperties[i]; } } - diff --git a/stella/src/emucore/Props.hxx b/stella/src/emucore/Props.hxx index ff93f17dc..09f9fcbea 100644 --- a/stella/src/emucore/Props.hxx +++ b/stella/src/emucore/Props.hxx @@ -13,7 +13,7 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: Props.hxx,v 1.1.1.1 2001-12-27 19:54:23 bwmott Exp $ +// $Id: Props.hxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #ifndef PROPERTIES_HXX @@ -30,7 +30,7 @@ if the property key is not found in the original property list. @author Bradford W. Mott - @version $Id: Props.hxx,v 1.1.1.1 2001-12-27 19:54:23 bwmott Exp $ + @version $Id: Props.hxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ */ class Properties { @@ -147,4 +147,3 @@ class Properties unsigned int mySize; }; #endif - diff --git a/stella/src/emucore/PropsSet.cxx b/stella/src/emucore/PropsSet.cxx index 25d59d79b..7e0e02f28 100644 --- a/stella/src/emucore/PropsSet.cxx +++ b/stella/src/emucore/PropsSet.cxx @@ -13,7 +13,7 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: PropsSet.cxx,v 1.1.1.1 2001-12-27 19:54:23 bwmott Exp $ +// $Id: PropsSet.cxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #include @@ -21,139 +21,103 @@ #include "PropsSet.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -PropertiesSet::PropertiesSet(const string& key) - : myKey(key) +PropertiesSet::PropertiesSet() { - myCapacity = 16; - myProperties = new Properties[myCapacity]; + root = 0; mySize = 0; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -PropertiesSet::PropertiesSet(const PropertiesSet& p) - : myKey(p.myKey) -{ - myCapacity = p.myCapacity; - myProperties = new Properties[myCapacity]; - mySize = p.mySize; - - // Copy the properties from the other set - for(uInt32 i = 0; i < mySize; ++i) - { - myProperties[i] = p.myProperties[i]; - } -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PropertiesSet::~PropertiesSet() { - delete[] myProperties; + deleteNode(root); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const Properties& PropertiesSet::get(uInt32 i) +Properties* PropertiesSet::getMD5(string md5) { - // Make sure index is within range - assert(i < mySize); + // Make sure tree isn't empty + if(root == 0) + return 0; - return myProperties[i]; + // Else, do a BST search for the node with the given md5 + TreeNode *current = root; + bool found = false; + + while(current) + { + string currentMd5 = current->props->get("Cartridge.MD5"); + + if(currentMd5 == md5) + { + found = true; + break; + } + else + { + if(md5 < currentMd5) + current = current->left; + else + current = current->right; + } + } + + if(found) + return current->props; + else + return 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void PropertiesSet::insert(const Properties& properties) { - uInt32 i; - uInt32 j; - - // Get the key of the properties - string name = properties.get(myKey); - - // See if the key already exists (we could use a binary search here...) - for(i = 0; i < mySize; ++i) - { - if(name == myProperties[i].get(myKey)) - { - // Copy the properties which are being inserted - myProperties[i] = properties; - return; - } - } - - // See if the properties array needs to be resized - if(mySize == myCapacity) - { - Properties* newProperties = new Properties[myCapacity *= 2]; - - for(i = 0; i < mySize; ++i) - { - newProperties[i] = myProperties[i]; - } - - delete[] myProperties; - - myProperties = newProperties; - } - - // Find the correct place to insert the properties at - for(i = 0; (i < mySize) && (myProperties[i].get(myKey) < name); ++i); - - // Okay, make room for the properties - for(j = mySize; j > i; --j) - { - myProperties[j] = myProperties[j - 1]; - } - - // Now, put the properties in the array - myProperties[i] = properties; - - ++mySize; + insertNode(root, properties); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 PropertiesSet::size() const +void PropertiesSet::insertNode(TreeNode* &t, const Properties& properties) { - return mySize; + if(t) + { + string md5 = properties.get("Cartridge.MD5"); + string currentMd5 = t->props->get("Cartridge.MD5"); + + if(md5 < currentMd5) + insertNode(t->left, properties); + else if(md5 > currentMd5) + insertNode(t->right, properties); + else + { + delete t->props; + t->props = new Properties(properties); + } + } + else + { + t = new TreeNode; + t->props = new Properties(properties); + t->left = 0; + t->right = 0; + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PropertiesSet::erase(uInt32 i) +void PropertiesSet::deleteNode(TreeNode *node) { - // Make sure index is within range - assert(i < mySize); - - for(uInt32 j = i + 1; j < mySize; ++j) + if(node) { - myProperties[j - 1] = myProperties[j]; + deleteNode(node->left); + deleteNode(node->right); + delete node->props; } - - --mySize; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void PropertiesSet::load(istream& in, const Properties* defaults) { - // Empty my properties array - mySize = 0; - // Loop reading properties for(;;) { - // Read char's until we see a quote as the next char or EOF is reached - while(in && (in.peek() != '"')) - { - char c; - in.get(c); - - // If we see the comment character then ignore the line - if(c == ';') - { - while(in && (c != '\n')) - { - in.get(c); - } - } - } - // Make sure the stream is still good or we're done if(!in) { @@ -171,36 +135,27 @@ void PropertiesSet::load(istream& in, const Properties* defaults) } } } - + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void PropertiesSet::save(ostream& out) { - // Write each of the properties out - for(uInt32 i = 0; i < mySize; ++i) + saveNode(out, root); +} + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void PropertiesSet::saveNode(ostream& out, TreeNode *node) +{ + if(node) { - myProperties[i].save(out); + saveNode(out, node->left); + saveNode(out, node->right); + node->props->save(out); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -PropertiesSet& PropertiesSet::operator = (const PropertiesSet& p) +uInt32 PropertiesSet::size() const { - if(this != &p) - { - delete[] myProperties; - - myKey = p.myKey; - myCapacity = p.myCapacity; - myProperties = new Properties[myCapacity]; - mySize = p.mySize; - - // Copy the properties from the other set - for(uInt32 i = 0; i < mySize; ++i) - { - myProperties[i] = p.myProperties[i]; - } - } - - return *this; + return mySize; } - diff --git a/stella/src/emucore/PropsSet.hxx b/stella/src/emucore/PropsSet.hxx index b990d5862..32941b85e 100644 --- a/stella/src/emucore/PropsSet.hxx +++ b/stella/src/emucore/PropsSet.hxx @@ -13,79 +13,51 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: PropsSet.hxx,v 1.1.1.1 2001-12-27 19:54:23 bwmott Exp $ +// $Id: PropsSet.hxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #ifndef PROPERTIESSET_HXX #define PROPERTIESSET_HXX -class Properties; +#include #include "bspf.hxx" #include "Props.hxx" +class Properties; + /** - This class maintains a sorted collection of properties. Upon - construction one property is distinguished as the key for sorting. + This class maintains a sorted collection of properties. The objects + are maintained in a binary search tree sorted by md5, since this is + the attribute most likely to be present in each entry in stella.pro + and least likely to change. A change in MD5 would mean a change in + the game rom image (essentially a different game) and this would + necessitate a new entry in the stella.pro file anyway. - @author Bradford W. Mott - @version $Id: PropsSet.hxx,v 1.1.1.1 2001-12-27 19:54:23 bwmott Exp $ + @author Stephen Anthony */ class PropertiesSet { public: /** - Create an empty properties set object using the specified - property as the key for sorting. - - @param key The property to use as the key + Create an empty properties set object using the md5 as the + key to the BST. */ - PropertiesSet(const string& key); - - /** - Create a properties set object by copying another one - - @param set The properties set to copy - */ - PropertiesSet(const PropertiesSet& set); + PropertiesSet(); /** Destructor */ virtual ~PropertiesSet(); - public: /** - Get the i'th properties from the set + Get the property from the set with the given MD5. - @param i The index of the properties to get - @return The properties stored at the i'th location + @param md5 The md5 of the property to get + @return The property with the given MD5, or 0 if not found */ - const Properties& get(uInt32 i); + Properties* getMD5(string md5); - /** - Insert the properties into the set. If a duplicate is inserted - the old properties are overwritten with the new ones. - - @param properties The collection of properties - */ - void insert(const Properties& properties); - - /** - Get the number of properties in the collection. - - @return The number of properties in the collection - */ - uInt32 size() const; - - /** - Erase the i'th properties from the collection. - - @param i The profile index - */ - void erase(uInt32 i); - - public: /** Load properties from the specified input stream. Use the given defaults properties as the defaults for any properties loaded. @@ -102,27 +74,61 @@ class PropertiesSet */ void save(ostream& out); - public: /** - Overloaded assignment operator + Get the number of properties in the collection. - @param propertiesSet The properties set to set myself equal to - @return Myself after assignment has taken place + @return The number of properties in the collection */ - PropertiesSet& operator = (const PropertiesSet& propertiesSet); + uInt32 size() const; private: + + struct TreeNode + { + Properties *props; + TreeNode *left; + TreeNode *right; + + }; + + /** + Insert the properties into the set. If a duplicate is inserted + the old properties are overwritten with the new ones. + + @param properties The collection of properties + */ + void insert(const Properties& properties); + + /** + Insert a node in the bst, keeping the tree sorted. + + @param node The current subroot of the tree + @param properties The collection of properties + */ + void insertNode(TreeNode* &node, const Properties& properties); + + /** + Deletes a node from the bst. Does not preserve bst sorting. + + @param node The current subroot of the tree + */ + void deleteNode(TreeNode *node); + + /** + Save current node properties to the specified output stream + + @param out The output stream to use + @param node The current subroot of the tree + */ + void saveNode(ostream& out, TreeNode *node); + + // The root of the BST + TreeNode* root; + // Property to use as the key string myKey; - // Pointer to a dynamically allocated array of properties - Properties* myProperties; - - // Current capacity of the properties array - unsigned int myCapacity; - - // The size of the properties array (i.e. the number of properties in it) - unsigned int mySize; + // The size of the properties bst (i.e. the number of properties in it) + uInt32 mySize; }; #endif - diff --git a/stella/src/ui/sound/OSS.c b/stella/src/ui/sound/OSS.c index 5ca749971..0e7c87af4 100644 --- a/stella/src/ui/sound/OSS.c +++ b/stella/src/ui/sound/OSS.c @@ -13,7 +13,7 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: OSS.c,v 1.1.1.1 2001-12-27 19:54:35 bwmott Exp $ +// $Id: OSS.c,v 1.2 2002-01-08 17:11:32 stephena Exp $ //==========================================================================*/ /** @@ -21,7 +21,7 @@ Open Sound System (OSS) API. @author Bradford W. Mott - @version $Id: OSS.c,v 1.1.1.1 2001-12-27 19:54:35 bwmott Exp $ + @version $Id: OSS.c,v 1.2 2002-01-08 17:11:32 stephena Exp $ */ #include @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef __FreeBSD__ #include @@ -47,11 +48,17 @@ */ unsigned long computeFragmentSize(int sampleRate); +/* Mixer function prototypes */ +void openMixer(int changeVolume); +void closeMixer(); + +/* dsp and mixer file descriptors */ +int fd, mixer_fd; +int originalVolume; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ int main(int argc, char* argv[]) { - int fd; int numberAndSizeOfFragments; int fragmentSize; unsigned char* fragmentBuffer; @@ -59,6 +66,16 @@ int main(int argc, char* argv[]) int format; int stereo; int mute = 0; + int newVolume = 75; + + if(argc == 3) /* check to see if volume has been given */ + { + if(!strncmp(argv[1], "-volume", 7)) + { + if((atoi(argv[2]) >= 0) && (atoi(argv[2]) <= 100)) + newVolume = atoi(argv[2]); + } + } /* Open the sound device for writing */ if((fd = open("/dev/dsp", O_WRONLY, 0)) == -1) @@ -117,6 +134,8 @@ int main(int argc, char* argv[]) /* Allocate fragment buffer */ fragmentBuffer = (unsigned char*)malloc(fragmentSize); + /* Now open the mixer for changing the volume */ + openMixer(newVolume); /* Initialize the TIA Sound Library */ Tia_sound_init(31400, sampleRate); @@ -176,6 +195,8 @@ int main(int argc, char* argv[]) case 6: /* Quit */ close(fd); + free(fragmentBuffer); + closeMixer(); return 1; break; @@ -225,3 +246,45 @@ unsigned long computeFragmentSize(int sampleRate) return 8; } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +void openMixer(int changeVolume) +{ + int volume; + + if((mixer_fd = open("/dev/mixer", O_RDWR, 0)) == -1) + { + printf("stella-sound: Unable to open /dev/mixer device!\n"); + mixer_fd = 0; + return; + } + + volume = 0; + if(ioctl(mixer_fd, MIXER_READ(SOUND_MIXER_PCM), &originalVolume) == -1) + { + printf("stella-sound: Unable to read mixer settings!\n"); + close(mixer_fd); + mixer_fd = 0; + return; + } + + volume = changeVolume | (changeVolume << 8); + if(ioctl(mixer_fd, MIXER_WRITE(SOUND_MIXER_PCM), &volume) == -1) + { + printf("stella-sound: Unable to set new volume!\n"); + close(mixer_fd); + mixer_fd = 0; + return; + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +void closeMixer() +{ + if(mixer_fd) + { + if(ioctl(mixer_fd, MIXER_WRITE(SOUND_MIXER_PCM), &originalVolume) == -1) + printf("stella-sound: Unable to set original volume!\n"); + + close(mixer_fd); + } +} diff --git a/stella/src/ui/sound/SndUnix.cxx b/stella/src/ui/sound/SndUnix.cxx index aad327197..05690d698 100644 --- a/stella/src/ui/sound/SndUnix.cxx +++ b/stella/src/ui/sound/SndUnix.cxx @@ -13,18 +13,19 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: SndUnix.cxx,v 1.1.1.1 2001-12-27 19:54:35 bwmott Exp $ +// $Id: SndUnix.cxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #include #include #include #include +#include #include "SndUnix.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundUnix::SoundUnix() +SoundUnix::SoundUnix(int volume) : myDisabled(false), myMute(false) { @@ -55,7 +56,9 @@ SoundUnix::SoundUnix() close(pfd[1]); // Execute the stella-sound server - if(execlp("stella-sound", "stella-sound", (char*)0)) + char vol[3]; + sprintf(vol, "%d", volume); + if(execlp("stella-sound", "stella-sound", "-volume", vol, (char*)0)) { exit(1); } @@ -87,9 +90,6 @@ SoundUnix::~SoundUnix() unsigned char command = 0xC0; write(myFd, &command, 1); - // Kill the sound server - kill(myProcessId, SIGHUP); - // Close descriptors close(myFd); } @@ -231,4 +231,3 @@ void SoundUnix::mute(bool state) // Send sound command to the stella-sound process write(myFd, &command, 1); } - diff --git a/stella/src/ui/sound/SndUnix.hxx b/stella/src/ui/sound/SndUnix.hxx index fd6aef8d6..506188d24 100644 --- a/stella/src/ui/sound/SndUnix.hxx +++ b/stella/src/ui/sound/SndUnix.hxx @@ -13,7 +13,7 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: SndUnix.hxx,v 1.1.1.1 2001-12-27 19:54:35 bwmott Exp $ +// $Id: SndUnix.hxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #ifndef SOUNDUNIX_HXX @@ -30,7 +30,7 @@ "stella-sound" process is done through a pipe. @author Bradford W. Mott - @version $Id: SndUnix.hxx,v 1.1.1.1 2001-12-27 19:54:35 bwmott Exp $ + @version $Id: SndUnix.hxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ */ class SoundUnix : public Sound { @@ -38,7 +38,7 @@ class SoundUnix : public Sound /** Create a new sound object */ - SoundUnix(); + SoundUnix(int volume); /** Destructor @@ -84,4 +84,3 @@ class SoundUnix : public Sound uInt8 myAUDV1; }; #endif - diff --git a/stella/src/ui/x11/mainX11.cxx b/stella/src/ui/x11/mainX11.cxx index 5f31066f6..2881f3e49 100644 --- a/stella/src/ui/x11/mainX11.cxx +++ b/stella/src/ui/x11/mainX11.cxx @@ -13,25 +13,27 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: mainX11.cxx,v 1.1.1.1 2001-12-27 19:54:36 bwmott Exp $ +// $Id: mainX11.cxx,v 1.2 2002-01-08 17:11:32 stephena Exp $ //============================================================================ #include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include - -#include +#include +#include #include #include #include #include #include +#include #include "bspf.hxx" #include "Console.hxx" @@ -42,6 +44,10 @@ #include "SndUnix.hxx" #include "System.hxx" +#ifdef IMLIB2_SNAPSHOT + #include +#endif + #ifdef LINUX_JOYSTICK #include #include @@ -59,16 +65,111 @@ int theScreen; Visual* theVisual; Window theWindow; Colormap thePrivateColormap; -bool theUsePrivateColormapFlag = false; +Cursor normalCursor; +Cursor blankCursor; // A graphic context for each of the 2600's colors GC theGCTable[256]; -// Enumeration of the possible window sizes -enum WindowSize { Small = 1, Medium = 2, Large = 3 }; +// function prototypes +void setupDisplay(); +void setupJoystick(); +void createCursors(); -// Indicates the current size of the window -WindowSize theWindowSize = Medium; +void updateDisplay(MediaSource& mediaSource); +void handleEvents(); + +void doQuit(); +void resizeWindow(int mode); +void centerWindow(); +void showCursor(bool show); +void grabMouse(bool grab); +void toggleFullscreen(); +void takeSnapshot(); +void togglePause(); +void setupProperties(PropertiesSet& set); +void handleCommandLineArguments(int argc, char* argv[]); +void handleRCFile(); +void parseRCOptions(istream& in); +void usage(); +uInt32 maxWindowSizeForScreen(); + +// Global event stuff +struct Switches +{ + KeySym scanCode; + Event::Type eventCode; +}; + +static Switches list[] = { + { XK_1, Event::KeyboardZero1 }, + { XK_2, Event::KeyboardZero2 }, + { XK_3, Event::KeyboardZero3 }, + { XK_q, Event::KeyboardZero4 }, + { XK_w, Event::KeyboardZero5 }, + { XK_e, Event::KeyboardZero6 }, + { XK_a, Event::KeyboardZero7 }, + { XK_s, Event::KeyboardZero8 }, + { XK_d, Event::KeyboardZero9 }, + { XK_z, Event::KeyboardZeroStar }, + { XK_x, Event::KeyboardZero0 }, + { XK_c, Event::KeyboardZeroPound }, + + { XK_8, Event::KeyboardOne1 }, + { XK_9, Event::KeyboardOne2 }, + { XK_0, Event::KeyboardOne3 }, + { XK_i, Event::KeyboardOne4 }, + { XK_o, Event::KeyboardOne5 }, + { XK_p, Event::KeyboardOne6 }, + { XK_k, Event::KeyboardOne7 }, + { XK_l, Event::KeyboardOne8 }, + { XK_semicolon, Event::KeyboardOne9 }, + { XK_comma, Event::KeyboardOneStar }, + { XK_period, Event::KeyboardOne0 }, + { XK_slash, Event::KeyboardOnePound }, + + { XK_Down, Event::JoystickZeroDown }, + { XK_Up, Event::JoystickZeroUp }, + { XK_Left, Event::JoystickZeroLeft }, + { XK_Right, Event::JoystickZeroRight }, + { XK_space, Event::JoystickZeroFire }, + { XK_Return, Event::JoystickZeroFire }, + { XK_z, Event::BoosterGripZeroTrigger }, + { XK_x, Event::BoosterGripZeroBooster }, + + { XK_w, Event::JoystickZeroUp }, + { XK_s, Event::JoystickZeroDown }, + { XK_a, Event::JoystickZeroLeft }, + { XK_d, Event::JoystickZeroRight }, + { XK_Tab, Event::JoystickZeroFire }, + { XK_1, Event::BoosterGripZeroTrigger }, + { XK_2, Event::BoosterGripZeroBooster }, + + { XK_l, Event::JoystickOneDown }, + { XK_o, Event::JoystickOneUp }, + { XK_k, Event::JoystickOneLeft }, + { XK_semicolon, Event::JoystickOneRight }, + { XK_j, Event::JoystickOneFire }, + { XK_n, Event::BoosterGripOneTrigger }, + { XK_m, Event::BoosterGripOneBooster }, + + { XK_F1, Event::ConsoleSelect }, + { XK_F2, Event::ConsoleReset }, + { XK_F3, Event::ConsoleColor }, + { XK_F4, Event::ConsoleBlackWhite }, + { XK_F5, Event::ConsoleLeftDifficultyA }, + { XK_F6, Event::ConsoleLeftDifficultyB }, + { XK_F7, Event::ConsoleRightDifficultyA }, + { XK_F8, Event::ConsoleRightDifficultyB } +}; + +static Event keyboardEvent; + +// Default window size of 0, meaning it must be set somewhere else +uInt32 theWindowSize = 0; + +// Indicates the maximum window size for the current screen +uInt32 theMaxWindowSize; // Indicates the width and height of the game display based on properties uInt32 theHeight; @@ -80,11 +181,41 @@ Console* theConsole; // Event object to use Event theEvent; +// Indicates if the user wants to quit +bool theQuitIndicator = false; + +// Indicates if the emulator should be paused +bool thePauseIndicator = false; + // Indicates if the entire frame should be redrawn bool theRedrawEntireFrameFlag = true; -// Indicates if the user wants to quit -bool theQuitIndicator = false; +// Indicates whether to use fullscreen +bool theUseFullScreenFlag = false; + +// Indicates whether mouse can leave the game window +bool theGrabMouseFlag = false; + +// Indicates whether to center the game window +bool theCenterWindowFlag = false; + +// Indicates whether to show some game info on program exit +bool theShowFpsFlag = false; + +// Indicates whether to show cursor in the game window +bool theHideCursorFlag = false; + +// Indicates whether the game is currently in fullscreen +bool isFullscreen = false; + +// Indicates whether to allocate colors from a private color map +bool theUsePrivateColormapFlag = false; + +// Indicates whether the window is currently centered +bool isCentered = false; + +// Indicates what the desired volume is +uInt32 theDesiredVolume = 75; // Indicates what the desired frame rate is uInt32 theDesiredFrameRate = 60; @@ -101,30 +232,8 @@ uInt32 thePaddleMode = 0; This routine should be called once the console is created to setup the X11 connection and open a window for us to use */ -void setupX11() +void setupDisplay() { - // Get the desired width and height of the display - theHeight = theConsole->mediaSource().height(); - theWidth = theConsole->mediaSource().width(); - - // Figure out the desired size of the window - int width = theWidth; - int height = theHeight; - if(theWindowSize == Small) - { - width *= 2; - } - else if(theWindowSize == Medium) - { - width *= 4; - height *= 2; - } - else - { - width *= 6; - height *= 3; - } - // Open a connection to the X server if(theDisplayName == "") theDisplay = XOpenDisplay(NULL); @@ -142,10 +251,44 @@ void setupX11() theVisual = DefaultVisual(theDisplay, theScreen); Window rootWindow = RootWindow(theDisplay, theScreen); + // Get the desired width and height of the display + theWidth = theConsole->mediaSource().width(); + theHeight = theConsole->mediaSource().height(); + + // Get the maximum size of a window for THIS screen + // Must be called after display and screen are known, as well as + // theWidth and theHeight + theMaxWindowSize = maxWindowSizeForScreen(); + + // If theWindowSize is not 0, then it must have been set on the commandline + // Now we check to see if it is within bounds + if(theWindowSize != 0) + { + if(theWindowSize < 1) + theWindowSize = 1; + else if(theWindowSize > theMaxWindowSize) + theWindowSize = theMaxWindowSize; + } + else // theWindowSize hasn't been set so we do the default + { + if(theMaxWindowSize < 2) + theWindowSize = 1; + else + theWindowSize = 2; + } + + // Figure out the desired size of the window + int width = theWidth * 2 * theWindowSize; + int height = theHeight * theWindowSize; + theWindow = XCreateSimpleWindow(theDisplay, rootWindow, 0, 0, width, height, CopyFromParent, CopyFromParent, BlackPixel(theDisplay, theScreen)); + // Create normal and blank cursors. This must be called AFTER + // theDisplay and theWindow are defined + createCursors(); + // If requested create a private colormap for the window if(theUsePrivateColormapFlag) { @@ -163,7 +306,7 @@ void setupX11() sprintf(name, "Stella: \"%s\"", theConsole->properties().get("Cartridge.Name").c_str()); - XSetStandardProperties(theDisplay, theWindow, name, name, None, 0, 0, &hints); + XSetStandardProperties(theDisplay, theWindow, name, "xstella", None, 0, 0, &hints); // Allocate colors in the default colormap const uInt32* palette = theConsole->mediaSource().palette(); @@ -196,13 +339,17 @@ void setupX11() XSelectInput(theDisplay, theWindow, ExposureMask); XMapWindow(theDisplay, theWindow); + // Center the window if centering is selected and not fullscreen + if(theCenterWindowFlag && !theUseFullScreenFlag) + centerWindow(); + XEvent event; do { XNextEvent(theDisplay, &event); } while (event.type != Expose); - uInt32 mask = ExposureMask | KeyPressMask | KeyReleaseMask; + uInt32 mask = ExposureMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask; // If we're using the mouse for paddle emulation then enable mouse events if(((theConsole->properties().get("Controller.Left") == "Paddles") || @@ -212,7 +359,38 @@ void setupX11() mask |= (PointerMotionMask | ButtonPressMask | ButtonReleaseMask); } + // Keep mouse in game window if grabmouse is selected + grabMouse(theGrabMouseFlag); + + // Show or hide the cursor depending on the 'hidecursor' argument + showCursor(!theHideCursorFlag); + XSelectInput(theDisplay, theWindow, mask); + + // If imlib snapshots are enabled, set up some imlib stuff +#ifdef IMLIB2_SNAPSHOT + imlib_context_set_display(theDisplay); + imlib_context_set_drawable(theWindow); + imlib_context_set_visual(DefaultVisual(theDisplay, theScreen)); + + if(theUsePrivateColormapFlag) + imlib_context_set_colormap(thePrivateColormap); + else + imlib_context_set_colormap(DefaultColormap(theDisplay, theScreen)); +#endif +} + +/** + This routine should be called once setupDisplay is called + to create the joystick stuff +*/ +void setupJoystick() +{ +#ifdef LINUX_JOYSTICK + // Open the joystick devices + theLeftJoystickFd = open("/dev/js0", O_RDONLY | O_NONBLOCK); + theRightJoystickFd = open("/dev/js1", O_RDONLY | O_NONBLOCK); +#endif } /** @@ -357,81 +535,11 @@ void updateDisplay(MediaSource& mediaSource) */ void handleEvents() { - struct Switches - { - KeySym scanCode; - Event::Type eventCode; - }; - - static Switches list[] = { - { XK_1, Event::KeyboardZero1 }, - { XK_2, Event::KeyboardZero2 }, - { XK_3, Event::KeyboardZero3 }, - { XK_q, Event::KeyboardZero4 }, - { XK_w, Event::KeyboardZero5 }, - { XK_e, Event::KeyboardZero6 }, - { XK_a, Event::KeyboardZero7 }, - { XK_s, Event::KeyboardZero8 }, - { XK_d, Event::KeyboardZero9 }, - { XK_z, Event::KeyboardZeroStar }, - { XK_x, Event::KeyboardZero0 }, - { XK_c, Event::KeyboardZeroPound }, - - { XK_8, Event::KeyboardOne1 }, - { XK_9, Event::KeyboardOne2 }, - { XK_0, Event::KeyboardOne3 }, - { XK_i, Event::KeyboardOne4 }, - { XK_o, Event::KeyboardOne5 }, - { XK_p, Event::KeyboardOne6 }, - { XK_k, Event::KeyboardOne7 }, - { XK_l, Event::KeyboardOne8 }, - { XK_semicolon, Event::KeyboardOne9 }, - { XK_comma, Event::KeyboardOneStar }, - { XK_period, Event::KeyboardOne0 }, - { XK_slash, Event::KeyboardOnePound }, - - { XK_Down, Event::JoystickZeroDown }, - { XK_Up, Event::JoystickZeroUp }, - { XK_Left, Event::JoystickZeroLeft }, - { XK_Right, Event::JoystickZeroRight }, - { XK_space, Event::JoystickZeroFire }, - { XK_Return, Event::JoystickZeroFire }, - { XK_z, Event::BoosterGripZeroTrigger }, - { XK_x, Event::BoosterGripZeroBooster }, - - { XK_w, Event::JoystickZeroUp }, - { XK_s, Event::JoystickZeroDown }, - { XK_a, Event::JoystickZeroLeft }, - { XK_d, Event::JoystickZeroRight }, - { XK_Tab, Event::JoystickZeroFire }, - { XK_1, Event::BoosterGripZeroTrigger }, - { XK_2, Event::BoosterGripZeroBooster }, - - { XK_l, Event::JoystickOneDown }, - { XK_o, Event::JoystickOneUp }, - { XK_k, Event::JoystickOneLeft }, - { XK_semicolon, Event::JoystickOneRight }, - { XK_j, Event::JoystickOneFire }, - { XK_n, Event::BoosterGripOneTrigger }, - { XK_m, Event::BoosterGripOneBooster }, - - { XK_F1, Event::ConsoleSelect }, - { XK_F2, Event::ConsoleReset }, - { XK_F3, Event::ConsoleColor }, - { XK_F4, Event::ConsoleBlackWhite }, - { XK_F5, Event::ConsoleLeftDifficultyA }, - { XK_F6, Event::ConsoleLeftDifficultyB }, - { XK_F7, Event::ConsoleRightDifficultyA }, - { XK_F8, Event::ConsoleRightDifficultyB } - }; - - static Event keyboardEvent; - XEvent event; while(XCheckWindowEvent(theDisplay, theWindow, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | - ButtonReleaseMask | PointerMotionMask, &event)) + ButtonReleaseMask | PointerMotionMask | PropertyChangeMask, &event)) { char buffer[20]; KeySym key; @@ -442,43 +550,41 @@ void handleEvents() XLookupString(&event.xkey, buffer, 20, &key, &compose); if((key == XK_Escape) && (event.type == KeyPress)) { - theQuitIndicator = true; + doQuit(); } else if((key == XK_equal) && (event.type == KeyPress)) { - if(theWindowSize == Small) - theWindowSize = Medium; - else if(theWindowSize == Medium) - theWindowSize = Large; - else - theWindowSize = Small; - - // Figure out the desired size of the window - int width = theWidth; - int height = theHeight; - if(theWindowSize == Small) + resizeWindow(1); + } + else if((key == XK_minus) && (event.type == KeyPress)) + { + resizeWindow(0); + } + else if((key == XK_F11) && (event.type == KeyPress)) + { + toggleFullscreen(); + } + else if((key == XK_F12) && (event.type == KeyPress)) + { + takeSnapshot(); + } + else if((key == XK_Pause) && (event.type == KeyPress)) + { + togglePause(); + } + else if((key == XK_g) && (event.type == KeyPress)) + { + // don't turn off grabmouse in fullscreen or we may lose the keyboard + if(!isFullscreen) { - width *= 2; + theGrabMouseFlag = !theGrabMouseFlag; + grabMouse(theGrabMouseFlag); } - else if(theWindowSize == Medium) - { - width *= 4; - height *= 2; - } - else - { - width *= 6; - height *= 3; - } - - XSizeHints hints; - hints.flags = PSize | PMinSize | PMaxSize; - hints.min_width = hints.max_width = hints.width = width; - hints.min_height = hints.max_height = hints.height = height; - XSetStandardProperties(theDisplay, theWindow, 0, 0, None, 0, 0, &hints); - XResizeWindow(theDisplay, theWindow, width, height); - - theRedrawEntireFrameFlag = true; + } + else if((key == XK_h) && (event.type == KeyPress)) + { + theHideCursorFlag = !theHideCursorFlag; + showCursor(!theHideCursorFlag); } else { @@ -497,22 +603,10 @@ void handleEvents() else if(event.type == MotionNotify) { Int32 resistance = 0; + uInt32 width = theWidth * 2 * theWindowSize; - if(theWindowSize == Small) - { - int x = (theWidth * 2) - event.xmotion.x; - resistance = (Int32)((1000000.0 * x) / (theWidth * 2)); - } - else if(theWindowSize == Medium) - { - int x = (theWidth * 4) - event.xmotion.x; - resistance = (Int32)((1000000.0 * x) / (theWidth * 4)); - } - else - { - int x = (theWidth * 6) - event.xmotion.x; - resistance = (Int32)((1000000.0 * x) / (theWidth * 6)); - } + int x = width - event.xmotion.x; + resistance = (Int32)((1000000.0 * x) / width); // Now, set the event of the correct paddle to the calculated resistance if(thePaddleMode == 0) @@ -550,6 +644,20 @@ void handleEvents() { theRedrawEntireFrameFlag = true; } + else if(event.type == PropertyNotify) + { + XWindowAttributes attr; + XGetWindowAttributes(theDisplay, theWindow, &attr); + + if(attr.map_state == IsUnmapped) + { + if(!thePauseIndicator) + { + togglePause(); + cerr << "todo: Pause on minimize.\n"; + } + } + } } #ifdef LINUX_JOYSTICK @@ -680,12 +788,284 @@ void handleEvents() #endif } +/** + This routine is called when the program is about to quit. +*/ +void doQuit() +{ + theQuitIndicator = true; +} + +/** + This routine is called when the user wants to resize the window. + A '1' argument indicates that the window should increase in size, while '0' + indicates that the windows should decrease in size. + Can't resize in fullscreen mode. Will only resize up to the maximum size + for the '-winsize' argument. +*/ +void resizeWindow(int mode) +{ + if(isFullscreen) + return; + + if(mode == 1) // increase size + { + if(theWindowSize == theMaxWindowSize) + theWindowSize = 1; + else + theWindowSize++; + } + else // decrease size + { + if(theWindowSize == 1) + theWindowSize = theMaxWindowSize; + else + theWindowSize--; + } + + // Figure out the desired size of the window + int width = theWidth * 2 * theWindowSize; + int height = theHeight * theWindowSize; + + XWindowChanges wc; + wc.width = width; + wc.height = height; + XConfigureWindow(theDisplay, theWindow, CWWidth | CWHeight, &wc); + + XSizeHints hints; + hints.flags = PSize | PMinSize | PMaxSize; + hints.min_width = hints.max_width = hints.width = width; + hints.min_height = hints.max_height = hints.height = height; + XSetWMNormalHints(theDisplay, theWindow, &hints); + + theRedrawEntireFrameFlag = true; + + // A resize may mean that the window is no longer centered + isCentered = false; + + if(theCenterWindowFlag) + centerWindow(); +} + +/** + Centers the game window onscreen. +*/ +void centerWindow() +{ + if(isFullscreen || isCentered) + return; + + int x, y, w, h; + + w = DisplayWidth(theDisplay, theScreen); + h = DisplayHeight(theDisplay, theScreen); + x = (w - (theWidth * 2 * theWindowSize)) / 2; + y = (h - (theHeight * theWindowSize)) / 2; + + XWindowChanges wc; + wc.x = x; + wc.y = y; + XConfigureWindow(theDisplay, theWindow, CWX | CWY, &wc); + + XSizeHints hints; + hints.flags = PPosition; + hints.x = x; + hints.y = y; + XSetWMNormalHints(theDisplay, theWindow, &hints); + + isCentered = true; +} + +/** + Toggles between fullscreen and window mode. Grabmouse and hidecursor + activated when in fullscreen mode. +*/ +void toggleFullscreen() +{ +#if 0 + // code not yet written +#else + cerr << "Fullscreen mode not supported.\n"; +#endif +} + +/** + Toggles pausing of the emulator +*/ +void togglePause() +{ +// todo: implement pause functionality + + if(thePauseIndicator) // emulator is already paused so continue + { + thePauseIndicator = false; + } + else // we want to pause the game + { + thePauseIndicator = true; + } +} + +/** + Shows or hides the cursor based on the given boolean value. +*/ +void showCursor(bool show) +{ + if(!normalCursor || !blankCursor) + return; + + if(show) + XDefineCursor(theDisplay, theWindow, normalCursor); + else + XDefineCursor(theDisplay, theWindow, blankCursor); +} + +/** + Grabs or ungrabs the mouse based on the given boolean value. +*/ +void grabMouse(bool grab) +{ + if(grab) + { + int result = XGrabPointer(theDisplay, theWindow, True, 0, GrabModeAsync, + GrabModeAsync, theWindow, None, CurrentTime); + + if(result != GrabSuccess) + cerr << "Couldn't grab mouse!!\n"; + } + else + XUngrabPointer(theDisplay, CurrentTime); +} + +/** + Creates a normal and blank cursor which may be needed if hidecursor or + fullscreen is toggled. +*/ +void createCursors() +{ + if(!theDisplay || !theWindow) + return; + + Pixmap cursormask; + XGCValues xgc; + XColor dummycolour; + GC gc; + + // First create the blank cursor + cursormask = XCreatePixmap(theDisplay, theWindow, 1, 1, 1); + xgc.function = GXclear; + gc = XCreateGC(theDisplay, cursormask, GCFunction, &xgc); + XFillRectangle(theDisplay, cursormask, gc, 0, 0, 1, 1); + dummycolour.pixel = 0; + dummycolour.red = 0; + dummycolour.flags = 04; + blankCursor = XCreatePixmapCursor(theDisplay, cursormask, cursormask, + &dummycolour, &dummycolour, 0, 0); + XFreeGC(theDisplay, gc); + XFreePixmap(theDisplay, cursormask); + + // Now create the normal cursor + normalCursor = XCreateFontCursor(theDisplay, XC_left_ptr); +} + +/** + Called when the user wants to take a snapshot of the current display. + Currently, images are stored in png format in the users home directory + name consecutively as "Cartridge.Name".png. If that name exists, they are + named as Cartridge.Name"_x.png, where x starts with 1 and + increases if the previous name already exists. All spaces in filenames + are converted to underscore '_'. +*/ +void takeSnapshot() +{ +#ifdef IMLIB2_SNAPSHOT + // Figure out the actual size of the window + int width = theWidth * 2 * theWindowSize; + int height = theHeight * theWindowSize; + + Imlib_Image image = imlib_create_image_from_drawable(0, 0, 0, width, height, 1); + + if(image == NULL) + { + cerr << "Could not create snapshot!!\n"; + return; + } + + // Now find the correct name for the snapshot + string filename = getenv("HOME"); + filename = filename + "/" + theConsole->properties().get("Cartridge.Name"); + + // Replace all spaces in name with underscores + replace(filename.begin(), filename.end(), ' ', '_'); + + // Determine if the file already exists, checking each successive filename + // until one doesn't exist + string extFilename = filename + ".png"; + if(access(extFilename.c_str(), F_OK) == 0 ) + { + uInt32 i; + char buffer[1024]; + + for(i = 1; ;++i) + { + snprintf(buffer, 1023, "%s_%d.png", filename.c_str(), i); + if(access(buffer, F_OK) == -1 ) + break; + } + filename = buffer; + } + else + filename = extFilename; + + // Now save the png snapshot file + imlib_context_set_image(image); + imlib_image_set_format("png"); + imlib_save_image(filename.c_str()); + imlib_free_image(); + + cerr << "Snapshot saved as " << filename << endl; +#else + cerr << "Snapshot mode not supported.\n"; +#endif +} + +/** + Calculate the maximum window size that the current screen can hold +*/ +uInt32 maxWindowSizeForScreen() +{ + int screenWidth = DisplayWidth(theDisplay, theScreen); + int screenHeight = DisplayHeight(theDisplay, theScreen); + + uInt32 multiplier = screenWidth / (theWidth * 2); + bool found = false; + + while(!found && (multiplier > 0)) + { + // Figure out the desired size of the window + int width = theWidth * 2 * multiplier; + int height = theHeight * multiplier; + + if((width < screenWidth) && (height < screenHeight)) + found = true; + else + multiplier--; + } + + if(found) + return multiplier; + else + return 1; +} + /** Display a usage message and exit the program */ void usage() { static const char* message[] = { + "", + "XStella version 1.2", "", "Usage: xstella [option ...] file", "", @@ -694,12 +1074,19 @@ void usage() " -display Connect to the designated X display", " -fps Display the given number of frames per second", " -owncmap Install a private colormap", + " -winsize Makes initial window be 'size' times normal", + " -fullscreen Play the game in fullscreen mode", + " -grabmouse Keeps the mouse in the game window", + " -hidecursor Hides the mouse cursor in the game window", + " -center Centers the game window onscreen", + " -volume Set the volume from 0 to 100", #ifdef LINUX_JOYSTICK " -paddle <0|1|2|3|real> Indicates which paddle the mouse should emulate", " or that real Atari 2600 paddles are being used", #else " -paddle <0|1|2|3> Indicates which paddle the mouse should emulate", #endif + " -showfps Shows some game info on exit", "", 0 }; @@ -712,33 +1099,34 @@ void usage() } /** - Setup the properties set by loading builtin defaults and then a - set of user specific ones from the file $HOME/.stella.pro + Setup the properties set by first checking for a user file ".stella.pro", + then a system-wide file "/etc/stella.pro", or finally a default + properties set. @param set The properties set to setup */ void setupProperties(PropertiesSet& set) { - // Try to load the file $HOME/.stella.pro file - string filename = getenv("HOME"); - filename += "/.stella.pro"; + string homePropertiesFile = getenv("HOME"); + homePropertiesFile += "/.stella.pro"; - // See if we can open the file $HOME/.stella.pro - ifstream stream(filename.c_str()); - if(stream) + if(access(homePropertiesFile.c_str(), R_OK) == 0) { - // File was opened so load properties from it - set.load(stream, &Console::defaultProperties()); + ifstream homeStream(homePropertiesFile.c_str()); + set.load(homeStream, &Console::defaultProperties()); + } + else if(access("/etc/stella.pro", R_OK) == 0) + { + ifstream systemStream("/etc/stella.pro"); + set.load(systemStream, &Console::defaultProperties()); } else { - // Couldn't open the file so use the builtin properties file strstream builtin; for(const char** p = defaultPropertiesFile(); *p != 0; ++p) { builtin << *p << endl; } - set.load(builtin, &Console::defaultProperties()); } } @@ -752,10 +1140,8 @@ void setupProperties(PropertiesSet& set) void handleCommandLineArguments(int argc, char* argv[]) { // Make sure we have the correct number of command line arguments - if((argc < 2) || (argc > 9)) - { + if(argc < 2) usage(); - } for(Int32 i = 1; i < (argc - 1); ++i) { @@ -796,9 +1182,201 @@ void handleCommandLineArguments(int argc, char* argv[]) { theDisplayName = argv[++i]; } + else if(string(argv[i]) == "-fullscreen") + { + theUseFullScreenFlag = true; + } + else if(string(argv[i]) == "-grabmouse") + { + theGrabMouseFlag = true; + } + else if(string(argv[i]) == "-hidecursor") + { + theHideCursorFlag = true; + } + else if(string(argv[i]) == "-center") + { + theCenterWindowFlag = true; + } + else if(string(argv[i]) == "-showfps") + { + theShowFpsFlag = true; + } + else if(string(argv[i]) == "-winsize") + { + uInt32 size = atoi(argv[++i]); + theWindowSize = size; + } + else if(string(argv[i]) == "-volume") + { + // They're setting the desired volume + Int32 volume = atoi(argv[++i]); + if(volume < 0) + volume = 0; + if(volume > 100) + volume = 100; + + theDesiredVolume = volume; + } else { - usage(); + cout << "Undefined option " << argv[i] << endl; + } + } +} + +/** + Should be called to determine if an rc file exists. First checks if there + is a user specified file ".stellarc" and then if there is a system-wide + file "/etc/stellarc". +*/ +void handleRCFile() +{ + string homeRCFile = getenv("HOME"); + homeRCFile += "/.stellarc"; + + if(access(homeRCFile.c_str(), R_OK) == 0 ) + { + ifstream homeStream(homeRCFile.c_str()); + parseRCOptions(homeStream); + } + else if(access("/etc/stellarc", R_OK) == 0 ) + { + ifstream systemStream("/etc/stellarc"); + parseRCOptions(systemStream); + } +} + +/** + Parses each line of the given rcfile and sets options accordingly. + + @param in The file to parse for options +*/ +void parseRCOptions(istream& in) +{ + string line, key, value; + uInt32 equalPos; + + while(getline(in, line)) + { + // Strip all whitespace and tabs from the line + uInt32 garbage; + while((garbage = line.find(" ")) != string::npos) + line.erase(garbage, 1); + while((garbage = line.find("\t")) != string::npos) + line.erase(garbage, 1); + + // Ignore commented and empty lines + if((line.length() == 0) || (line[0] == ';')) + continue; + + // Search for the equal sign and discard the line if its not found + if((equalPos = line.find("=")) == string::npos) + continue; + + key = line.substr(0, equalPos); + value = line.substr(equalPos + 1, line.length() - key.length() - 1); + + // Check for absent key or value + if((key.length() == 0) || (value.length() == 0)) + continue; + + // Now set up the options by key + if(key == "fps") + { + // They're setting the desired frame rate + uInt32 rate = atoi(value.c_str()); + if((rate < 1) || (rate > 300)) + { + rate = 60; + } + + theDesiredFrameRate = rate; + } + else if(key == "paddle") + { + // They're trying to set the paddle emulation mode + uInt32 pMode; + if(value == "real") + { + thePaddleMode = 4; + } + else + { + pMode = atoi(value.c_str()); + if((pMode > 0) && (pMode < 4)) + thePaddleMode = pMode; + } + } + else if(key == "owncmap") + { + uInt32 option = atoi(value.c_str()); + if(option == 1) + theUsePrivateColormapFlag = true; + else if(option == 0) + theUsePrivateColormapFlag = false; + } + else if(key == "display") + { + theDisplayName = value; + } + else if(key == "fullscreen") + { + uInt32 option = atoi(value.c_str()); + if(option == 1) + theUseFullScreenFlag = true; + else if(option == 0) + theUseFullScreenFlag = false; + } + else if(key == "grabmouse") + { + uInt32 option = atoi(value.c_str()); + if(option == 1) + theGrabMouseFlag = true; + else if(option == 0) + theGrabMouseFlag = false; + } + else if(key == "hidecursor") + { + uInt32 option = atoi(value.c_str()); + if(option == 1) + theHideCursorFlag = true; + else if(option == 0) + theHideCursorFlag = false; + } + else if(key == "center") + { + uInt32 option = atoi(value.c_str()); + if(option == 1) + theCenterWindowFlag = true; + else if(option == 0) + theCenterWindowFlag = false; + } + else if(key == "showfps") + { + uInt32 option = atoi(value.c_str()); + if(option == 1) + theShowFpsFlag = true; + else if(option == 0) + theShowFpsFlag = false; + } + else if(key == "winsize") + { + // They're setting the initial window size + // Don't do bounds checking here, it will be taken care of later + uInt32 size = atoi(value.c_str()); + theWindowSize = size; + } + else if(key == "volume") + { + // They're setting the desired volume + uInt32 volume = atoi(value.c_str()); + if(volume < 0) + volume = 0; + if(volume > 100) + volume = 100; + + theDesiredVolume = volume; } } } @@ -806,6 +1384,9 @@ void handleCommandLineArguments(int argc, char* argv[]) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int main(int argc, char* argv[]) { + // Load in any user defined settings from an RC file + handleRCFile(); + // Handle the command line arguments handleCommandLineArguments(argc, argv); @@ -826,11 +1407,11 @@ int main(int argc, char* argv[]) in.close(); // Create a properties set for us to use and set it up - PropertiesSet propertiesSet("Cartridge.Name"); + PropertiesSet propertiesSet; setupProperties(propertiesSet); // Create a sound object for use with the console - SoundUnix sound; + SoundUnix sound(theDesiredVolume); // Get just the filename of the file containing the ROM image const char* filename = (!strrchr(file, '/')) ? file : strrchr(file, '/') + 1; @@ -842,20 +1423,17 @@ int main(int argc, char* argv[]) // Free the image since we don't need it any longer delete[] image; -#ifdef LINUX_JOYSTICK - // Open the joystick devices - theLeftJoystickFd = open("/dev/js0", O_RDONLY | O_NONBLOCK); - theRightJoystickFd = open("/dev/js1", O_RDONLY | O_NONBLOCK); -#endif - - // Setup X windows - setupX11(); + // Setup X window and joysticks + setupDisplay(); + setupJoystick(); // Get the starting time in case we need to print statistics timeval startingTime; gettimeofday(&startingTime, 0); uInt32 numberOfFrames = 0; +// uInt32 delayFactor = int(60.0 / float(theDesiredFrameRate) * 9500); + for( ; ; ++numberOfFrames) { // Exit if the user wants to quit @@ -868,7 +1446,6 @@ int main(int argc, char* argv[]) timeval before; gettimeofday(&before, 0); - // Draw the frame and handle events theConsole->mediaSource().update(); updateDisplay(theConsole->mediaSource()); handleEvents(); @@ -886,27 +1463,47 @@ int main(int argc, char* argv[]) { break; } +/* else + { + after.tv_sec = 0; + after.tv_usec = delayFactor - delta; + select(0, NULL, NULL, NULL, &after); + }*/ } } - timeval endingTime; - gettimeofday(&endingTime, 0); - double executionTime = (endingTime.tv_sec - startingTime.tv_sec) + - ((endingTime.tv_usec - startingTime.tv_usec) / 1000000.0); - double framesPerSecond = numberOfFrames / executionTime; + if(theShowFpsFlag) + { + timeval endingTime; + gettimeofday(&endingTime, 0); + double executionTime = (endingTime.tv_sec - startingTime.tv_sec) + + ((endingTime.tv_usec - startingTime.tv_usec) / 1000000.0); + double framesPerSecond = numberOfFrames / executionTime; - cout << endl; - cout << numberOfFrames << " total frames drawn\n"; - cout << framesPerSecond << " frames/second\n"; - cout << theConsole->mediaSource().scanlines() << " scanlines in last frame\n"; - cout << endl; + cout << endl; + cout << numberOfFrames << " total frames drawn\n"; + cout << framesPerSecond << " frames/second\n"; + cout << theConsole->mediaSource().scanlines() << " scanlines in last frame\n"; + cout << endl; + } + // Cleanup time ... delete theConsole; + XFreeCursor(theDisplay, normalCursor); + XFreeCursor(theDisplay, blankCursor); + // If we're using a private colormap then let's free it to be safe if(theUsePrivateColormapFlag) { XFreeColormap(theDisplay, thePrivateColormap); } -} +#ifdef LINUX_JOYSTICK + // Close the joystick devices + if(theLeftJoystickFd) + close(theLeftJoystickFd); + if(theRightJoystickFd) + close(theRightJoystickFd); +#endif +}