Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!mnetor!uunet!seismo!husc6!mit-eddie!ll-xn!cit-vax!ucla-cs!zen!ucbvax!s.cc.purdue.edu!dpvc From: dpvc@ur-tut.UUCP (Davide P. Cervone) Newsgroups: comp.sources.amiga Subject: Journal and Playback (sources) Message-ID: <486@s.cc.purdue.edu> Date: Fri, 10-Jul-87 14:58:56 EDT Article-I.D.: s.486 Posted: Fri Jul 10 14:58:56 1987 Date-Received: Sun, 12-Jul-87 12:56:38 EDT Expires: Fri, 7-Aug-87 09:34:41 EDT Sender: doc@s.cc.purdue.edu Reply-To: doc@s.cc.purdue.edu (Craig Norborg) Distribution: world Organization: Purdue University Computing Center Lines: 1498 Approved: doc@s.cc.purdue.edu Here are the sources to a neat utility that allows you to record and playback events that happen in a window. Binaries available in comp.binaries.amiga, documentation available in another article in this group. -Doc # This is a shell archive. # Remove everything above and including the cut line. # Then run the rest of the file through sh. #----cut here-----cut here-----cut here-----cut here----# #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # journal.c # playback.c # journal.h # handlerstub.a # journal.lnk # playback.lnk # This archive created: Sun Jun 21 22:39:23 1987 # By: (Davide P. Cervone) cat << \SHAR_EOF > journal.c /* * 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); } SHAR_EOF cat << \SHAR_EOF > playback.c /* * PLAYBACK.C - Plays back mouse and keyboard events that were recorded * by the JOURNAL program. * * 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 = "Playback v1.0 (June 1987)"; char *author = "Copyright (c) 1987 by Davide P. Cervone"; /* * Usage string */ #define USAGE "PLAYPACK [FROM] file [EVENTS n] [[NO]SMOOTH" /* * Macros to tell whether the user pressed CTRL-C */ #define CONTROL IEQUALIFIER_CONTROL #define KEY_C 0x33 #define CTRL_C(e) (((e)->ie_Qualifier & CONTROL) && ((e)->ie_Code == KEY_C)) /* * The packed code for a RAWMOUSE event with NOBUTTON pressed (i.e., one * that probably contains more than one event compressed into a single * entry in the file). */ #define MOUSEMOVE 0xE2 /* * Macro to check whether a command-line argument matches a given string */ #define ARGMATCH(s) (stricmp(s,*argv) == 0) /* * The functions that PLAYBACK can perform */ #define SHOW_USAGE 0 #define READ_JOURNAL 1 #define JUST_EXIT 2 /* * Global Variables */ struct MsgPort *InputPort = NULL; /* Port for the Input.Device */ struct IOStdReq *InputBlock = NULL; /* Request block for the Input.Device */ struct Task *theTask = NULL; /* pointer to the main process */ int HandlerActive = FALSE; /* TRUE when handler has been added */ LONG InputDevice = FALSE; /* TRUE when Input.Device is open */ LONG theSignal = 0; /* used when an event is freed */ LONG theMask; /* 1 << theSignal */ UWORD Ticks = 0; /* number of ticks between events */ LONG TimerMics = 0; /* last timer event's micros field */ LONG TimerSecs = 0; /* last timer event's seconds field */ struct InputEvent *Event = NULL; /* pointer to array of input events */ struct SmallEvent TinyEvent; /* a compressed event from the file */ long MaxEvents = 50; /* size of the Event array */ short Smoothing = TRUE; /* TRUE if smoothing requested */ short LastPosted = 0; /* Event index for last-posted event */ short NextToPost = 0; /* Event index for next event to post */ short NextFree = 0; /* Event index for next event to use */ FILE *InFile = NULL; /* journal file pointer */ char *JournalFile = NULL; /* name of journal file */ 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 */ }; /* * myHandler() * * This is the input handler that posts the events read from the journal file. * * First, free any events that were posted last time myHandler was * called by the Input.Device. Signal the main process when any are freed, * in case it is waiting for an event to be freed. * * Then, look through the list of events received from the Input.Device. * Check whether a new event is ready to be posted (i.e., one is available * and the proper number of ticks have been counted). If so, then set its * time fields to the proper time, add it into the event list, and look at * the next event. Set the tick count to zero again and check the next * event in the array. * * Once any new events have been added, check whether the current event * from the Input.Device is a timer event. If so, then increment the tick * count and record its time field. If not, then check whether it is a * raw mouse or raw key event. If it is, then if it is a CTRL-C, signal the * main process that the user wants to abort the playback. Remove the mouse * or key event from the event list so that it will not interfere with the * playback events (i.e., the keyboard and mouse are disabled while PLAYBACK * is running). * * Finally, go on to the the next event in the chain and continue the loop. * Once all the events have been processed, return the modified list * (with new events added from the file and keyboard and mouse events removed) * so that Intuition can act on them. */ struct InputEvent *myHandler(EventList,data) struct InputEvent *EventList; APTR data; { struct InputEvent **EventPtr = &EventList; struct InputEvent *toPost = &Event[NextToPost]; while (NextToPost != LastPosted) { Event[LastPosted].my_InUse = FALSE; Event[LastPosted].my_Ready = FALSE; LastPosted = (LastPosted + 1) % MaxEvents; Signal(theTask,theMask); } Forbid(); while (*EventPtr) { while (toPost->my_Ready && Ticks >= toPost->my_Ticks) { toPost->ie_Secs = TimerSecs; toPost->ie_Mics += TimerMics; if (toPost->ie_Mics > MILLION) { toPost->ie_Secs++; toPost->ie_Mics -= MILLION; } toPost->ie_NextEvent = *EventPtr; *EventPtr = toPost; EventPtr = &(toPost->ie_NextEvent); NextToPost = (NextToPost + 1) % MaxEvents; toPost = &Event[NextToPost]; Ticks = 0; } if ((*EventPtr)->ie_Class == IECLASS_TIMER) { Ticks++; TimerSecs = (*EventPtr)->ie_Secs; TimerMics = (*EventPtr)->ie_Mics; } else { if ((*EventPtr)->ie_Class == IECLASS_RAWMOUSE || (*EventPtr)->ie_Class == IECLASS_RAWKEY) { if (CTRL_C(*EventPtr)) Signal(theTask,SIGBREAKF_CTRL_C); *EventPtr = (*EventPtr)->ie_NextEvent; } } EventPtr = &((*EventPtr)->ie_NextEvent); } Permit(); return(EventList); } /* * 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. Remove the handler (if * it is active), 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 (HandlerActive) RemoveHandler(); if (Event) FreeMem(Event,IE_SIZE * MaxEvents); if (InFile) fclose(InFile); if (InputDevice) CloseDevice(InputBlock); if (InputBlock) DeleteStdIO(InputBlock); if (InputPort) DeletePort(InputPort); if (theSignal) FreeSignal(theSignal); exit(status); } /* * 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 "FROM". If no file is specified, then show the usage. EVENTS * regulates the size of the Event array used for buffering event * communication between the main process and the handler. SMOOTH and * NOSMOOTH regulate the interpolation of mouse movements between recorded * events. The default is SMOOTH. */ int ParseArguments(argc,argv) int argc; char **argv; { int function = READ_JOURNAL; while (--argc > 0) { argv++; if (argc > 1 && ARGMATCH("FROM")) { JournalFile = *(++argv); argc--; } else if (argc > 1 && ARGMATCH("EVENTS")) { argc--; if (sscanf(*(++argv),"%ld",&MaxEvents) != 1) { printf("Event count must be numeric: '%s'\n",*argv); function = JUST_EXIT; } if (MaxEvents <= 1) { printf("Event count must be greater than 1: '%d'\n",MaxEvents); function = JUST_EXIT; } } else if (ARGMATCH("NOSMOOTH")) Smoothing = FALSE; else if (ARGMATCH("SMOOTH")) Smoothing = TRUE; else if (JournalFile == NULL) JournalFile = *argv; else function = SHOW_USAGE; } if (JournalFile == NULL && function == READ_JOURNAL) function = SHOW_USAGE; return(function); } /* * OpenJournal() * * Open the journal file and check for errors. Read the version * information to the file (someday we may need to check this). */ void OpenJournal() { char fileversion[32]; InFile = fopen(JournalFile,"r"); if (InFile == NULL) DoExit("Can't Open Journal File '%s', error %ld",JournalFile,_OSERR); if (fread(fileversion,sizeof(fileversion),1,InFile) != 1) DoExit("Can't read version from '%s', error %ld",JournalFile,_OSERR); } /* * GetEventMemory() * * Allocate memory for the Event array (of size MaxEvents, specified by * the EVENT option). */ void GetEventMemory() { Event = AllocMem(IE_SIZE * MaxEvents, MEMF_CLEAR); if (Event == NULL) DoExit("Can't get memory for %d Events",MaxEvents); } /* * 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 Allocate 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 PLAYBACK was run) and allocate a signal for * when the handler frees an event. */ void SetupTask() { theTask = FindTask(NULL); SetSignal(0L,SIGBREAKF_ANY); GetSignal(&theSignal,&theMask); #ifndef MANX onbreak(&Ctrl_C); #endif } /* * AddHandler() * * Add the input handler to the Input.Device handler chain. Since the * priority is 51 it will appear BEFORE intuition, so when we insert * new events into the chain, Intuition will process them just as though * they came from the Input.Device. */ 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-C to Cancel\n",version); HandlerActive = TRUE; } /* * RemoveHandler() * * Remove the input handler from the Input.Device handler chain. */ void RemoveHandler() { long status; if (HandlerActive && InputDevice && InputBlock) { HandlerActive = FALSE; InputBlock->io_Command = IND_REMHANDLER; InputBlock->io_Data = (APTR) &HandlerData; if (status = DoIO(InputBlock)) DoExit("Error from DoIO: %ld",status); } printf("Playback Complete\n"); } /* * Create an event that moves the pointer to the upper, left-hand corner * of the screen so that the pointer is at a known position. This is a * large relative move (-1000,-1000). */ void PointerToHome() { struct InputEvent *theEvent = &(Event[0]); theEvent->ie_Class = IECLASS_RAWMOUSE; theEvent->ie_Code = IECODE_NOBUTTON; theEvent->ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE; theEvent->ie_X = -1000; theEvent->ie_Y = -1000; theEvent->my_Ticks = 0; theEvent->my_Time = 0; theEvent->my_Ready = READY; } /* * CheckForCTRLC() * * Read the current task signals (without changing them) and check whether * a CTRL-C has been signalled. If so, abort the playback. */ void CheckForCTRLC() { LONG signals = SetSignal(0,0); if (signals & SIGBREAKF_CTRL_C) DoExit("Playback Aborted"); } /* * GetNextFree() * * Set NextFree to point to the next free event in the Event array. * If there are no free events, Wait() for the handler to signal that it * has freed one (or for CTRL-C to be pressed). */ void GetNextFree() { LONG signals; NextFree = (NextFree + 1) % MaxEvents; while (Event[NextFree].my_InUse) { signals = Wait(theMask | SIGBREAKF_CTRL_C); if (signals & SIGBREAKF_CTRL_C) DoExit("Playback Aborted"); } } #define ABS(x) (((x)<0)?-(x):(x)) /* * MovePointer() * * Interpolate mouse move events that were compressed into one record in * the journal file. The se_Count field holds the number of events that * were compressed into one. * * First, unpack the X and Y movements. Record their directions in dx and dy * and their magnitudes in abs_x and abs_y. Reduce 'count' if there would * be events with offset of (0,0). 'x_move' specifies the x-offset for each * event and 'x_add' specifies the fraction of a pixel correction that must * be made (x_add/count is the fraction). 'x_count' counts the fraction of * a pixel that has been added so far (when x_count/count >= 1 (i.e., when * x_count >= count) we add another pixel to the x-offset). Similarly for * the y and t variables (t is for ticks). Starting the counts at 'count/2' * makes for smoother movement. * * Once these are set up, we create new mouse move events with the proper * offsets, adding up the fractions of pixels and adding in addional * movements whenever the fractions add up to a whole pixel (or tick). * When a new event is set up, we mark it as READY so that the handler will * see it and post it. * * Once we have sent all the events, the mouse should be in the proper * position, so we set the tick count and XY-offset fields to 0. */ void MovePointer() { WORD abs_x,abs_y, x_count,y_count, dx,dy, x_add,y_add, x_move,y_move; WORD t_count, t_add, t_move; WORD x = TinyEvent.se_XY & 0xFFF; WORD y = (TinyEvent.se_XY >> 12) & 0xFFF; WORD i, count = TinyEvent.se_Count & COUNTMASK; LONG Time = TinyEvent.se_Micros & 0xFFFFF; LONG Ticks = TinyEvent.se_Ticks >> 20; struct InputEvent *NewEvent; x_count = y_count = t_count = 0; if (x & 0x800) x |= 0xF000; if (x < 0) dx = -1; else dx = 1; if (y & 0x800) y |= 0xF000; if (y < 0) dy = -1; else dy = 1; abs_x = ABS(x); abs_y = ABS(y); if (abs_x > abs_y) { if (count > abs_x) count = abs_x; } else { if (count > abs_y) count = abs_y; } if (count) { x_move = x / count; y_move = y / count; t_move = Ticks / count; x_add = abs_x % count; y_add = abs_y % count; t_add = Ticks % count; } else { x_move = x; y_move = y; t_move = Ticks; x_add = y_add = t_add = -1; count = 1; } x_count = y_count = t_count = count / 2; for (i = count; i > 0; i--) { GetNextFree(); NewEvent = &Event[NextFree]; NewEvent->ie_Class = IECLASS_RAWMOUSE; NewEvent->ie_Code = IECODE_NOBUTTON; NewEvent->ie_Qualifier = TinyEvent.se_Qualifier; NewEvent->ie_X = x_move; NewEvent->ie_Y = y_move; NewEvent->my_Ticks = t_move; NewEvent->my_Time = Time; if ((x_count += x_add) >= count) { x_count -= count; NewEvent->ie_X += dx; } if ((y_count += y_add) >= count) { y_count -= count; NewEvent->ie_Y += dy; } if ((t_count += t_add) > count) { t_count -= count; NewEvent->my_Ticks++; } NewEvent->my_Ready = READY; } TinyEvent.se_XY &= 0xFF000000; TinyEvent.se_Ticks &= 0xFFFFF; } /* * PostNextEvent() * * Read an event from the journal file. If we are smoothing and the * event is a mouse movement, them interpolate the compressed mouse * movements. Get the next event in the Event array and unpack the * proper values from the TinyEvent read from the file. Mark the finished * event as READY so the handler will see it and post it. */ void PostNextEvent() { struct InputEvent *NewEvent = NULL; if (fread((char *)&TinyEvent,sizeof(TinyEvent),1,InFile) == 1) { if (Smoothing && TinyEvent.se_Type == MOUSEMOVE) MovePointer(); GetNextFree(); NewEvent = &Event[NextFree]; NewEvent->ie_Class = TinyEvent.se_Type & 0x1F; NewEvent->ie_Qualifier = TinyEvent.se_Qualifier; NewEvent->my_Ticks = TinyEvent.se_Ticks >> 20; NewEvent->my_Time = TinyEvent.se_Micros & 0xFFFFF; switch(NewEvent->ie_Class) { case IECLASS_RAWKEY: NewEvent->ie_Code = TinyEvent.se_Code; NewEvent->ie_X = NewEvent->ie_Y = TinyEvent.se_Prev; break; case IECLASS_RAWMOUSE: NewEvent->ie_Code = (TinyEvent.se_Type >> 5) & 0x03; if (NewEvent->ie_Code == 0x03) NewEvent->ie_Code = IECODE_NOBUTTON; else NewEvent->ie_Code |= (TinyEvent.se_Type & IECODE_UP_PREFIX) | (IECODE_LBUTTON & ~(0x03 | IECODE_UP_PREFIX)); NewEvent->ie_X = TinyEvent.se_XY & 0xFFF; NewEvent->ie_Y = (TinyEvent.se_XY >> 12) & 0xFFF; NewEvent->my_Ticks = TinyEvent.se_Ticks >> 20; if (NewEvent->ie_X & 0x800) NewEvent->ie_X |= 0xF000; if (NewEvent->ie_Y & 0x800) NewEvent->ie_Y |= 0xF000; break; default: printf("[ Unknown Event Class: %02X]\n",NewEvent->ie_Class); break; } NewEvent->my_Ready = READY; } } /* * WaitForEvents() * * Wait for the handler to finish posting all the events in the Event * array (so we don't remove the handler before it is done). */ void WaitForEvents() { short LastFree = NextFree; do GetNextFree(); while (NextFree != LastFree); } /* * PlayJournal() * * Open the journal file, set up the task and signals, and allocate the * Event array. Add the input handler and send the pointer to the upper, * left-hand corner of the screen. While there are still events in the * journal file, check whether the user wants to cancel the playback and * if not, post the next event in the file. When the end-of-file is reached * wait for the handler to finish posting all the events in the array, and * then remove the handler. */ void PlayJournal() { OpenJournal(); SetupTask(); GetEventMemory(); AddHandler(&myHandler); PointerToHome(); while (feof(InFile) == 0) { CheckForCTRLC(); PostNextEvent(); } WaitForEvents(); RemoveHandler(); } /* * main() * * Parse the command-line arguments, and perform the proper function * (either show the usage, read a journal, or fall through and exit). */ void main(argc,argv) int argc; char **argv; { switch(ParseArguments(argc,argv)) { case SHOW_USAGE: printf("Usage: %s\n",USAGE); break; case READ_JOURNAL: PlayJournal(); break; } DoExit(NULL); } SHAR_EOF cat << \SHAR_EOF > journal.h /* * JOURNAL.H - Common header file for JOURNAL.C and PLAYBACK.C * * Copyright (c) 1987 by Davide P. Cervone * You may use this code provided this copyright notice is kept intact. */ #include#include #include #include #include #include #include #define ONE 1L extern struct MsgPort *CreatePort(); extern struct IOStdReq *CreateStdIO(); extern LONG AllocSignal(), Wait(), SetSignal(); extern struct Task *FindTask(); extern struct InputEvent *AllocMem(); extern FILE *fopen(); extern long errno, _OSERR; /* Lattice and DOS error numbers */ extern void RemoveHandler(); /* defined later on */ /* * assembly routine that gets called by the Input.Device which sets up * the stack and calls our input handler */ extern void myHandlerStub(); /* * Structure used to pack event data into a small space. This is the * format used to record that data in the journal file */ struct SmallEvent { union { struct { UBYTE se_IDType; /* ie_Class and ie_Code combined */ UBYTE se_Raw; /* RawKey Code */ UWORD se_PrevChar; /* previous key and qualifier */ } se_ID; ULONG se_XYpos; /* X in bits 0-11, Y in 12-23 (ie_Type in 24-31) */ } se_Long1; UWORD se_Qualifier; ULONG se_Long2; /* Micros in 0-19, Ticks in 20-32 */ }; #define se_Type se_Long1.se_ID.se_IDType #define se_Code se_Long1.se_ID.se_Raw #define se_Prev se_Long1.se_ID.se_PrevChar #define se_XY se_Long1.se_XYpos #define se_Ticks se_Long2 #define se_Micros se_Long2 #define se_Count se_Long2 /* * Some shorthands for InputEvent fields */ #define my_Prev ie_X #define my_Time ie_Secs /* micros since last event */ #define my_Ticks ie_Mics /* ticks since last event */ #define my_Ready ie_NextEvent /* TRUE when it can be posted */ #define my_InUse ie_Class /* TRUE when it is in use */ #define my_Saved ie_SubClass /* TRUE if is has been recorded */ #define my_Count my_Time /* number of compressed events */ #define ie_Secs ie_TimeStamp.tv_secs #define ie_Mics ie_TimeStamp.tv_micro #define COUNTMASK 0x3F /* how much of my_Count is count */ #define READY ((struct InputEvent *) TRUE) #define TIME (theEvent->ie_Mics-TimerMics) #define MILLION 1000000 #define XMINMOUSE xminmove #define YMINMOUSE yminmove #define XDEFMIN 8 /* default DX */ #define YDEFMIN 8 /* default DY */ #define LONGTIME 8 /* if this many ticks occur, we */ /* write out a dummy move to */ /* record the pause */ #define IE_SIZE sizeof(struct InputEvent) #define NEWEVENT AllocMem(IE_SIZE,0) #define FREEVENT(ev) FreeMem(ev,IE_SIZE) #define SIGBREAKF_ANY (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D |\ SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F) SHAR_EOF cat << \SHAR_EOF > handlerstub.a CSECT text XREF _myHandler XDEF _myHandlerStub _myHandlerStub: MOVEM.L A0/A1,-(A7) JSR _myHandler ADDQ.L #8,A7 RTS END SHAR_EOF cat << \SHAR_EOF > journal.lnk FROM LIB:c.o+journal.o+handlerstub.o TO journal LIB LIB:lc.lib+LIB:amiga.lib NODEBUG SHAR_EOF cat << \SHAR_EOF > playback.lnk FROM LIB:c.o+playback.o+handlerstub.o TO playback LIB LIB:lc.lib+LIB:amiga.lib NODEBUG SHAR_EOF # End of shell archive exit 0