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: BOOL←TRUE;
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:
BOOL←
TRUE] ={
hotXltaX; hotYltaY; 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:
BOOL←
TRUE] = {
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: BOOL ← FALSE;
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: BOOL ← FALSE;
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:
BOOL←
FALSE] = {
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:
INTEGER ←
IF 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: BOOL←FALSE] = {
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: BOOL←FALSE] = {
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:
BOOL ←
TRUE] = {
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: BOOL ← FALSE;
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];
}.