@make(article)
@device(x9700)
@majorheading(RECORDER)
Jeffrey S. Shulman

Last revised on:  July 7, 1984

@section(Introduction)

@b(RECORDER) is a package that allows you to record and playback
mouse and keyboard events.  These events are recorded as you
perform them in (more or less@footnote[N.B. Due to the way playback
occurs interrupts will not be seen in real time.  For example, if you
were pretty-printing a long function to the typescript window and
↑E'ed it in the middle, the printing would (during recording) stop immediately.
However, during playback, the ↑E would not be @i(seen) until the
pretty-printing was finished.]) real time.  During playback these
recorded events are re-performed as if you were there doing it again.

@b(RECORDER) performs these feats of magic by redefining the low level
mouse and keyboard handlers.  Specifically @b(RECORDER) provides its
own versions of GETMOUSESTATE and \GETSYSBUF (as well as a slightly
modified version of \KEYHANDLER1.)

@section(Use)

To use you first load @b(RECORDER.DCOM).  After this file is loaded it
will BKSYSBUF its initialization function as well as a @b(HARDRESET)
(this is necessary in order for the modified \KEYHANDLER1 to take affect.)

@subsection(Starting a recording)

To start a recording session you use the function @b(RECORD.START).  This
function returns immediately with the array the recording will be placed
in.  This array is a SMALLP array of @b(\RASIZE) elements.

At this point the recording has not actually started.  You should now
position the mouse where you want it to be when the recording starts.
To actually start the recording you press the @b(CONTROL) and @b(LEFT SHIFT)
key simultaneously (this fact also appears in the PROMPTWINDOW after you
call @b(RECORD.START) as a reminder.)

When these keys are pressed the recording starts.  The message 
@b(Recording....) will appear in the PROMPTWINDOW.  Recording proceeds
until either you press @b(CTRL-LSHIFT) again or the array fills up.
When either of these events occur the message @b(stopped.) will appear
in the PROMPTWINDOW.

A @i(sync event) (described below) is recorded automatically when you start
and when you stop a recording.

@newpage
Thus, an example of the sequence of events would be:
@example[
(SETQ A (RECORD.START))
@i(position the mouse to the starting position)
Press @b(CTRL-LSHIFT)
@i(perform the desired actions)
Press @b(CTRL-LSHIFT)
]

This array may be saved on a file using the @b(UGLYVARS) @i(File Package) 
command.  The function @b(SQUEEZE.RECORDING), described below, can be used
to eliminate much of the dead space.

@subsection(Playing back a previous recording)

To playback a previous recording you may use the function
@b(REPLAY) whose single argument is the array returned by a
previous call to @b(RECORD.START).  @b(REPLAY) will set in
motion what is needed to playback an old recording and will return
after the playback is finished.

During playback the mouse and keyboard will @b(NOT) respond to anything
you do.  The only exceptions to this are:
@begin(enumerate)
Pressing @b(CTRL-LSHIFT) will stop playback and return control to you.

Pressing @b(CTRL-LSHIFT-DELETE) (the emergency interrupt) will stop playback
and reset the system.
@end(enumerate)

You will again receive control of the keyboard and mouse when the playback
is finished.

If you typed what was in the previous "How to record" example you would play
it back via:
@example[
(REPLAY A)
]

@section(Synchronization)

During playback you may wish to perform some "outside" event.  For
example while the mouse was moving around or when choosing various
menu items you might want to print some accompanying text.  An easy
method of synchronizing just these kinds of things is provided so
that things "happen" at the appropriate moment.

During playback this synchronization is accomplished by the function
@b(RP.SYNC).  If while playback is happening a @i(sync event) occurs
(more about how to put one in below) the recording will essentially
suspend itself until a call to @b(RP.SYNC) has been performed.  If a
call to @b(RP.SYNC) occurs @b(before) a @i(sync event) it will not
return from it until this @i(sync event) happens.

A @i(sync event) is automatically recorded at the start and at the end
of each recording.

The function @b(REPLAY) is really a call to
@b(PLAYBACK.START) followed by  two calls to @b(RP.SYNC)
(one for the start of the recording and one for the ending.)

The function @b(PLAYBACK.START) (whose single argument in the recording array)
is what @i(really) starts playback of a 
previous recording.  This function queues up the recording for playback
and returns @i(immediately).  You should use this function and provide your
own @b(RP.SYNC)'s if you want to "do" something during a playback.

This should be come clear by an example.
Suppose you wanted to print "I will now move to menu X", move to the menu,
print "Now I will select an item from this menu" and then select an item
from this menu.

During recording you would move the mouse over
to the menu, cause a @i(sync event), and then select the menu item.  

A function that would playback this back correctly would look like:
@example[
(LAMBDA (RECORDED-ARRAY)
	(PLAYBACK.START RECORDED-ARRAY)
	(printout T "I will now move to menu X" T)
	(RP.SYNC)
	(RP.SYNC)
	(printout T "Now I will select an item from this menu" T)
	(RP.SYNC))]

What happens in this function is:
@begin(enumerate)
The recording is queued up.  Since the act of making the recording puts
in a @i(sync event) at the start, the mouse does not move.

"I will now move to menu X" is printed.

The first (RP.SYNC) starts the playback.  The mouse begins moving to the menu.

The second (RP.SYNC) does not return until the second @i(sync event)
happens.  This is the one that was intentionally put in during recording.

"Now I will select an item from this menu" is printed after the second
@i(sync event).

The third (RP.SYNC) waits for the last @i(sync event) that was automatically
recorded when the recording stopped to happen.
@end(enumerate)

@subsection(Sync events)

@i(Sync events) are caused to occur during recording by pressing the
@i(sync) key.  This key's @b(number) (returned by \KEYNAMETONUMBER)
is bound to the global variable @b(\SYNC.KEY).  The default is 91
which is the @b(AGAIN) key on the Dandelion's keyboard.

When this key is pressed during recording a @i(sync event) is recorded
at that instant.  Feedback is provided by printing a letter @b(S) in
the PROMPTWINDOW each time this key is pressed.

N.B.:  If you are not careful it is possible to have the wrong number of
@i(sync events) to the number of @b(RP.SYNC) function calls.  This could
cause the mouse to freeze waiting for a @b(RP.SYNC) function call (which
you cannot type in yourself since the keyboard is locked out during playback.)
Should this happen it is possible to stop playback and regain control
via the @b(CTRL-LSHIFT) abort.

@section(Caveats)

It should go without saying that it is important for the @i(same exact) set of
circumstances to exist before any given playback as they did when the
recording was made.

Little @i(gotcha's) will pop up in the unexpected places so it is important
to "set up" before any playback.

An example of a @i(gotcha) are popup menus that use the MENUOFFSET field
to determine where they will next appear.  If this is different at playback
time from what it was at record time the results will not be the same (N.B.:
the global menus @b(WindowMenu), @b(IconWindowMenu) and @b(BackgroundMenu)
are examples menus that use the MENUOFFSET field.  Since these are the most
commonly used menus that are all set to NIL (so they will be recreated from
their corresponding @i(Commands) list) by both @b(RECORD.START) and
@b(PLAYBACK.START).)

@section(Miscellaneous stuff)

During playback mouse key presses are indicated by the letter L, M and R
shown along the bottom of the cursor bitmap (at the obvious positions.)

The function @b(SQUEEZE.RECORDING) is provided to eliminate @i(dead space)
at the end of a recording array.  Its single argument is an array previously
returned by @b(RECORD.START).  Its value is a @b(new) array of minimum size.