//
//  rui.c
//  MacRUI [Version 0.9]
//
//  Created by Bill Stevenson on 3 January 2005
//
//  Last Modified by Bill Stevenson on 31 July 2005 20:25
//

#include <Carbon/Carbon.h>
#include <ApplicationServices/ApplicationServices.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>

#define NUM_RECORDING_EVENT_TYPES 5
#define NUM_PLAYBACK_EVENT_TYPES 1
#define RECORD 0
#define PLAY 1
#define MOUSEACTION 0
#define KEYSTROKE 1
// maximum expected line length, for fgets
#define LINE_LENGTH 80
#define kShowMouse TRUE

OSStatus RUIRecordingEventOccurred(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData);
OSStatus RUIPlaybackEventOccurred(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData);

void startRecording();	// called on CTRL+R or GUI Record Button press
void stopRecording();	// called on CTRL+S or GUI Stop Button press
void startReplaying();	// called on CTRL+R (in play mode) or GUI Play Button press
void prepareToRecord();	// install the event handler, wait for record signal
void prepareToPlay();	// get ready to play back the log file, wait for play signal
void playLog();			// play back the log file of keyboard and mouse events


// note that keyboard character codes are found in Figure C2 of the document
// Inside Macintosh: Text available from http://developer.apple.com
char * keyStringForKeyCode(int keyCode);	// get the RUI representation of the Mac keycode
int keyCodeForKeyString(char * keyString);	// get the Mac keycode for the RUI representation

// Global Variables
int dieNow = 0;				// should RUI terminate (CTRL+S)
int logging = 0;			// Is RUI logging? (CTRL+R to start)
char *subjectName = NULL;	// Subject name, if specified
char *filename = NULL;		// Log file name
FILE *fd = NULL;			// Log file descriptor
int recordorplay = RECORD;	// Recording to file or playing from (not supported)

struct timeval thetime;		// for gettimeofday
long normalizer;			// subtract from current time to normalize
long currenttime;			// the current time in milliseconds

int main(int argc, char* argv[])
{
    // @TODO: playback option
    int ch;					// CLI flags
    int pandr = 0;			// make sure both p and r can't be specified
    int counter = 0;		// counter variable used outside loop  
	
	// process the command-line arguments
    while ((ch = getopt(argc, argv, "s:pr")) != -1 ) {
        switch (ch) {
            case 's':	// subject name specification
                subjectName = optarg;
                break;
            case 'p':	// playback (not supported)
                recordorplay = PLAY;
                pandr += 1;
                break;
            case 'r':	// recording
                recordorplay = RECORD;
                pandr += 1;
                break;
            default:	// invalid
                fprintf(stderr, "Usage: rui [-s \"Subject Name\"] [-p/-r] file\n");
                return (EXIT_FAILURE);
        }
    }
    
    // enforce that only one of -p and -r may be specified
    if (pandr > 1) {
        fprintf(stderr, "rui: only one of -p or -r may be used.\n");
        return (EXIT_FAILURE);
    }

    // bias the argv/argc to skip over the processed args
    argc -= optind;
    argv += optind;
    
    // get the log file parameter
    for (counter = 0; counter < argc; counter++) {
        if (counter == 1) {
            fprintf(stderr, "rui: illegal argument format\n");
            fprintf(stderr, "Usage: rui [-s \"Subject Name\"] [-p/-r] file\n");
            return (EXIT_FAILURE);
        }
        filename = argv[counter];

		// open file with access as appropriate        
        if (recordorplay == PLAY) {
            fd = fopen(filename, "r");
        } else { 
            fd = fopen(filename, "w");
        }
        if (fd == NULL) {
            fprintf(stderr, "could not open %s: error %d (%s)\n",
                    filename, errno, strerror(errno));
            return (EXIT_FAILURE);
        }
    }
    
    // Make sure only one subsequent parameter was given
    if (counter == 0) {
        fprintf(stderr, "rui: illegal argument format - no filename specified\n");
        fprintf(stderr, "Usage: rui [-s \"Subject Name\"] [-p/-r] file\n");
        return (EXIT_FAILURE);
    }
    
    // Get RUI ready to record or play, based off of mode
	if (recordorplay == RECORD)
		prepareToRecord();
	else if (recordorplay == PLAY)
		prepareToPlay();
	    
	return EXIT_SUCCESS;
}

