diff -urNx '*.o' Nestopia137_orig/Makefile Nestopia137/Makefile
--- Nestopia137_orig/Makefile	2007-07-02 23:42:29.000000000 -0700
+++ Nestopia137/Makefile	2007-07-02 23:42:36.000000000 -0700
@@ -17,9 +17,11 @@
 EXE  = nst
 LIBS = -lm -lz -lasound  `sdl-config --libs` `pkg-config --libs gtk+-2.0`
 
+GENNSTCONTROLS = gennstcontrols
+
 # Linux objs
 OBJS = objs/linux/main.o objs/linux/oss.o objs/linux/interface.o objs/linux/support.o objs/linux/settings.o 
-OBJS += objs/linux/auxio.o objs/linux/input.o
+OBJS += objs/linux/auxio.o objs/linux/input.o objs/linux/kentry.o
 
 # 7-zip decoder
 OBJS += objs/linux/7zip/7zAlloc.o objs/linux/7zip/7zBuffer.o objs/linux/7zip/7zCrc.o objs/linux/7zip/7zDecode.o objs/linux/7zip/7zExtract.o 
@@ -128,7 +130,7 @@
 	@echo Compiling $<...
 	@$.o) $(CFLAGS) $.oFLAGS) $< -o $@
 	
-all: maketree $(EXE)
+all: maketree $(EXE) $(GENNSTCONTROLS)
 
 maketree: $(sort $(OBJDIRS))
 
@@ -138,8 +140,17 @@
 
 # link the commandline exe
 $(EXE): $(OBJS)
-	@echo Linking $(EXE)...
-	@$(CPP) -g -o $(EXE) $(OBJS) $(LIBS)
+	@echo Linking $@...
+	@$(CPP) -g -o $(EXE) $^ $(LIBS)
+
+# link the gennstcontrols utility
+$(GENNSTCONTROLS): objs/linux/kentry.o objs/linux/gennstcontrols.o
+	@echo Linking $@...
+	@$(CPP) -g -o $@ $^ $(LIBS)
 
 clean:
-	rm -f $(OBJS) $(EXE)
+	-@rm -f $(OBJS) $(EXE) $(GENNSTCONTROLS)
+
+install: all
+	@cp $(EXE) $(GENNSTCONTROLS) /usr/games/bin
+
diff -urNx '*.o' Nestopia137_orig/README.Linux Nestopia137/README.Linux
--- Nestopia137_orig/README.Linux	2007-05-20 16:53:40.000000000 -0700
+++ Nestopia137/README.Linux	2007-07-02 23:28:54.000000000 -0700
@@ -38,14 +38,31 @@
 * Get the official 1.37 source from http://nestopia.sourceforge.net/
 * Unpack it
 * Unpack this overlay over it
-* Go to the directory where you unpacked this and type "make".  ("make -j2" for dual-core, "make -j4" for quad).
+* Go to the directory where you unpacked this and type "make".  ("make -j2" for
+  dual-core, "make -j4" for quad).
 
-Before running the emulator, copy your nstcontrols file to a folder named .nestopia in your home directory.
-This is also where you should put the NEStopia database (NstDatabase.dat), the Disk System BIOS named 
-"disksys.rom", and this is also where NEStopia saves your settings (nstsettings).
+Before running the emulator, copy the appropriate nstcontrols file (rename to
+"nstcontrols") to a folder named .nestopia in your home directory, e.g. to use
+XBox controllers:
 
-Marty and I try to ensure maximum portability of the code, but at this time it has not been tested on 64-bit
-or big-endian targets, although the Mac version obviously runs fine on big-endian.
+	$ mkdir ~/.nestopia
+	$ cp nstcontrols.xbox ~/.nestopia/nstcontrols
+
+If you're using another controller, or want to define your keys interactively,
+run the gennstcontrols utility and enter your keys; it will write out a config
+file, so for example use:
+
+	$ ./gennstcontrols > ~/.nestopia/nstcontrols
+
+to write the generated file to the default location.
+
+This is also where you should put the NEStopia database (NstDatabase.dat), the
+Disk System BIOS named "disksys.rom", and this is also where NEStopia saves
+your settings (nstsettings).
+
+Marty and I try to ensure maximum portability of the code, but at this time it
+has not been tested on 64-bit or big-endian targets, although the Mac version
+obviously runs fine on big-endian.
 
 
 Changes from preview #4
