home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Elysian Archive
/
AmigaElysianArchive.iso
/
prog
/
source
/
journals.lha
/
journal.c
Wrap
C/C++ Source or Header
|
1987-07-15
|
45KB
|
1,509 lines
Message-ID: #31.pnet02.amiga/netsrc 45622 chars.
From: dpvc@ur-tut.UUCP (Davide P. Cervone)
Newsgroups: comp.sources.amiga
Subject: Journal and Playback (sources)
Date: 10 Jul 87 18:58:56 GMT
Reply-To: doc@s.cc.purdue.edu (Craig Norborg)
Organization: Purdue University Computing Center
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 <libraries/dos.h>
#include <exec/io.h>
#include <exec/interrupts.h>
#include <exec/memory.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <stdio.h>
#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