home *** CD-ROM | disk | FTP | other *** search
/ Amiga Elysian Archive / AmigaElysianArchive.iso / prog / source / journals.lha / journal.c
C/C++ Source or Header  |  1987-07-15  |  45KB  |  1,509 lines

  1.  
  2. Message-ID: #31.pnet02.amiga/netsrc 45622 chars.
  3. From: dpvc@ur-tut.UUCP (Davide P. Cervone)
  4. Newsgroups: comp.sources.amiga
  5. Subject: Journal and Playback (sources)
  6. Date: 10 Jul 87 18:58:56 GMT
  7. Reply-To: doc@s.cc.purdue.edu (Craig Norborg)
  8. Organization: Purdue University Computing Center
  9.  
  10.  
  11.     Here are the sources to a neat utility that allows you to record
  12. and playback events that happen in a window.  Binaries available in
  13. comp.binaries.amiga, documentation available in another article in this
  14. group.
  15.     -Doc
  16.  
  17.  
  18. #       This is a shell archive.
  19. #       Remove everything above and including the cut line.
  20. #       Then run the rest of the file through sh.
  21. #----cut here-----cut here-----cut here-----cut here----#
  22. #!/bin/sh
  23. # shar:    Shell Archiver
  24. #       Run the following text with /bin/sh to create:
  25. #       journal.c
  26. #       playback.c
  27. #       journal.h
  28. #       handlerstub.a
  29. #       journal.lnk
  30. #       playback.lnk
  31. # This archive created: Sun Jun 21 22:39:23 1987
  32. # By:    (Davide P. Cervone)
  33. cat << \SHAR_EOF > journal.c
  34. /*
  35.  *  JOURNAL.C  -  Records all mouse and keyboard activity so that
  36.  *                it can be played back for demonstration of products,
  37.  *                reporting errors, etc.
  38.  *
  39.  *             Copyright (c) 1987 by Davide P. Cervone
  40.  *  You may use this code provided this copyright notice is kept intact.
  41.  */
  42.  
  43. #include "journal.h"
  44.  
  45. /*
  46.  *  Version number and author:
  47.  */
  48. char version[32] = "Journal v1.0 (June 1987)";
  49. char *author     = "Copyright (c) 1987 by Davide P. Cervone";
  50.  
  51.  
  52. /*
  53.  *  Macros used to check for end-of-journal
  54.  */
  55. #define CTRL_AMIGA      (IEQUALIFIER_CONTROL | IEQUALIFIER_LCOMMAND)
  56. #define KEY_E           0x12
  57. #define CTRL_AMIGA_E(e) ((((e)->ie_Qualifier & CTRL_AMIGA) == CTRL_AMIGA) &&\
  58.                           ((e)->ie_Code == KEY_E))
  59.  
  60. /*
  61.  *  Match a command-line argument against a string (case insensitive)
  62.  */
  63. #define ARGMATCH(s)   (stricmp(s,*Argv) == 0)
  64.  
  65. /*
  66.  *  Functions that JOURNAL can perform
  67.  */
  68. #define SHOW_USAGE      0
  69. #define WRITE_JOURNAL   1
  70. #define JUST_EXIT       2
  71.  
  72. /*
  73.  *  Largest mouse move we want to record
  74.  */
  75. #define MAXMOUSEMOVE   32
  76.  
  77. /*
  78.  *  Macros to tell whether a mouse movement event can be compressed with
  79.  *  other mouse movement events
  80.  */
  81. #define MOUSEMOVE(e)\
  82.    ((e)->ie_Class == IECLASS_RAWMOUSE && (e)->ie_Code == IECODE_NOBUTTON &&\
  83.    ((e)->ie_Qualifier & IEQUALIFIER_RELATIVEMOUSE))
  84. #define BIGX(e)         ((e)->ie_X >= XMINMOUSE || (e)->ie_X <= -XMINMOUSE)
  85. #define BIGY(e)         ((e)->ie_Y >= YMINMOUSE || (e)->ie_Y <= -YMINMOUSE)
  86. #define BIGTICKS(e)     ((e)->my_Ticks > LONGTIME)
  87. #define NOTSAVED(e)     ((e)->my_Saved == FALSE)
  88. #define SETSAVED(e)     ((e)->my_Saved = TRUE)
  89.  
  90.  
  91. /*
  92.  *  Global Variables:
  93.  */
  94.  
  95. struct MsgPort *InputPort = NULL;     /* Port used to talk to Input.Device */
  96. struct IOStdReq *InputBlock = NULL;   /* request block used with Input.Device */
  97. struct Task *theTask = NULL;          /* pointer to our task */
  98. LONG InputDevice = 0;                 /* flag whether Input.Device is open */
  99. LONG theSignal = 0;                   /* signal used when an event is ready */
  100. LONG ErrSignal = 0;                   /* signal used when an error occured */
  101. LONG theMask;                         /* 1 << theSignal */
  102. LONG ErrMask;                         /* 1 << ErrSignal */
  103.  
  104. UWORD Ticks = 0;                      /* number of timer ticks between events */
  105. LONG  TimerMics = 0;                  /* last timer event's micros field */
  106.  
  107. WORD xmove = XDEFMIN;                 /* distance to compress into one event */
  108. WORD ymove = YDEFMIN;                 /* distance to compress into one event */
  109. WORD smoothxmove = 1;                 /* distance for smoothed events */
  110. WORD smoothymove = 1;                 /* distnace for smoothed events */
  111. WORD xminmove, yminmove;              /* distance actually in use */
  112. UWORD SmoothMask = IEQUALIFIER_LCOMMAND;
  113.                                       /* what keys are required for smoothing */
  114. UWORD SmoothTrigger = 0xFFFF;         /* any of these keys trigger smoothing */
  115.  
  116. int Action = WRITE_JOURNAL;           /* action to be perfomed by JOURNAL */
  117. int ArgMatched = FALSE;               /* TRUE if a parameter matched OK */
  118. int Argc;                             /* global version of argc */
  119. char **Argv;                          /* global version of argv */
  120.  
  121. struct InputEvent **EventPtr = NULL;  /* pointer to (pointer to next event) */
  122. struct InputEvent *OldEvent = NULL;   /* pointer to last event */
  123. struct SmallEvent TinyEvent;          /* packed event (ready to record) */
  124.  
  125. FILE *OutFile = NULL;                 /* where the events will be written */
  126. char *JournalFile = NULL;             /* name of the output file */
  127.  
  128. int NotDone = TRUE;                   /* continue looking for events? */
  129. int NotFirstEvent = FALSE;            /* TRUE after an event was recorded */
  130. int PointerNotHomed = TRUE;           /* TRUE until pointer is moved */
  131.  
  132. struct Interrupt HandlerData =        /* used to add an input handler */
  133. {
  134.    {NULL, NULL, 0, 51, NULL},           /* Node structure (nl_Pri = 51) */
  135.    NULL,                                /* data pointer */
  136.    &myHandlerStub                       /* code pointer */
  137. };
  138.  
  139. struct InputEvent PointerToHome =     /* event to put pointer in upper-left */
  140. {
  141.    NULL,                                /* pointer to next event */
  142.    IECLASS_RAWMOUSE,                    /* ie_Class = RAWMOUSE */
  143.    0,                                   /* ie_SubClass */
  144.    IECODE_NOBUTTON,                     /* ie_Code = NOBUTTON (just a move) */
  145.    IEQUALIFIER_RELATIVEMOUSE,           /* ie_Qualifier = relative move */
  146.    {-1000,-1000},                       /* move far to left and top */
  147.    {0L,0L}                              /* seconds and micros */
  148. };
  149.  
  150. struct SmallEvent TimeEvent =         /* pause written at beginning of file */
  151. {
  152.    {0xE2, 0, 0},                        /* MOUSEMOVE, NOBUTTON, X=0, Y=0 */
  153.    IEQUALIFIER_RELATIVEMOUSE,           /* Qualifier */
  154.    0x00A00000                           /* 1 second pause */
  155. };
  156.  
  157.  
  158. /*
  159.  *  myHandler()
  160.  *
  161.  *  This is the input handler that makes copies of the input events and sends 
  162.  *  them the to main process to be written to the output file.
  163.  *
  164.  *  The first time around, we add the PointerToHome event into the stream
  165.  *  so that the pointer is put into a known position.
  166.  *
  167.  *  We check the event type of each event in the list, and do the following:
  168.  *  for Timer events, we increment the tick count (which tells how many ticks
  169.  *  have occured since the last recorded event); for raw key events, we check
  170.  *  whether a CTRL-AMIGA-E has been pressed (if so, we signal the main process
  171.  *  with a CTRL-E which tells it to remove the handler and quit); for raw 
  172.  *  mouse and raw key events, we allocate memory for a new copy of the event
  173.  *  (and signal an error if we can't), and copy the pertinent information
  174.  *  from the current event into the copy event and mark it as not-yet-saved.
  175.  *  We link it into the copied-event list (via EventPtr), and signal the
  176.  *  main task that a new event is ready, and then zero the tick count.
  177.  *
  178.  *  Any other type of event is ignored.
  179.  *
  180.  *  When we are through with the event list, we return it so that Intuition
  181.  *  can use it to do its thing.
  182.  */
  183.  
  184. struct InputEvent *myHandler(event,data)
  185. struct InputEvent *event;
  186. APTR data;
  187. {
  188.    struct InputEvent *theEvent = event;
  189.    struct InputEvent *theCopy;
  190.  
  191.    Forbid();
  192.    if (PointerNotHomed)
  193.    {
  194.       PointerToHome.ie_NextEvent = event;
  195.       event = &PointerToHome;
  196.       PointerNotHomed = FALSE;
  197.    }
  198.    while(theEvent)
  199.    {
  200.       switch(theEvent->ie_Class)
  201.       {
  202.          case IECLASS_TIMER:
  203.             Ticks++;
  204.             TimerMics = theEvent->ie_Mics;
  205.             break;
  206.  
  207.          case IECLASS_RAWKEY:
  208.             if (CTRL_AMIGA_E(theEvent)) Signal(theTask,SIGBREAKF_CTRL_E);
  209.  
  210.          case IECLASS_RAWMOUSE:
  211.             theCopy = NEWEVENT;
  212.             if (theCopy == NULL)
  213.             {
  214.                Signal(theTask,ErrMask);
  215.             } else {
  216.                theCopy->ie_NextEvent    = NULL;
  217.                theCopy->ie_Class        = theEvent->ie_Class;
  218.                theCopy->ie_Code         = theEvent->ie_Code;
  219.                theCopy->ie_Qualifier    = theEvent->ie_Qualifier;
  220.                theCopy->ie_EventAddress = theEvent->ie_EventAddress;
  221.                theCopy->my_Time         = TIME;
  222.                theCopy->my_Ticks        = Ticks;
  223.                theCopy->my_Saved        = FALSE;
  224.                *EventPtr = theCopy;
  225.                EventPtr = &(theCopy->ie_NextEvent);
  226.                Signal(theTask,theMask);
  227.                Ticks = 0;
  228.             }
  229.             break;
  230.       }
  231.       theEvent = theEvent->ie_NextEvent;
  232.    }
  233.    Permit();
  234.    return(event);
  235. }
  236.  
  237.  
  238. /*
  239.  *  Ctrl_C()
  240.  *
  241.  *  Dummy routine to disable Lattice-C CTRL-C trapping.
  242.  */
  243.  
  244. #ifndef MANX
  245. int Ctrl_C()
  246. {
  247.    return(0);
  248. }
  249. #endif
  250.  
  251.  
  252. /*
  253.  *  DoExit()
  254.  *
  255.  *  General purpose exit routine.  If 's' is not NULL, then print an
  256.  *  error message with up to three parameters.  Free any memory, close
  257.  *  any open files, delete any ports, free any used signals, etc.
  258.  */
  259.  
  260. void DoExit(s,x1,x2,x3)
  261. char *s, *x1, *x2, *x3;
  262. {
  263.    long status = 0;
  264.    
  265.    if (s != NULL)
  266.    {
  267.       printf(s,x1,x2,x3);
  268.       printf("\n");
  269.       status = RETURN_ERROR;
  270.    }
  271.    if (OldEvent)    FREEVENT(OldEvent);
  272.    if (OutFile)     fclose(OutFile);
  273.    if (InputDevice) CloseDevice(InputBlock);
  274.    if (InputBlock)  DeleteStdIO(InputBlock);
  275.    if (InputPort)   DeletePort(InputPort);
  276.    if (theSignal)   FreeSignal(theSignal);
  277.    if (ErrSignal)   FreeSignal(ErrSignal);
  278.    exit(status);
  279. }
  280.  
  281.  
  282. /*
  283.  *  CheckNumber()
  284.  *
  285.  *  Check a command-line argument for the given keyword, and if it matches,
  286.  *  makes sure that the next parameter is a positive numeric value that
  287.  *  is less than the maximum expected value.
  288.  */
  289.  
  290. void CheckNumber(keyword,value)
  291. char *keyword;
  292. WORD *value;
  293. {
  294.    long lvalue = *value;
  295.  
  296.    if (Argc > 1 && ARGMATCH(keyword))
  297.    {
  298.       ArgMatched = TRUE;
  299.       Argc--;
  300.       if (sscanf(*(++Argv),"%ld",&lvalue) != 1)
  301.       {
  302.          printf("%s must be numeric:  '%s'\n",keyword,*Argv);
  303.          Action = JUST_EXIT;
  304.       }
  305.       if (lvalue < 1 || lvalue > MAXMOUSEMOVE)
  306.       {
  307.          printf("%s must be positive and less than %d:  '%ld'\n",
  308.             keyword,MAXMOUSEMOVE,lvalue);
  309.          Action = JUST_EXIT;
  310.       }
  311.       *value = lvalue;
  312.    }
  313. }
  314.  
  315.  
  316. /*
  317.  *  CheckHexNum()
  318.  *
  319.  *  Check a command-line argument for the given keyword, and if it
  320.  *  matches, make sure that the next parameter is a legal HEX value.
  321.  */
  322.  
  323. void CheckHexNum(keyword,value)
  324. char *keyword;
  325. WORD *value;
  326. {
  327.    ULONG lvalue = *value;
  328.  
  329.    if (Argc > 1 && ARGMATCH(keyword))
  330.    {
  331.       ArgMatched = TRUE;
  332.       Argc--;
  333.       if (sscanf(*(++Argv),"%lx",&lvalue) != 1)
  334.       {
  335.          printf("%s must be a HEX number:  '%s'\n",keyword,*Argv);
  336.          Action = JUST_EXIT;
  337.       }
  338.       *value = lvalue;
  339.    }
  340. }
  341.  
  342.  
  343. /*
  344.  *  ParseArguements()
  345.  *
  346.  *  Check that all the command-line arguments are valid and set the 
  347.  *  proper variables as requested by the user.  If no keyword is specified,
  348.  *  assume "TO".  If no file is specified, then show the usage.  DX and DY
  349.  *  set the "granularity" of the mouse moves recorded (moves are combined
  350.  *  into a single event until it's movement excedes either DX or DY).
  351.  *  Similarly, SMOOTHX and SMOOTHY set alternate DX and DY values
  352.  *  for when extra precision is needed (i.e., when drawing curves in a paint
  353.  *  program).  SMOOTH specifies what qualifier keys MUST be present to 
  354.  *  activate the SMOOTHX and SMOOTHY values, and TRIGGER specifies a set of
  355.  *  qualifiers any one of which (together with the SMOOTH qualifiers)
  356.  *  will active the SMOOTHX and SMOOTHY values.  In other words, all the
  357.  *  SMOOTH qualifiers plus at least one of the TRIGGER qualifiers must be
  358.  *  pressed in order to activate the smooth values.  For example, if SMOOTH
  359.  *  is 0 and TRIGGER is 0x6000, then holding down either the left or the
  360.  *  right button will activate the smooth values.  If SMOOTH is 0x0040 rather
  361.  *  than 0, then the left Amiga button must also be held down in order to 
  362.  *  activate SMOOTHX and SMOOTHY.  The qualifier flags are listed in
  363.  *  DEVICES/INPUTEVENT.H
  364.  *
  365.  *  The default values are DX = 8, DY = 8, SMOOTHX = 1, SMOOTHY = 1,
  366.  *  SMOOTH = left Amiga, TRIGGER = 0xFFFF.
  367.  */
  368.  
  369. int ParseArguments(argc,argv)
  370. int argc;
  371. char **argv;
  372. {
  373.    Argc = argc;
  374.    Argv = argv;
  375.  
  376.    while (--Argc > 0)
  377.    {
  378.       ArgMatched = FALSE;
  379.       Argv++;
  380.       if (Argc > 1 && ARGMATCH("TO"))
  381.       {
  382.          JournalFile = *(++Argv);
  383.          Argc--;
  384.          ArgMatched = TRUE;
  385.       }
  386.       CheckNumber("DX",&xmove);
  387.       CheckNumber("DY",&ymove);
  388.       CheckNumber("SMOOTHX",&smoothxmove);
  389.       CheckNumber("SMOOTHY",&smoothymove);
  390.       CheckHexNum("SMOOTH",&SmoothMask);
  391.       CheckHexNum("TRIGGER",&SmoothTrigger);
  392.       if (ArgMatched == FALSE)
  393.       {
  394.          if (JournalFile == NULL)
  395.             JournalFile = *Argv;
  396.            else
  397.             Action = SHOW_USAGE;
  398.       }
  399.    }
  400.    if (JournalFile == NULL && Action == WRITE_JOURNAL) Action = SHOW_USAGE;
  401.    return(Action);
  402. }
  403.  
  404.  
  405. /*
  406.  *  OpenJournal()
  407.  *
  408.  *  Open the journal file and check for errors.  Write the version
  409.  *  information to the file.
  410.  */
  411.  
  412. void OpenJournal()
  413. {
  414.    OutFile = fopen(JournalFile,"w");
  415.    if (OutFile == NULL)
  416.       DoExit("Can't Open Journal File '%s', error %ld",JournalFile,_OSERR);
  417.    if (fwrite(version,sizeof(version),1,OutFile) != 1)
  418.       DoExit("Error writing to output file:  %ld",_OSERR);
  419. }
  420.  
  421.  
  422. /*
  423.  *  GetSignal()
  424.  *
  425.  *  Allocate a signal (error if none available) and set the mask to
  426.  *  the proper value.
  427.  */
  428.  
  429. void GetSignal(theSignal,theMask)
  430. LONG *theSignal, *theMask;
  431. {
  432.    LONG signal;
  433.  
  434.    if ((signal = AllocSignal(-ONE)) == -ONE) DoExit("Can't Get Signal");
  435.    *theSignal = signal;
  436.    *theMask = (ONE << signal);
  437. }
  438.  
  439.  
  440. /*
  441.  *  SetupTask()
  442.  *
  443.  *  Find the task pointer for the main task (so the input handler can 
  444.  *  signal it).  Clear the CTRL signal flags (so we don't get any left
  445.  *  over from before JOURNAL was run) and allocate some signals for 
  446.  *  new events and errors (so the input handler can signal them).
  447.  */
  448.  
  449. void SetupTask()
  450. {
  451.    theTask = FindTask(NULL);
  452.    SetSignal(0L,SIGBREAKF_ANY);
  453.    GetSignal(&theSignal,&theMask);
  454.    GetSignal(&ErrSignal,&ErrMask);
  455.    #ifndef MANX
  456.       onbreak(&Ctrl_C);
  457.    #endif
  458. }
  459.  
  460.  
  461. /*
  462.  *  SetupEvents()
  463.  *
  464.  *  Get a fake old-event to start off with, and mark it as saved (so we don't
  465.  *  really try to use it).  Make it the end of the list (set its next pointer
  466.  *  to NULL.  Tell the input handler where to start allocating new events
  467.  *  by setting EventPtr to point the the next-pointer.  When the input
  468.  *  handler allocates a new copy of an event, it will link it to this one
  469.  *  so the main process can find it by following the next-pointer from the
  470.  *  old event.
  471.  */
  472.  
  473. void SetupEvents()
  474. {
  475.    if ((OldEvent = NEWEVENT) == NULL) DoExit("No Memory for OldEvent");
  476.    SETSAVED(OldEvent);
  477.    OldEvent->ie_NextEvent = NULL;
  478.    EventPtr = &(OldEvent->ie_NextEvent);
  479. }
  480.  
  481.  
  482. /*
  483.  *  AddHandler()
  484.  *
  485.  *  Add the input handler to the input.device handler chain.  Since the
  486.  *  priority is 51, it will appear BEFORE intuition, so all it should
  487.  *  see are raw key, raw mouse, timer, and disk insert/remove events.
  488.  */
  489.  
  490. void AddHandler()
  491. {
  492.    long status;
  493.  
  494.    if ((InputPort = CreatePort(0,0)) == NULL)
  495.       DoExit("Can't Create Port");
  496.    if ((InputBlock = CreateStdIO(InputPort)) == NULL)
  497.       DoExit("Can't Create Standard IO Block");
  498.    InputDevice = (OpenDevice("input.device",0,InputBlock,0) == 0);
  499.    if (InputDevice == 0) DoExit("Can't Open Input Device");
  500.    
  501.    InputBlock->io_Command = IND_ADDHANDLER;
  502.    InputBlock->io_Data    = (APTR) &HandlerData;
  503.    if (status = DoIO(InputBlock)) DoExit("Error from DoIO:  %ld",status);
  504.    printf("%s - Press CTRL-AMIGA-E to End Journal\n",version);
  505. }
  506.  
  507.  
  508. /*
  509.  *  RemoveHandler()
  510.  *
  511.  *  Remove the input handler from the input.device handler chain.
  512.  */
  513.  
  514. void RemoveHandler()
  515. {
  516.    long status;
  517.  
  518.    if (InputDevice && InputBlock)
  519.    {
  520.       InputBlock->io_Command = IND_REMHANDLER;
  521.       InputBlock->io_Data = (APTR) &HandlerData;
  522.       if (status = DoIO(InputBlock)) DoExit("Error from DoIO:  %ld",status);
  523.    }
  524.    printf("Journal Complete\n");
  525. }
  526.  
  527.  
  528. /*
  529.  *  SaveEvent()
  530.  *
  531.  *  Pack an InputEvent into a SmallEvent (by shifting bits around) so that
  532.  *  it takes up less space in the output file.  Write the SmallEvent to the
  533.  *  output file, and mark it as already-saved.
  534.  */
  535.  
  536. void SaveEvent(theEvent)
  537. struct InputEvent *theEvent;
  538. {
  539.    if (theEvent->my_Time > MILLION) theEvent->my_Time += MILLION;
  540.    TinyEvent.se_XY        = 0;
  541.    TinyEvent.se_Type      = theEvent->ie_Class;
  542.    TinyEvent.se_Qualifier = theEvent->ie_Qualifier;
  543.    TinyEvent.se_Long2     = (theEvent->my_Ticks << 20) |
  544.                             (theEvent->my_Time & 0xFFFFF);
  545.  
  546.    if (theEvent->ie_Class == IECLASS_RAWKEY)
  547.    {
  548.       TinyEvent.se_Code  = theEvent->ie_Code;
  549.       TinyEvent.se_Prev  = theEvent->my_Prev;
  550.    } else {
  551.       TinyEvent.se_Type |= (theEvent->ie_Code & IECODE_UP_PREFIX) |
  552.                            ((theEvent->ie_Code & 0x03) << 5);
  553.       TinyEvent.se_XY |= (theEvent->ie_X & 0xFFF) |
  554.                          ((theEvent->ie_Y & 0xFFF) << 12);
  555.    }
  556.  
  557.    if (fwrite((char *)&TinyEvent,sizeof(TinyEvent),1,OutFile) != 1)
  558.       DoExit("Error writing to output file:  %ld",_OSERR);
  559.    SETSAVED(theEvent);
  560.    NotFirstEvent = TRUE;
  561. }
  562.  
  563.  
  564. /*
  565.  *  SaveTime()
  566.  *
  567.  *  Save a fake mouse event that doesn't move anywhere but that includes a
  568.  *  tick count.  That is, pause without moving the mouse.
  569.  */
  570.  
  571. void SaveTime(theEvent)
  572. struct InputEvent *theEvent;
  573. {
  574.    if (NotFirstEvent) TimeEvent.se_Ticks = (theEvent->my_Ticks << 20);
  575.    if (fwrite((char *)&TimeEvent,sizeof(TimeEvent),1,OutFile) != 1)
  576.       DoExit("Error writing to output file:  %ld",_OSERR);
  577.    theEvent->my_Ticks = 0;
  578. }
  579.  
  580.  
  581. /*
  582.  *  SaveEventList()
  583.  *
  584.  *  Write the events in the event list (built by the input handler) out
  585.  *  to the output file, compressing multiple, small mouse moves into larger,
  586.  *  single mouse moves (to save space in the output file) in the following 
  587.  *  way:
  588.  *
  589.  *    if the current event is a mouse move (not a button press), then
  590.  *      set its event count to 1 (the number of events compressed into it),
  591.  *      if the user is requesting smooth movement, then use the smooth
  592.  *        movement variables, otherwise use the course (normal) values. 
  593.  *      if the event's x or y movement is big enough, or if there was a long
  594.  *           pause before the movement occured, then
  595.  *        if the old event was not saved, save it.
  596.  *        if the pause was long enough, save a separate pause (so that the
  597.  *           smoothing algorithm in PLAYBACK does not spread the pause over
  598.  *           the entire mouse move).
  599.  *        save the current event.
  600.  *      otherwise, (we can compress the movement)
  601.  *        if there was an old mouse event that was not saved,
  602.  *          add it to the current event,
  603.  *          if the new x or y movement is big enough to record, do so.
  604.  *    otherwise, (this was not a mouse movement)
  605.  *      if there was a previous mouse movement that was not saved, save it.
  606.  *      finally, save the current event.
  607.  *  At this point the OldEvent is either posted, or has been combined with the
  608.  *  current event, so we can free the old event.  The current event then
  609.  *  becomes the old event.
  610.  */
  611.  
  612. void SaveEventList()
  613. {
  614.    struct InputEvent *theEvent;
  615.    
  616.    while ((theEvent = OldEvent->ie_NextEvent) != NULL)
  617.    {
  618.       if (MOUSEMOVE(theEvent))
  619.       {
  620.          theEvent->my_Count &= (~COUNTMASK);
  621.          theEvent->my_Count++;
  622.          if ((theEvent->ie_Qualifier & SmoothMask) == SmoothMask &&
  623.              (theEvent->ie_Qualifier & SmoothTrigger))
  624.          {
  625.             xminmove = smoothxmove;
  626.             yminmove = smoothymove;
  627.          } else {
  628.             xminmove = xmove;
  629.             yminmove = ymove;
  630.          }
  631.          if (BIGX(theEvent) || BIGY(theEvent) || BIGTICKS(theEvent))
  632.          {
  633.             if (NOTSAVED(OldEvent)) SaveEvent(OldEvent);
  634.             if (BIGTICKS(theEvent)) SaveTime(theEvent);
  635.             SaveEvent(theEvent);
  636.          } else {
  637.             if (NOTSAVED(OldEvent))
  638.             {
  639.                theEvent->ie_X += OldEvent->ie_X;
  640.                theEvent->ie_Y += OldEvent->ie_Y;
  641.                theEvent->my_Ticks += OldEvent->my_Ticks;
  642.                theEvent->my_Count += OldEvent->my_Count & COUNTMASK;
  643.                if (BIGX(theEvent) || BIGY(theEvent)) SaveEvent(theEvent);
  644.             }
  645.          }
  646.       } else {
  647.          if (NOTSAVED(OldEvent)) SaveEvent(OldEvent);
  648.          SaveEvent(theEvent);
  649.       }
  650.       FREEVENT(OldEvent);
  651.       OldEvent = theEvent;
  652.    }
  653. }
  654.  
  655.  
  656. /*
  657.  *  RecordJournal()
  658.  *
  659.  *  Open the journal file, set up the task and signals, and set up the
  660.  *  initial pointers for the event list.  Then add the input handler
  661.  *  into the Input.Device handler chain.  
  662.  *
  663.  *  Wait for the input handler to signal us that an event is ready (or that
  664.  *  an error occured), or that the user to press CTRL-AMIGA-E.  If it's the 
  665.  *  latter, cancel the Wait loop, otherwise save the events that are in the
  666.  *  list into the file.  If the error signal was sent, inform the user that
  667.  *  some events were lost.
  668.  *
  669.  *  Once we are signaled to end the journal, remove the handler, and
  670.  *  record any remaining, unsaved events to the file.
  671.  */
  672.  
  673. void RecordJournal()
  674. {
  675.    LONG signals;
  676.    LONG SigMask;
  677.    
  678.    OpenJournal();
  679.    SetupTask();
  680.    SetupEvents();
  681.    SigMask = theMask | ErrMask | SIGBREAKF_CTRL_E;
  682.  
  683.    AddHandler(&myHandler);
  684.    while (NotDone)
  685.    {
  686.       signals = Wait(SigMask);
  687.       if (signals & SIGBREAKF_CTRL_E)
  688.          NotDone = FALSE;
  689.         else
  690.          SaveEventList();
  691.       if (signals & ErrMask)
  692.          printf("[ Out of memory - some events not recorded ]\n");
  693.    }
  694.    RemoveHandler(&myHandler);
  695.    SaveEventList();
  696. }
  697.  
  698.  
  699. /*
  700.  *  main()
  701.  *
  702.  *  Parse the command-line arguments and perform the proper function
  703.  *  (either show the usage, write a journal, or fall through and exit).
  704.  */
  705.  
  706. void main(argc,argv)
  707. int argc;
  708. char **argv;
  709. {
  710.    switch(ParseArguments(argc,argv))
  711.    {
  712.       case SHOW_USAGE:
  713.          printf("Usage:  JOURNAL [TO] file [DX x] [DY y]\n");
  714.          printf("                [SMOOTHX x] [SMOOTHY y] [SMOOTH mask]");
  715.          printf(               " [TRIGGER mask]\n");
  716.          break;
  717.  
  718.       case WRITE_JOURNAL:
  719.          RecordJournal();
  720.          break;
  721.    }
  722.    DoExit(NULL);
  723. }
  724. SHAR_EOF
  725. cat << \SHAR_EOF > playback.c
  726. /*
  727.  *  PLAYBACK.C  -  Plays back mouse and keyboard events that were recorded
  728.  *                 by the JOURNAL program.
  729.  *
  730.  *             Copyright (c) 1987 by Davide P. Cervone
  731.  *  You may use this code provided this copyright notice is kept intact.
  732.  */
  733.  
  734. #include "journal.h"
  735.  
  736. /*
  737.  *  Version number and author
  738.  */
  739. char *version = "Playback v1.0 (June 1987)";
  740. char *author  = "Copyright (c) 1987 by Davide P. Cervone";
  741.  
  742. /*
  743.  *  Usage string
  744.  */
  745. #define USAGE   "PLAYPACK [FROM] file [EVENTS n] [[NO]SMOOTH"
  746.  
  747. /*
  748.  *  Macros to tell whether the user pressed CTRL-C
  749.  */
  750. #define CONTROL       IEQUALIFIER_CONTROL
  751. #define KEY_C         0x33
  752. #define CTRL_C(e)     (((e)->ie_Qualifier & CONTROL) && ((e)->ie_Code == KEY_C))
  753.  
  754. /*
  755.  *  The packed code for a RAWMOUSE event with NOBUTTON pressed (i.e., one
  756.  *  that probably contains more than one event compressed into a single 
  757.  *  entry in the file).
  758.  */
  759. #define MOUSEMOVE     0xE2
  760.  
  761. /*
  762.  *  Macro to check whether a command-line argument matches a given string
  763.  */
  764. #define ARGMATCH(s)   (stricmp(s,*argv) == 0)
  765.  
  766. /*
  767.  *  The functions that PLAYBACK can perform
  768.  */
  769. #define SHOW_USAGE    0
  770. #define READ_JOURNAL  1
  771. #define JUST_EXIT     2
  772.  
  773.  
  774. /*
  775.  *  Global Variables
  776.  */
  777.  
  778. struct MsgPort *InputPort = NULL;       /* Port for the Input.Device */
  779. struct IOStdReq *InputBlock = NULL;     /* Request block for the Input.Device */
  780. struct Task *theTask = NULL;            /* pointer to the main process */
  781. int  HandlerActive = FALSE;             /* TRUE when handler has been added */
  782. LONG InputDevice = FALSE;               /* TRUE when Input.Device is open */
  783. LONG theSignal = 0;                     /* used when an event is freed */
  784. LONG theMask;                           /* 1 << theSignal */
  785.  
  786. UWORD Ticks = 0;                        /* number of ticks between events */
  787. LONG  TimerMics = 0;                    /* last timer event's micros field */
  788. LONG  TimerSecs = 0;                    /* last timer event's seconds field */
  789.  
  790. struct InputEvent *Event = NULL;        /* pointer to array of input events */
  791. struct SmallEvent TinyEvent;            /* a compressed event from the file */
  792. long   MaxEvents  = 50;                 /* size of the Event array */
  793. short  Smoothing  = TRUE;               /* TRUE if smoothing requested */
  794. short  LastPosted = 0;                  /* Event index for last-posted event */
  795. short  NextToPost = 0;                  /* Event index for next event to post */
  796. short  NextFree   = 0;                  /* Event index for next event to use */
  797.  
  798. FILE *InFile = NULL;                    /* journal file pointer */
  799. char *JournalFile = NULL;               /* name of journal file */
  800.  
  801.  
  802. struct Interrupt HandlerData =          /* used to add an input handler */
  803. {
  804.    {NULL, NULL, 0, 51, NULL},             /* Node structure (nl_Pri = 51) */
  805.    NULL,                                  /* data pointer */
  806.    &myHandlerStub                         /* code pointer */
  807. };
  808.  
  809.  
  810. /*
  811.  *  myHandler()
  812.  *
  813.  *  This is the input handler that posts the events read from the journal file.
  814.  *
  815.  *  First, free any events that were posted last time myHandler was
  816.  *  called by the Input.Device.  Signal the main process when any are freed,
  817.  *  in case it is waiting for an event to be freed.
  818.  *
  819.  *  Then, look through the list of events received from the Input.Device.
  820.  *  Check whether a new event is ready to be posted (i.e., one is available
  821.  *  and the proper number of ticks have been counted).  If so, then set its
  822.  *  time fields to the proper time, add it into the event list, and look at
  823.  *  the next event.  Set the tick count to zero again and check the next
  824.  *  event in the array.
  825.  *
  826.  *  Once any new events have been added, check whether the current event
  827.  *  from the Input.Device is a timer event.  If so, then increment the tick 
  828.  *  count and record its time field.  If not, then check whether it is a
  829.  *  raw mouse or raw key event.  If it is, then if it is a CTRL-C, signal the 
  830.  *  main process that the user wants to abort the playback.  Remove the mouse
  831.  *  or key event from the event list so that it will not interfere with the 
  832.  *  playback events (i.e., the keyboard and mouse are disabled while PLAYBACK
  833.  *  is running).
  834.  *
  835.  *  Finally, go on to the the next event in the chain and continue the loop.
  836.  *  Once all the events have been processed, return the modified list
  837.  *  (with new events added from the file and keyboard and mouse events removed)
  838.  *  so that Intuition can act on them.
  839.  */
  840.  
  841. struct InputEvent *myHandler(EventList,data)
  842. struct InputEvent *EventList;
  843. APTR data;
  844. {
  845.    struct InputEvent **EventPtr = &EventList;
  846.    struct InputEvent *toPost = &Event[NextToPost];
  847.    
  848.    while (NextToPost != LastPosted)
  849.    {
  850.       Event[LastPosted].my_InUse = FALSE;
  851.       Event[LastPosted].my_Ready = FALSE;
  852.       LastPosted = (LastPosted + 1) % MaxEvents;
  853.       Signal(theTask,theMask);
  854.    }
  855.    Forbid();
  856.    while (*EventPtr)
  857.    {
  858.       while (toPost->my_Ready && Ticks >= toPost->my_Ticks)
  859.       {
  860.          toPost->ie_Secs = TimerSecs;
  861.          toPost->ie_Mics += TimerMics;
  862.          if (toPost->ie_Mics > MILLION)
  863.          {
  864.             toPost->ie_Secs++;
  865.             toPost->ie_Mics -= MILLION;
  866.          }
  867.          toPost->ie_NextEvent = *EventPtr;
  868.          *EventPtr = toPost;
  869.          EventPtr = &(toPost->ie_NextEvent);
  870.          NextToPost = (NextToPost + 1) % MaxEvents;
  871.          toPost = &Event[NextToPost];
  872.          Ticks = 0;
  873.       }
  874.       if ((*EventPtr)->ie_Class == IECLASS_TIMER)
  875.       {
  876.          Ticks++;
  877.          TimerSecs = (*EventPtr)->ie_Secs;
  878.          TimerMics = (*EventPtr)->ie_Mics;
  879.       } else {
  880.          if ((*EventPtr)->ie_Class == IECLASS_RAWMOUSE ||
  881.              (*EventPtr)->ie_Class == IECLASS_RAWKEY)
  882.          {
  883.             if (CTRL_C(*EventPtr)) Signal(theTask,SIGBREAKF_CTRL_C);
  884.             *EventPtr = (*EventPtr)->ie_NextEvent;
  885.          }
  886.       }
  887.       EventPtr = &((*EventPtr)->ie_NextEvent);
  888.    }
  889.    Permit();
  890.    return(EventList);
  891. }
  892.  
  893.  
  894. /*
  895.  *  Ctrl_C()
  896.  *
  897.  *  Dummy routine to disable Lattice-C CTRL-C trapping.
  898.  */
  899.  
  900. #ifndef MANX
  901. int Ctrl_C()
  902. {
  903.    return(0);
  904. }
  905. #endif
  906.  
  907.  
  908. /*
  909.  *  DoExit()
  910.  *
  911.  *  General purpose exit routine.  If 's' is not NULL, then print an
  912.  *  error message with up to three parameters.  Remove the handler (if
  913.  *  it is active), free any memory, close any open files, delete any ports, 
  914.  *  free any used signals, etc.
  915.  */
  916.  
  917. void DoExit(s,x1,x2,x3)
  918. char *s, *x1, *x2, *x3;
  919. {
  920.    long status = 0;
  921.    
  922.    if (s != NULL)
  923.    {
  924.       printf(s,x1,x2,x3);
  925.       printf("\n");
  926.       status = RETURN_ERROR;
  927.    }
  928.    if (HandlerActive) RemoveHandler();
  929.    if (Event)         FreeMem(Event,IE_SIZE * MaxEvents);
  930.    if (InFile)        fclose(InFile);
  931.    if (InputDevice)   CloseDevice(InputBlock);
  932.    if (InputBlock)    DeleteStdIO(InputBlock);
  933.    if (InputPort)     DeletePort(InputPort);
  934.    if (theSignal)     FreeSignal(theSignal);
  935.    exit(status);
  936. }
  937.  
  938. /*
  939.  *  ParseArguements()
  940.  *
  941.  *  Check that all the command-line arguments are valid and set the 
  942.  *  proper variables as requested by the user.  If no keyword is specified,
  943.  *  assume "FROM".  If no file is specified, then show the usage.  EVENTS
  944.  *  regulates the size of the Event array used for buffering event 
  945.  *  communication between the main process and the handler.  SMOOTH and
  946.  *  NOSMOOTH regulate the interpolation of mouse movements between recorded
  947.  *  events.  The default is SMOOTH.
  948.  */
  949.  
  950. int ParseArguments(argc,argv)
  951. int argc;
  952. char **argv;
  953. {
  954.    int function = READ_JOURNAL;
  955.  
  956.    while (--argc > 0)
  957.    {
  958.       argv++;
  959.       if (argc > 1 && ARGMATCH("FROM"))
  960.       {
  961.          JournalFile = *(++argv);
  962.          argc--;
  963.       }
  964.       else if (argc > 1 && ARGMATCH("EVENTS"))
  965.       {
  966.          argc--;
  967.          if (sscanf(*(++argv),"%ld",&MaxEvents) != 1)
  968.          {
  969.             printf("Event count must be numeric:  '%s'\n",*argv);
  970.             function = JUST_EXIT;
  971.          }
  972.          if (MaxEvents <= 1)
  973.          {
  974.             printf("Event count must be greater than 1:  '%d'\n",MaxEvents);
  975.             function = JUST_EXIT;
  976.          }
  977.       }
  978.       else if (ARGMATCH("NOSMOOTH")) Smoothing = FALSE;
  979.       else if (ARGMATCH("SMOOTH"))   Smoothing = TRUE;
  980.       else if (JournalFile == NULL) JournalFile = *argv;
  981.       else function = SHOW_USAGE;
  982.    }
  983.    if (JournalFile == NULL && function == READ_JOURNAL) function = SHOW_USAGE;
  984.    return(function);
  985. }
  986.  
  987. /*
  988.  *  OpenJournal()
  989.  *
  990.  *  Open the journal file and check for errors.  Read the version
  991.  *  information to the file (someday we may need to check this).
  992.  */
  993.  
  994. void OpenJournal()
  995. {
  996.    char fileversion[32];
  997.  
  998.    InFile = fopen(JournalFile,"r");
  999.    if (InFile == NULL)
  1000.       DoExit("Can't Open Journal File '%s', error %ld",JournalFile,_OSERR);
  1001.    if (fread(fileversion,sizeof(fileversion),1,InFile) != 1)
  1002.       DoExit("Can't read version from '%s', error %ld",JournalFile,_OSERR);
  1003. }
  1004.  
  1005.  
  1006. /*
  1007.  *  GetEventMemory()
  1008.  *
  1009.  *  Allocate memory for the Event array (of size MaxEvents, specified by
  1010.  *  the EVENT option).
  1011.  */
  1012.  
  1013. void GetEventMemory()
  1014. {
  1015.    Event = AllocMem(IE_SIZE * MaxEvents, MEMF_CLEAR);
  1016.    if (Event == NULL) DoExit("Can't get memory for %d Events",MaxEvents);
  1017. }
  1018.  
  1019.  
  1020. /*
  1021.  *  GetSignal()
  1022.  *
  1023.  *  Allocate a signal (error if none available) and set the mask to
  1024.  *  the proper value.
  1025.  */
  1026.  
  1027. void GetSignal(theSignal,theMask)
  1028. LONG *theSignal, *theMask;
  1029. {
  1030.    LONG signal;
  1031.  
  1032.    if ((signal = AllocSignal(-ONE)) == -ONE) DoExit("Can't Allocate Signal");
  1033.    *theSignal = signal;
  1034.    *theMask = (ONE << signal);
  1035. }
  1036.  
  1037.  
  1038. /*
  1039.  *  SetupTask();
  1040.  *
  1041.  *  Find the task pointer for the main task (so the input handler can 
  1042.  *  signal it).  Clear the CTRL signal flags (so we don't get any left
  1043.  *  over from before PLAYBACK was run) and allocate a signal for 
  1044.  *  when the handler frees an event.
  1045.  */
  1046.  
  1047. void SetupTask()
  1048. {
  1049.    theTask = FindTask(NULL);
  1050.    SetSignal(0L,SIGBREAKF_ANY);
  1051.    GetSignal(&theSignal,&theMask);
  1052.    #ifndef MANX
  1053.       onbreak(&Ctrl_C);
  1054.    #endif
  1055. }
  1056.  
  1057.  
  1058. /*
  1059.  *  AddHandler()
  1060.  *
  1061.  *  Add the input handler to the Input.Device handler chain.  Since the
  1062.  *  priority is 51 it will appear BEFORE intuition, so when we insert
  1063.  *  new events into the chain, Intuition will process them just as though
  1064.  *  they came from the Input.Device.
  1065.  */
  1066.  
  1067. void AddHandler()
  1068. {
  1069.    long status;
  1070.  
  1071.    if ((InputPort = CreatePort(0,0)) == NULL)
  1072.       DoExit("Can't Create Port");
  1073.    if ((InputBlock = CreateStdIO(InputPort)) == NULL)
  1074.       DoExit("Can't Create Standard IO Block");
  1075.    InputDevice = (OpenDevice("input.device",0,InputBlock,0) == 0);
  1076.    if (InputDevice == 0) DoExit("Can't Open Input.Device");
  1077.    
  1078.    InputBlock->io_Command = IND_ADDHANDLER;
  1079.    InputBlock->io_Data    = (APTR) &HandlerData;
  1080.    if (status = DoIO(InputBlock)) DoExit("Error from DoIO:  %ld",status);
  1081.    printf("%s - Press CTRL-C to Cancel\n",version);
  1082.    HandlerActive = TRUE;
  1083. }
  1084.  
  1085. /*
  1086.  *  RemoveHandler()
  1087.  *
  1088.  *  Remove the input handler from the Input.Device handler chain.
  1089.  */
  1090.  
  1091. void RemoveHandler()
  1092. {
  1093.    long status;
  1094.  
  1095.    if (HandlerActive && InputDevice && InputBlock)
  1096.    {
  1097.       HandlerActive = FALSE;
  1098.       InputBlock->io_Command = IND_REMHANDLER;
  1099.       InputBlock->io_Data = (APTR) &HandlerData;
  1100.       if (status = DoIO(InputBlock)) DoExit("Error from DoIO:  %ld",status);
  1101.    }
  1102.    printf("Playback Complete\n");
  1103. }
  1104.  
  1105.  
  1106. /*
  1107.  *  Create an event that moves the pointer to the upper, left-hand corner
  1108.  *  of the screen so that the pointer is at a known position.  This is a
  1109.  *  large relative move (-1000,-1000).
  1110.  */
  1111.  
  1112. void PointerToHome()
  1113. {
  1114.    struct InputEvent *theEvent = &(Event[0]);
  1115.    
  1116.    theEvent->ie_Class     = IECLASS_RAWMOUSE;
  1117.    theEvent->ie_Code      = IECODE_NOBUTTON;
  1118.    theEvent->ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE;
  1119.    theEvent->ie_X         = -1000;
  1120.    theEvent->ie_Y         = -1000;
  1121.    theEvent->my_Ticks     = 0;
  1122.    theEvent->my_Time      = 0;
  1123.    theEvent->my_Ready     = READY;
  1124. }
  1125.  
  1126.  
  1127. /*
  1128.  *  CheckForCTRLC()
  1129.  *
  1130.  *  Read the current task signals (without changing them) and check whether
  1131.  *  a CTRL-C has been signalled.  If so, abort the playback.
  1132.  */
  1133.  
  1134. void CheckForCTRLC()
  1135. {
  1136.    LONG signals = SetSignal(0,0);
  1137.    
  1138.    if (signals & SIGBREAKF_CTRL_C) DoExit("Playback Aborted");
  1139. }
  1140.  
  1141.  
  1142. /*
  1143.  *  GetNextFree()
  1144.  *
  1145.  *  Set NextFree to point to the next free event in the Event array.
  1146.  *  If there are no free events, Wait() for the handler to signal that it
  1147.  *  has freed one (or for CTRL-C to be pressed).
  1148.  */
  1149.  
  1150. void GetNextFree()
  1151. {
  1152.    LONG signals;
  1153.  
  1154.    NextFree = (NextFree + 1) % MaxEvents;
  1155.    while (Event[NextFree].my_InUse)
  1156.    {
  1157.       signals = Wait(theMask | SIGBREAKF_CTRL_C);
  1158.       if (signals & SIGBREAKF_CTRL_C) DoExit("Playback Aborted");
  1159.    }
  1160. }
  1161.  
  1162.  
  1163. #define ABS(x)  (((x)<0)?-(x):(x))
  1164.  
  1165.  
  1166. /*
  1167.  *  MovePointer()
  1168.  *
  1169.  *  Interpolate mouse move events that were compressed into one record in
  1170.  *  the journal file.  The se_Count field holds the number of events that
  1171.  *  were compressed into one.
  1172.  *
  1173.  *  First, unpack the X and Y movements.  Record their directions in dx and dy
  1174.  *  and their magnitudes in abs_x and abs_y.  Reduce 'count' if there would
  1175.  *  be events with offset of (0,0).  'x_move' specifies the x-offset for each
  1176.  *  event and 'x_add' specifies the fraction of a pixel correction that must
  1177.  *  be made (x_add/count is the fraction).  'x_count' counts the fraction of
  1178.  *  a pixel that has been added so far (when x_count/count >= 1 (i.e., when
  1179.  *  x_count >= count) we add another pixel to the x-offset).  Similarly for
  1180.  *  the y and t variables (t is for ticks).  Starting the counts at 'count/2' 
  1181.  *  makes for smoother movement.
  1182.  *
  1183.  *  Once these are set up, we create new mouse move events with the proper
  1184.  *  offsets, adding up the fractions of pixels and adding in addional 
  1185.  *  movements whenever the fractions add up to a whole pixel (or tick).
  1186.  *  When a new event is set up, we mark it as READY so that the handler will
  1187.  *  see it and post it.
  1188.  *
  1189.  *  Once we have sent all the events, the mouse should be in the proper
  1190.  *  position, so we set the tick count and XY-offset fields to 0.
  1191.  */
  1192.  
  1193. void MovePointer()
  1194. {
  1195.    WORD abs_x,abs_y, x_count,y_count, dx,dy, x_add,y_add, x_move,y_move;
  1196.    WORD t_count, t_add, t_move;
  1197.    WORD x = TinyEvent.se_XY & 0xFFF;
  1198.    WORD y = (TinyEvent.se_XY >> 12) & 0xFFF;
  1199.    WORD i, count = TinyEvent.se_Count & COUNTMASK;
  1200.    LONG Time = TinyEvent.se_Micros & 0xFFFFF;
  1201.    LONG Ticks = TinyEvent.se_Ticks >> 20;
  1202.    struct InputEvent *NewEvent;
  1203.  
  1204.    x_count = y_count = t_count = 0;
  1205.    if (x & 0x800) x |= 0xF000;
  1206.    if (x < 0) dx = -1; else dx = 1;
  1207.    if (y & 0x800) y |= 0xF000;
  1208.    if (y < 0) dy = -1; else dy = 1;
  1209.    abs_x = ABS(x); abs_y = ABS(y);
  1210.    if (abs_x > abs_y)
  1211.    {
  1212.       if (count > abs_x) count = abs_x;
  1213.    } else {
  1214.       if (count > abs_y) count = abs_y;
  1215.    }
  1216.    if (count)
  1217.    {
  1218.       x_move = x / count; y_move = y / count; t_move = Ticks / count;
  1219.       x_add = abs_x % count; y_add = abs_y % count; t_add = Ticks % count;
  1220.    } else {
  1221.       x_move = x; y_move = y; t_move = Ticks;
  1222.       x_add = y_add = t_add = -1; count = 1;
  1223.    }
  1224.    x_count = y_count = t_count = count / 2;
  1225.    for (i = count; i > 0; i--)
  1226.    {
  1227.       GetNextFree();
  1228.       NewEvent = &Event[NextFree];
  1229.       NewEvent->ie_Class     = IECLASS_RAWMOUSE;
  1230.       NewEvent->ie_Code      = IECODE_NOBUTTON;
  1231.       NewEvent->ie_Qualifier = TinyEvent.se_Qualifier;
  1232.       NewEvent->ie_X         = x_move;
  1233.       NewEvent->ie_Y         = y_move;
  1234.       NewEvent->my_Ticks     = t_move;
  1235.       NewEvent->my_Time      = Time;
  1236.       if ((x_count += x_add) >= count)
  1237.       {
  1238.          x_count -= count;
  1239.          NewEvent->ie_X += dx;
  1240.       }
  1241.       if ((y_count += y_add) >= count)
  1242.       {
  1243.          y_count -= count;
  1244.          NewEvent->ie_Y += dy;
  1245.       }
  1246.       if ((t_count += t_add) > count)
  1247.       {
  1248.          t_count -= count;
  1249.          NewEvent->my_Ticks++;
  1250.       }
  1251.       NewEvent->my_Ready = READY;
  1252.    }
  1253.    TinyEvent.se_XY    &= 0xFF000000;
  1254.    TinyEvent.se_Ticks &= 0xFFFFF;
  1255. }
  1256.  
  1257.  
  1258. /*
  1259.  *  PostNextEvent()
  1260.  *
  1261.  *  Read an event from the journal file.  If we are smoothing and the 
  1262.  *  event is a mouse movement, them interpolate the compressed mouse
  1263.  *  movements.  Get the next event in the Event array and unpack the
  1264.  *  proper values from the TinyEvent read from the file.  Mark the finished
  1265.  *  event as READY so the handler will see it and post it.
  1266.  */
  1267.  
  1268. void PostNextEvent()
  1269. {
  1270.    struct InputEvent *NewEvent = NULL;
  1271.  
  1272.    if (fread((char *)&TinyEvent,sizeof(TinyEvent),1,InFile) == 1)
  1273.    {
  1274.       if (Smoothing && TinyEvent.se_Type == MOUSEMOVE) MovePointer();
  1275.  
  1276.       GetNextFree();
  1277.       NewEvent = &Event[NextFree];
  1278.    
  1279.       NewEvent->ie_Class     = TinyEvent.se_Type & 0x1F;
  1280.       NewEvent->ie_Qualifier = TinyEvent.se_Qualifier;
  1281.       NewEvent->my_Ticks     = TinyEvent.se_Ticks >> 20;
  1282.       NewEvent->my_Time      = TinyEvent.se_Micros & 0xFFFFF;
  1283.       
  1284.       switch(NewEvent->ie_Class)
  1285.       {
  1286.          case IECLASS_RAWKEY:
  1287.             NewEvent->ie_Code = TinyEvent.se_Code;
  1288.             NewEvent->ie_X = NewEvent->ie_Y = TinyEvent.se_Prev;
  1289.             break;
  1290.  
  1291.          case IECLASS_RAWMOUSE:
  1292.             NewEvent->ie_Code = (TinyEvent.se_Type >> 5) & 0x03;
  1293.             if (NewEvent->ie_Code == 0x03)
  1294.                NewEvent->ie_Code = IECODE_NOBUTTON;
  1295.               else
  1296.                NewEvent->ie_Code |= (TinyEvent.se_Type & IECODE_UP_PREFIX) |
  1297.                   (IECODE_LBUTTON & ~(0x03 | IECODE_UP_PREFIX));
  1298.             NewEvent->ie_X = TinyEvent.se_XY & 0xFFF;
  1299.             NewEvent->ie_Y = (TinyEvent.se_XY >> 12) & 0xFFF;
  1300.             NewEvent->my_Ticks = TinyEvent.se_Ticks >> 20;
  1301.             if (NewEvent->ie_X & 0x800) NewEvent->ie_X |= 0xF000;
  1302.             if (NewEvent->ie_Y & 0x800) NewEvent->ie_Y |= 0xF000;
  1303.             break;
  1304.  
  1305.          default:
  1306.             printf("[ Unknown Event Class:  %02X]\n",NewEvent->ie_Class);
  1307.             break;
  1308.       }
  1309.       NewEvent->my_Ready = READY;
  1310.    }
  1311. }
  1312.  
  1313.  
  1314. /*
  1315.  *  WaitForEvents()
  1316.  *
  1317.  *  Wait for the handler to finish posting all the events in the Event
  1318.  *  array (so we don't remove the handler before it is done).
  1319.  */
  1320.  
  1321. void WaitForEvents()
  1322. {
  1323.    short LastFree = NextFree;
  1324.    
  1325.    do GetNextFree(); while (NextFree != LastFree);
  1326. }
  1327.  
  1328.  
  1329. /*
  1330.  *  PlayJournal()
  1331.  *
  1332.  *  Open the journal file, set up the task and signals, and allocate the
  1333.  *  Event array.  Add the input handler and send the pointer to the upper,
  1334.  *  left-hand corner of the screen.  While there are still events in the
  1335.  *  journal file, check whether the user wants to cancel the playback and
  1336.  *  if not, post the next event in the file.  When the end-of-file is reached
  1337.  *  wait for the handler to finish posting all the events in the array, and 
  1338.  *  then remove the handler.
  1339.  */
  1340.  
  1341. void PlayJournal()
  1342. {
  1343.    OpenJournal();
  1344.    SetupTask();
  1345.    GetEventMemory();
  1346.  
  1347.    AddHandler(&myHandler);
  1348.    PointerToHome();
  1349.  
  1350.    while (feof(InFile) == 0)
  1351.    {
  1352.       CheckForCTRLC();
  1353.       PostNextEvent();
  1354.    }
  1355.    
  1356.    WaitForEvents();
  1357.    RemoveHandler();
  1358. }
  1359.  
  1360.  
  1361. /*
  1362.  *  main()
  1363.  *
  1364.  *  Parse the command-line arguments, and perform the proper function
  1365.  *  (either show the usage, read a journal, or fall through and exit).
  1366.  */
  1367.  
  1368. void main(argc,argv)
  1369. int argc;
  1370. char **argv;
  1371. {
  1372.    switch(ParseArguments(argc,argv))
  1373.    {
  1374.       case SHOW_USAGE:
  1375.          printf("Usage:  %s\n",USAGE);
  1376.          break;
  1377.  
  1378.       case READ_JOURNAL:
  1379.          PlayJournal();
  1380.          break;
  1381.    }
  1382.    DoExit(NULL);
  1383. }
  1384. SHAR_EOF
  1385. cat << \SHAR_EOF > journal.h
  1386. /*
  1387.  *  JOURNAL.H  -  Common header file for JOURNAL.C and PLAYBACK.C
  1388.  *
  1389.  *             Copyright (c) 1987 by Davide P. Cervone
  1390.  *  You may use this code provided this copyright notice is kept intact.
  1391.  */
  1392.  
  1393. #include <libraries/dos.h>
  1394. #include <exec/io.h>
  1395. #include <exec/interrupts.h>
  1396. #include <exec/memory.h>
  1397. #include <devices/input.h>
  1398. #include <devices/inputevent.h>
  1399. #include <stdio.h>
  1400.  
  1401. #define ONE         1L
  1402.  
  1403. extern struct MsgPort *CreatePort();
  1404. extern struct IOStdReq *CreateStdIO();
  1405. extern LONG AllocSignal(), Wait(), SetSignal();
  1406. extern struct Task *FindTask();
  1407. extern struct InputEvent *AllocMem();
  1408. extern FILE *fopen();
  1409. extern long errno, _OSERR;              /* Lattice and DOS error numbers */
  1410.  
  1411. extern void RemoveHandler();            /* defined later on */
  1412.  
  1413. /*
  1414.  *  assembly routine that gets called by the Input.Device which sets up
  1415.  *  the stack and calls our input handler
  1416.  */
  1417. extern void myHandlerStub();
  1418.  
  1419.  
  1420. /*
  1421.  *  Structure used to pack event data into a small space.  This is the 
  1422.  *  format used to record that data in the journal file */
  1423.  
  1424. struct SmallEvent
  1425. {
  1426.    union
  1427.    {
  1428.       struct
  1429.       {
  1430.          UBYTE se_IDType;   /* ie_Class and ie_Code combined */
  1431.          UBYTE se_Raw;      /* RawKey Code */
  1432.          UWORD se_PrevChar; /* previous key and qualifier */
  1433.       } se_ID;
  1434.       ULONG se_XYpos;       /* X in bits 0-11, Y in 12-23 (ie_Type in 24-31) */
  1435.    } se_Long1;
  1436.    UWORD se_Qualifier;
  1437.    ULONG se_Long2;          /* Micros in 0-19, Ticks in 20-32 */
  1438. };
  1439. #define se_Type   se_Long1.se_ID.se_IDType
  1440. #define se_Code   se_Long1.se_ID.se_Raw
  1441. #define se_Prev   se_Long1.se_ID.se_PrevChar
  1442. #define se_XY     se_Long1.se_XYpos
  1443. #define se_Ticks  se_Long2
  1444. #define se_Micros se_Long2
  1445. #define se_Count  se_Long2
  1446.  
  1447. /*
  1448.  *  Some shorthands for InputEvent fields
  1449.  */
  1450. #define my_Prev   ie_X
  1451. #define my_Time   ie_Secs                   /* micros since last event */
  1452. #define my_Ticks  ie_Mics                   /* ticks since last event */
  1453. #define my_Ready  ie_NextEvent              /* TRUE when it can be posted */
  1454. #define my_InUse  ie_Class                  /* TRUE when it is in use */
  1455. #define my_Saved  ie_SubClass               /* TRUE if is has been recorded */
  1456. #define my_Count  my_Time                   /* number of compressed events */
  1457. #define ie_Secs   ie_TimeStamp.tv_secs
  1458. #define ie_Mics   ie_TimeStamp.tv_micro
  1459.  
  1460. #define COUNTMASK 0x3F                      /* how much of my_Count is count */
  1461. #define READY     ((struct InputEvent *) TRUE)
  1462. #define TIME      (theEvent->ie_Mics-TimerMics)
  1463. #define MILLION   1000000
  1464.  
  1465. #define XMINMOUSE      xminmove
  1466. #define YMINMOUSE      yminmove
  1467. #define XDEFMIN        8                    /* default DX */
  1468. #define YDEFMIN        8                    /* default DY */
  1469. #define LONGTIME       8                    /* if this many ticks occur, we */
  1470.                                             /*   write out a dummy move to */
  1471.                                             /*   record the pause */    
  1472.  
  1473. #define IE_SIZE        sizeof(struct InputEvent)
  1474. #define NEWEVENT       AllocMem(IE_SIZE,0)
  1475. #define FREEVENT(ev)   FreeMem(ev,IE_SIZE)
  1476.  
  1477. #define SIGBREAKF_ANY  (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D |\
  1478.                         SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F)
  1479. SHAR_EOF
  1480. cat << \SHAR_EOF > handlerstub.a
  1481.         CSECT   text
  1482.  
  1483.         XREF    _myHandler
  1484.         XDEF    _myHandlerStub
  1485.         
  1486. _myHandlerStub:
  1487.         MOVEM.L A0/A1,-(A7)
  1488.         JSR     _myHandler
  1489.         ADDQ.L  #8,A7
  1490.         RTS
  1491.         
  1492.         END
  1493. SHAR_EOF
  1494. cat << \SHAR_EOF > journal.lnk
  1495. FROM LIB:c.o+journal.o+handlerstub.o
  1496. TO journal
  1497. LIB LIB:lc.lib+LIB:amiga.lib
  1498. NODEBUG
  1499. SHAR_EOF
  1500. cat << \SHAR_EOF > playback.lnk
  1501. FROM LIB:c.o+playback.o+handlerstub.o
  1502. TO playback
  1503. LIB LIB:lc.lib+LIB:amiga.lib
  1504. NODEBUG
  1505. SHAR_EOF
  1506. #       End of shell archive
  1507. exit 0
  1508.  
  1509.