Heading:
Cedar Terminal Input Facilities
Page Numbers: Yes X: 527 Y: 10.5"
Inter-Office Memorandum
To Cedar Interest Date January 15, 1981
From Dan Swinehart Location Palo Alto
Subject Cedar Terminal Input Facilities Organization CSL
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.
Heading:
Appendix: Cedar Terminal Input Facilities
Page Numbers: Yes X: 527 Y: 10.5"
The Interminal Definitions
The definitions module Interminal supplies a set of data types that describe the "Alto" terminal input configuration: keyboard, keyset, and mouse. The InterminalImpl module, part of the current implementation, does not export anything to Interminal, so the names are in that sense coincidental. A later section discusses this implementation.
The intent of the Interminal types is indicated in the previous Instreams sections; the details are included here for completeness.
Data Types
Interminal.MousePosition: TYPE = MACHINE DEPENDENT RECORD [
mouseX: INTEGER, mouseY: INTEGER];
Interminal.updown: TYPE = {down, up};
Interminal.KeyView: TYPE = {keyNames, keyFields, words, bits};
Interminal.KeyState: TYPE = MACHINE DEPENDENT RECORD [
SELECT OVERLAID KeyView FROM
bits => [bits: PACKED ARRAY Interminal.KeyName OF Interminal.updown],
words => [words: Interminal.KeyArray],
keyNames => [keyNames: Interminal.KeyNames],
keyFields => [keyFields: Interminal.KeyFields],
ENDCASE
];
Interminal.KeyName: TYPE = {
x0, x1, x2, x3, x4, x5, x6, x7, Keyset1, Keyset2, Keyset3, Keyset4, Keyset5, Red, Blue, Yellow,
Five, Four, Six, E, Seven, D, U, V, Zero, K, Dash, P, Slash, BackSlash, LF, BS,
Three, Two, W, Q, S, A, Nine, I, X, O, L, Comma, Quote, RightBracket, Spare2, BW,
One, ESC, TAB, F, Ctrl, C, J, B, Z, LeftShift, Period, SemiColon, Return, Arrow, DEL, FL3,
R, T, G, Y, H, Eight, N, M, Lock, Space, LeftBracket, Equal, RightShift, Spare3, FL4, -- FR5, -- allUp
};
This implementation has purloined the code for FR5 -- "Cedar" terminals do not use it -- in order to obtain a code for the use of the implementation. The client may see this code, renamed allUp, as part of a stillDown action -- see the Increeks section.
The following INLINE functions are just pretty loopholes for the implementation's benefit.
Interminal.Kn: PROCEDURE [value: UNSPECIFIED] RETURNS [kN: KeyName] = INLINE {
kN ← LOOPHOLE[value]};
Interminal.Kv: PROCEDURE [value: UNSPECIFIED] RETURNS [kV: CARDINAL] = INLINE {
kV ← LOOPHOLE[value]};
Interminal.KbdKeyName: TYPE = KeyName [Five..allUp];
Interminal.ButtonKeyName: TYPE = KeyName [Red..Yellow];
Interminal.PaddleKeyName: TYPE = KeyName [Keyset1..Keyset5];
Interminal.KeyArray: TYPE = ARRAY [0..5) OF WORD;
Interminal.KeyNames: TYPE = MACHINE DEPENDENT RECORD [
blank: [0..377B],
Keyset1, Keyset2, Keyset3, Keyset4, Keyset5: updown, Red, Blue, Yellow: updown,
Five, Four, Six, E, Seven, D, U, V, Zero, K, Dash, P, Slash, BackSlash, LF, BS: updown,
Three, Two, W, Q, S, A, Nine, I, X, O, L, Comma, Quote, RightBracket, Spare2, BW: updown,
One, ESC, TAB, F, Ctrl, C, J, B, Z, LeftShift, Period, SemiColon, Return, Arrow, DEL, FL3: updown,
R, T, G, Y, H, Eight, N, M, Lock, Space, LeftBracket, Equal, RightShift, Spare3, FL4, -- FR5 -- allUp: updown
];
Interminal.KeyFields: TYPE = MACHINE DEPENDENT RECORD [
mesaMemorialBlankField: [0..377B],
paddles: Interminal.Paddles,
buttons: Interminal.Buttons,
keys: ARRAY [0..3] OF WORD];
Interminal.ButtonView: TYPE = {buttonChord, buttonNames, buttonValue};
Interminal.Buttons: TYPE = MACHINE DEPENDENT RECORD [
SELECT OVERLAID ButtonView FROM
buttonChord => [buttonChord: [0..10B)],
buttonNames => [buttonNames: Interminal.ButtonNames],
buttonValue => [buttonValue: Interminal.ButtonValue],
ENDCASE
];
Interminal.ButtonNames: TYPE = MACHINE DEPENDENT RECORD
[Red, Blue, Yellow: updown];
Several of the enumerated types that follow specify their implementation representations explicitly so that said representations can be powers of two. This allows them to be used in conjunction with the PowerSet functions, described with Instreams.
Interminal.ButtonName: TYPE = MACHINE DEPENDENT{Red(0), Blue(1), Yellow(2)};
Interminal.ButtonValue: TYPE = MACHINE DEPENDENT{
RedYellowBlue(0), RedBlue(1), RedYellow(2), Red(3), BlueYellow(4),
Blue(5), Yellow(6), None(7)};
Interminal.PaddleView: TYPE = {paddleChord, paddleNames, paddleValue};
Interminal.Paddles: TYPE = MACHINE DEPENDENT RECORD [
SELECT OVERLAID PaddleView FROM
paddleChord => [paddleChord: [0..40B)],
paddleNames => [paddleNames: Interminal.PaddleNames],
paddleValue => [paddleValue: Interminal.PaddleValue],
ENDCASE];
Interminal.PaddleNames: TYPE = RECORD [
Keyset1, Keyset2, Keyset3, Keyset4, Keyset5: updown];
Interminal.PaddleName: TYPE = MACHINE DEPENDENT{
Keyset1(0), Keyset2(1), Keyset3(2), Keyset4(3), Keyset5(4)};
Interminal.PaddleValue: TYPE = MACHINE DEPENDENT{
Keyset1(17B), Keyset2(27B), Keyset3(33B), Keyset4(35B), Keyset5(36B),
None(37B)};
Interminal.allUp: KeyState;
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];