// event handler for RUI recorder
OSStatus RUIRecordingEventOccurred(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData)
{
	// Determine class and kind of event
    int eventClass = GetEventClass(theEvent);
    int eventKind = GetEventKind(theEvent);

    /* Handle Keyboard Events */
    if((eventClass == kEventClassKeyboard) && (eventKind == kEventRawKeyDown)) /* key release implied */ {
        int keyCode, modifiers;		// what did the user press? any modifier keys down?

		// gather keystroke information
        GetEventParameter(theEvent, kEventParamKeyCode, typeInteger, NULL, sizeof(keyCode), NULL, &keyCode);
        GetEventParameter(theEvent, kEventParamKeyModifiers, typeInteger, NULL, sizeof(modifiers), NULL, &modifiers);
        
        // if ctrl+r, start logging (ignore during logging)
        if ((keyCode == 15) && (modifiers & controlKey) && (logging == 0)) {
			startRecording();
			return EXIT_SUCCESS;
        }
		
		// if we're not logging yet, wait for the next event (note this is below the startRecording() call because
		// we need to give RUI the opportunity to turn logging on (CTRL+R handling)
        if (logging == 0) return EXIT_SUCCESS;        

        // if ctrl+s (during logging), stop logging and quit
        if ((keyCode == 1) && (modifiers & controlKey) && (logging == 1)) {
			stopRecording();
			return EXIT_SUCCESS;
        }

        // What time is it?
        gettimeofday(&thetime, NULL);
        currenttime =(((thetime.tv_sec*1000000) + (thetime.tv_usec)) - normalizer);

        // It's a key, indicate this
        fprintf(fd, "%ld\tKEY\t", currenttime);

        // first check for modifiders
        if (modifiers & cmdKey)
            fprintf(fd, "COMMAND + ");
        if (modifiers & shiftKey)
            fprintf(fd, "SHIFT + ");
        if (modifiers & optionKey)
            fprintf(fd, "OPTION + ");
        if (modifiers & controlKey)
            fprintf(fd, "CTRL + ");
        if (modifiers & rightShiftKey)
            fprintf(fd, "SHIFT + ");
        if (modifiers & rightOptionKey)
            fprintf(fd, "OPTION + ");
        if (modifiers & rightControlKey)
            fprintf(fd, "CTRL + ");
        
		fprintf(fd, "%s\n", keyStringForKeyCode(keyCode));        
    } else if (eventClass == kEventClassMouse) {
        /* Handle Mouse Events */
        
        // We're often interested in the mouse location
        Point pointAsCarbonPoint;
        
        // if we're not logging, skip
        if (logging == 0) return EXIT_SUCCESS;

        // What time is it?
        gettimeofday(&thetime, NULL);
        currenttime =(((thetime.tv_sec*1000000) + (thetime.tv_usec)) - normalizer);
            
        // What kind of mouse event occurred
        switch (eventKind) {
            case kEventMouseDown:	// left click
                if (logging) 
                    fprintf(fd, "%ld\tPressed\tLeft\n", currenttime);
                break;
            
            case kEventMouseUp:		// left release
                if (logging) 
                    fprintf(fd, "%ld\tReleased\tLeft\n", currenttime);
                break;

            case kEventMouseMoved: 	// mouse movement
            case kEventMouseDragged: // We're treating mouse drags as mouse moves for parity with WinRUI
                GetMouse(&pointAsCarbonPoint);          
                if (logging) 
                    fprintf(fd, "%ld\tMoved\t%d\t%d\n", currenttime, pointAsCarbonPoint.h, pointAsCarbonPoint.v);
                break;
                
            default:
                fprintf(stderr, "Unexpected Event Kind %d\n", eventKind);
                return EXIT_FAILURE;
                break;
        }
    }
    
    return EXIT_SUCCESS;
}

