@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.