Intime
Intime provides data types for representing EventTime values in a number of sizes; all represent times, or time increments, expressed in milliseconds. There are also some incremental time values expressed in a courser grain (the actual value stored in Increek Actions.) This interface also supplies a number of procedures for manipulating these various times and their incremental derivatives.
Data Types
Intime.MsTicks: TYPE = Process.Milliseconds;
Intime.MSTicks: TYPE = LONG CARDINAL; -- long milliseconds --
Intime.DeltaTicks: TYPE = Process.Ticks; -- probably 1/60 sec.
Intime.DeltaTime: TYPE = Intime.DeltaTicks [0..256);
Intime.maxDeltaTime: Intime.DeltaTime = LAST[Intime.DeltaTime];
Intime.msPerDeltaTick: Intime.MsTicks; -- converts between the representations
Intime.deltaTicksPerSecond: Intime.DeltaTicks;
Intime.DeltaDeltaTime: TYPE = Intime.DeltaTime [0..32);
Intime.maxDeltaDeltaTime: Intime.DeltaDeltaTime = LAST[Intime.DeltaDeltaTime];
Intime.Overlap: TYPE = {loShort, hiShort};
Client should think of EventTime as TYPE[3].
Intime.EventTime: TYPE = PRIVATE MACHINE DEPENDENT RECORD
[
SELECT OVERLAID Overlap FROM
loShort => [lo: MsTicks, hi: LONG CARDINAL],
hiShort => [lower: MSTicks, higher: CARDINAL],
ENDCASE
];
ReadEventTime returns the current time of day, as an EventTime.
Intime.ReadEventTime: PROCEDURE RETURNS [EventTime];
These functions perform arithmetic on EventTimes, or answer questions about them. IsLaterTime is TRUE if t1 is later than t2. The result of EventTimeDifference is positive under the same circumstances.
MsTicksToDeltaTicks returns its input, converted to DeltaTicks units; the rem return value is approximately rem←s-(dT*msPerDeltaTick). This is clearly intended for a very specialized use, to which it is put in IncreekImpl.mesa.
Intime.EventTimeDifference: PROCEDURE [t1, t2: LONG POINTER TO READONLY Intime.EventTime]
RETURNS [Intime.MsTicks];
Intime.IsLaterTime: PROCEDURE [t1, t2: Intime.EventTime] RETURNS [BOOLEAN] ;
Intime.AddDeltaTimeToEventTime: PROCEDURE [eT: Intime.EventTime, dT: Intime.DeltaTime] RETURNS [rT: Intime.EventTime];
Intime.SubtractMsTicksFromEventTime: PROCEDURE [
eT: LONG POINTER TO Intime.EventTime, ticks: Intime.MsTicks];
Intime.MsTicksToDeltaTime: PROCEDURE [s: Intime.MsTicks]
RETURNS [dT: Intime.DeltaTicks, rem: Intime.MsTicks];