void startRecording()
{
    DateTimeRec dateNow;	// holds the current date and time

	SysBeep(30);
	logging = 1;
	// note that recording has started
	fprintf(stderr, "rui: recording started - press ctrl+s to stop recording...\n");
	
	// Process the subject name (if specified)
	if (subjectName != NULL)
		fprintf(fd, "Subject Name: %s\n", subjectName);
	else
		fprintf(fd, "Subject Name: Not Defined\n");

	// Get date and time (nonscientific precision) for log file header
	GetTime(&dateNow);

	// Print nicely formatted date & time to log file
	fprintf(fd, "File Created: %d/%d/%d ", dateNow.month, dateNow.day, dateNow.year);
	// hours
	if ((dateNow.hour < 10) || ((dateNow.hour > 12) && (dateNow.hour < 22)))
		fprintf(fd, "0");
	if (dateNow.hour > 12)
		fprintf(fd, "%d:", dateNow.hour-12);
	else
		fprintf(fd, "%d:", dateNow.hour);
	// minutes
	if (dateNow.minute < 10)
		fprintf(fd, "0");
	fprintf(fd, "%d:", dateNow.minute);
	// seconds
	if (dateNow.second < 10)
		fprintf(fd, "0");
	fprintf(fd, "%d ", dateNow.second);
	
	// AM/PM
	 if (dateNow.hour >= 12)
		fprintf(fd, "PM\n");
	else
		fprintf(fd, "AM\n");
	
	fprintf(fd, "Elapsed Time\tAction\tX\tY\n");
	
	// Get the current time (scientific precision)
	gettimeofday(&thetime, NULL);
	
	// Get normalization factor such that logging starts at time 0
	normalizer = (thetime.tv_sec*1000000) + (thetime.tv_usec);
	
	// we need to set the initial mouse position into the log so that playback works properly
	// if there is a click before a motion
	Point pointAsCarbonPoint;
	GetMouse(&pointAsCarbonPoint);          
	fprintf(fd, "0\tMoved\t%d\t%d\n", pointAsCarbonPoint.h, pointAsCarbonPoint.v);
}

void stopRecording()
{
	SysBeep(30);
	fclose(fd);
	fprintf(stderr, "rui: recording stopped - log written to %s\n\n", filename);
	dieNow = 1;
}

void prepareToRecord()
{
	EventRecord event;		// holds an event for examination
	
    // Types of events to listen for
    EventTypeSpec eventTypes[NUM_RECORDING_EVENT_TYPES] = {{kEventClassKeyboard, kEventRawKeyDown}, {kEventClassMouse, kEventMouseUp}, {kEventClassMouse, kEventMouseDown}, {kEventClassMouse, kEventMouseMoved}, {kEventClassMouse, kEventMouseDragged}};
    
	// Install the event handler
    InstallEventHandler(GetEventMonitorTarget(), NewEventHandlerUPP(RUIRecordingEventOccurred), NUM_RECORDING_EVENT_TYPES, eventTypes, nil, nil);

    // Tell user that RUI is ready to record
    fprintf(stderr, "rui: standing by - press ctrl+r to start recording...\n");
    
    // event loop - get events until ctrl+s
	do {
        WaitNextEvent((everyEvent),&event,GetCaretTime(),nil);
	} while (dieNow == 0);
}

void prepareToPlay()
{
	EventRecord event;		// holds an event for examination

    // Types of events to listen for -- here, just the keyboard, as we're only listening for a signal to start playing
    EventTypeSpec eventTypes[1] = {{kEventClassKeyboard, kEventRawKeyDown}};
    
	// Install the event handler
    InstallEventHandler(GetEventMonitorTarget(), NewEventHandlerUPP(RUIPlaybackEventOccurred), NUM_PLAYBACK_EVENT_TYPES, eventTypes, nil, nil);

	// Tell user that RUI is ready to replay the log
    fprintf(stderr, "rui: standing by - press ctrl+r to start replay...\n");

    // event loop - listen for CTRL+R
	do {
        WaitNextEvent((everyEvent), &event, GetCaretTime(), nil);
	} while (dieNow == 0);
}

