home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Club Amiga de Montreal - CAM
/
CAM_CD_1.iso
/
files
/
051.lha
/
Journal
/
journal.c
< prev
next >
Wrap
C/C++ Source or Header
|
1986-11-20
|
21KB
|
691 lines
/*
* 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);
}