InOS
The Inscript facilities have been implemented for both the Alto/Mesa and the Pilot/Mesa versions of Cedar. The source changes have been limited to two implementation modules: the implementation of ClassInscript, and the implementation of an interface intended to confine operating system dependent functions, InOS.
InOS contains redefinitions of most of the CWF formatted-write functions, for two reasons. The first is that different initialization procedures are needed to use these functions in the two OS worlds (the implementation of InOS is responsible for providing default initialization if the functions are to be used). The second is that a "production" implementation may choose to supply dummy procedures for these operations -- they are used only for diagnostic purposes in the Inscript package.
Procedures
InOS.WF0: PROC [s: STRING];
. . .
InOS.WF4: PROC [s: STRING, a,b,c,d: LONG POINTER];
InOS.WF: PROC [s: STRING, a,b,c,d: LONG POINTERNIL];
InOS.WFN: PROC [s: STRING, array: DESCRIPTOR FOR ARRAY OF LONG POINTER];
InOS.WFC: PROC [CHARACTER];
InOS.WFCR: PROC;
InOS.SetCode: PROC[CHARACTER, PROC[LONG POINTER, STRING, PROC[CHARACTER]]];
InOS.SetWriteProcedure: PROC [PROC[CHARACTER]] RETURNS [PROC[CHARACTER]];
InOS.WFError: ERROR;
The remainder of this interface will be of interest only to those providing new implementations of ClassInscript and/or Interminal. It supplies procedures for dealing with the keyboard hardware, the real time facilities, and the high-priority process activities required to provide the keyboard "interrupt" process (supplied by InterminalImpl.)
These functions merely return the fixed long addresses that point to the states of the keyboard, mouse, and cursor. They appear here because Pilot supplies functions obscuring the absolute locations, while the Alto world assumes that the client knows the addresses.
InOS.GetKeyboard: PROC RETURNS[LONG POINTER TO READONLY UNSPECIFIED];
InOS.GetMousePosition: PROC RETURNS[LONG POINTER TO READONLY UNSPECIFIED];
InOS.GetCursorPosition: PROC RETURNS[LONG POINTER TO UNSPECIFIED];
Similarly, the following functions return the current values of the high-resolution interval timer (ReadFastClock), and the long-term seconds-resolution clock (GetTimeParameters). The latter function also returns a value corresponding to the number of interval-timer ticks in a second; in addition, it should perform any initialization required to perform these two functions (an issue in the current Alto implementation).
InOS.ReadFastClock: PROC RETURNS [LONG CARDINAL];
InOS.GetTimeParameters: PROC RETURNS [seconds: LONG CARDINAL,
fastOneSecond: LONG CARDINAL]; -- pulses per second
SetupTerminalHandler must perform any OS-dependent activities related to shutting off any existing terminal handlers and starting ours. WaitForTick provides a non-busy wait until the next process-scheduling interval (approx. 60 hz.). MakeCallerResident must lock the code segment of the caller into physical memory.
InOS.SetupTerminalHandler: PROC;
InOS.WaitForTick: PROC;
InOS.MakeCallerResident: PROC;