// event handler for RUI playback
// we're really only listening for CTRL+R - when we see it, we call playLog()
OSStatus RUIPlaybackEventOccurred(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData)
{
	// Determine class and kind of event
	int eventClass = GetEventClass(theEvent);
	int eventKind = GetEventKind(theEvent);

	// We're only looking for keyboard events
	if((eventClass == kEventClassKeyboard) && (eventKind == kEventRawKeyDown)) /* key release implied */ {
		int keyCode, modifiers;		// what did the user press? any modifier keys down?

		// gather keystroke information
		GetEventParameter(theEvent, kEventParamKeyCode, typeInteger, NULL, sizeof(keyCode), NULL, &keyCode);
		GetEventParameter(theEvent, kEventParamKeyModifiers, typeInteger, NULL, sizeof(modifiers), NULL, &modifiers);
		
		// if ctrl+r, start playback
		if ((keyCode == 15) && (modifiers & controlKey)) {
			playLog();
			dieNow = 1;
		}
	}
}

void playLog()
{
	int mouseOrKeyboard;	// is it a mouse click or a keystroke
	char line[LINE_LENGTH];
	char *arg;
	char *keyString;
	CGPoint mouseLoc;
	
	enum events {KEY=1, Moved, Pressed, Released};
	int event;

	enum buttons {Left=1}; // @TODO: Add Right Mouse Support, Control-Click support?
	char *buttonString;
	int button;
	
	long timestamp = 0, oldtimestamp = 0, difference = 0;
	struct timespec duration;
	
	int xcoord = 0, ycoord = 0;
	int modCOMMAND = 0, modSHIFT = 0, modOPTION = 0, modCTRL = 0;

	int foo = 0;		// counter variable
	int numToSkip = 0;	// number of modifier key tokens to skip over

	fprintf(stderr, "rui: playing log file...\n");
	
	// the first three lines of the log are not useful, throw them away
	fgets(line, LINE_LENGTH, fd);
	fprintf(stderr, "%s", line);
	fgets(line, LINE_LENGTH, fd);
	fprintf(stderr, "%s", line);
	fgets(line, LINE_LENGTH, fd);
	fprintf(stderr, "%s", line);

	// at this point, provided we have a valid log file, we have a log of the following format:
	// TIME [KEY/Moved/Pressed/Released] ... (resolves to)
	// TIME KEY [modifier +]+ key
	// TIME Moved x y
	// TIME Pressed button
	// TIME Released button
	while (fgets(line, LINE_LENGTH, fd) != NULL) {
		// get the timestamp
		arg = strtok(line, "\t");
		oldtimestamp = timestamp;
		timestamp = atol(arg);
		
		// sleep for the appropriate amount of time
		difference = timestamp-oldtimestamp;
		//fprintf(stderr, "Going to sleep for %ld\n", difference);
		duration.tv_sec = difference / 1000000;
		duration.tv_nsec = (difference % 1000000) * 1000;
		nanosleep(&duration, NULL);
		
		// what kind of event is it?
		arg = strtok(NULL, "\t");
		if (strcmp(arg, "KEY") == 0)
			event = KEY;
		else if (strcmp(arg, "Moved") == 0)
			event = Moved;
		else if (strcmp(arg, "Pressed") == 0)
			event = Pressed;
		else if (strcmp(arg, "Released") == 0)
			event = Released;

		switch (event) {
			case KEY:
				// [modifier +]+ key -- (modifier={CTRL, SHIFT, OPTION, COMMAND})
				fprintf(stderr, "%ld\tKEY\t", timestamp, event);
				arg = strtok(NULL, "\n"); // pull off the newline
				strcpy(line, arg);
				
				if (strstr(line, "CTRL") != NULL) {
					modCTRL = 1;
					fprintf(stderr, "CTRL + ");
				}
				if (strstr(line, "SHIFT") != NULL) {
					modSHIFT = 1;
					fprintf(stderr, "SHIFT + ");
				}
				if (strstr(line, "OPTION") != NULL) {
					modOPTION = 1;
					fprintf(stderr, "OPTION + ");
				}
				if (strstr(line, "COMMAND") != NULL) {
					modCOMMAND = 1;
					fprintf(stderr, "COMMAND + ");
				}
				
				// skip over the modifier keys
				numToSkip = modCTRL + modSHIFT + modOPTION + modCOMMAND;
				arg = strtok(line, "+ ");
				for (foo = 0; foo < numToSkip - 0 ; foo++) {
					arg=strtok(NULL, "+ ");
				}
								
				// set any modifiers
				if (modCTRL == 1) CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)59, true);
				if (modSHIFT == 1) CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)56, true);
				if (modOPTION == 1) CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)58, true);
				if (modCOMMAND == 1) CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)55, true);
				
				// post the keyboard event for the keystroke
				CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)keyCodeForKeyString(arg), true);
				CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)keyCodeForKeyString(arg), false);

				// unset any modifiers
				if (modCTRL == 1) CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)59, false);
				if (modSHIFT == 1) CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)56, false);
				if (modOPTION == 1) CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)58, false);
				if (modCOMMAND == 1) CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)55, false);

				
				// reset modifier keys and tokenizer
				modCTRL = modSHIFT = modOPTION = modCOMMAND = numToSkip = 0;
				
				break;
			case Moved:
				// x y
				xcoord = atoi(strtok(NULL, "\t"));
				ycoord = atoi(strtok(NULL, "\t"));

				fprintf(stderr, "%ld\tMoved\t%d\t%d\n", timestamp, xcoord, ycoord);
				mouseLoc.x = xcoord; mouseLoc.y = ycoord;
				CGWarpMouseCursorPosition(mouseLoc);
				//CGPostMouseEvent(mouseLoc, kShowMouse, 1, 0);
				break;
			case Pressed:
				// button
				buttonString = strtok(NULL, "\n");
				if (strcmp(buttonString, "Left") == 0)
					button = Left;
				else {
					fprintf(stderr, "Unknown button %s!\n", buttonString);
					exit(EXIT_FAILURE);
				}
				fprintf(stderr, "%ld\tPressed\t%s\n", timestamp, buttonString);
				
				CGPostMouseEvent(mouseLoc, kShowMouse, 1, 1);
				break;
			case Released:
				// button
				buttonString = strtok(NULL, "\n");
				if (strcmp(buttonString, "Left") == 0)
					button = Left;
				else {
					fprintf(stderr, "Unknown button %s!\n", buttonString);
					exit(EXIT_FAILURE);
				}
				fprintf(stderr, "%ld\tReleased\t%s\n", timestamp, buttonString);
				
				CGPostMouseEvent(mouseLoc, kShowMouse, 1, 0);
				break;
		}

	}

	if (mouseOrKeyboard == MOUSEACTION) {
		//CGPostMouseEvent(mouseLoc, kShowMouse, 1, 1);
		//CGPostMouseEvent(mouseLog, kShowMouse, 1, 0);
	} else if (mouseOrKeyboard == KEYSTROKE) {
	
	}
	
	fprintf(stderr, "rui: done playing log file...\n");
}

