Heading:
Cedar Terminal Input Facilities
Page Numbers: Yes X: 527 Y: 10.5"
Inter-Office Memorandum
ToCedar InterestDateJanuary 15, 1981
FromDan SwinehartLocationPalo Alto
SubjectCedar Terminal Input FacilitiesOrganizationCSL
XEROX
Filed on: [Ivy]<CedarDocs>User>InscriptImplementation.memo
[Ivy]<CedarDocs>User>InscriptImplAppendix.memo
Reference: [Ivy]<CedarDocs>User>InscriptDesign.memo (what I intended to do)
These will currently all be found on [Ivy]<Swinehart>Inscript>6.0>. They have also been released as part of Cedar.bcd.
Definitions files:
ClassInstream.bcd,
ClassIncreek.bcd,
Interminal.bcd,
Intime.bcd
InDiag.bcd
InOS.bcd
Implementation:
Instream.bcd, exports above interfaces, includes symbols
Instream.config
other source files, configs, etc.
Introduction
This memo describes the existing facilities for obtaining user terminal input. The most accurate interface description can be found in the cited definitions files; this discussion is intended to present both the main ideas and the details, but it may lag behind the truth from time to time.
User information is recorded in a file called an Inscript. The inscript conceptually contains a time-stamped record of every activity, or action, the user has ever performed at the terminal. In reality, old information may be removed from this record, and it is possible that during some intervals not all user input is recorded (e.g., the detailed trajectories of the mouse.)
Most clients will obtain information from the inscript by creating an Instream, which provides quite a bit of help with the interpretation of the user’s activities -- primarily filtering and code-conversion assistance. Any number of instreams can be created, each of which may be examining different epochs in the input history.
Clients that wish to deal more directly with the recorded actions, perhaps to create a high-level input stream with different behavior from the Instream defined here, may create a lower-level stream-like object called an Increek. The Increek provides operations for time-based positioning within the Inscript, and for examining Inscript actions.
Instreams
An Instream provides a stream of Events, currently confined to keyboard, keyset, and mouse button (henceforth shortened to "key") activations. Instream provisions for tracking the mouse in the absence of key activations are TBD, although complete facilities exist at the Increek level. Not all user activities, or Actions, result in Events, and some Events are not the immediate result of user activity. The Instream client can control to some extent which Actions result in Events.
The client can choose to obtain and examine Events directly from the Instream, or to obtain derivative data types based on the type of Event (keyboard characters, mouse button clicks, etc.) After obtaining an Event or a derivative, the client can also examine the state of the Instream to obtain time stamp information, the position of the mouse, or the state of various keys (shift, lock, control, etc., or any other key for that matter) at the time of the event.
Data Types
What follows is a discussion of the more important data types used or viewed by Instream and Increek clients.
From ClassInstream
ClassInstream.Instream: TYPE = REF InstreamObject;
This is an object that implements the operations described below.
ClassInstream.Device: TYPE = {nullDevice, char, button, paddle, motion};
An Instream delivers Events to its clients. A field in each Event identifies which device generated it (keyboard, mouse, mouse motion only, or keyset), and what kind of activity caused it. Motion events are not yet implemented.
ClassInstream.Cause: TYPE = MACHINE DEPENDENT {none(0),
strobeDown(1), strobeUp(2), heldDown(4), heldUp(10B), canBeChord(20B)};
ClassInstream.Causes: TYPE = -- Powerset of -- Cause;
The Instream client uses values of type Causes to specify which of the user activities are of interest for each of the three devices (there are standard defaults.) strobeDown means that an Event will occur whenever a key is depressed. strobeUp means that an Event will accompany the release of a key on the selected device. heldDown and heldUp will occur only after a key has been depressed (released) for a specified duration, in the absence of additional keystrokes. This is in support of multiple click (and later "typeamatic") Events, described further in a later section. For simple situations one merely specifies strobeDown, strobeUp, or both.
Events also contain Cause fields, to indicate to the client which kind of activation occurred.
Cause has its values explicitly specified so that values can be ORed together to specify multiple activations. There are "powerset" functions, described below, for creating the desired configurations.
ClassInstream.Event: TYPE = RECORD [
device: Device,
keyName: Interminal.KeyName,
cause: Cause,
clicks: CARDINAL
];
The Event records which device was involved, what kind of activation occurred, and which key was depressed (using names assigned from a space containing them all). Each Event also contains a clicks field, described further in the Clicks section. The stream stores additional information about the current state of all the input devices just after the Event occurred. This state includes the current activation state of all of the keys, the mouse position at the time of the Event, the time of the Event, the "clicks" information, and a chord field that has collected all key depressions since the last time all keys were undepressed. The Instream provides operations for obtaining these values.
ClassInstream.EventTime: TYPE=Intime.EventTime;
Intime.EventTime: --(effectively)-- TYPE[3];
This is not really an exported type at present, being fully specified in the definitions file. But its internal fields are not meaningful to clients. An EventTime represents the number of milliseconds since some specific time early in the century. Functions exist (in the Intime interface) for converting the present time to an EventTime, for performing simple arithmetic and comparisons on these times, etc. EventTimes may be used to denote positions within the Instream.
ClassInstream.MousePosition: TYPE = Interminal.MousePosition;
Interminal.MousePosition: TYPE = MACHINE DEPENDENT RECORD [
mouseX: CARDINAL, mouseY: CARDINAL];
These are expressed in Alto screen coordinates, at present. Expect these representations to change in order to adapt to Cedar graphics’ more abstract view of things.
ClassInstream.PosResult: TYPE = ClassIncreek.PosResult;
ClassIncreek.PosResult: TYPE = {tooEarly, tooLate, onTime};
Describes the nature of the results of some of the Instream operations below.
ClassInstream.MsTicks: TYPE = Intime.MsTicks;
Intime.MsTicks: TYPE = Process.Milliseconds -- ... = CARDINAL --;
Describes the nature of the results of some of the Instream operations below.
From Interminal
Interminal.KeyView: TYPE = {keyNames, keyFields, words, bits};
Interminal.KeyState: TYPE = MACHINE DEPENDENT RECORD [
SELECT OVERLAID KeyView FROM
bits => [bits: PACKED ARRAY KeyName OF updown],
words => [words: KeyArray],
keyNames => [keyNames: KeyNames],
keyFields=> [keyFields: KeyFields],
ENDCASE];
Interminal.KeyName: TYPE = {
x0, x1, ..., x7,
Keyset1,Keyset2,...,Keyset5,
Red,Blue,Yellow,
Five,...,U,V,
...
Lock,Space,...,FR5};
There are a number of useful ways to look at a representation of the keyboard state; these are the ones that have been chosen for Cedar input. The bits array can be indexed by the KeyName values, which are also the values that are stored in Actions and Events. The keyNames variant lets you treat the key state as a record of named bits. keyFields breaks the state up into smaller records, by device, once you know which device you’re interested in. The client of Instreams and Events will normally require only KeyNames, but may need to access other values to examine the control, shift, shift lock keys, or other "non-strobing" situations in efficient ways.
Interminal.PaddleKeyName, KeyArray, KeyNames, KeyFields, Buttons, ButtonNames, Paddles, PaddleNames: TYPE = ...;
These types are used to implement the ones described above. Every implementor of an Instream or Increek client should read through them and pick out the types that seem relevant to the particular way the client code is going to use the package; the implementor will probably settle on a fairly small number of them.
Interminal.Spare1: CHARACTER=LOOPHOLE[201B];
Interminal.Spare2: CHARACTER=LOOPHOLE[202B];
Interminal.Spare3: CHARACTER=LOOPHOLE[203B];
Interminal.ShiftSpare1: CHARACTER=LOOPHOLE[204B];
Interminal.ShiftSpare2: CHARACTER=LOOPHOLE[205B];
Interminal.ShiftSpare3: CHARACTER=LOOPHOLE[206B];
These definitions provide representations in the "Ascii" domain for the three spare keys and their shifted versions. They were arbitrarily chosen.
Procedures and Operations
Some of these functions are currently implemented as INLINE procedures, and are thus tied to a particular implementation of the Instream data record type. These will be changed if necessary.
ClassInstream.EventIncorrect: ERROR[e: Event];
ClassInstream.EventVanished: ERROR[eT: Intime.EventTime];
ClassInstream.NewStdInstream: PROCEDURE RETURNS [ClassInstream.Instream];
Creates an Instream on the standard Inscript that is filled with user terminal actions. It is possible to produce Instream implementations that obtains actions from a different source, in which case this function would not be used to create them. The result is an Instream object. It will have been positioned at the end of (the latest point in) the Inscript (corresponding to "now".)
The implementation does not enforce a limit on the number of Instreams that may be created to examine the same Inscript. This design will make more sense if at least two Instreams get created by somebody in some set of Cedar applications;.
ClassInstream.GetEvent: PROCEDURE[self: ClassInstream.Instream] RETURNS [e: ClassInstream.Event];
Returns the next Event from the stream that satisfies the client’s specifications (see below.) If the stream is currently positioned at the end of the Inscript, this operation will wait as necessary until actions constituting an acceptable Event have occurred. The client program should not be able to detect a difference between waiting and non-waiting situations, unless the client is also keeping track of real time.
Raises ClassInstream.EventVanished if the Events denoted by the current Instream position have disappeared to make room for newer ones. This will only happen if the Instream is reviewing ancient history. The EventTime parameter denotes the time of the earliest Action remaining in the Inscript.
ClassInstream.GetChar: PROCEDURE[self: ClassInstream.Instream, event: ClassInstream.Event←nilEvent] RETURNS [c: CHARACTER];
If event is nilEvent (not supplied in the call), an event is obtained from the Instream. If the event represents a keyboard keystroke, its Ascii code is returned. Otherwise GetChar raises ClassInstream.EventIncorrect, with the Event as a parameter. The intent is that a "character loop" can call GetChar repeatedly, acting on the incoming characters until an event occurs that is not a character event. Alternatively, the client can obtain an Event using GetEvent, determine that it is a char event, then obtain the corresponding Ascii code using GetChar.
There may never be any "character loop" types of applications, in which case we should simplify this interface. The original plan also called for leaving the stream positioned at its pre-call location when EventIncorrect was raised, returning just the event type as an error value, so that the client could retry the operation. The current implementation does not do this, on the grounds of expense. The plan also called for functions to reposition the stream by small integral numbers of Events in either direction, and other such (hopefully) nonsense.
ClassInstream.GetPaddle: PROCEDURE[self: ClassInstream.Instream, event: ClassInstream.Event←nilEvent] RETURNS [e: ClassInstream.Event];
ClassInstream.GetButton: PROCEDURE[self: ClassInstream.Instream, event: ClassInstream.Event←nilEvent] RETURNS [e: ClassInstream.Event];
These are like GetChar, but directed at the other input devices. Each returns the full Event that GetEvent would return, but only if the Event is caused by the requested device; otherwise, they raise EventIncorrect.
ClassInstream.GetPaddleName: PROCEDURE[self: ClassInstream.Instream, keyName: Interminal.KeyName]
RETURNS [name: Interminal.PaddleName];
ClassInstream.GetButtonName: PROCEDURE[self: ClassInstream.Instream, keyName: Interminal.KeyName]
RETURNS [name: Interminal.ButtonName];
These apply appropriate offsets to the keyName values to produce values from smaller ranges specific to their devices. They should, but do not, complain when they produce values that are out of range.
ClassInstream.GetMousePosition: PROCEDURE[self: ClassInstream.Instream]
RETURNS [mP: ClassInstream.MousePosition];
Returns the mouse position that obtained just after the (occurrence of the) Last Action (in the most recently obtained Event).
ClassInstream.GetEventTime: PROCEDURE[self: ClassInstream.Instream]
RETURNS [eT: ClassInstream.EventTime];
Returns the time at which the Last Action occurred.
ClassInstream.GetCurrentTime: PROCEDURE[self: ClassInstream.Instream]
RETURNS [eT: ClassInstream.EventTime];
Defines "now" so that you can come back to it later. There are a number of functions defined in Intime that allow for calculations on and comparisons of EventTimes. This function is not dependent on Instream position.
ClassInstream.GetStateOfKey: PROCEDURE[self: ClassInstream.Instream, keyName: Interminal.KeyName]
RETURNS [state: Interminal.updown];
Returns the state of the selected key just after the Last Action.
ClassInstream.GetKeyState: PROCEDURE[self: ClassInstream.Instream]
RETURNS [keyState: Interminal.KeyState];
ClassInstream.GetChordState: PROCEDURE[self: ClassInstream.Instream]
RETURNS [chordState: Interminal.KeyState];
GetKeyState returns the entire state of the key devices just after the Last Action. GetChordState is similar, returning a KeyState representing all the the keys that have been depressed since they were all up (downCount was last zero).
ClassInstream.GetDownCount: PROCEDURE[self: ClassInstream.Instream,
device: ClassInstream.Device←nullDevice] RETURNS [downCount: CARDINAL];
Returns the number of keys that were depressed just after the Last Action, for the specified device. If device is omitted or is nullDevice, returns the number of keys were depressed for all devices (this is somewhat more efficient.)
ClassInstream.GetChord: PROCEDURE[self: ClassInstream.Instream] RETURNS [s: keyFields Interminal.KeyState];
This procedure is not yet implemented, nor is it fully designed. Its intent is to provide a high-level method for obtaining the complete value of the "next chord", once the client detects that a chord-like activity is in progress.
ClassInstream.SetAtEarliest: PROCEDURE[self: ClassInstream.Instream];
Positions the stream preceding the earliest known action. For reviewing the history of the universe. In some implementations (this one, for example), one is well-advised to proceed with dispatch to examine these earlier actions before they go away to make room for new ones; if that occurs, the Get... routines will raise a signal, but this aspect of the implementation is not worked out very well yet.
ClassInstream.SetAtLatest: PROCEDURE[self: ClassInstream.Instream];
Positions the stream to "now".
ClassInstream.SetAtTime: PROCEDURE[self: ClassInstream.Instream, eventTime: ClassInstream.EventTime] RETURNS [pR: PosResult];
Positions the stream to the first point at or following the specified time. The PosResult return value indicates whether the specified time preceeds the earliest known action, follows the last known action, or lies somewhere within the known history. In any case, the stream is positioned at the nearest approximation to the selected time.
SetEventSpecifications: PROCEDURE[self: ClassInstream.Instream,
device: ClassInstream.Device, causes: ClassInstream.Causes, clickTime: ClassInstream.MsTicks←0];
device is used to specify the device, causes the Actions that are to result in Events for that device, and clickTime the timeout value, in milliseconds, that will subsequently be used to control "click" computations for all the devices (see the section on multiple clicks). The initial defaults for the devices are:
[device: char, causes: strobeDown]
[device: button, causes: {strobeDown, strobeUp}] (e.g., PowerSet[sD, sU])
[device: paddle, causes: {strobeDown, strobeUp}]
[device: motion, causes: <not applicable, not implemented.>]
This produces Events only when keyboard keys are depressed, and when mouse/keyset keys are activated one way or the other.
Some Inlines defined in ClassInstream
ClassInstream.PowerSet: PROCEDURE [e1, e2, e3, e4: ClassInstream.Cause←none] RETURNS [ClassInstream.Causes] = ...;
ClassInstream.In: PROCEDURE[candidate: ClassInstream.Cause, target: ClassInstream.Causes] RETURNS [BOOLEAN] = = ...;
PowerSet[...] creates a Causes value, given two or more Cause or Causes values. The result represents a set of Causes. What really happens is that the bits are OR’ed together; Cause is defined so that the named values are powers of 2.
In[...] determines whether the candidate is a member of the set of Causes in the target. If the candidate is a Causes value, all of its members must be in the target. (TRUE iff LOGAND[candidate, target] = candidate.)
These functions use inline definitions available in Powerset.Mesa/bcd, redefining their input and output types.
Multiple "Clicks"
Many user interfaces, including Tioga’s, use the concept of multiple activations of the mouse, typically within a given time and without excess motion of the mouse, to increase the number of interpretations that can be assigned to mouse buttons. In the Inscript world, with its emphasis on the recording of actions, and the ability to respond to them more than once, and perhaps long after they occur, it seemed prudent to capture as much of the complexity of multi-click activities as possible at the Instream level of the user input facilities.
The current interpretation is based almost entirely on Tioga’s needs. Most of the extensions that come to mind could be readily accommodated.
The client uses SetEventSpecifications to specify, for each device, what kinds of events are interesting. If either heldDown or heldUp is specified, the client must also supply a time, in milliseconds, that is used as follows.
Suppose that all keys are "up", and have been for some time. The client has specified that, on the mouse, only heldDown and heldUp Events are interesting (at present, specifying one is equivalent to specifying both -- an implementation expedience), with a clickTime of 100 ms. The user now depresses a mouse button. The Instream would not immediately interpret this Action as an Event, but would "start a timer" to expire in 100 ms. If the user moves the mouse "too far" (currently a constant -- what should it be?) within that 100 ms., or depresses or releases some other key, or does not perform any more Actions within the 100 ms., a heldDown event results. If the user releases the button within the 100 ms., the timer is restarted, and no event results. When the user finally waits long enough, moves far enough, or activates some other key, a heldDown or heldUp event results.
If strobeDown and/or strobeUp had also been specified in the above example, each of the actual key activations would also have resulted in an Event; these additional events would have been properly interleaved with exactly the same held-style Events that occurred in the example.
If held... events have been specified, each event will include, in the clickCount field, the number of key activations (down or up), including the one that resulted in the event, that have occurred within the same multi-click sequence. Otherwise, the clickCount field will always contain a 1 or something.
Note that all of these time-related operations use the times that are recorded in the Inscript; the semantics of the click activities are independent of the celerity with which the client is examining the Inscript. However, the amount of real time that a given call on, say, GetEvent will require is dependent on whether the stream is positioned at "end of script."
There may well be other useful things to say about this implementation; what are they?
Increeks
An Increek is a lower-level object that can be used to gain access to an Inscript. The Instream implementation, in fact, simply provides interpretations for collections of low-level individual user Actions which are extracted using Increeks. Increek operations largely parallel the Instream operations. Details and discussion of the Increek level follow.
Data Types
From ClassIncreek
One obtains Actions by invoking operations in an object called an Increek. The kind field of the Action indicates what happened, and the other fields provide the details.
ClassIncreek.Increek: TYPE = REF IncreekObject;
ClassIncreek.ActionKind: TYPE = {deltaEventTime, eventTime, deltaMouseX, deltaMouseY, mousePosition, keyDown, keyUp, keyStillDown};
ClassIncreek.Action: TYPE = LONG POINTER TO ClassIncreek.ActionBody;
ClassIncreek.ActionBody: TYPE = RECORD [
deltaDeltaTime: Intime.DeltaDeltaTime ← 0,
contents: SELECT kind: ClassIncreek.ActionKind FROM
deltaEventTime => [value: Intime.DeltaTime ← NULL],
deltaMouseX, deltaMouseY =>
[value: ClassIncreek.DeltaMouseValue ← NULL],
keyDown, keyStillDown, keyUp =>
[value: Interminal.KeyName ← NULL],
eventTime => [eventTime: Intime.EventTime ← NULL],
mousePosition => [mousePosition: Interminal.MousePosition ← NULL],
ENDCASE
];
If the time between Actions is short enough, the time difference can be recorded in the deltaDeltaTime field of the next Action. Otherwise it will appear as a separate Action preceding the "real" one, either as a deltaEventTime Action if the interval is not too long, or as a full eventTime value. Similarly, mouse motions are recorded either as small incremental mouseX and mouseY Actions or as full mousePosition values. The keyUp and keyDown Actions correspond to actual user keyboard activities.
keyStillDown is used to record efficiently the entire keyboard state at the beginning of each "session", and at the beginning of each inscript page; one Action appears for each key that is "still down", typically zero, one, or two. This allows the state to be recreated efficiently without scanning the entire history. The full time and full mouse position are also inserted into the inscript at these times. keyStillDown[value: allUp] should be interpreted as a request to clear the state to indicate no depressed keys.
As with an Instream, an Increek defines a position within its Inscript. This position represents an EventTime at which the input devices were in a particular state, defined by the type InscriptPosition (ViewPosition was intended as a READONLY version of this type, but I couldn’t get it to work right.)
ClassIncreek.InscriptPosition: TYPE = REF ClassIncreek.InscriptPositionBody;
ClassIncreek.ViewPosition: TYPE = REF -- can’t get READONLY to work right -- ClassIncreek.InscriptPositionBody;
ClassIncreek.InscriptPositionBody: TYPE = RECORD [
-- location in inscript file --
inscript: PRIVATE ClassInscript.Inscript, -- for releasing
inscriptPage: PRIVATE ClassInscript.InscriptPageDescriptor,
-- absolute state at that point --
eventTime: Intime.EventTime ← NULL,
mousePosition: Interminal.MousePosition ← NULL,
keyState: Interminal.keyState ← NULL,
chordState: Interminal.keyState ← NULL,
downCount: INTEGER ← 0
];
chordState is cleared to allUp whenever keyState is about to leave the allUp condition; keyDown and keyStillDown events cause the corresponding chordState bits to be set, but keyUp events do not effect chordState. Probably the shift lock key should not participate in the chordState.
PosResult describes the nature of the results of some of the Increek operations below. Acceptance is a client-provided parameter to GetAction. DeltaTime and DeltaDeltaTime are time values of differing lengths, in units corresponding to the process-scheduling "tick" interval. Intervals at this resolution can be represented in fewer bits. Unless the client examines these Action fields directly, it will not have to deal with these units, since most of the time-related functions convert these times back to millisecond-resolution units. MousePosition, KeyState, etc., are as defined in the Instreams section. DeltaMouseValue is a short, incremental version of the components of MousePosition. WaitMode determines the timeout behavior of the GetAction operation.
ClassIncreek.PosResult: TYPE = {tooEarly, tooLate, onTime};
ClassIncreek.Acceptance: TYPE = {clicks, clicksAndMotion, all};
ClassIntime.DeltaTime: TYPE = ... CARDINAL [0..256);
ClassIntime.DeltaDeltaTime: TYPE = DeltaTime [0..32);
ClassIncreek.DeltaMouseValue: TYPE = [-128..128);
ClassInscript.WaitMode: TYPE = {forever, dontWait, timed};
Procedures and Operations
ClassIncreek.IncreekError: ERROR[code: ClassIncreek.IncreekErrorCode];
ClassIncreek.IncreekErrorCode: TYPE = {
outOfBounds -- position no longer valid during ReadAction
};
NewStdIncreek provides the standard Increek implementation. Its Actions are obtained from the standard Inscript implementation, which directly records user terminal events. If the template argument is NIL, the result is a new Increek on which the SetAtLatest operation has just been performed.
One can also call this function to obtain an Increek that is a copy of another Increek, the template; the copy represents another source of Actions, positioned at the same point in the script. This "produce copy" function should be an object operation.
The CopyIncreek operation is equivalent to SetAtTime[self, GetTime[template]];
ClassIncreek.NewStdIncreek: PROCEDURE[template: ClassIncreek.Increek←NIL] RETURNS [ClassIncreek.Increek];
ClassIncreek.Release: PROCEDURE [self: ClassIncreek.Increek] RETURNS [nilIncreek: ClassIncreek.Increek];
ClassIncreek.CopyIncreek: PROCEDURE [self: ClassIncreek.Increek, template: ClassIncreek.Increek];
The SetAt... functions in the ClassInstream interface parallel the ones provided here. Each of these functions positions the Increek such that the next Action is the earliest in the Inscript that occurred after the specified time. In the process of positioning the Increek, it updates the Increek’s ViewPosition (state record) to represent the state of the terminal corresponding to the new position.
The Get...Time functions return the same values as the corresponding functions in the ClassInstream interface. GetPositionFrom returns the ViewPosition record describing the terminal state at the current position. It is provided in lieu of the slew of functions (as in ClassInstream) that would otherwise be required; the ClassIncreek interface is intended for use by system implementors to provide higher-level interfaces.
ClassIncreek.SetAtEarliest: PROCEDURE [self: ClassIncreek.Increek];
ClassIncreek.SetAtLatest: PROCEDURE [self: ClassIncreek.Increek];
ClassIncreek.SetAtTime: PROCEDURE [self: Increek, eventTime: Intime.EventTime]
RETURNS [pR: ClassIncreek.PosResult];
ClassIncreek.GetTime: PROCEDURE [self: ClassIncreek.Increek]
RETURNS [eT: ClassIncreek.EventTime];
ClassIncreek.GetCurrentTime: PROCEDURE [self: ClassIncreek.Increek]
RETURNS [eT: ClassIncreek.EventTime];
ClassIncreek.GetPositionFrom: PROCEDURE [self: ClassIncreek.Increek]
RETURNS [p: ClassIncreek.ViewPosition];
Finally, to examine the next action in the Inscript, use GetAction. It returns an Action that is extremely unsafe -- it is a LONG POINTER to an ActionBody whose data is guaranteed not to change only until the next call on GetAction or SetAt... Copy it if you want it to last longer. The waitMode and waitInterval parameters allow for the implementation of higher level abstractions that involve timeouts. They operate based on the times recorded in the file, rather than the present real time. Thus, GetAction may return with a timeout indication immediately after the client calls it. If waitMode is forever, GetAction will not return until an Action has occurred; if dontWait, return is immediate unless additional Actions remain in the Inscript. If waitMode is timed, the next Action will be returned only if it occurred within waitInterval ticks following the preceding Action. Exception: if the Increek is at "end of file", GetChar will wait the indicated interval (in the absense of new Actions) before returning; and this interval begins at the real time of the call. These semantics are considered to be a compromise.
GetAction will raise IncreekError[outOfBounds] if the Increek is examining sufficiently old Actions that their storage must be released to make way for new ones.
ClassIncreek.GetAction: PROCEDURE [self: ClassIncreek.Increek, waitMode: ClassInscript.WaitMode ← forever, waitInterval: Intime.MsTicks ← 100,
acceptance: ClassIncreek.Acceptance ← clicks]
RETURNS [a: ClassIncreek.Action];
GetAction returns NIL if the timeout criteria hold or if it encounters an Action not requested by the acceptance parameter. This makes it very difficult, without rechecking the timeout criteria in the client, to determine why a NIL resulted; for some reason this state of affairs is acceptable to the current implementation, but it should probably be changed.
How to Use
The entire package is available as Instream.bcd, on the above-cited directory. The definitions files of interest to the client are also available there. It imports the CWF package rather than including it; all other imports (it is claimed) come from the system.
Start the system by creating an Instream via ClassInstream.NewStdInstream (or, if you’re not using Instreams, ClassIncreek.NewStdIncreek. It will take several seconds and will create a file "Inscript.Inscript." This file is made to reflect "truth" every few seconds; subsequent "runs" will find the current position in this file.
Once the package has started, the standard Mesa keyboard/cursor packages have been disabled (but not necessarily removed from storage.) The <shift>SWAT, <ctrl>SWAT, and <ctrl><shift>SWAT functions still work, although they also cause Actions to be recorded, and will not work on replay.
Current Shortfalls
1. There’s no way to reduce the number of actions placed into the inscript by the terminal code -- i.e., by eliminating or reducing the frequency of mouse motion actions, when not accompanied by clicks, at times when such actions are not of interest (most of the time.) Implementing this feature would not be without risk -- during type ahead situations, there might not be enough available information.
2. There is currently no event filtering based on mouse coordinates. This could be supplied elsewhere, but it was always intended to provide such facilities here. I believe that the clipping capabilities of Cedar graphics can be used to make this filtering very efficient and effective. Whether this is done at this level depends to some extent on how many independent processes end up "pulling." I’ll discuss this further with relevant parties. Mouse coordinates are also currently expressed in screen units, rather than any device-independent manner. They clearly should be recorded this way, but may want to be presented to clients differently.
3. There is currently no version that records actions forever. This should perhaps be done, if at all, at the event level, after it is determined which actions are interesting. The current inscript is a circular buffer of (now a constant 100 Alto pages, soon to be variable) length, which can capture about a minute’s worth of reasonable continuous activity on each page.
4. There is no specific mechanism included for changing cursor shape based on mouse position (and perhaps the state of the mouse buttons.) Is this needed? If so, I can treat it along with point 2.
5. Chord-fetching operations are perhaps still too primitive. ClassInstream.GetChord is not implemented, nor is it very well defined. ClassInstream.GetChordState, along with ClassInstream.GetDownCount, leave one with not much work remaining, however. If Shift lock is locked down, simple algorithms for dealing with chords will fail.
The interfaces described here should be sufficient for most clients of the Inscript system, except for those who desire to implement the interfaces for different sources, or using different filing methods. For the more adventurous, an appendix follows. It describes the Intime and Interminal interfaces in more detail, and discusses some aspects of the current implementation.