ClassInscript and Interminal
The ClassInscript interface provides an Inscript object, whose function is to record variable-length entries in a disk file, and to supply these entries on request at a later time. In addition, the Inscript (with the help of its clients) periodically records sufficient information to allow the current output position to be reestablished after a system crash or other uncontrolled termination of the package. Beyond that, the Inscript knows little of the semantics of the entries it records. For the courageous implementor, there follows a brief description of the ClassInscript interface. Following that is an even briefer exposition of the current implementation.
Data Types
An Inscript object represents the inscript file as a whole, along with all the operations pertaining to it. The InscriptPageDescriptor describes a particular page within the file, and a position within that page; there may be more than one of them. It does not supply any operations. In retrospect, probably the InscriptPageDescriptor should become the Inscript object. This would reduce considerably the clumsiness of some of the following specifications.
There are two kinds of Inscript clients; those who wish to record into the inscript -- recording clients -- and those who wish to read entries from them -- plain old clients. At present there is but one recording client for an inscript, although that is not a requirement. Recording clients will typically wish to read as well.
An InscriptPageNumber is a "virtual page number" of a page in the Inscript file.
ClassInscript.Inscript: TYPE = REF InscriptObject;
ClassInscript.InscriptPageDescriptor: TYPE = REF InscriptPageDescBody;
ClassInscript.InscriptPageNumber: TYPE = INTEGER;
During initialization, the recording client must supply a COmProc and a KEyProc to assist in reestablishing the current "end of file" position within the inscript file. KEyProc should assume that descriptor is positioned at the beginning of a page, and should extract an OrderKey from that page (this clearly requires that the client must record an OrderKey at a known place within each page as it writes the page.) COmProc, given two OrderKeys, should return TRUE iff a is "less than" b. In the Increek implementations, OrderKeys are EventTimes. The Increek implementation knows nothing about OrderKeys except their size.
ClassInscript.OrderKey: TYPE = RECORD [a, b, c: WORD];
ClassInscript.COmProc: TYPE = PROCEDURE [a, b: ClassInscript.OrderKey] RETURNS [aLessB: BOOLEAN];
ClassInscript.KEyProc: TYPE = PROCEDURE [
inscript: ClassInscript.Inscript, descriptor: ClassInscript.InscriptPageDescriptor] RETURNS [ClassInscript.OrderKey];
In order to read entries from the inscript, the client must supply a ReadAssignment procedure. Its job is to coerce the LONG POINTER argument, assumed to denote an entry of interest to the client, into a comprehensible data type, determine the entry's length, copy the entry to a new location if desired, and return the length of the entry to the caller, for use in advancing to the next entry..
ClassInscript.ReadAssignment: TYPE = PROCEDURE [p: LONG POINTER TO UNSPECIFIED]
RETURNS [wordsToAdvance: CARDINAL];
WaitMode is used in the primitive WaitForEntry function used to implement the waiting activities in ClassIncreek.GetAction. Its use is described in detail in the Increek section.
ClassInscript.WaitMode: TYPE = {forever, dontWait, timed};
ClassInscript.InscriptErrorCode: TYPE = {
entryOutOfBounds, -- trying to position out of bounds or old stuff has disappeared
invalidInscriptSpecs, -- bad arguments to NewStd...
invalidInscriptFile, -- while opening old inscript file
descriptorUninitialized -- in AdvancePage--,
invalidPageKey -- in KeyProc during intitialization --};
Procedures
NewStdInscript creates an Inscript using the standard implementation, discussed below. In addition to the obvious parameters, initializeFile will force a reinitialization of the inscript file even if it already exists, the inscript file will contain 2^lnFileSize data pages, and the inscript history will be discarded in groups of 2^lnGroupSize pages to make room for new entries. The Release operation cleanly terminates an input session (no one calls this one, in current implementations.) GetPageLimits returns the "virtual page numbers" of the pages that currently exist within the inscript file. Functions like ClassIncreek.SetAtTime use this information to initialize their search activities.
ClassInscript.NewStdInscript: PROCEDURE [fileName: STRING,
KeyProc: ClassInscript.KEyProc, ClassInscript.ComProc: COmProc, initializeFile: BOOLEANFALSE,
lnFileSize: CARDINAL𡤇, lnGroupSize: CARDINAL𡤄]
RETURNS [ClassInscript.Inscript];
ClassInscript.Release: PROCEDURE [self: ClassInscript.Inscript] RETURNS [nilInscript: ClassInscript.Inscript];
ClassInscript.GetPageLimits: PROCEDURE [self: ClassInscript.Inscript]
RETURNS [earliestPage: ClassInscript.InscriptPageNumber, latestPage: ClassInscript.InscriptPageNumber] ;
An InscriptPageDescriptor, when created, does not refer to any inscript page; other operations make it valid. CopyPageDescriptor copies the information of one descriptor into another, maintaining any necessary internal reference counts (don't perform this function any other way.) ReleasePageDescriptor discards one.
ClassInscript.CreatePageDescriptor: PROCEDURE [self: ClassInscript.Inscript]
RETURNS [rDescriptor: ClassInscript.InscriptPageDescriptor];
ClassInscript.CopyPageDescriptor: PROCEDURE [self: ClassInscript.Inscript, dest: InscriptPageDescriptor,
source: ClassInscript.InscriptPageDescriptor];
ClassInscript.ReleasePageDescriptor: PROCEDURE [self: Inscript, descriptor: InscriptPageDescriptor]
RETURNS [nilPageDescriptor: InscriptPageDescriptor];
Using the KEyProc and the COmProc, the ClassInscript implementation must establish a valid output position when the Inscript object is created. Subsequently, the only valid recording operations are SetWritePage and WriteEntry.
WriteEntry records the value of the entry array into the next available locations in the current page (using internally maintained page descriptors), failing if there is not enough room. The recording client must then call SetWritePage in order to move on. Things are done this way so that the client can record per-page information (e.g., the page's OrderKey) at the beginning of each page. After initialization, the client should call WriteEntry without any initial call to SetWritePage, and the initialization must take care of guaranteeing that this works -- the output position must be set at "end of file".
SetWritePage will advance to the next "virtual" page, increasing the value of GetPageLimits[...].latestPage by one, and possibly increasing ditto.earliestPage, thus destroying some history. It will fail only under the circumstances (which should be made vanishingly unlikely) that no physical page in the inscript file is available for use. On return, the page descriptor will represent the first data position within the new page.
ClassInscript.WriteEntry: PROCEDURE [self: ClassInscript.Inscript, entry: LONG DESCRIPTOR FOR ARRAY OF WORD]
RETURNS [success: BOOLEAN];
ClassInscript.SetWritePage: PROCEDURE [self: ClassInscript.Inscript] RETURNS [success: BOOLEAN];
To read from the inscript, a client performs ReadEntry operations, supplying a ReadAssignment procedure in order to interpret the entries and determine their length. When ReadEntry fails, no more entries remain in the current page; the client should next call SetPage or AdvancePage. Again, this is done to allow the client to do something special at page boundaries. If the InscriptPageDescriptor's page disappears to make way for new entries, ReadEntry will raise InscriptError[code: entryOutOfBounds].
SetPage adjusts its InscriptPageDescriptor to the beginning of the indicated "virtual" page, failing if that page no longer exists or doesn't exist yet. AdvancePage performs SetPage of "current plus one." If AdvancePage fails, it is because no further entries exist in the file. The client might then call WaitForEntry, which will return only if a timeout condition (described in the Increek section) is satisfied, or if one or more new entries have been added to the inscript since the operation was invoked.
ClassInscript.ReadEntry: PROCEDURE [self: ClassInscript.Inscript, descriptor: ClassInscript.InscriptPageDescriptor, Read: ClassInscript.ReadAssignment]
RETURNS [success: BOOLEAN];
ClassInscript.SetPage: PROCEDURE [self: ClassInscript.Inscript, descriptor: ClassInscript.InscriptPageDescriptor,
pageNumber: ClassInscript.InscriptPageNumber] RETURNS [success: BOOLEAN];
ClassInscript.AdvancePage: PROCEDURE [self: ClassInscript.Inscript, descriptor: ClassInscript.InscriptPageDescriptor]
RETURNS [success: BOOLEAN];
ClassInscript.WaitForEntry: PROCEDURE [self: ClassInscript.Inscript, waitMode: ClassInscript.WaitMode, waitInterval: Intime.MsTicks ← 1000,
waitStartTime: LONG POINTER TO Intime.EventTime ← NIL]
RETURNS [moreEntries: BOOLEAN];
ClassInscript.InscriptError: ERROR [code: ClassInscript.InscriptErrorCode];
In the existing package, InscriptImplAlto and InscriptImplPilot provide two functionally equivalent implementations of ClassInscript. InterminalImpl acts as the recording client, polling the terminal hardware once a tick and recording any changes it sees. This implementation records its Actions into a disk file that behaves as a large ring buffer, filling seldom more than one Alto-sized page per minute under normal conditions. Thus Actions are remembered for quite some time, but not indefinitely (the standard Increek uses a 128 page file.) Both implementations run the recording client at high priority. They both lock inscript pages into memory to prevent page fault delays. They seem to perform efficiently, without losing events. They are monitored so that clients from more than one process can access their inscripts.
The interfaces were designed so that other implementations of Inscripts could be provided, for instance to immutably record user Actions, possibly condensed (retaining fewer actions), to supply an application with Actions from a source other than the attached terminal, etc. These possibilities are discussed in more detail in the design document cited as a reference.