char * keyStringForKeyCode(int keyCode)
{
	// Proper key detection seems to want a switch statement, unfortunately
	switch (keyCode) {
		case 0: return("a");
		case 1: return("s");
		case 2: return("d");
		case 3: return("f");
		case 4: return("h");
		case 5: return("g");
		case 6: return("z");
		case 7: return("x");
		case 8: return("c");
		case 9: return("v");
		// what is 10?
		case 11: return("b");
		case 12: return("q");
		case 13: return("w");
		case 14: return("e");
		case 15: return("r");
		case 16: return("y");
		case 17: return("t");
		case 18: return("1");
		case 19: return("2");
		case 20: return("3");
		case 21: return("4");
		case 22: return("6");
		case 23: return("5");
		case 24: return("=");
		case 25: return("9");
		case 26: return("7");
		case 27: return("-");
		case 28: return("8");
		case 29: return("0");
		case 30: return("]");
		case 31: return("o");
		case 32: return("u");
		case 33: return("[");
		case 34: return("i");
		case 35: return("p");
		case 36: return("RETURN");
		case 37: return("l");
		case 38: return("j");
		case 39: return("'");
		case 40: return("k");
		case 41: return(";");
		case 42: return("\\");
		case 43: return(",");
		case 44: return("/");
		case 45: return("n");
		case 46: return("m");
		case 47: return(".");
		case 48: return("TAB");
		case 49: return("SPACE");
		case 50: return("`");
		case 51: return("DELETE");
		case 52: return("ENTER");
		case 53: return("ESCAPE");
		
		// some more missing codes abound, reserved I presume, but it would
		// have been helpful for Apple to have a document with them all listed
		
		case 65: return(".");
			
		case 67: return("*");
		
		case 69: return("+");
		
		case 71: return("CLEAR");
		
		case 75: return("/");
		case 76: return("ENTER");   // numberpad on full kbd
		
		case 78: return("-");
		
		case 81: return("=");
		case 82: return("0");
		case 83: return("1");
		case 84: return("2");
		case 85: return("3");
		case 86: return("4");
		case 87: return("5");
		case 88: return("6");
		case 89: return("7");
			
		case 91: return("8");
		case 92: return("9");
		
		case 96: return("F5");
		case 97: return("F6");
		case 98: return("F7");
		case 99: return("F3");
		case 100: return("F8");
		case 101: return("F9");

		case 103: return("F11");

		case 105: return("F13");

		case 107: return("F14");

		case 109: return("F10");

		case 111: return("F12");

		case 113: return("F15");
		case 114: return("HELP");
		case 115: return("HOME");
		case 116: return("PGUP");
		case 117: return("DELETE");  // full keyboard right side numberpad
		case 118: return("F4");
		case 119: return("END");
		case 120: return("F2");
		case 121: return("PGDN");
		case 122: return("F1");
		case 123: return("LEFT");
		case 124: return("RIGHT");
		case 125: return("DOWN");
		case 126: return("UP");

		default:
			// Unknown key, bail and note that RUI needs improvement
			fprintf(stderr, "%ld\tKey\t%c (DEBUG: %d)\n", currenttime, keyCode);
			exit(EXIT_FAILURE);
	}
}

