home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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);
- }
-