home *** CD-ROM | disk | FTP | other *** search
- /*
- * JOURNAL.C - Records all mouse and keyboard activity so that
- * it can be played back for demonstration of products,
- * reporting errors, etc.
- *
- * Copyright (c) 1987 by Davide P. Cervone
- * You may use this code provided this copyright notice is kept intact.
- */
-
- #include "journal.h"
-
- /*
- * Version number and author:
- */
- char version[32] = "Journal v1.0 (June 1987)";
- char *author = "Copyright (c) 1987 by Davide P. Cervone";
-
-
- /*
- * Macros used to check for end-of-journal
- */
- #define CTRL_AMIGA (IEQUALIFIER_CONTROL | IEQUALIFIER_LCOMMAND)
- #define KEY_E 0x12
- #define CTRL_AMIGA_E(e) ((((e)->ie_Qualifier & CTRL_AMIGA) == CTRL_AMIGA) &&\
- ((e)->ie_Code == KEY_E))
-
- /*
- * Match a command-line argument against a string (case insensitive)
- */
- #define ARGMATCH(s) (stricmp(s,*Argv) == 0)
-
- /*
- * Functions that JOURNAL can perform
- */
- #define SHOW_USAGE 0
- #define WRITE_JOURNAL 1
- #define JUST_EXIT 2
-
- /*
- * Largest mouse move we want to record
- */
- #define MAXMOUSEMOVE 32
-
- /*
- * Macros to tell whether a mouse movement event can be compressed with
- * other mouse movement events
- */
- #define MOUSEMOVE(e)\
- ((e)->ie_Class == IECLASS_RAWMOUSE && (e)->ie_Code == IECODE_NOBUTTON &&\
- ((e)->ie_Qualifier & IEQUALIFIER_RELATIVEMOUSE))
- #define BIGX(e) ((e)->ie_X >= XMINMOUSE || (e)->ie_X <= -XMINMOUSE)
- #define BIGY(e) ((e)->ie_Y >= YMINMOUSE || (e)->ie_Y <= -YMINMOUSE)
- #define BIGTICKS(e) ((e)->my_Ticks > LONGTIME)
- #define NOTSAVED(e) ((e)->my_Saved == FALSE)
- #define SETSAVED(e) ((e)->my_Saved = TRUE)
-
-
- /*
- * Global Variables:
- */
-
- struct MsgPort *InputPort = NULL; /* Port used to talk to Input.Device */
- struct IOStdReq *InputBlock = NULL; /* request block used with Input.Device */
- struct Task *theTask = NULL; /* pointer to our task */
- LONG InputDevice = 0; /* flag whether Input.Device is open */
- LONG theSignal = 0; /* signal used when an event is ready */
- LONG ErrSignal = 0; /* signal used when an error occured */
- LONG theMask; /* 1 << theSignal */
- LONG ErrMask; /* 1 << ErrSignal */
-
- UWORD Ticks = 0; /* number of timer ticks between events */
- LONG TimerMics = 0; /* last timer event's micros field */
-
- WORD xmove = XDEFMIN; /* distance to compress into one event */
- WORD ymove = YDEFMIN; /* distance to compress into one event */
- WORD smoothxmove = 1; /* distance for smoothed events */
- WORD smoothymove = 1; /* distnace for smoothed events */
- WORD xminmove, yminmove; /* distance actually in use */
- UWORD SmoothMask = IEQUALIFIER_LCOMMAND;
- /* what keys are required for smoothing */
- UWORD SmoothTrigger = 0xFFFF; /* any of these keys trigger smoothing */
-
- int Action = WRITE_JOURNAL; /* action to be perfomed by JOURNAL */
- int ArgMatched = FALSE; /* TRUE if a parameter matched OK */
- int Argc; /* global version of argc */
- char **Argv; /* global version of argv */
-
- struct InputEvent **EventPtr = NULL; /* pointer to (pointer to next event) */
- struct InputEvent *OldEvent = NULL; /* pointer to last event */
- struct SmallEvent TinyEvent; /* packed event (ready to record) */
-
- FILE *OutFile = NULL; /* where the events will be written */
- char *JournalFile = NULL; /* name of the output file */
-
- int NotDone = TRUE; /* continue looking for events? */
- int NotFirstEvent = FALSE; /* TRUE after an event was recorded */
- int PointerNotHomed = TRUE; /* TRUE until pointer is moved */
-
- struct Interrupt HandlerData = /* used to add an input handler */
- {
- {NULL, NULL, 0, 51, NULL}, /* Node structure (nl_Pri = 51) */
- NULL, /* data pointer */
- &myHandlerStub /* code pointer */
- };
-
- struct InputEvent PointerToHome = /* event to put pointer in upper-left */
- {
- NULL, /* pointer to next event */
- IECLASS_RAWMOUSE, /* ie_Class = RAWMOUSE */
- 0, /* ie_SubClass */
- IECODE_NOBUTTON, /* ie_Code = NOBUTTON (just a move) */
- IEQUALIFIER_RELATIVEMOUSE, /* ie_Qualifier = relative move */
- {-1000,-1000}, /* move far to left and top */
- {0L,0L} /* seconds and micros */
- };
-
- struct SmallEvent TimeEvent = /* pause written at beginning of file */
- {
- {0xE2, 0, 0}, /* MOUSEMOVE, NOBUTTON, X=0, Y=0 */
- IEQUALIFIER_RELATIVEMOUSE, /* Qualifier */
- 0x00A00000 /* 1 second pause */
- };
-
-
- /*
- * myHandler()
- *
- * This is the input handler that makes copies of the input events and sends
- * them the to main process to be written to the output file.
- *
- * The first time around, we add the PointerToHome event into the stream
- * so that the pointer is put into a known position.
- *
- * We check the event type of each event in the list, and do the following:
- * for Timer events, we increment the tick count (which tells how many ticks
- * have occured since the last recorded event); for raw key events, we check
- * whether a CTRL-AMIGA-E has been pressed (if so, we signal the main process
- * with a CTRL-E which tells it to remove the handler and quit); for raw
- * mouse and raw key events, we allocate memory for a new copy of the event
- * (and signal an error if we can't), and copy the pertinent information
- * from the current event into the copy event and mark it as not-yet-saved.
- * We link it into the copied-event list (via EventPtr), and signal the
- * main task that a new event is ready, and then zero the tick count.
- *
- * Any other type of event is ignored.
- *
- * When we are through with the event list, we return it so that Intuition
- * can use it to do its thing.
- */
-
- struct InputEvent *myHandler(event,data)
- struct InputEvent *event;
- APTR data;
- {
- struct InputEvent *theEvent = event;
- struct InputEvent *theCopy;
-
- Forbid();
- if (PointerNotHomed)
- {
- PointerToHome.ie_NextEvent = event;
- event = &PointerToHome;
- PointerNotHomed = FALSE;
- }
- while(theEvent)
- {
- switch(theEvent->ie_Class)
- {
- case IECLASS_TIMER:
- Ticks++;
- TimerMics = theEvent->ie_Mics;
- break;
-
- case IECLASS_RAWKEY:
- if (CTRL_AMIGA_E(theEvent)) Signal(theTask,SIGBREAKF_CTRL_E);
-
- case IECLASS_RAWMOUSE:
- theCopy = NEWEVENT;
- if (theCopy == NULL)
- {
- Signal(theTask,ErrMask);
- } else {
- theCopy->ie_NextEvent = NULL;
- theCopy->ie_Class = theEvent->ie_Class;
- theCopy->ie_Code = theEvent->ie_Code;
- theCopy->ie_Qualifier = theEvent->ie_Qualifier;
- theCopy->ie_EventAddress = theEvent->ie_EventAddress;
- theCopy->my_Time = TIME;
- theCopy->my_Ticks = Ticks;
- theCopy->my_Saved = FALSE;
- *EventPtr = theCopy;
- EventPtr = &(theCopy->ie_NextEvent);
- Signal(theTask,theMask);
- Ticks = 0;
- }
- break;
- }
- theEvent = theEvent->ie_NextEvent;
- }
- Permit();
- return(event);
- }
-
-
- /*
- * Ctrl_C()
- *
- * Dummy routine to disable Lattice-C CTRL-C trapping.
- */
-
- #ifndef MANX
- int Ctrl_C()
- {
- return(0);
- }
- #endif
-
-
- /*
- * DoExit()
- *
- * General purpose exit routine. If 's' is not NULL, then print an
- * error message with up to three parameters. Free any memory, close
- * any open files, delete any ports, free any used signals, etc.
- */
-
- void DoExit(s,x1,x2,x3)
- char *s, *x1, *x2, *x3;
- {
- long status = 0;
-
- if (s != NULL)
- {
- printf(s,x1,x2,x3);
- printf("\n");
- status = RETURN_ERROR;
- }
- if (OldEvent) FREEVENT(OldEvent);
- if (OutFile) fclose(OutFile);
- if (InputDevice) CloseDevice(InputBlock);
- if (InputBlock) DeleteStdIO(InputBlock);
- if (InputPort) DeletePort(InputPort);
- if (theSignal) FreeSignal(theSignal);
- if (ErrSignal) FreeSignal(ErrSignal);
- exit(status);
- }
-
-
- /*
- * CheckNumber()
- *
- * Check a command-line argument for the given keyword, and if it matches,
- * makes sure that the next parameter is a positive numeric value that
- * is less than the maximum expected value.
- */
-
- void CheckNumber(keyword,value)
- char *keyword;
- WORD *value;
- {
- long lvalue = *value;
-
- if (Argc > 1 && ARGMATCH(keyword))
- {
- ArgMatched = TRUE;
- Argc--;
- if (sscanf(*(++Argv),"%ld",&lvalue) != 1)
- {
- printf("%s must be numeric: '%s'\n",keyword,*Argv);
- Action = JUST_EXIT;
- }
- if (lvalue < 1 || lvalue > MAXMOUSEMOVE)
- {
- printf("%s must be positive and less than %d: '%ld'\n",
- keyword,MAXMOUSEMOVE,lvalue);
- Action = JUST_EXIT;
- }
- *value = lvalue;
- }
- }
-
-
- /*
- * CheckHexNum()
- *
- * Check a command-line argument for the given keyword, and if it
- * matches, make sure that the next parameter is a legal HEX value.
- */
-
- void CheckHexNum(keyword,value)
- char *keyword;
- WORD *value;
- {
- ULONG lvalue = *value;
-
- if (Argc > 1 && ARGMATCH(keyword))
- {
- ArgMatched = TRUE;
- Argc--;
- if (sscanf(*(++Argv),"%lx",&lvalue) != 1)
- {
- printf("%s must be a HEX number: '%s'\n",keyword,*Argv);
- Action = JUST_EXIT;
- }
- *value = lvalue;
- }
- }
-
-
- /*
- * ParseArguements()
- *
- * Check that all the command-line arguments are valid and set the
- * proper variables as requested by the user. If no keyword is specified,
- * assume "TO". If no file is specified, then show the usage. DX and DY
- * set the "granularity" of the mouse moves recorded (moves are combined
- * into a single event until it's movement excedes either DX or DY).
- * Similarly, SMOOTHX and SMOOTHY set alternate DX and DY values
- * for when extra precision is needed (i.e., when drawing curves in a paint
- * program). SMOOTH specifies what qualifier keys MUST be present to
- * activate the SMOOTHX and SMOOTHY values, and TRIGGER specifies a set of
- * qualifiers any one of which (together with the SMOOTH qualifiers)
- * will active the SMOOTHX and SMOOTHY values. In other words, all the
- * SMOOTH qualifiers plus at least one of the TRIGGER qualifiers must be
- * pressed in order to activate the smooth values. For example, if SMOOTH
- * is 0 and TRIGGER is 0x6000, then holding down either the left or the
- * right button will activate the smooth values. If SMOOTH is 0x0040 rather
- * than 0, then the left Amiga button must also be held down in order to
- * activate SMOOTHX and SMOOTHY. The qualifier flags are listed in
- * DEVICES/INPUTEVENT.H
- *
- * The default values are DX = 8, DY = 8, SMOOTHX = 1, SMOOTHY = 1,
- * SMOOTH = left Amiga, TRIGGER = 0xFFFF.
- */
-
- int ParseArguments(argc,argv)
- int argc;
- char **argv;
- {
- Argc = argc;
- Argv = argv;
-
- while (--Argc > 0)
- {
- ArgMatched = FALSE;
- Argv++;
- if (Argc > 1 && ARGMATCH("TO"))
- {
- JournalFile = *(++Argv);
- Argc--;
- ArgMatched = TRUE;
- }
- CheckNumber("DX",&xmove);
- CheckNumber("DY",&ymove);
- CheckNumber("SMOOTHX",&smoothxmove);
- CheckNumber("SMOOTHY",&smoothymove);
- CheckHexNum("SMOOTH",&SmoothMask);
- CheckHexNum("TRIGGER",&SmoothTrigger);
- if (ArgMatched == FALSE)
- {
- if (JournalFile == NULL)
- JournalFile = *Argv;
- else
- Action = SHOW_USAGE;
- }
- }
- if (JournalFile == NULL && Action == WRITE_JOURNAL) Action = SHOW_USAGE;
- return(Action);
- }
-
-
- /*
- * OpenJournal()
- *
- * Open the journal file and check for errors. Write the version
- * information to the file.
- */
-
- void OpenJournal()
- {
- OutFile = fopen(JournalFile,"w");
- if (OutFile == NULL)
- DoExit("Can't Open Journal File '%s', error %ld",JournalFile,_OSERR);
- if (fwrite(version,sizeof(version),1,OutFile) != 1)
- DoExit("Error writing to output file: %ld",_OSERR);
- }
-
-
- /*
- * GetSignal()
- *
- * Allocate a signal (error if none available) and set the mask to
- * the proper value.
- */
-
- void GetSignal(theSignal,theMask)
- LONG *theSignal, *theMask;
- {
- LONG signal;
-
- if ((signal = AllocSignal(-ONE)) == -ONE) DoExit("Can't Get Signal");
- *theSignal = signal;
- *theMask = (ONE << signal);
- }
-
-
- /*
- * SetupTask()
- *
- * Find the task pointer for the main task (so the input handler can
- * signal it). Clear the CTRL signal flags (so we don't get any left
- * over from before JOURNAL was run) and allocate some signals for
- * new events and errors (so the input handler can signal them).
- */
-
- void SetupTask()
- {
- theTask = FindTask(NULL);
- SetSignal(0L,SIGBREAKF_ANY);
- GetSignal(&theSignal,&theMask);
- GetSignal(&ErrSignal,&ErrMask);
- #ifndef MANX
- onbreak(&Ctrl_C);
- #endif
- }
-
-
- /*
- * SetupEvents()
- *
- * Get a fake old-event to start off with, and mark it as saved (so we don't
- * really try to use it). Make it the end of the list (set its next pointer
- * to NULL. Tell the input handler where to start allocating new events
- * by setting EventPtr to point the the next-pointer. When the input
- * handler allocates a new copy of an event, it will link it to this one
- * so the main process can find it by following the next-pointer from the
- * old event.
- */
-
- void SetupEvents()
- {
- if ((OldEvent = NEWEVENT) == NULL) DoExit("No Memory for OldEvent");
- SETSAVED(OldEvent);
- OldEvent->ie_NextEvent = NULL;
- EventPtr = &(OldEvent->ie_NextEvent);
- }
-
-
- /*
- * AddHandler()
- *
- * Add the input handler to the input.device handler chain. Since the
- * priority is 51, it will appear BEFORE intuition, so all it should
- * see are raw key, raw mouse, timer, and disk insert/remove events.
- */
-
- void AddHandler()
- {
- long status;
-
- if ((InputPort = CreatePort(0,0)) == NULL)
- DoExit("Can't Create Port");
- if ((InputBlock = CreateStdIO(InputPort)) == NULL)
- DoExit("Can't Create Standard IO Block");
- InputDevice = (OpenDevice("input.device",0,InputBlock,0) == 0);
- if (InputDevice == 0) DoExit("Can't Open Input Device");
-
- InputBlock->io_Command = IND_ADDHANDLER;
- InputBlock->io_Data = (APTR) &HandlerData;
- if (status = DoIO(InputBlock)) DoExit("Error from DoIO: %ld",status);
- printf("%s - Press CTRL-AMIGA-E to End Journal\n",version);
- }
-
-
- /*
- * RemoveHandler()
- *
- * Remove the input handler from the input.device handler chain.
- */
-
- void RemoveHandler()
- {
- long status;
-
- if (InputDevice && InputBlock)
- {
- InputBlock->io_Command = IND_REMHANDLER;
- InputBlock->io_Data = (APTR) &HandlerData;
- if (status = DoIO(InputBlock)) DoExit("Error from DoIO: %ld",status);
- }
- printf("Journal Complete\n");
- }
-
-
- /*
- * SaveEvent()
- *
- * Pack an InputEvent into a SmallEvent (by shifting bits around) so that
- * it takes up less space in the output file. Write the SmallEvent to the
- * output file, and mark it as already-saved.
- */
-
- void SaveEvent(theEvent)
- struct InputEvent *theEvent;
- {
- if (theEvent->my_Time > MILLION) theEvent->my_Time += MILLION;
- TinyEvent.se_XY = 0;
- TinyEvent.se_Type = theEvent->ie_Class;
- TinyEvent.se_Qualifier = theEvent->ie_Qualifier;
- TinyEvent.se_Long2 = (theEvent->my_Ticks << 20) |
- (theEvent->my_Time & 0xFFFFF);
-
- if (theEvent->ie_Class == IECLASS_RAWKEY)
- {
- TinyEvent.se_Code = theEvent->ie_Code;
- TinyEvent.se_Prev = theEvent->my_Prev;
- } else {
- TinyEvent.se_Type |= (theEvent->ie_Code & IECODE_UP_PREFIX) |
- ((theEvent->ie_Code & 0x03) << 5);
- TinyEvent.se_XY |= (theEvent->ie_X & 0xFFF) |
- ((theEvent->ie_Y & 0xFFF) << 12);
- }
-
- if (fwrite((char *)&TinyEvent,sizeof(TinyEvent),1,OutFile) != 1)
- DoExit("Error writing to output file: %ld",_OSERR);
- SETSAVED(theEvent);
- NotFirstEvent = TRUE;
- }
-
-
- /*
- * SaveTime()
- *
- * Save a fake mouse event that doesn't move anywhere but that includes a
- * tick count. That is, pause without moving the mouse.
- */
-
- void SaveTime(theEvent)
- struct InputEvent *theEvent;
- {
- if (NotFirstEvent) TimeEvent.se_Ticks = (theEvent->my_Ticks << 20);
- if (fwrite((char *)&TimeEvent,sizeof(TimeEvent),1,OutFile) != 1)
- DoExit("Error writing to output file: %ld",_OSERR);
- theEvent->my_Ticks = 0;
- }
-
-
- /*
- * SaveEventList()
- *
- * Write the events in the event list (built by the input handler) out
- * to the output file, compressing multiple, small mouse moves into larger,
- * single mouse moves (to save space in the output file) in the following
- * way:
- *
- * if the current event is a mouse move (not a button press), then
- * set its event count to 1 (the number of events compressed into it),
- * if the user is requesting smooth movement, then use the smooth
- * movement variables, otherwise use the course (normal) values.
- * if the event's x or y movement is big enough, or if there was a long
- * pause before the movement occured, then
- * if the old event was not saved, save it.
- * if the pause was long enough, save a separate pause (so that the
- * smoothing algorithm in PLAYBACK does not spread the pause over
- * the entire mouse move).
- * save the current event.
- * otherwise, (we can compress the movement)
- * if there was an old mouse event that was not saved,
- * add it to the current event,
- * if the new x or y movement is big enough to record, do so.
- * otherwise, (this was not a mouse movement)
- * if there was a previous mouse movement that was not saved, save it.
- * finally, save the current event.
- * At this point the OldEvent is either posted, or has been combined with the
- * current event, so we can free the old event. The current event then
- * becomes the old event.
- */
-
- void SaveEventList()
- {
- struct InputEvent *theEvent;
-
- while ((theEvent = OldEvent->ie_NextEvent) != NULL)
- {
- if (MOUSEMOVE(theEvent))
- {
- theEvent->my_Count &= (~COUNTMASK);
- theEvent->my_Count++;
- if ((theEvent->ie_Qualifier & SmoothMask) == SmoothMask &&
- (theEvent->ie_Qualifier & SmoothTrigger))
- {
- xminmove = smoothxmove;
- yminmove = smoothymove;
- } else {
- xminmove = xmove;
- yminmove = ymove;
- }
- if (BIGX(theEvent) || BIGY(theEvent) || BIGTICKS(theEvent))
- {
- if (NOTSAVED(OldEvent)) SaveEvent(OldEvent);
- if (BIGTICKS(theEvent)) SaveTime(theEvent);
- SaveEvent(theEvent);
- } else {
- if (NOTSAVED(OldEvent))
- {
- theEvent->ie_X += OldEvent->ie_X;
- theEvent->ie_Y += OldEvent->ie_Y;
- theEvent->my_Ticks += OldEvent->my_Ticks;
- theEvent->my_Count += OldEvent->my_Count & COUNTMASK;
- if (BIGX(theEvent) || BIGY(theEvent)) SaveEvent(theEvent);
- }
- }
- } else {
- if (NOTSAVED(OldEvent)) SaveEvent(OldEvent);
- SaveEvent(theEvent);
- }
- FREEVENT(OldEvent);
- OldEvent = theEvent;
- }
- }
-
-
- /*
- * RecordJournal()
- *
- * Open the journal file, set up the task and signals, and set up the
- * initial pointers for the event list. Then add the input handler
- * into the Input.Device handler chain.
- *
- * Wait for the input handler to signal us that an event is ready (or that
- * an error occured), or that the user to press CTRL-AMIGA-E. If it's the
- * latter, cancel the Wait loop, otherwise save the events that are in the
- * list into the file. If the error signal was sent, inform the user that
- * some events were lost.
- *
- * Once we are signaled to end the journal, remove the handler, and
- * record any remaining, unsaved events to the file.
- */
-
- void RecordJournal()
- {
- LONG signals;
- LONG SigMask;
-
- OpenJournal();
- SetupTask();
- SetupEvents();
- SigMask = theMask | ErrMask | SIGBREAKF_CTRL_E;
-
- AddHandler(&myHandler);
- while (NotDone)
- {
- signals = Wait(SigMask);
- if (signals & SIGBREAKF_CTRL_E)
- NotDone = FALSE;
- else
- SaveEventList();
- if (signals & ErrMask)
- printf("[ Out of memory - some events not recorded ]\n");
- }
- RemoveHandler(&myHandler);
- SaveEventList();
- }
-
-
- /*
- * main()
- *
- * Parse the command-line arguments and perform the proper function
- * (either show the usage, write a journal, or fall through and exit).
- */
-
- void main(argc,argv)
- int argc;
- char **argv;
- {
- switch(ParseArguments(argc,argv))
- {
- case SHOW_USAGE:
- printf("Usage: JOURNAL [TO] file [DX x] [DY y]\n");
- printf(" [SMOOTHX x] [SMOOTHY y] [SMOOTH mask]");
- printf( " [TRIGGER mask]\n");
- break;
-
- case WRITE_JOURNAL:
- RecordJournal();
- break;
- }
- DoExit(NULL);
- }
-