Files Nestopia137_orig/gennstcontrols and Nestopia137/gennstcontrols differ
Files Nestopia137_orig/nst and Nestopia137/nst differ
diff -urNx '*.o' Nestopia137_orig/nstcontrols Nestopia137/nstcontrols
--- Nestopia137_orig/nstcontrols	2007-03-19 20:38:22.000000000 -0700
+++ Nestopia137/nstcontrols	1969-12-31 16:00:00.000000000 -0800
@@ -1,44 +0,0 @@
-;
-; NEStopia Linux controls file
-;
-; First column cannot be changed or you'll break the emulator
-; Second column is the key for the first column, or one of these special names:
-; 
-; _ENTER, _LCTRL, _RCTRL, _LALT, _RALT, _SPACE, _LSHIFT, _RSHIFT, _TAB
-; _UP, _DOWN, _LEFT, _RIGHT, _KPENTER, _KP0, _KP1, _KP2, _KP3, _KP4, _KP5, _KP6,
-; _KP7, _KP8, _KP9, _KPDOT, _KPSLASH, _KPSTAR, _KPMINUS, _KPPLUS (use __ to bind the actual _)
-;
-; Third column maps joypads:
-; _JxAyPLUS = joystick #x axis #y positive direction
-; _JxAyMINUS = joystick #x axis #y negative direction
-; _JxBy = joystick #x button #y pressed
-;
-; Some keys are special:
-; ESC always exits
-; ALT-ENTER always toggles screen modes
-;
-
-; player 1
-; default joypad values are for a PlayStation 2 controller via an EMS USB adaptor
-;
-P1UP	_UP	_J0A1MINUS
-P1DN	_DOWN	_J0A1PLUS
-P1LT	_LEFT	_J0A0MINUS
-P1RT	_RIGHT	_J0A0PLUS
-P1A	,	_J0B2
-P1B	.	_J0B3
-P1START	1	_J0B9
-P1SELECT	2 _J0B8
-
-; player 2
-P2UP	_KP8	_J1A1MINUS
-P2DN	_KP2	_J1A1PLUS
-P2LT	_KP4	_J1A0MINUS
-P2RT	_KP6	_J1A0PLUS
-P2A	_KPSLASH	_J1B2
-P2B	_KPSTAR	_J1B3
-P2START	_KPENTER	_J1B9
-P2SELECT	_KPPLUS	_J1B8
-
-; end of file
-
diff -urNx '*.o' Nestopia137_orig/nstcontrols.ps2 Nestopia137/nstcontrols.ps2
--- Nestopia137_orig/nstcontrols.ps2	1969-12-31 16:00:00.000000000 -0800
+++ Nestopia137/nstcontrols.ps2	2007-07-03 14:50:02.000000000 -0700
@@ -0,0 +1,55 @@
+;
+; NEStopia Linux controls file
+;
+; First column cannot be changed or you'll break the emulator
+; Second column is the key for the first column, or one of these special names:
+; 
+; _ENTER, _LCTRL, _RCTRL, _LALT, _RALT, _SPACE, _LSHIFT, _RSHIFT, _TAB
+; _UP, _DOWN, _LEFT, _RIGHT, _KPENTER, _KP0, _KP1, _KP2, _KP3, _KP4, _KP5, _KP6,
+; _KP7, _KP8, _KP9, _KPDOT, _KPSLASH, _KPSTAR, _KPMINUS, _KPPLUS (use __ to bind the actual _)
+;
+; Third column maps joypads:
+; _JxAyPLUS = joystick #x axis #y positive direction
+; _JxAyMINUS = joystick #x axis #y negative direction
+; _JxBy = joystick #x button #y pressed
+;
+; Some keys are special:
+; ESC always exits
+; ALT-ENTER always toggles screen modes
+;
+
+; player 1
+; default joypad values are for a PlayStation 2 controller via an EMS USB adaptor
+;
+P1UP	_UP	_J0A1MINUS
+P1DN	_DOWN	_J0A1PLUS
+P1LT	_LEFT	_J0A0MINUS
+P1RT	_RIGHT	_J0A0PLUS
+P1A	,	_J0B2
+P1B	.	_J0B3
+P1START	1	_J0B9
+P1SELECT	2 _J0B8
+
+; player 2
+P2UP	_KP8	_J1A1MINUS
+P2DN	_KP2	_J1A1PLUS
+P2LT	_KP4	_J1A0MINUS
+P2RT	_KP6	_J1A0PLUS
+P2A	_KPSLASH	_J1B2
+P2B	_KPSTAR	_J1B3
+P2START	_KPENTER	_J1B9
+P2SELECT	_KPPLUS	_J1B8
+
+; meta
+MSAVE _F1
+MLOAD _F2
+MSTOP _F3
+RESET _F4
+FLIP _F5
+SAVE _F7
+LOAD _F8
+RBACK _BACKSPACE
+RFORE \
+
+; end of file
+
diff -urNx '*.o' Nestopia137_orig/nstcontrols.xbox Nestopia137/nstcontrols.xbox
--- Nestopia137_orig/nstcontrols.xbox	1969-12-31 16:00:00.000000000 -0800
+++ Nestopia137/nstcontrols.xbox	2007-07-02 23:24:55.000000000 -0700
@@ -0,0 +1,67 @@
+;
+; NEStopia Linux controls file
+;
+; First column cannot be changed or you'll break the emulator
+; Subsequent columns are keys or one of these special names:
+; 
+; _ENTER, _LCTRL, _RCTRL, _LALT, _RALT, _SPACE, _LSHIFT, _RSHIFT, _TAB
+; _UP, _DOWN, _LEFT, _RIGHT, _KPENTER, _KP0, _KP1, _KP2, _KP3, _KP4, _KP5, _KP6,
+; _KP7, _KP8, _KP9, _KPDOT, _KPSLASH, _KPSTAR, _KPMINUS, _KPPLUS (use __ to bind the actual _)
+; _BACKSPACE, _F1.._F12, _ESCAPE
+;
+; Third column maps joypads:
+; _JxAy+ = joystick #x axis #y positive direction
+; _JxAy- = joystick #x axis #y negative direction
+; _JxBy = joystick #x button #y pressed
+; _JxHy+ = joystick #x hat #y positive direction
+; _JxHy- = joystick #x hat #y negative direction
+;
+; Some keys are special:
+; ESC always exits
+; ALT-ENTER always toggles screen modes
+;
+
+; player 1
+; default joypad values are for a (wired) XBox 360 USB controller
+
+; Xbox controller:
+; B0 = "A", B1 = "B", B3 = "Y", ; B4 = "X", B5 = unused?, B6 = "LB", B7 = "RB"
+; B8 = "start", B9 = center "X", B16 = "back" (use as select)
+; B12-15 = "+" sub-controller up/left/down/right resp.,
+; Use A2-/A5- for the back lower left/right push-buttons resp.
+
+P1UP	_UP	_J0A1-
+P1DN	_DOWN	_J0A1+
+P1LT	_LEFT	_J0A0-
+P1RT	_RIGHT	_J0A0+
+P1A	,	_J0B1
+P1B	.	_J0B0
+P1START	1	_J0B8
+P1SELECT	2 _J0B16
+
+; player 2
+; default joypad values are for a (wired) XBox 360 USB controller
+P2UP	_KP8	_J1A1-
+P2DN	_KP2	_J1A1+
+P2LT	_KP4	_J1A0-
+P2RT	_KP6	_J1A0+
+P2A	_KPSLASH	_J1B1
+P2B	_KPSTAR	_J1B0
+P2START	_KPENTER	_J1B8
+P2SELECT	_KPPLUS	_J1B16
+
+; meta-commands
+QSAVE1 _F1 _J0B7
+QLOAD1 _F2 _J0B6
+QSAVE2 _F3 _J0A5-
+QLOAD2 _F4 _J0A2-
+RESET _F5
+SAVE _F7
+LOAD _F8
+RBACK _BACKSPACE
+RFORE \
+STOP _ESCAPE
+EXIT Q _J0B9
+
+; end of file
+
Files Nestopia137_orig/source/linux/.main.cpp.swp and Nestopia137/source/linux/.main.cpp.swp differ
diff -urNx '*.o' Nestopia137_orig/source/linux/SDL-event/Makefile Nestopia137/source/linux/SDL-event/Makefile
--- Nestopia137_orig/source/linux/SDL-event/Makefile	1969-12-31 16:00:00.000000000 -0800
+++ Nestopia137/source/linux/SDL-event/Makefile	2007-07-02 23:43:18.000000000 -0700
@@ -0,0 +1,19 @@
+# makefile for SDL-event
+# dbr 20070701
+
+SDL_CFLAGS = $(shell sdl-config --cflags)
+SDL_LDFLAGS = $(shell sdl-config --libs)
+
+CXXFLAGS += $(SDL_CFLAGS) -Wall
+LDFLAGS += $(SDL_LDFLAGS)
+
+TARGET = SDL-event
+OBJS = sdlevent.o
+
+all: $(TARGET)
+
+$(TARGET): $(OBJS)
+	$(CXX) $(LDFLAGS) $(OBJS) -o $(TARGET)
+
+clean:
+	-@rm $(TARGET) $(OBJS)
Files Nestopia137_orig/source/linux/SDL-event/SDL-event and Nestopia137/source/linux/SDL-event/SDL-event differ
diff -urNx '*.o' Nestopia137_orig/source/linux/SDL-event/sdlevent.cpp Nestopia137/source/linux/SDL-event/sdlevent.cpp
--- Nestopia137_orig/source/linux/SDL-event/sdlevent.cpp	1969-12-31 16:00:00.000000000 -0800
+++ Nestopia137/source/linux/SDL-event/sdlevent.cpp	2007-07-02 21:26:58.000000000 -0700
@@ -0,0 +1,135 @@
+/* SDL-event.cpp */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "SDL.h"
+
+int main(int argc, const char **argv)
+{
+	if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
+	{
+		fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
+		exit(1);
+	}
+	atexit(SDL_Quit);
+
+	// set up joysticks
+	SDL_JoystickEventState(SDL_ENABLE);
+	int cjoy = SDL_NumJoysticks();
+	printf("%d joystick(s) detected.\n", cjoy);
+	for (int ijoy = 0; ijoy < cjoy; ++ijoy)
+	{
+		SDL_Joystick *pjoy = SDL_JoystickOpen(0);
+		printf("Joystick %d is '%s' with %d axes, %d buttons, %d balls.\n",
+		 ijoy, SDL_JoystickName(ijoy), SDL_JoystickNumAxes(pjoy),
+		 SDL_JoystickNumButtons(pjoy), SDL_JoystickNumBalls(pjoy));
+	}
+	printf("\n");
+
+	// we need to set a video mode to get keyboard events
+	if (!SDL_SetVideoMode(64, 64, 0, SDL_ANYFORMAT))
+	{
+		fprintf(stderr, "SDL_SetVideoMode failed: %s\n", SDL_GetError());
+		exit(1);
+	}
+
+	printf("SDL initialized.\n"
+	 "Make sure the black window is active and start generating keyboard or "
+	 "joystick events; press ESC to quit.\n\n");
+
+	int quit = 0;
+	while (!quit)
+	{
+		SDL_Event evt;
+		if (!SDL_WaitEvent(&evt))
+			{
+			fprintf(stderr, "SDL_WaitEvent failed: %s\n", SDL_GetError());
+			exit(1);
+			}
+
+		switch (evt.type)
+		{
+		case SDL_NOEVENT:
+			printf("SDL_NOEVENT");
+			break;
+
+		case SDL_ACTIVEEVENT:
+			printf("SDL_ACTIVEEVENT %s%s%s%s",
+				evt.active.gain ? "gain" : "loss",
+				(evt.active.state & SDL_APPMOUSEFOCUS) ? " mouse focus" : "",
+				(evt.active.state & SDL_APPINPUTFOCUS) ? " input focus" : "",
+				(evt.active.state & SDL_APPACTIVE)     ?
+					(evt.active.gain ? " restored" : " iconified") : "");
+			break;
+
+		case SDL_KEYDOWN:
+		case SDL_KEYUP:
+			printf("SDL_KEY%s %s %s (scan %d sym %d mod 0x%x)",
+				(evt.type == SDL_KEYDOWN) ? "DOWN" : "UP",
+				SDL_GetKeyName(evt.key.keysym.sym),
+				(evt.key.state == SDL_PRESSED) ? "pressed" : "released",
+				evt.key.keysym.scancode, evt.key.keysym.sym, evt.key.keysym.mod);
+			if (evt.type == SDL_KEYDOWN && evt.key.state == SDL_PRESSED
+				&& evt.key.keysym.sym == SDLK_ESCAPE && evt.key.keysym.mod == 0)
+				{
+				quit = 1;
+				}
+			break;
+
+		case SDL_MOUSEMOTION:
+			printf("SDL_MOUSEMOTION state 0x%x pos (%d, %d) rel (%d, %d)",
+			 	evt.motion.state, evt.motion.x, evt.motion.y,
+				evt.motion.xrel, evt.motion.yrel);
+			break;
+
+		case SDL_MOUSEBUTTONDOWN:
+		case SDL_MOUSEBUTTONUP:
+			printf("SDL_MOUSEBUTTON%s %s %s pos (%d, %d)",
+				(evt.type == SDL_MOUSEBUTTONDOWN) ? "DOWN" : "UP",
+				(evt.button.button == SDL_BUTTON_LEFT) ? "left" :
+				 (evt.button.button == SDL_BUTTON_RIGHT) ? "right" :
+				 (evt.button.button == SDL_BUTTON_MIDDLE) ? "middle" : "?",
+				(evt.button.state == SDL_PRESSED) ? "pressed" : "released",
+				evt.button.x, evt.button.y);
+			break;
+
+		case SDL_JOYAXISMOTION:
+		case SDL_JOYBALLMOTION:
+		case SDL_JOYHATMOTION:
+			printf("SDL_JOY%sMOTION device %d axis %d value %d",
+				(evt.type == SDL_JOYAXISMOTION) ? "AXIS" :
+				 (evt.type == SDL_JOYBALLMOTION) ? "BALL" : "HAT",
+			 	evt.jaxis.which, evt.jaxis.axis, evt.jaxis.value);
+			if (evt.type == SDL_JOYBALLMOTION)
+				printf(" rel (%d, %d)", evt.jball.xrel, evt.jball.yrel);
+			break;
+
+		case SDL_JOYBUTTONDOWN:
+		case SDL_JOYBUTTONUP:
+			printf("SDL_JOYBUTTON%s device %d button %d %s",
+				(evt.type == SDL_JOYBUTTONDOWN) ? "DOWN" : "UP",
+				evt.jbutton.which, evt.jbutton.button,
+				(evt.jbutton.state == SDL_PRESSED) ? "pressed" : "released");
+			break;
+
+		case SDL_QUIT:
+			printf("SDL_QUIT");
+			quit = 1;
+			break;
+
+		case SDL_SYSWMEVENT:
+    case SDL_EVENT_RESERVEDA:
+    case SDL_EVENT_RESERVEDB:
+		case SDL_VIDEORESIZE:
+		case SDL_VIDEOEXPOSE:
+			/*ignore*/
+			break;
+
+		default:
+			printf("Unknown event (%d)", evt.type);
+		}
+		printf ("\n\n");
+	}
+
+	printf("\nExiting.\n");
+}
diff -urNx '*.o' Nestopia137_orig/source/linux/gennstcontrols.cpp Nestopia137/source/linux/gennstcontrols.cpp
--- Nestopia137_orig/source/linux/gennstcontrols.cpp	1969-12-31 16:00:00.000000000 -0800
+++ Nestopia137/source/linux/gennstcontrols.cpp	2007-07-03 15:08:24.000000000 -0700
@@ -0,0 +1,334 @@
+/* gennstcontrols.cpp: generate nstcontrols file interactively
+ *
+ * David B. Robins for NEStopia Linux overlay, 2007-07-02
+ *
+ * Generates nstcontrols Linux controller configuration (writes to stdout;
+ * use as "gennstcontrols > nstcontrols").
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <assert.h>
+#include <string>
+#include <vector>
+#include "SDL.h"
+#include "kentry.h"
+#include "input.h"
+
+
+// represents an axis that is not centered in the "dead zone"
+struct AxisNoise
+{
+	int which;
+	int axis;
+	Sint16 pos;
+
+	AxisNoise(int _which, int _axis, Sint16 _pos) :
+		which(_which), axis(_axis), pos(_pos) {}
+	
+	bool match(const SDL_Event &evt) const
+	{
+		assert(evt.type == SDL_JOYAXISMOTION);
+		return evt.jaxis.which == which && evt.jaxis.axis == axis
+			&& abs(evt.jaxis.value - pos) <= DEADZONE;
+	}
+};
+
+typedef std::vector<AxisNoise> AxisNoiseVec;
+static AxisNoiseVec axisNoise;
+
+struct EventMatch
+{
+	SDL_Event evt;
+	int len;						// of the user text
+	std::string code;		// code for the nstcontrols file
+
+	EventMatch(const SDL_Event &_evt, int _len, const char *_code) :
+		evt(_evt), len(_len), code(_code) {}
+};
+typedef std::vector<EventMatch> EventVec;
+
+
+// user strings for meta-events, "!<key>" means just use that key
+const KEntry metanames[] =
+{
+	{ "reset emulator", RESET },
+	{ "quick save slot 1", QSAVE1 },
+	{ "quick load slot 1", QLOAD1 },
+	{ "quick save slot 2", QSAVE2 },
+	{ "quick load slot 2", QLOAD2 },
+	{ "full save", SAVE },
+	{ "full load", LOAD },
+	{ "save movie", MSAVE },
+	{ "load movie", MLOAD },
+	{ "stop movie", MSTOP },
+	{ "flip FDS disk", FLIP },
+	{ "!_BACKSPACE", RBACK },
+	{ "!\\", RFORE },
+	{ "stop emulator", STOP },
+	{ "exit emulator", EXIT },
+	{ NULL, -1 }
+};
+
+
+// check for noisy axis that shouldn't count as motion
+inline static bool noisy_axis(const SDL_Event &evt)
+{
+	for (AxisNoiseVec::const_iterator i = axisNoise.begin();
+		i != axisNoise.end(); ++i)
+	{
+		if (i->match(evt))
+			return true;
+	}
+	return false;
+}
+
+
+// have we already used this input (or axis) for this event?
+// (it's OK to assign the same key to multiple events, just not the same one!)
+static bool match_event(const EventVec &evtvec, const SDL_Event &evt)
+{
+	for (EventVec::const_iterator i = evtvec.begin(); i != evtvec.end(); ++i)
+	{
+		const SDL_Event &evtT = i->evt;
+		if (evtT.type != evt.type)
+			continue;
+		switch (evtT.type)
+		{
+		case SDL_KEYDOWN:
+			if (evtT.key.keysym.sym == evt.key.keysym.sym)
+				return true;
+			break;
+
+		case SDL_JOYBUTTONDOWN:
+			if (evtT.jbutton.which == evt.jbutton.which
+				&& evtT.jbutton.button == evt.jbutton.button)
+				{
+				return true;
+				}
+			break;
+		
+		case SDL_JOYAXISMOTION:
+			if (evtT.jaxis.which == evt.jaxis.which
+				&& evtT.jaxis.axis == evt.jaxis.axis)
+				{
+				return true;
+				}
+			break;
+		}
+	}
+	return false;
+}
+
+
+// read an event from SDL and output it in nstcontrols format
+static void read_event(const char *entry, const char *desc = NULL)
+{
+	if (!desc)
+		desc = entry;
+	fprintf(stderr, "%s:", desc);
+
+	// use to ensure we don't record multiple events for same axis movement
+	static struct
+	{
+		time_t t;		// time when movement counts as new
+		int which;	// which joystick?
+		int axis;		// which axis?
+		char dir;		// direction ('+' or '-')
+	}
+	lastAxis;
+
+	int quit = 0;
+	EventVec evtvec;	// input events already seen
+	while (!quit)
+	{
+		SDL_Event evt;
+		if (!SDL_WaitEvent(&evt))
+			{
+			fprintf(stderr, "SDL_WaitEvent failed: %s\n", SDL_GetError());
+			exit(1);
+			}
+
+		// if we've already seen this input, ignore it
+		if (match_event(evtvec, evt))
+			continue;
+
+		char match[20] = { 0 };
+		char input[20] = { 0 };
+		switch (evt.type)
+		{
+		case SDL_KEYDOWN:
+		{
+			int ksym = int(evt.key.keysym.sym);
+			if (ksym == SDLK_RETURN)
+			{
+				fprintf(stderr, " %s\n", evtvec.empty() ? "<skip>" : "");
+				quit = 1;
+				break;
+			}
+			else if (ksym == SDLK_BACKSPACE && !evtvec.empty())
+			{
+				// remove the last input
+				const EventMatch &evtm = evtvec.back();
+				evtvec.pop_back();
+				int len = evtm.len + (evtvec.empty() ? 1 : 2);
+				for (int i = 0; i < len; ++i)
+					fprintf(stderr, "\b \b");
+				break;
+			}
+
+			const char *key = kentry_find_code(keycodes, ksym);
+			if (key != NULL)
+			{
+				sprintf(input, "_%s", key);
+			}
+			else if (isprint(ksym))
+			{
+				sprintf(input, "%c", ksym);
+			}
+			else
+			{
+				fprintf(stderr, "\a");	// beep
+				break;
+			}
+			sprintf(match, "%s", SDL_GetKeyName(SDLKey(ksym)));
+			break;
+		}
+
+		case SDL_JOYAXISMOTION:
+			if (abs(evt.jaxis.value) > DEADZONE)
+			{
+				// check for a noisy axis with bad centering
+				if (noisy_axis(evt))
+					break;
+
+				char dir = (evt.jaxis.value) > 0 ? '+' : '-';
+				time_t t = time(NULL);
+
+				if (lastAxis.dir == dir && lastAxis.which == evt.jaxis.which
+					&& lastAxis.axis == evt.jaxis.axis && t < lastAxis.t)
+					{
+					break;
+					}
+				lastAxis.dir = dir;
+				lastAxis.which = evt.jaxis.which;
+				lastAxis.axis = evt.jaxis.axis;
+				lastAxis.t = t + 2;
+
+				sprintf(match, "joy %d axis %d %c",
+					evt.jaxis.which + 1, evt.jaxis.axis, dir);
+				sprintf(input, "_J%dA%d%c", evt.jaxis.which, evt.jaxis.axis, dir);
+			}
+			break;
+
+		case SDL_JOYBUTTONDOWN:
+			sprintf(match, "joy %d button %d",
+				evt.jbutton.which + 1, evt.jbutton.button);
+			sprintf(input, "_J%dB%d", evt.jbutton.which, evt.jbutton.button);
+			break;
+
+		case SDL_QUIT:
+			exit(1);
+			break;
+		}
+
+		if (match[0])
+		{
+			fprintf(stderr, "%s %s", evtvec.empty() ? "" : ",", match);
+			evtvec.push_back(EventMatch(evt, strlen(match), input));
+		}
+	}
+
+	// write the nstcontrols line
+	printf("%s", entry);
+	for (EventVec::const_iterator i = evtvec.begin(); i != evtvec.end(); ++i)
+		printf("\t%s", i->code.c_str());
+	printf("\n");
+}
+
+
+int main(int argc, const char **argv)
+{
+	if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
+	{
+		fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
+		exit(1);
+	}
+	atexit(SDL_Quit);
+
+	fprintf(stderr,
+	 "gennstcontrols utility version 1.0 by David B. Robins (c)2007.\n"
+	 "Released under the GNU General Public License (GPL) v2.\n\n");
+
+	// set up joysticks
+	int cjoy = SDL_NumJoysticks();
+	fprintf(stderr, "%d joystick(s) detected.\n", cjoy);
+	for (int ijoy = 0; ijoy < cjoy; ++ijoy)
+	{
+		SDL_Joystick *pjoy = SDL_JoystickOpen(ijoy);
+		int caxis = SDL_JoystickNumAxes(pjoy);
+		fprintf(stderr, "Joystick %d is '%s' has %d axes and %d buttons.\n",
+		 ijoy + 1, SDL_JoystickName(ijoy), caxis, SDL_JoystickNumButtons(pjoy));
+
+		// calibrate - read axis positions and store as "centered"
+		// we expect joysticks to be centered in (-DEADZONE, DEADZONE) but
+		// need to filter out noise
+		for (int iaxis = 0; iaxis < caxis; ++iaxis)
+		{
+			Sint16 pos = SDL_JoystickGetAxis(pjoy, iaxis);
+			if (abs(pos) > DEADZONE)
+				axisNoise.push_back(AxisNoise(ijoy, iaxis, pos));
+		}
+	}
+	SDL_JoystickEventState(SDL_ENABLE);
+	fprintf(stderr, "\n");
+
+	// we need to set a video mode to get keyboard events
+	if (!SDL_SetVideoMode(64, 64, 0, SDL_ANYFORMAT))
+	{
+		fprintf(stderr, "SDL_SetVideoMode failed: %s\n", SDL_GetError());
+		exit(1);
+	}
+
+	fprintf(stderr, "For each entry, press keys or joystick buttons, ENTER when "
+		"done (or to skip).\nMake sure the small black window is active.\n\n");
+
+	time_t t = time(NULL);
+	struct tm *tmNow = localtime(&t);
+	char now[17];   // yyyy-mm-dd HH:MM
+	strftime(now, sizeof(now), "%Y-%m-%d %H:%M", tmNow);
+	printf(";\n; nstcontrols file generated by gennstcontrols on %s\n;\n\n", now);
+
+	// get controls for both players
+	for (int player = 1; player <= 2; ++player)
+	{
+		printf("; player %d controls\n", player);
+		std::string prefix = std::string("P") + std::string(1, '0' + player);
+		for (const KEntry *ctrl = controlcodes; ctrl->string != NULL; ++ctrl)
+		{
+			std::string entry = prefix + ctrl->string;  // e.g. "P1LEFT"
+			read_event(entry.c_str());
+		}
+		fprintf(stderr, "\n");
+		printf("\n");
+	}
+
+	// get meta-controls
+	printf("; meta-controls\n");
+	for (const KEntry *meta = metanames; meta->string != NULL; ++meta)
+	{
+		const char *code = kentry_find_code(metacodes, meta->code);
+		assert(code != NULL);
+		if (meta->string[0] == '!')
+		{
+			printf("%s\t%s\n", code, meta->string + 1);
+		}
+		else
+			read_event(code, meta->string);
+	}
+
+	fprintf(stderr, "Done.\n");
+	printf("\n; end of file\n");
+	exit(0);
+}
diff -urNx '*.o' Nestopia137_orig/source/linux/input.cpp Nestopia137/source/linux/input.cpp
--- Nestopia137_orig/source/linux/input.cpp	2007-03-19 15:57:30.000000000 -0700
+++ Nestopia137/source/linux/input.cpp	2007-07-03 11:27:34.000000000 -0700
@@ -15,7 +15,6 @@
 #include "core/api/NstApiEmulator.hpp"
 #include "core/api/NstApiVideo.hpp"
 #include "core/api/NstApiSound.hpp"
-#include "core/api/NstApiInput.hpp"
 #include "core/api/NstApiMachine.hpp"
 #include "core/api/NstApiUser.hpp"
 #include "core/api/NstApiNsf.hpp"
@@ -26,119 +25,193 @@
 #include "settings.h"
 #include "auxio.h"
 #include "input.h"
+#include "kentry.h"
 
 static char linebuf[256];
 static FILE *infile;
 
-typedef struct
-{
-	char *string;
-	int code;
-} KEntry;
 
-static KEntry keycodes[] =
+// translate player control to Nes::Api code
+static int translate_code(const char *str)
 {
-	{ "ENTER", SDLK_RETURN },
-	{ "LCTRL", SDLK_LCTRL },
-	{ "RCTRL", SDLK_RCTRL },
-	{ "LALT", SDLK_LALT },
-	{ "RALT", SDLK_RALT },
-	{ "SPACE", SDLK_SPACE },
-	{ "LSHIFT", SDLK_LSHIFT },
-	{ "RSHIFT", SDLK_RSHIFT },
-	{ "TAB", SDLK_TAB },
-	{ "UP", SDLK_UP },
-	{ "DOWN", SDLK_DOWN },
-	{ "LEFT", SDLK_LEFT },
-	{ "RIGHT", SDLK_RIGHT },
-	{ "KPENTER", SDLK_KP_ENTER },
-	{ "KP0", SDLK_KP0 },
-	{ "KP1", SDLK_KP1 },
-	{ "KP2", SDLK_KP2 },
-	{ "KP3", SDLK_KP3 },
-	{ "KP4", SDLK_KP4 },
-	{ "KP5", SDLK_KP5 },
-	{ "KP6", SDLK_KP6 },
-	{ "KP7", SDLK_KP7 },
-	{ "KP8", SDLK_KP8 },
-	{ "KP9", SDLK_KP9 },
-	{ "KPDOT", SDLK_KP_PERIOD },
-	{ "KPSLASH", SDLK_KP_DIVIDE },
-	{ "KPSTAR", SDLK_KP_MULTIPLY },
-	{ "KPMINUS", SDLK_KP_MINUS },
-	{ "KPPLUS", SDLK_KP_PLUS },
-	{ "", -1 }
-};
+	int i = kentry_find_str(controlcodes, str);
+	if (i == -1)
+		std::cout << "Error understanding control type '" << str << "'\n";
+	return i;
+}
 
-static int translate_code(char *substr)
+// translate key descriptor into SDL keycode
+static bool translate_key(const char *substr, SDL_Event &evt)
 {
-	if (!strcmp(substr, "UP"))
-	{
-		return Nes::Api::Input::Controllers::Pad::UP;
-	}
-	else if (!strcmp(substr, "DN"))
-	{
-		return Nes::Api::Input::Controllers::Pad::DOWN;
-	}
-	else if (!strcmp(substr, "LT"))
+	evt.type = SDL_KEYDOWN;
+
+	if (substr[0] != '_')	// literal case
+		{
+		evt.key.keysym.sym = SDLKey(tolower(substr[0]));
+		return true;
+		}
+
+	if (substr[1] == '_')	// quoted _
+		{
+		evt.key.keysym.sym = SDLKey('_');
+		return true;
+		}
+
+	int i = kentry_find_str(keycodes, substr + 1);
+
+	if (i == -1)
 	{
-		return Nes::Api::Input::Controllers::Pad::LEFT;
+		std::cout << "unknown key " << substr << "\n";
+		return false;
 	}
-	else if (!strcmp(substr, "RT"))
+
+	evt.key.keysym.sym = SDLKey(i);
+	return true;
+}
+
+
+// translate event type into InputEvtT value
+static int translate_meta(const char *str)
+{
+	int i = kentry_find_str(metacodes, str);
+	if (i == -1)
+		std::cout << "invalid event type '" << str << "'\n";
+	return i;
+}
+
+
+// add a control to the list, fail if we're full
+bool add_control(const InputDefT &control, 
+ InputDefT *pcontrol, int &icontrol, int ccontrol)
+{
+	if (icontrol >= ccontrol)
 	{
-		return Nes::Api::Input::Controllers::Pad::RIGHT;
+		std::cout << "Too many controls (max " << ccontrol << ")\n";
+		return false;
 	}
-	else if (!strcmp(substr, "A"))
+	memcpy(pcontrol + icontrol++, &control, sizeof(control));
+
+	return true;
+}
+
+
+// translate joystick/gamepad spec:
+// _<num> ( A<axis #><PLUS|MINUS|+|-> | B<button #> )
+static bool translate_joy(const char* str, SDL_Event &evt)
+{
+	if (!isdigit(str[0]))
 	{
-		return Nes::Api::Input::Controllers::Pad::A;
+		std::cout << "Bad joypad number '" << str[0] << "' (expected 0-9)\n";
+		return false;
 	}
-	else if (!strcmp(substr, "B"))
+	evt.jbutton.which = str[0] - '0';
+
+	if (str[1] == 'A')		// axis
 	{
-		return Nes::Api::Input::Controllers::Pad::B;
+		evt.type = SDL_JOYAXISMOTION;
+		evt.jaxis.axis = str[2] - '0';
+		evt.jaxis.value = (str[3] == 'M' || str[3] == '-') ? -1 : 1;
 	}
-	else if (!strcmp(substr, "START"))
+	else if (str[1] == 'B')	// button
 	{
-		return Nes::Api::Input::Controllers::Pad::START;
+		evt.type = SDL_JOYBUTTONDOWN;
+		evt.jbutton.button = str[2] - '0';
+		if (isdigit(str[3]))	// 2 digit button number?
+			evt.jbutton.button = evt.jbutton.button * 10 + str[3] - '0';
 	}
-	else if (!strcmp(substr, "SELECT"))
+	else
 	{
-		return Nes::Api::Input::Controllers::Pad::SELECT;
+		std::cout << "Malformed joypad widget (A for axis, B for button)\n";
+		return false;
 	}
 
-	std::cout << "Error understanding control type " << substr << "\n";
-	return 0;
+	return true;
+}
+
+
+// helper for translate_event: punctuation or alphanumeric is valid
+inline static bool isvalid(char c)
+{
+	return isalnum(c) || ispunct(c);
 }
 
-static int translate_key(char *substr)
+
+// translate an input line event into an InputDefT
+// returns true on success, false on failure (and outputs error message)
+bool translate_event(char *linebuf, InputDefT *pcontrol, int &icontrol,
+ int ccontrol)
 {
-	int i;
+	InputDefT control;
 
-	if (substr[0] != '_')	// literal case
+	// break line into "words" and insert null terminators
+	char *pbuf = linebuf;
+	const char *words[10] = { 0 };
+
+	for (int iword = 0; iword < CRg(words) - 1 && isvalid(*pbuf); ++iword)
 	{
-		return substr[0];
+		words[iword] = pbuf;
+		for ( ; isvalid(*pbuf); ++pbuf);
+		if (*pbuf == '\0')
+			break;
+		*pbuf++ = '\0';
+		for ( ; isblank(*pbuf); ++pbuf);
 	}
 
-	if (substr[1] == '_')	// quoted _
+	int iword = 0;
+	const char *word = words[iword++];
+
+	// format is: <event> <key...> (where "key" could be a joystick button/axis)
+	//
+	// 'P<digits><code>' (e.g. P1START) means a player event
+	if (word[0] == 'P' && isdigit(word[1]))
 	{
-		return substr[1];
+		control.player = word[1] - '0';
+		if (control.player < 1 || control.player > 4)
+		{
+			std::cout << "Invalid player number (" << word[1] << ") in file\n";
+			return false;
+		}
+		control.codeout = translate_code(word + 2);
+	}
+	// non-player ("meta") event
+	else
+	{
+		control.player = 0; 	// non-player code
+		control.codeout = translate_meta(word);
 	}
 
-	i = 0;
-	while (keycodes[i].code != -1)
+	if (control.codeout == -1)
+		return false;
+
+	// now reading key(s) or joystick events
+	while ((word = words[iword++]) != NULL)
 	{
-		if (!strcmp(&substr[1], keycodes[i].string))
+		if (word[0] == '_' && word[1] == 'J')
 		{
-			return keycodes[i].code;
+			if (!translate_joy(word + 2, control.evt))
+			{
+				std::cout << "Malformed joypad spec\n";
+				return false;
+			}
 		}
-
-		i++;
+		else
+		{
+			if (!translate_key(word, control.evt))
+			{
+				std::cout << "Malformed key spec\n";
+				return false;
+			}
+		}
+		if (!add_control(control, pcontrol, icontrol, ccontrol))
+			return false;
 	}
-
-	printf("unknown key %s\n", substr);
-	return 0;
+	return true;
 }
 
-InputDefT *parse_input_file(void)
+
+const int ccontrolMax = 128;
+
+InputDefT *parse_input_file()
 {
 	InputDefT *controls;
 	int curentry = 0, i, j;
@@ -150,136 +223,35 @@
 
  	infile = fopen(dirname, "r");
 	if (!infile)
-	{
 		return NULL;
-	}
 
-	controls = (InputDefT *)malloc(sizeof(InputDefT) * 128);
-	memset(controls, 0, sizeof(InputDefT) * 128);
+	controls = (InputDefT *)malloc(sizeof(InputDefT) * ccontrolMax);
+	memset(controls, 0, sizeof(InputDefT) * ccontrolMax);
+
+	// pre-insert ALT + ENTER fullscreen toggle since our configuration file
+	// doesn't support modifiers
+	controls[curentry].evt.type = SDL_KEYDOWN;
+	controls[curentry].evt.key.keysym.sym = SDLK_RETURN;
+	controls[curentry].evt.key.keysym.mod = KMOD_LALT;
+	controls[curentry].player = 0;
+	controls[curentry++].codeout = FSCREEN;
 
 	while (!feof(infile))
 	{
 		fgets(linebuf, 256, infile);
 
-		if ((linebuf[0] != ';') && (isalnum(linebuf[0])))
+		if ((linebuf[0] != ';') && isalnum(linebuf[0]))
 		{
-			if (linebuf[0] == 'P')
-			{
-				controls[curentry].player = linebuf[1] - '1';
-				if (controls[curentry].player > 3)
-				{
-					std::cout << "Invalid player number in file\n";
-					free(controls);
-					return NULL;
-				}
-
-				memset(substr, 0, sizeof(substr));
-				i = 2;
-				do
-				{
-					substr[i-2] = linebuf[i];
-					i++;
-				} while (isalnum(linebuf[i]));
-
-				controls[curentry].codeout = translate_code(substr);
-
-				while (isblank(linebuf[i]))
-				{
-					i++;
-				}
-
-				j = 0;
-				memset(substr, 0, sizeof(substr));
-				do
-				{
-					substr[j++] = linebuf[i++];
-
-				} while (!isblank(linebuf[i]));
-
-				controls[curentry].kcode = translate_key(substr);
-//				printf("keycode [%s] => %d\n", substr, controls[curentry].kcode);
-
-				while (isblank(linebuf[i]))
-				{
-					i++;
-				}
-
-				j = 0;
-				i++;	// skip the _
-				memset(substr, 0, sizeof(substr));
-				do
-				{
-					substr[j++] = linebuf[i++];
-
-				} while (!isblank(linebuf[i]));
-
-				if (substr[0] == 'J')
-				{
-					controls[curentry].device = substr[1] - '0';
-					if ((controls[curentry].device < 0) || (controls[curentry].device > 9))
-					{
-						std::cout << "Bad joypad number (0-9)\n";
-						free(controls);
-						return NULL;
-					}
-
-					if (substr[2] == 'A')		// axis
-					{
-						controls[curentry].axis = substr[3] - '0';
-						controls[curentry].button = -1;
-						if (substr[4] == 'M')
-						{
-							controls[curentry].posneg = -1;
-						}
-						else
-						{
-							controls[curentry].posneg = 1;
-						}
-					}
-					else if (substr[2] == 'B')	// button
-					{
-						if (isdigit(substr[4]))	// 2 digit button number?
-						{
-							controls[curentry].button = (substr[3] - '0')*10;
-							controls[curentry].button += (substr[4] - '0');
-						}
-						else
-						{
-							controls[curentry].button = substr[3] - '0';
-						}
-						controls[curentry].axis = -1;
-					}
-					else
-					{
-				 		std::cout << "Malformed joypad widget (A for axis, B for button)\n";
-						free(controls);
-						return NULL;
-					}
-				}
-				else
-				{
-					std::cout << "Malformed joypad spec\n";
-					free(controls);
-					return NULL;
-				}
-
-//				printf("Entry %d: kcode %d codeout %d player %d\n", curentry,
-//					controls[curentry].kcode, controls[curentry].codeout, controls[curentry].player);
-
-				curentry++;
-			}
-			else
-			{
-				std::cout << "Bad line start at " << linebuf;
-				free(controls);
-				return NULL;
-			}
+			// leave space for the terminating entry
+			if (!translate_event(linebuf, controls, curentry, ccontrolMax - 1))
+				goto LFail;
 		}
 	}
 
-	controls[curentry].kcode = -1;
-	controls[curentry].codeout = 0;
 	controls[curentry].player = -1;
-
 	return controls;
+
+LFail:
+	free(controls);
+	return NULL;
 }
diff -urNx '*.o' Nestopia137_orig/source/linux/input.h Nestopia137/source/linux/input.h
--- Nestopia137_orig/source/linux/input.h	2007-03-17 15:37:26.000000000 -0700
+++ Nestopia137/source/linux/input.h	2007-07-03 12:02:43.000000000 -0700
@@ -1,18 +1,43 @@
 #ifndef INPUT_H_
 #define INPUT_H_
 
-typedef struct 
+#define CRg(rg) (sizeof(rg) / sizeof(rg[0]))
+
+#define DEADZONE (32768/3)
+
+
+// input events
+enum InputEvtT
+{
+	RESET,		// reset machine
+	QSAVE1,		// quick save state
+	QSAVE2,
+	QLOAD1,		// quick load state
+	QLOAD2,
+	SAVE,			// normal save (prompt)
+	LOAD,			// normal load (prompt)
+	MSAVE,		// movie save (prompt)
+	MLOAD,		// movie load (prompt)
+	MSTOP,		// movie stop
+	FLIP,			// flip disk
+	FSCREEN,	// full screen toggle
+	RBACK,		// rewind backward
+	RFORE,		// rewind forward
+	STOP,			// stop game
+	EXIT,			// stop game and exit emulator
+};
+
+
+struct InputDefT
 {
-	int kcode;
-	int device;
-	int axis;
-	int posneg;
-	int button;
-
-	int codeout;
-	int player;
-} InputDefT;
+	SDL_Event evt;	// event to match
+
+	int player;			// player action, -1 if not player-related
+	int codeout;		// Nes::Api::Input::Controllers::Pad constant if player,
+									//  InputEvtT otherwise
+};
+
 
-InputDefT *parse_input_file(void);
+InputDefT *parse_input_file();
 
 #endif
diff -urNx '*.o' Nestopia137_orig/source/linux/kentry.cpp Nestopia137/source/linux/kentry.cpp
--- Nestopia137_orig/source/linux/kentry.cpp	1969-12-31 16:00:00.000000000 -0800
+++ Nestopia137/source/linux/kentry.cpp	2007-07-03 12:47:43.000000000 -0700
@@ -0,0 +1,116 @@
+/*
+	NEStopia / Linux
+	David B. Robins (nestopia@davidrobins.net)
+	
+	kentry.cpp - key code mapping
+*/
+
+#include "kentry.h"
+#include "input.h"
+#include "core/api/NstApiInput.hpp"
+
+const KEntry keycodes[] =
+{
+	{ "ENTER", SDLK_RETURN },
+	{ "LCTRL", SDLK_LCTRL },
+	{ "RCTRL", SDLK_RCTRL },
+	{ "LALT", SDLK_LALT },
+	{ "RALT", SDLK_RALT },
+	{ "SPACE", SDLK_SPACE },
+	{ "LSHIFT", SDLK_LSHIFT },
+	{ "RSHIFT", SDLK_RSHIFT },
+	{ "TAB", SDLK_TAB },
+	{ "UP", SDLK_UP },
+	{ "DOWN", SDLK_DOWN },
+	{ "LEFT", SDLK_LEFT },
+	{ "RIGHT", SDLK_RIGHT },
+	{ "KPENTER", SDLK_KP_ENTER },
+	{ "KP0", SDLK_KP0 },
+	{ "KP1", SDLK_KP1 },
+	{ "KP2", SDLK_KP2 },
+	{ "KP3", SDLK_KP3 },
+	{ "KP4", SDLK_KP4 },
+	{ "KP5", SDLK_KP5 },
+	{ "KP6", SDLK_KP6 },
+	{ "KP7", SDLK_KP7 },
+	{ "KP8", SDLK_KP8 },
+	{ "KP9", SDLK_KP9 },
+	{ "KPDOT", SDLK_KP_PERIOD },
+	{ "KPSLASH", SDLK_KP_DIVIDE },
+	{ "KPSTAR", SDLK_KP_MULTIPLY },
+	{ "KPMINUS", SDLK_KP_MINUS },
+	{ "KPPLUS", SDLK_KP_PLUS },
+	{ "ESCAPE", SDLK_ESCAPE },
+	{ "BACKSPACE", SDLK_BACKSPACE },
+	{ "F1", SDLK_F1 },
+	{ "F2", SDLK_F2 },
+	{ "F3", SDLK_F3 },
+	{ "F4", SDLK_F4 },
+	{ "F5", SDLK_F5 },
+	{ "F6", SDLK_F6 },
+	{ "F7", SDLK_F7 },
+	{ "F8", SDLK_F8 },
+	{ "F9", SDLK_F9 },
+	{ "F10", SDLK_F10 },
+	{ "F11", SDLK_F11 },
+	{ "F12", SDLK_F12 },
+	{ NULL, -1 }
+};
+
+
+#define DEFINE_META(x) { #x, x }
+const KEntry metacodes[] =
+{
+	DEFINE_META(RESET),
+	DEFINE_META(QSAVE1),
+	DEFINE_META(QLOAD1),
+	DEFINE_META(QSAVE2),
+	DEFINE_META(QLOAD2),
+	DEFINE_META(SAVE),
+	DEFINE_META(LOAD),
+	DEFINE_META(MSAVE),
+	DEFINE_META(MLOAD),
+	DEFINE_META(MSTOP),
+	DEFINE_META(FLIP),
+	DEFINE_META(FSCREEN),
+	DEFINE_META(RBACK),
+	DEFINE_META(RFORE),
+	DEFINE_META(STOP),
+	DEFINE_META(EXIT),
+	{ NULL, -1 }
+};
+#undef DEFINE_META
+
+
+#define DEFINE_CONTROL(string, code) \
+	{ #string, Nes::Api::Input::Controllers::Pad::code }
+const KEntry controlcodes[] =
+{
+	DEFINE_CONTROL(UP, UP),
+	DEFINE_CONTROL(DN, DOWN),
+	DEFINE_CONTROL(LT, LEFT),
+	DEFINE_CONTROL(RT, RIGHT),
+	DEFINE_CONTROL(A, A),
+	DEFINE_CONTROL(B, B),
+	DEFINE_CONTROL(START, START),
+	DEFINE_CONTROL(SELECT, SELECT),
+	{ NULL, -1 }
+};
+#undef DEFINE_CONTROL
+
+
+// try to match str in kentry; returns -1 on failure
+int kentry_find_str(const KEntry *kentry, const char *str)
+{
+	for (; kentry->code != -1 && strcmp(str, kentry->string); ++kentry);
+	return kentry->code;
+}
+
+
+// try to match code in kentry; returns null on failure
+const char *kentry_find_code(const KEntry *kentry, int code)
+{
+	for (; kentry->code != -1 && kentry->code != code; ++kentry);
+	return kentry->string;
+}
+
diff -urNx '*.o' Nestopia137_orig/source/linux/kentry.h Nestopia137/source/linux/kentry.h
--- Nestopia137_orig/source/linux/kentry.h	1969-12-31 16:00:00.000000000 -0800
+++ Nestopia137/source/linux/kentry.h	2007-07-03 11:53:50.000000000 -0700
@@ -0,0 +1,26 @@
+/*
+	NEStopia / Linux
+	David B. Robins (nestopia@davidrobins.net)
+	
+	kentry.h - key code mapping
+*/
+
+#ifndef KENTRY_H_
+#define KENTRY_H_
+
+#include "SDL.h"
+
+typedef struct
+{
+	char *string;
+	int code;
+} KEntry;
+
+extern const KEntry keycodes[];
+extern const KEntry metacodes[];
+extern const KEntry controlcodes[];
+
+int kentry_find_str(const KEntry *kentry, const char *str);
+const char *kentry_find_code(const KEntry *kentry, int code);
+
+#endif // KENTRY_H_
diff -urNx '*.o' Nestopia137_orig/source/linux/main.cpp Nestopia137/source/linux/main.cpp
--- Nestopia137_orig/source/linux/main.cpp	2007-05-20 13:44:05.000000000 -0700
+++ Nestopia137/source/linux/main.cpp	2007-07-03 12:02:22.000000000 -0700
@@ -8,8 +8,14 @@
 #include <iostream>
 #include <fstream>
 #include <strstream>
+#include <sstream>
+#include <iomanip>
 #include <SDL/SDL.h>
 #include <string.h>
+#include <cassert>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
 
 #include "core/api/NstApiEmulator.hpp"
 #include "core/api/NstApiVideo.hpp"
@@ -281,7 +287,7 @@
 }
 
 // shuts everything down