int keyCodeForKeyString(char * keyString)
{
	if (strcmp(keyString, "a") == 0) return 0;
	if (strcmp(keyString, "s") == 0) return 1;
	if (strcmp(keyString, "d") == 0) return 2;
	if (strcmp(keyString, "f") == 0) return 3;
	if (strcmp(keyString, "h") == 0) return 4;
	if (strcmp(keyString, "g") == 0) return 5;
	if (strcmp(keyString, "z") == 0) return 6;
	if (strcmp(keyString, "x") == 0) return 7;
	if (strcmp(keyString, "c") == 0) return 8;
	if (strcmp(keyString, "v") == 0) return 9;
	// what is 10?
	if (strcmp(keyString, "b") == 0) return 11;
	if (strcmp(keyString, "q") == 0) return 12;
	if (strcmp(keyString, "w") == 0) return 13;
	if (strcmp(keyString, "e") == 0) return 14;
	if (strcmp(keyString, "r") == 0) return 15;
	if (strcmp(keyString, "y") == 0) return 16;
	if (strcmp(keyString, "t") == 0) return 17;
	if (strcmp(keyString, "1") == 0) return 18;
	if (strcmp(keyString, "2") == 0) return 19;
	if (strcmp(keyString, "3") == 0) return 20;
	if (strcmp(keyString, "4") == 0) return 21;
	if (strcmp(keyString, "6") == 0) return 22;
	if (strcmp(keyString, "5") == 0) return 23;
	if (strcmp(keyString, "=") == 0) return 24;
	if (strcmp(keyString, "9") == 0) return 25;
	if (strcmp(keyString, "7") == 0) return 26;
	if (strcmp(keyString, "-") == 0) return 27;
	if (strcmp(keyString, "8") == 0) return 28;
	if (strcmp(keyString, "0") == 0) return 29;
	if (strcmp(keyString, "]") == 0) return 30;
	if (strcmp(keyString, "o") == 0) return 31;
	if (strcmp(keyString, "u") == 0) return 32;
	if (strcmp(keyString, "[") == 0) return 33;
	if (strcmp(keyString, "i") == 0) return 34;
	if (strcmp(keyString, "p") == 0) return 35;
	if (strcmp(keyString, "RETURN") == 0) return 36;
	if (strcmp(keyString, "l") == 0) return 37;
	if (strcmp(keyString, "j") == 0) return 38;
	if (strcmp(keyString, "'") == 0) return 39;
	if (strcmp(keyString, "k") == 0) return 40;
	if (strcmp(keyString, ";") == 0) return 41;
	if (strcmp(keyString, "\\") == 0) return 42;
	if (strcmp(keyString, ",") == 0) return 43;
	if (strcmp(keyString, "/") == 0) return 44;
	if (strcmp(keyString, "n") == 0) return 45;
	if (strcmp(keyString, "m") == 0) return 46;
	if (strcmp(keyString, ".") == 0) return 47;
	if (strcmp(keyString, "TAB") == 0) return 48;
	if (strcmp(keyString, "SPACE") == 0) return 49;
	if (strcmp(keyString, "`") == 0) return 50;
	if (strcmp(keyString, "DELETE") == 0) return 51;
	if (strcmp(keyString, "ENTER") == 0) return 52;
	if (strcmp(keyString, "ESCAPE") == 0) return 53;

	// some more missing codes abound, reserved I presume, but it would
	// have been helpful for Apple to have a document with them all listed

	if (strcmp(keyString, ".") == 0) return 65;

	if (strcmp(keyString, "*") == 0) return 67;

	if (strcmp(keyString, "+") == 0) return 69;

	if (strcmp(keyString, "CLEAR") == 0) return 71;

	if (strcmp(keyString, "/") == 0) return 75;
	if (strcmp(keyString, "ENTER") == 0) return 76;  // numberpad on full kbd

	if (strcmp(keyString, "=") == 0) return 78;
	
	if (strcmp(keyString, "=") == 0) return 81;
	if (strcmp(keyString, "0") == 0) return 82;
	if (strcmp(keyString, "1") == 0) return 83;
	if (strcmp(keyString, "2") == 0) return 84;
	if (strcmp(keyString, "3") == 0) return 85;
	if (strcmp(keyString, "4") == 0) return 86;
	if (strcmp(keyString, "5") == 0) return 87;
	if (strcmp(keyString, "6") == 0) return 88;
	if (strcmp(keyString, "7") == 0) return 89;
	
	if (strcmp(keyString, "8") == 0) return 91;
	if (strcmp(keyString, "9") == 0) return 92;

	if (strcmp(keyString, "F5") == 0) return 96;
	if (strcmp(keyString, "F6") == 0) return 97;
	if (strcmp(keyString, "F7") == 0) return 98;
	if (strcmp(keyString, "F3") == 0) return 99;
	if (strcmp(keyString, "F8") == 0) return 100;
	if (strcmp(keyString, "F9") == 0) return 101;
	
	if (strcmp(keyString, "F11") == 0) return 103;
	
	if (strcmp(keyString, "F13") == 0) return 105;
	
	if (strcmp(keyString, "F14") == 0) return 107;
	
	if (strcmp(keyString, "F10") == 0) return 109;
	
	if (strcmp(keyString, "F12") == 0) return 111;

	if (strcmp(keyString, "F15") == 0) return 113;
	if (strcmp(keyString, "HELP") == 0) return 114;
	if (strcmp(keyString, "HOME") == 0) return 115;
	if (strcmp(keyString, "PGUP") == 0) return 116;
	if (strcmp(keyString, "DELETE") == 0) return 117;
	if (strcmp(keyString, "F4") == 0) return 118;
	if (strcmp(keyString, "END") == 0) return 119;
	if (strcmp(keyString, "F2") == 0) return 120;
	if (strcmp(keyString, "PGDN") == 0) return 121;
	if (strcmp(keyString, "F1") == 0) return 122;
	if (strcmp(keyString, "LEFT") == 0) return 123;
	if (strcmp(keyString, "RIGHT") == 0) return 124;
	if (strcmp(keyString, "DOWN") == 0) return 125;
	if (strcmp(keyString, "UP") == 0) return 126;

	fprintf(stderr, "keyString %s Not Found. Aborting...\n", keyString);
	exit(EXIT_FAILURE);
}
