InterminalImpl.mesa
Copyright (C) 1982, 1983, 1985 by Xerox Corporation. All rights reserved.
Andrew Birrell, August 23, 1983 9:49 am keyboard handler performance
Stone, June 23, 1982 2:21 pm color cursor and pen
McGregor, 16-Apr-82 9:26:01 Flushed delta x&y mouse ops,
Swinehart, April 29, 1982 5:16 pm, register with multiplexor, etc.
Paul Rovner, August 15, 1983 10:01 am
Russ Atkinson, September 23, 1983 3:30 pm
Doug Wyatt, January 23, 1985 3:15:07 pm PST added Sindhu's UserProfile options
DIRECTORY
Basics USING[LongMult, LongDiv],
Booting USING [ CheckpointProc, RollbackProc, RegisterProcs ],
ClassIncreek USING [ Action, ActionBody ],
ClassInscript USING [ Inscript, SetWritePage, WriteEntry ],
DisplayFace USING [ refreshRate ],
Keys USING [ KeyBits ],
Interminal,
InterminalExtra USING [], --exports only
InterminalExtras USING [], --exports only
Intime USING [
AdjustEventTime, DeltaDeltaTime, DeltaTime, EventTime, EventTimeDifference, maxDeltaDeltaTime, maxDeltaTime, msPerDeltaTick, MsTicks, MsTicksToDeltaTime, ReadEventTime, SubtractMsTicksFromEventTime
],
Loader USING [ MakeProcedureResident, MakeGlobalFrameResident ],
PrincOpsUtils USING[COPY],
Process USING [ Priority, GetPriority, SetPriority ],
Terminal USING [
Create, GetBWCursorPattern, GetBWCursorPosition, GetColorCursorPattern, GetKeys, GetMousePosition, Position, RegisterNotifier, SetBWCursorPattern, SetBWCursorPosition, SetColorCursorPattern, SetColorCursorPosition, SetColorCursorState, SetMousePosition, SwapAction, SwapNotifier, Virtual, WaitForBWVerticalRetrace],
UserProfile USING [ProfileChangedProc, Number, CallWhenProfileChanges];
InterminalImpl: MONITOR
IMPORTS Basics, Booting, ClassInscript, DisplayFace, Intime, Loader, PrincOpsUtils, Process, Terminal, UserProfile
EXPORTS Interminal, InterminalExtra, InterminalExtras
SHARES Interminal
= {
OPEN  Interminal, ClassIncreek, ClassInscript, Intime;
inscript: ClassInscript.Inscript ← NIL;
terminal: PUBLIC Terminal.Virtual ← Terminal.Create[];
Interminal.MousePosition now has a boolean on it to declare which display the point is on to keep things consistent with the hardware world, we do all our internal calculations with InternalMP, then construct a MousePosition when recording the Action
wordlength: CARDINAL = 16;
Internal: ERROR[code: INTEGER] = CODE; -- "can't happen errors"
Values at time t, maintained correct by ActionRecorder main loop
mousePosition: Terminal.Position ← terminal.GetMousePosition[];
keyState: KeyState; -- 𡤌urrentKeys^, see init --
eventTime: EventTime; -- ←ReadEventTime[], initialized below
Values at time t-1, also maintained by main loop
lastMousePosition: Terminal.Position;
lastKeyState: KeyState; -- ←keyState, to start, see init
lastEventTime: EventTime;
allUp: PUBLIC KeyState ←
[words[words: [177777B, 177777B, 177777B, 177777B, 177777B]]];
doTrack: BOOLTRUE;
hotX: INTEGER𡤀
hotY: INTEGER𡤀
vBWEscape: INTEGER ← 10; -- escape velocity from BW display
vColorEscape: INTEGER ← 10; -- escape velocity from color display
allZeros: CursorArray ← ALL[0];
Mouse grain threshhold stuff
grainDots: INTEGER;
defaultGrainDots: INTEGER𡤅
grainMsTicks: Intime.MsTicks;
defaultGrainTicks: Intime.MsTicks� -- 5 actions per second
grainTicks: INTEGER;
grainTicksLeft: INTEGER;
rR: INTEGER=DisplayFace.refreshRate*2;
grainMsPerTick: Intime.MsTicks ← (1000+rR-1)/rR; --System pulse duration in Ms.
SetCursorOffset: PUBLIC PROC[deltaX, deltaY: INTEGER, enableTracking: BOOLTRUE] ={
hotX�ltaX; hotY�ltaY; doTrack𡤎nableTracking;
};
GetCursorOffset: PUBLIC PROC RETURNS[deltaX, deltaY: INTEGER, trackingEnabled: BOOL] = {
RETURN[deltaX: hotX, deltaY: hotY, trackingEnabled: doTrack];
} ;
SetCursorPosition: PUBLIC PROC[posX, posY: INTEGER, enableTracking: BOOLTRUE] = {
doTrack𡤎nableTracking;
terminal.SetBWCursorPosition[[x: posX, y: posY]]
};
GetCursorPosition: PUBLIC PROC RETURNS[deltaX, deltaY: INTEGER, trackingEnabled: BOOL] = {
pos: Terminal.Position = terminal.GetBWCursorPosition[];
RETURN[deltaX: pos.x, deltaY: pos.y, trackingEnabled: doTrack];
};
hasColor: BOOLFALSE;
DisplayRec: TYPE = RECORD[xMin,xMax,yMin,yMax: INTEGER, color: BOOL];
left,right,display: POINTER TO DisplayRec;
leftRep: DisplayRec;
rightRep: DisplayRec;
This procedure runs as an independent process. It constitutes the low-level action recording mechanisms for user input actions.
ActionRecorder: PROC = { -- Runs as detached process, records terminal actions --
DO
WaitForTick[]; -- run once per tick
IF hasColor THEN HardCase[]
ELSE {
mousePosition ← terminal.GetMousePosition[];
mousePosition.x ← MAX[MIN[display.xMax, mousePosition.x], display.xMin];
mousePosition.y ← MAX[MIN[display.yMax, mousePosition.y], display.yMin];
terminal.SetMousePosition[mousePosition];
IF doTrack THEN
terminal.SetBWCursorPosition[[x: mousePosition.x+hotX, y: mousePosition.y+hotY]];
};
keyState ← GetTerminalKeys[];
IF keyState # lastKeyState THEN { eventTime ← ReadEventTime[]; []𡤎nterKeyState[] }
ELSE DoMousePosition[];
ENDLOOP;
};
HardCase: PROC = INLINE {
moves the mouse between displays
check if mouse is moving out of current display
pattern: CursorArray;
updateCursorBits: BOOLFALSE;
mouse: Terminal.Position ← terminal.GetMousePosition[];
toH,fromH: INTEGER;
hideCursor: PROC = INLINE {
IF display.color
THEN {
pattern ← terminal.GetColorCursorPattern[];
[] ← terminal.SetColorCursorPosition[[-100,-100]];
}
ELSE {
pattern ← terminal.GetBWCursorPattern[];
terminal.SetBWCursorPattern[allZeros];
terminal.SetBWCursorPosition[[-100,-100]] --hides bw cursor
};
};
setCursorBits: PROC = INLINE {
IF display.color THEN terminal.SetColorCursorPattern[pattern]
ELSE terminal.SetBWCursorPattern[pattern];
};
mouse.y ← MAX[MIN[mouse.y,display.yMax], display.yMin];
SELECT TRUE FROM
mouse.x>display.xMax => { -- the mouse is moving right
IF display=left AND (mouse.x-display.xMax>(IF display.color THEN vColorEscape ELSE vBWEscape)) THEN { -- move to right display
hideCursor[];
fromH ← display.yMax;
display ← right;
toH ← display.yMax;
mousePosition.x ← display.xMin;
terminal.SetMousePosition[mousePosition];
an integer divide, but we get our precision where we need it
mouse.y ← Basics.LongDiv[Basics.LongMult[mouse.y,toH],fromH];
setCursorBits[]}
ELSE mousePosition.x ← display.xMax };
mouse.x<display.xMin => { -- the mouse is moving left
IF display=right AND (display.xMin-mouse.x>(IF display.color THEN vColorEscape ELSE vBWEscape)) THEN { -- move to left display
hideCursor[];
fromH ← display.yMax;
display ← left;
toH ← display.yMax;
mousePosition.x ← display.xMax;
terminal.SetMousePosition[mousePosition];
an integer divide, but we get our precision where we need it
mouse.y ← Basics.LongDiv[Basics.LongMult[mouse.y,toH],fromH];
setCursorBits[]}
ELSE mousePosition.x ← display.xMin };
ENDCASE => mousePosition.x ← mouse.x;
mousePosition.y ← mouse.y;
terminal.SetMousePosition[mousePosition];
IF doTrack THEN {
cursorPosition: Terminal.Position ~ [x: mousePosition.x+hotX, y: mousePosition.y+hotY];
IF ~display.color THEN
terminal.SetBWCursorPosition[cursorPosition]
ELSE
terminal.SetColorCursorPosition[cursorPosition];
};
}; -- end HardCase
-- Mouse position and keyboard state recording functions --
DoMousePosition: PROC = INLINE { -- check threshholds
IF (grainTicksLeft←grainTicksLeft-1)>0 THEN RETURN;
grainTicksLeft←grainTicks;
grainDots test removed 4-15-82 DCS
IF mousePosition#lastMousePosition THEN
{ eventTime ← ReadEventTime[]; []𡤎nterMousePosition[FALSE] };
};
mouseAB: mousePosition ActionBody ←
[contents: mousePosition[mousePosition: [mousePosition.x, FALSE, mousePosition.y]]];
maxDeltaMouse: INTEGER = LAST[[-8..8)];
EnterMousePosition: PROC[enterFullPos: BOOL] RETURNS [enteredFull: BOOLFALSE] = {
in this procedure we convert the mousePosition, which corresponds to the cursor's coordinates to a value which is relative to the lower left corner of the display
dX: INTEGER = mousePosition.x-lastMousePosition.x;
dY: INTEGER = mousePosition.y-lastMousePosition.y;
mouseA: Action;
IF NOT enterFullPos AND (ABS[dX]<=maxDeltaMouse AND ABS[dY]<=maxDeltaMouse) THEN {
dMouseB: deltaMouse ActionBody ← [contents: deltaMouse[value: [dX, -dY]]]; --flip the origin
mouseA←@dMouseB; }
ELSE {
y: INTEGERIF display.color
THEN terminal.colorHeight-mousePosition.y-1
ELSE terminal.bwHeight-mousePosition.y-1;
mouseAB.mousePosition←[mousePosition.x, display.color, y];
mouseA←@mouseAB; };
IF EnterAction[mouseA].enteredFull THEN RETURN[TRUE];
lastMousePosition ← mousePosition;
};
KMethod: TYPE = {action, state};
EnterKeyState: PROC [method: KMethod ← action]
RETURNS [enteredFull: BOOLFALSE] = {
recording kbd action or recording current downness
Kn: PROCEDURE [v: CARDINAL] RETURNS [KeyName] = INLINE {RETURN[LOOPHOLE[v]]};
Kv: PROCEDURE [n: KeyName] RETURNS [CARDINAL] = INLINE {RETURN[LOOPHOLE[n]]};
Hi-res mouse recording
IF mousePosition#lastMousePosition AND EnterMousePosition[FALSE].enteredFull
THEN RETURN[TRUE];
FOR i: CARDINAL IN [0..SIZE[KeyState]) DO
IF keyState.words[i] # lastKeyState.words[i] THEN
FOR j: KeyName IN [Kn[i*wordlength]..Kn[(i + 1)*wordlength]) DO
x: updown = keyState.bits[j];
IF x = lastKeyState.bits[j] THEN LOOP; -- bit difference detected --
{aUpDown: keyUp ActionBody ←
IF x = up THEN [contents: keyUp[value: j]]
ELSE IF method = action THEN [contents: keyDown[value: j]]
ELSE [contents: keyStillDown[value: j]];
IF EnterAction[@aUpDown].enteredFull THEN RETURN[TRUE]; }; ENDLOOP; ENDLOOP;
lastKeyState ← keyState; };
EnterFullKeyState: PROCEDURE RETURNS [enteredFull: BOOL] = {
aAllUp: keyStillDown ActionBody ← [contents: keyStillDown[value: allUp]];
IF EnterKeyState[].enteredFull THEN RETURN[TRUE]; -- enter any new actions --
lastKeyState ← allUp;
IF EnterAction[@aAllUp].enteredFull THEN RETURN[TRUE];--reader should clear state next time
RETURN[EnterKeyState[state].enteredFull]; }; -- enter current state of all down keys --
Unconditionally enter the current time, preferably as an increment to the
previous entry. Assume that an absolute time value is entered occasionally.
EnterTime: PROCEDURE RETURNS [dTime: DeltaDeltaTime, enteredFull: BOOL] = INLINE {
a: deltaEventTime ActionBody;
difference: MsTicks ← EventTimeDifference[@eventTime, @lastEventTime];
deltaTime: CARDINAL; -- s/b DeltaTime, but can be out of range
[deltaTime, difference] ← MsTicksToDeltaTime[difference];
IF deltaTime > maxDeltaTime THEN RETURN[0, EnterFullTime[].enteredFull];
SubtractMsTicksFromEventTime[@eventTime, difference];
adjust time for roundoff in delta
IF deltaTime <= maxDeltaDeltaTime THEN RETURN[deltaTime, FALSE];
a ← [contents: deltaEventTime[value: deltaTime]];
RETURN[0, EnterAction[@a].enteredFull];
};
-- Unconditionally enter the current absolute time. --
EnterFullTime: PROCEDURE RETURNS [enteredFull: BOOL] = {
a: eventTime ActionBody ← [contents: eventTime[eventTime: eventTime]];
RETURN[EnterAction[@a].enteredFull];
};
EnterFullState: PROCEDURE = INLINE {
IF ~EnterFullTime[].enteredFull AND
~ EnterFullKeyState[].enteredFull THEN []𡤎nterMousePosition[TRUE];
};
InsertAction: PUBLIC PROCEDURE [action: ClassIncreek.ActionBody] = {
a procedure to fake actions
currently exported to InterminalExtra. Should be exported to Interminal
[] ← EnterAction[@action];
};
EnterAction: PROCEDURE [action: Action] RETURNS [enteredFull: BOOLFALSE] = {
may insert single action, or may record the full state of the input devices.
enteredFull tells which.
length: CARDINAL =
Would be nice if something in the language would handle this.
It's not only convenience and efficiency; the code should not
-- have to enumerate the tag type here --
WITH thisA: action SELECT FROM
deltaEventTime, keyUp, keyDown, keyStillDown =>
SIZE[keyUp ActionBody],
eventTime => SIZE[eventTime ActionBody],
mousePosition => SIZE[mousePosition ActionBody],
penPosition => SIZE[penPosition ActionBody],
deltaMouse => SIZE[deltaMouse ActionBody],
ENDCASE => ERROR Internal[1];
action.deltaDeltaTime ← 0;
WITH action SELECT FROM
eventTime, deltaEventTime => NULL;
ENDCASE => {
[action.deltaDeltaTime, enteredFull] ← EnterTime[];
IF enteredFull THEN RETURN[TRUE]; --full action record was made
};
IF ~WriteEntry[inscript, DESCRIPTOR[action, length]] THEN {
-- page full, start a new one with complete information --
IF ~SetWritePage[inscript] THEN RETURN[FALSE]; -- action lost because inscript could not be expanded
EnterFullState[];
RETURN[TRUE]; };
lastEventTime ← eventTime; };-- time only changes when something's been entered
used to change hardware's idea of where the mouse is; use with great care!!
SetMousePosition: PUBLIC PROC[pos: MousePosition] = {
terminal.SetMousePosition[[x: pos.mouseX, y: pos.mouseY]]; };
GetMousePosition: PUBLIC PROC RETURNS [pos: MousePosition] = {
p: Terminal.Position = terminal.GetMousePosition[];
pos←[p.x, display.color, p.y];};
DefaultMouseGrain: PUBLIC PROC RETURNS [ticks: Intime.MsTicks, dots: INTEGER] = {
RETURN[defaultGrainTicks, defaultGrainDots]; };
SetMouseGrain: PUBLIC PROC[ticks: Intime.MsTicks, dots: INTEGER] = {
grainDots←MIN[defaultGrainDots, dots];
grainMsTicks←MIN[defaultGrainTicks, ticks];
grainTicks←(grainMsTicks+grainMsPerTick-1)/grainMsPerTick;
grainTicksLeft𡤀 };
SetCursorPattern: PUBLIC PROC [cursorPattern: CursorArray] = {
IF display.color THEN terminal.SetColorCursorPattern[cursorPattern]
ELSE terminal.SetBWCursorPattern[cursorPattern];
};
GetCursorPattern: PUBLIC PROC RETURNS [cursorPattern: CursorArray] = {
IF display.color THEN RETURN[terminal.GetColorCursorPattern[]]
ELSE RETURN[terminal.GetBWCursorPattern[]];
};
this initializes the Color display. If onLeft is false, it is assumed that
the display is on the right.
TurnOnColorCursor: PUBLIC PROC[nbits: NAT, onLeft: BOOLTRUE] = {
color: POINTER TO DisplayRec;
IF hasColor THEN TurnOffColorCursor[];
IF NOT terminal.hasColorDisplay THEN RETURN;
IF onLeft THEN {display ← right; color ← left}
ELSE {display ← left; color ← right};
color^ ← [xMin: 0, xMax: terminal.colorWidth-1, yMin: 0, yMax: terminal.colorHeight-1,
color: TRUE];
hasColor ← TRUE;
terminal.SetColorCursorPosition[[-100, -100]];
[] ← terminal.SetColorCursorState[$visible];
};
TurnOffColorCursor: PUBLIC PROC = {
IF hasColor THEN {
set current display to bw
IF display.color THEN {
display ← (IF display=left THEN right ELSE left);
[] ← terminal.SetColorCursorState[$invisible];
terminal.SetBWCursorPattern[terminal.GetColorCursorPattern[]];
};
hasColor ← FALSE;
IF display=left THEN right^ ← left^ ELSE left^ ← right^;
make both recs bw
};
};
this declares on which display the pen is located
HasPen: PUBLIC PROC [display: HasPenType] = {};
Operating System Interface Procedures
WaitForTick: PROC = INLINE {
WaitForEnabled[];
terminal.WaitForBWVerticalRetrace[]};
WaitForEnabled: ENTRY PROC = INLINE {
UNTIL terminalHandlerEnabled DO
WAIT enableTerminalHandler;
ENDLOOP};
-- Initialization --
terminalProcess: PROCESS;
StartActionRecorder: PUBLIC PROC [scr: ClassInscript.Inscript] = {
-- <<Implementations of Intime and ClassInscript are assumed to be locked.>> --
KeyboardPriority: Process.Priority = 6;
save: Process.Priority = Process.GetPriority[];
initialize the display records to reflect the standard display
inscript ← scr;
right ← @rightRep; left ← @leftRep;
right^ ← left ^ ← [xMin: 0, xMax: terminal.bwWidth-1,
yMin: 0, yMax: terminal.bwHeight-1, color: FALSE];
display ← right; --most bw displays are to the right of the color monitor
Process.SetPriority[KeyboardPriority];
terminalProcess ← FORK ActionRecorder[];
Process.SetPriority[save];
Terminal.RegisterNotifier[terminal, InscriptController];
Booting.RegisterProcs[c: InscriptCPProc, r: InscriptRBProc];
SetMouseGrain[defaultGrainTicks, defaultGrainDots];
EnableTerminalHandler[]; -- InscriptController[enable]; We're it.
};
InscriptController: Terminal.SwapNotifier = TRUSTED {
[vt: Virtual, action: SwapAction, clientData: REF ANY]
SELECT action FROM
coming => {
IF terminalHandlerCurrent = 0 THEN EnableTerminalHandler[];
terminalHandlerCurrent ← terminalHandlerCurrent + 1; };
going => {
terminalHandlerCurrent ← terminalHandlerCurrent - 1;
IF terminalHandlerCurrent = 0 THEN DisableTerminalHandler[]; };
here, gone => NULL;
ENDCASE=>ERROR; };
InscriptCPProc: Booting.CheckpointProc = TRUSTED{
DisableTerminalHandler[]; };
InscriptRBProc: Booting.RollbackProc = TRUSTED{
EnableTerminalHandler[]; };
terminalHandlerCurrent: INTEGER ← 0;
terminalHandlerEnabled: BOOLFALSE;
enableTerminalHandler: CONDITION;
EnableTerminalHandler: ENTRY PROC = {ENABLE UNWIND => NULL;
-- set things up --
Intime.AdjustEventTime[TRUE];
eventTime ← lastEventTime ← ReadEventTime[];
keyState ← GetTerminalKeys[];
lastKeyState ← keyState;
allUp.keyNames.blank ← keyState.keyNames.blank;
EnterFullState[];
terminalHandlerEnabled←TRUE;
NOTIFY enableTerminalHandler; };
GetTerminalKeys: PROC RETURNS[keyState: KeyState] =
INLINE {
ks: Keys.KeyBits;
ks ← terminal.GetKeys[]; --currentKeys^; add pen here
PrincOpsUtils.COPY[to: @keyState, from: @ks, nwords: SIZE[KeyState]];
};
DisableTerminalHandler: ENTRY PROC = {terminalHandlerEnabled←FALSE};
SwitchSideOfColorDisplay: PUBLIC SAFE PROC ~ CHECKED { -- InterminalExtras
temp: POINTER TO DisplayRec ~ right; right ← left; left ← temp;
};
ColorDisplayOnLeft: PUBLIC SAFE PROC RETURNS[BOOL] ~ TRUSTED { -- InterminalExtras
RETURN[left.color];
};
ProcessProfile: UserProfile.ProfileChangedProc ~ CHECKED {
vBWEscape ← UserProfile.Number["Interminal.VBWEscape", 0];
vColorEscape ← UserProfile.Number["Interminal.VColorEscape", 0];
};
START HERE
Loader.MakeProcedureResident[--e.g.--ActionRecorder];
Loader.MakeGlobalFrameResident[--e.g.--ActionRecorder];
UserProfile.CallWhenProfileChanges[ProcessProfile];
}.