-void nst_stop_playing(void)
+static void nst_stop_playing()
 {
 	if (playing)
 	{
@@ -327,179 +333,247 @@
 	playing = 0;
 }
 
-// when a key is pressed
-void nst_kdown(Nes::Api::Input::Controllers *controllers, int ksym)
-{
-	int i;
+#define CRg(rg) (sizeof(rg) / sizeof(rg[0]))
+std::string svst[2];
 
-	keys[ksym & 0xffff] = 1;
+std::string StrQuickSaveFile(int isvst)
+{
+	const char *home = getenv("HOME");
+	if (!home)
+		{
+		std::cout << "couldn't get home directory\n";
+		return "";
+		}
+	std::ostringstream ossFile;
+	ossFile << home;
+	ossFile << "/.nestopia/qsave";
 
-	i = 0;
-	while (ctl_defs[i].player != -1)
-	{
-		if (ctl_defs[i].kcode == ksym)
+	if (mkdir(ossFile.str().c_str(), 0777) && errno != EEXIST)
 		{
-			controllers->pad[ctl_defs[i].player].buttons |= ctl_defs[i].codeout;
+		std::cout << "couldn't make qsave directory: " << errno << "\n";
+		return "";
 		}
-		i++;
-	}
 
-	switch (ksym)
+	// get cartridge information
+	Nes::Api::Cartridge cart( emulator );
+	const Nes::Api::Cartridge::Info *pinfo = cart.GetInfo();
+	if (!pinfo)
 	{
-		case SDLK_F1:
-			movie_save = 1;
-			break;
-		case SDLK_F2:
-			movie_load = 1;
-			break;
-		case SDLK_F3:
-			movie_stop = 1;
-			break;
-		case SDLK_F4:
-			{
-				Nes::Api::Machine machine( emulator );
-				Nes::Api::Fds fds( emulator );
+		std::cout << "couldn't get cartridge info\n";
+		return "";
+	}
+	ossFile << "/" << std::setbase(16) << std::setfill('0') << std::setw(8)
+		<< pinfo->crc << std::string("_") << isvst << ".nst";
 
-				machine.Reset(true);
+	return ossFile.str();
+}
 
-				// put the disk system back to disk 0 side 0
-				fds.EjectDisk();
-				fds.InsertDisk(0, 0);
-			}
-			break;
-		case SDLK_F5:
-			{
-				Nes::Api::Fds fds( emulator );
+// save state to memory slot
+static void QuickSave(int isvst)
+{
+	std::string strFile = StrQuickSaveFile(isvst);
+	if (strFile.empty())
+		return;
 
-				if (fds.CanChangeDiskSide())
-				{
-					fds.ChangeSide();
-				}
-			}
-			break;
-		case SDLK_F7:
-			state_save = 1;
-			break;
-		case SDLK_F8:
-			state_load = 1;
-			break;
-		case SDLK_RETURN:
-			if (keys[SDLK_LALT])
-			{
-				int i;
+	Nes::Api::Machine machine( emulator );
+	std::ofstream os(strFile.c_str());
+	// use "NO_COMPRESSION" to make it easier to hack save states
+	Nes::Result res = machine.SaveState(os, Nes::Api::Machine::USE_COMPRESSION);
+}
 
-				if (SDL_NumJoysticks() > 0)
-				{
-					for (i = 0; i < SDL_NumJoysticks(); i++)
-					{
-						// we only? support 10 joysticks
-						if (i < 10)
-						{
-							SDL_JoystickClose(joy[i]);
-						}
-					}
 
-					SDL_JoystickEventState(SDL_ENABLE);	// turn on regular updates
-				}
+// restore state from memory slot
+static void QuickLoad(int isvst)
+{
+	std::string strFile = StrQuickSaveFile(isvst);
+	if (strFile.empty())
+		return;
 
-				SDL_ShowCursor(1);
-				SDL_Quit();
-				Settings->SetFullscreen(Settings->GetFullscreen()^1);
-				SetupVideo();
-			}
-			break;
-		case 'q':
-		case 27:
-			schedule_stop = 1;
-			break;
-	}
+	Nes::Api::Machine machine( emulator );
+  std::ifstream is(strFile.c_str());
+	Nes::Result res = machine.LoadState(is);
 }
 
-// when a key is released
-void nst_kup(Nes::Api::Input::Controllers *controllers, int ksym)
-{
-	int i;
 
-	keys[ksym & 0xffff] = 0;
-
-	i = 0;
-	while (ctl_defs[i].player != -1)
+// toggle fullscreen state
+static void ToggleFullscreen()
+{
+	if (SDL_NumJoysticks() > 0)
 	{
-		if (ctl_defs[i].kcode == ksym)
+		for (int i = 0; i < SDL_NumJoysticks(); i++)
 		{
-			controllers->pad[ctl_defs[i].player].buttons &= ~ctl_defs[i].codeout;
+			// we only? support 10 joysticks
+			if (i < 10)
+			{
+				SDL_JoystickClose(joy[i]);
+			}
 		}
-		i++;
+
+		SDL_JoystickEventState(SDL_ENABLE);	// turn on regular updates
 	}
+
+	SDL_ShowCursor(1);
+	SDL_Quit();
+	Settings->SetFullscreen(Settings->GetFullscreen()^1);
+	SetupVideo();
 }
 
-#define DEADZONE (32768/3)
 
-// joystick axis moved
-void nst_jaxis(Nes::Api::Input::Controllers *controllers, int ctrl, int axis, int value)
+// handle input event
+static void handle_input_event(InputEvtT inevt)
 {
-	int i;
-
-	if (value < DEADZONE && value > -DEADZONE/3)	// axis centered, turn off
+	switch (inevt)
 	{
-		i = 0;
-		while (ctl_defs[i].player != -1)
+	case MSAVE:
+		movie_save = 1;
+		break;
+	case MLOAD:
+		movie_load = 1;
+		break;
+	case MSTOP:
+		movie_stop = 1;
+		break;
+
+	case RESET:
 		{
-			if ((ctl_defs[i].device == ctrl) && (ctl_defs[i].axis == axis))
-			{
-				controllers->pad[ctl_defs[i].player].buttons &= ~ctl_defs[i].codeout;
-			}
+		Nes::Api::Machine machine( emulator );
+		Nes::Api::Fds fds( emulator );
 
-			i++;
+		machine.Reset(true);
+
+		// put the disk system back to disk 0 side 0
+		fds.EjectDisk();
+		fds.InsertDisk(0, 0);
 		}
+		break;
 
-		return;
+	case FLIP:
+		{
+		Nes::Api::Fds fds( emulator );
+
+		if (fds.CanChangeDiskSide())
+			fds.ChangeSide();
+		}
+		break;
+
+	case FSCREEN:
+		ToggleFullscreen();
+		break;
+
+	case RBACK:
+		Nes::Api::Rewinder(emulator).SetDirection(Nes::Api::Rewinder::BACKWARD);
+		break;
+	case RFORE:
+		Nes::Api::Rewinder(emulator).SetDirection(Nes::Api::Rewinder::FORWARD);
+		break;
+
+	case QSAVE1:
+		QuickSave(0);
+		break;
+	case QLOAD1:
+		QuickLoad(0);
+		break;
+	case QSAVE2:
+		QuickSave(1);
+		break;
+	case QLOAD2:
+		QuickLoad(1);
+		break;
+
+	case SAVE:
+		state_save = 1;
+		break;
+	case LOAD:
+		state_load = 1;
+		break;
+
+	case STOP:
+		schedule_stop = 1;
+		break;
+
+	case EXIT:
+		schedule_stop = 1;
+		nst_quit = 1;
+		break;
+
+	default:
+		assert(0);
 	}
+}
 
-	i = 0;
-	while (ctl_defs[i].player != -1)
+
+// match input event; if pind is not NULL, continue after it
+// on is set if the key/button is hit, clear if key is up/axis centered
+static const InputDefT *nst_match(const SDL_Event &evt, const InputDefT *pind,
+  bool &on)
+{
+	pind = (pind == NULL) ? ctl_defs : pind + 1;
+
+	bool match = false;
+	for (; pind->player != -1 && !match; ++pind)
 	{
-		if ((ctl_defs[i].device == ctrl) && (ctl_defs[i].axis == axis))
+		switch (evt.type)
 		{
-			if ((ctl_defs[i].posneg < 0) && (value < 0))
-			{
-				controllers->pad[ctl_defs[i].player].buttons |= ctl_defs[i].codeout;
-			}
-			else if ((ctl_defs[i].posneg > 0) && (value > 0)) 
-			{
-				controllers->pad[ctl_defs[i].player].buttons |= ctl_defs[i].codeout;
-			}
-		}
+		case SDL_KEYDOWN:
+		case SDL_KEYUP:
+			match = pind->evt.type == SDL_KEYDOWN
+				&& pind->evt.key.keysym.sym == evt.key.keysym.sym
+				&& pind->evt.key.keysym.mod == evt.key.keysym.mod;
+			on = (evt.key.state == SDL_PRESSED);
+			break;
+
+		case SDL_JOYBUTTONDOWN:
+		case SDL_JOYBUTTONUP:
+			match = pind->evt.type == SDL_JOYBUTTONDOWN
+				&& pind->evt.jbutton.which == evt.jbutton.which
+				&& pind->evt.jbutton.button == evt.jbutton.button;
+			on = (evt.jbutton.state == SDL_PRESSED);
+			break;
 
-		i++;
+		case SDL_JOYAXISMOTION:
+		{
+			const Sint16 nvalue = (abs(evt.jaxis.value) < DEADZONE) ? 0 :
+				(evt.jaxis.value < 0) ? -1 : 1; 	// normalized axis direction
+			match = pind->evt.type == evt.type
+				&& pind->evt.jaxis.which == evt.jaxis.which
+				&& pind->evt.jaxis.axis == evt.jaxis.axis
+				&& (nvalue == 0 || pind->evt.jaxis.value == nvalue);
+			on = nvalue != 0;
+			break;
+		}
+		}
+		if (match)
+			return pind;
 	}
+
+	return NULL;
 }
 
-// joy button pressed
-void nst_jbutton(Nes::Api::Input::Controllers *controllers, int ctrl, int button, int value)
-{
-	int i;
 
-	i = 0;
-	while (ctl_defs[i].player != -1)
+// try to dispatch an input event
+static void nst_dispatch(Nes::Api::Input::Controllers *controllers,
+ const SDL_Event &evt)
+{
+	bool on;
+	const InputDefT *pind = NULL;
+	while ((pind = nst_match(evt, pind, on)) != NULL)
 	{
-		if ((ctl_defs[i].device == ctrl) && (ctl_defs[i].button == button))
+		if (on)
 		{
-			if (value)
-			{
-				controllers->pad[ctl_defs[i].player].buttons |= ctl_defs[i].codeout;
-			}
+			if (pind->player == 0)
+				handle_input_event((InputEvtT)pind->codeout);
 			else
-			{
-				controllers->pad[ctl_defs[i].player].buttons &= ~ctl_defs[i].codeout;
-			}
+				controllers->pad[pind->player - 1].buttons |= pind->codeout;
+		}
+		else
+		{
+			if (pind->player != 0)
+				controllers->pad[pind->player - 1].buttons &= ~pind->codeout;
 		}
-
-		i++;
 	}
-
 }
 
+
 // for various file operations, usually called during image file load, power on/off and reset
 static void NST_CALLBACK DoFileIO(void* userData,Nes::Api::User::File operation,Nes::Api::User::FileData& data)
 {
@@ -975,20 +1049,11 @@
 							break;
 
 						case SDL_KEYDOWN:
-							nst_kdown(cNstPads, event.key.keysym.sym&0xffff);
-							break;
-
 						case SDL_KEYUP:
-							nst_kup(cNstPads, event.key.keysym.sym&0xffff);
-							break;
-
 						case SDL_JOYAXISMOTION:
-							nst_jaxis(cNstPads, event.jaxis.which, event.jaxis.axis, event.jaxis.value);
-							break;
-
-					    	case SDL_JOYBUTTONDOWN:
+			    	case SDL_JOYBUTTONDOWN:
 						case SDL_JOYBUTTONUP:
-							nst_jbutton(cNstPads, event.jbutton.which, event.jbutton.button, (event.jbutton.state == SDL_PRESSED) ? 1 : 0);
+							nst_dispatch(cNstPads, event);
 							break;
 					}	
 				}
@@ -997,15 +1062,6 @@
 				{
 					Nes::Api::Rewinder(emulator).EnableSound(true);
 				}
-
-				if (keys[SDLK_BACKSPACE])
-				{
-					Nes::Api::Rewinder(emulator).SetDirection(Nes::Api::Rewinder::BACKWARD);
-				}
-				else if (keys['\\'])
-				{
-					Nes::Api::Rewinder(emulator).SetDirection(Nes::Api::Rewinder::FORWARD);
-				}
 			}
 
 			m1sdr_TimeCheck();
