-- FILE: InterminalImpl.mesa
-- Last Edited by Stone, September 18, 1982 10:25 am
-- Last Edited by Andrew Birrell, September 8, 1982 12:46 pm keyboard handler performance
-- Last Edited by Stone, June 23, 1982 2:21 pm color cursor and pen
-- Last Edited by McGregor, 16-Apr-82 9:26:01 Flushed delta x&y mouse ops,
-- Last Edited by Swinehart, April 29, 1982 5:16 pm, register with multiplexor, etc.
-- Compile /-C InterminalImpl !!!!!!
DIRECTORY
CedarSnapshot USING [ CheckpointProc, RollbackProc, Register ],
ClassIncreek USING [ Action, ActionBody ],
ClassInscript USING [ Inscript, SetWritePage, WriteEntry ],
DisplayFace USING [ refreshRate ],
Interminal,
InterminalExtra USING [], --exports only
Intime USING [
AdjustEventTime, DeltaTime, DeltaDeltaTime, EventTime, EventTimeDifference, maxDeltaTime,
maxDeltaDeltaTime, msPerDeltaTick, MsTicks, MsTicksToDeltaTime, ReadEventTime,
SubtractMsTicksFromEventTime ],
Process USING [ Priority, GetPriority, SetPriority ],
SpecialSpace USING [
MakeCodeResident, MakeGlobalFrameResident ],
TerminalMultiplex USING [ InputAction, RegisterInputController ],
ColorDisplay USING[HideCursor, SetCursorPattern, GetCursorPattern, SetCursorPosition,
RestoreCursor, width,height],
UserTerminal USING [ cursor, mouse, keyboard, screenWidth, screenHeight, WaitForScanLine, SetCursorPattern, GetCursorPattern],
Inline USING[LongMult,LongDiv];
InterminalImpl: MONITOR [inscript: ClassInscript.Inscript] -- START explicitly!
IMPORTS CedarSnapshot, ClassInscript, DisplayFace,
Interminal, Intime, P: Process, SpecialSpace,
TerminalMultiplex, UserTerminal, ColorDisplay, Inline
EXPORTS Interminal, InterminalExtra
SHARES Interminal = {
OPEN Interminal, ClassIncreek, ClassInscript, Intime;
--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
InternalMP: TYPE = MACHINE DEPENDENT RECORD [mouseX, mouseY: INTEGER];
currentKeys: LONG POINTER TO READONLY KeyState = LOOPHOLE[UserTerminal.keyboard];
currentMousePosition: LONG POINTER TO InternalMP = LOOPHOLE[UserTerminal.mouse];
cursorPosition: LONG POINTER TO InternalMP = LOOPHOLE[UserTerminal.cursor];
wordlength: CARDINAL = 16;
Internal: ERROR[code: INTEGER] = CODE; -- "can't happen errors"
-- Values at time t, maintained correct by ActionRecorder main loop
mousePosition: InternalMP ← currentMousePosition↑;
keyState: KeyState; -- ←currentKeys↑, see init --
eventTime: EventTime; -- ←ReadEventTime[], initialized below
-- Values at time t-1, also maintained by main loop --
lastMousePosition: InternalMP;
lastKeyState: KeyState; -- ←keyState, to start, see init --
lastEventTime: EventTime;
allUp: PUBLIC KeyState ←
[words[words: [177777B, 177777B, 177777B, 177777B, 177777B]]];
doTrack: BOOLEAN←TRUE;
hotX: INTEGER←0;
hotY: INTEGER←0;
allZeros: CursorArray ← ALL[0];
-- Mouse grain threshhold stuff
grainDots: INTEGER;
defaultGrainDots: INTEGER←5;
grainMsTicks: Intime.MsTicks;
defaultGrainTicks: Intime.MsTicks←200; -- 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: BOOLEAN←TRUE]={
hotX←deltaX; hotY←deltaY; doTrack←enableTracking; };
GetCursorOffset: PUBLIC PROC RETURNS[deltaX, deltaY: INTEGER, trackingEnabled: BOOLEAN] = {
RETURN[deltaX: hotX, deltaY: hotY, trackingEnabled: doTrack];} ;
hasColor: BOOLEAN ← FALSE;
DisplayRec: TYPE = RECORD[xMin,xMax,yMin,yMax: INTEGER, color: BOOLEAN];
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: PROCEDURE = { -- Runs as detached process, records terminal actions --
DO
WaitForTick[]; -- run once per tick
IF hasColor THEN HardCase[]
ELSE {
mousePosition.mouseX ← (currentMousePosition.mouseX ←
MAX[MIN[display.xMax, currentMousePosition.mouseX], display.xMin]);
mousePosition.mouseY ← (currentMousePosition.mouseY ←
MAX[MIN[display.yMax, currentMousePosition.mouseY], display.yMin]);
IF doTrack THEN {
cursorPosition.mouseX←mousePosition.mouseX+hotX;
cursorPosition.mouseY←mousePosition.mouseY+hotY};
};
keyState ← currentKeys↑; --add pen here
IF keyState # lastKeyState THEN { eventTime ← ReadEventTime[]; []←EnterKeyState[] }
ELSE DoMousePosition[];
ENDLOOP; };
--moves the mouse between displays
HardCase: PROC = INLINE { --check if mouse is moving out of current display
pattern: CursorArray;
updateCursorBits: BOOLEAN ← FALSE;
mouseX: INTEGER ← currentMousePosition.mouseX;
mouseY: INTEGER ← MAX[MIN[currentMousePosition.mouseY,display.yMax], display.yMin];
toH,fromH: INTEGER;
hideCursor: PROC = INLINE {
IF display.color THEN {
pattern ← ColorDisplay.GetCursorPattern[];
ColorDisplay.SetCursorPattern[allZeros];
ColorDisplay.SetCursorPosition[[-100,-100]];
}
ELSE {
pattern ← UserTerminal.GetCursorPattern[];
UserTerminal.SetCursorPattern[allZeros];
cursorPosition↑ ← [-100,-100]}; --hides bw cursor
};
setCursorBits: PROC = INLINE {
IF display.color THEN ColorDisplay.SetCursorPattern[pattern]
ELSE UserTerminal.SetCursorPattern[pattern];
};
SELECT TRUE FROM
mouseX>display.xMax => { --moving right
IF display=left THEN { --left to right
hideCursor[];
fromH ← display.yMax;
display ← right;
toH ← display.yMax;
mousePosition.mouseX ← currentMousePosition.mouseX ← display.xMin;
-- an integer divide, but we get our precision where we need it
mouseY ← Inline.LongDiv[Inline.LongMult[mouseY,toH],fromH];
setCursorBits[]}
ELSE mousePosition.mouseX ← currentMousePosition.mouseX ← display.xMax };
mouseX<display.xMin => { -- moving left
IF display=right THEN { --right to left
hideCursor[];
fromH ← display.yMax;
display ← left;
toH ← display.yMax;
mousePosition.mouseX ← currentMousePosition.mouseX ← display.xMax;
-- an integer divide, but we get our precision where we need it
mouseY ← Inline.LongDiv[Inline.LongMult[mouseY,toH],fromH];
setCursorBits[]}
ELSE mousePosition.mouseX ← currentMousePosition.mouseX ← display.xMin };
ENDCASE => mousePosition.mouseX ← currentMousePosition.mouseX ← mouseX;
mousePosition.mouseY ← currentMousePosition.mouseY ←mouseY;
IF doTrack THEN {
IF ~display.color THEN {
cursorPosition.mouseX←mousePosition.mouseX+hotX;
cursorPosition.mouseY←mousePosition.mouseY+hotY;}
ELSE
ColorDisplay.SetCursorPosition[[mousePosition.mouseX+hotX, mousePosition.mouseY+hotY]];
};
};
-- 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[]; []←EnterMousePosition[FALSE] };
};
mouseAB: mousePosition ActionBody ←
[contents: mousePosition[mousePosition: [mousePosition.mouseX, FALSE, mousePosition.mouseY]]];
maxDeltaMouse: INTEGER = LAST[[-8..8)];
EnterMousePosition: PROC[enterFullPos: BOOLEAN] 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.mouseX-lastMousePosition.mouseX;
dY: INTEGER = mousePosition.mouseY-lastMousePosition.mouseY;
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 ColorDisplay.height-mousePosition.mouseY-1
ELSE UserTerminal.screenHeight-mousePosition.mouseY-1;
mouseAB.mousePosition←[mousePosition.mouseX, display.color, y];
mouseA←@mouseAB; };
IF EnterAction[mouseA].enteredFull THEN RETURN[TRUE];
lastMousePosition ← mousePosition; };
KMethod: TYPE = {action, state};
-- recording kbd action or recording current downness
EnterKeyState: PROCEDURE [method: KMethod ← action]
RETURNS [enteredFull: BOOLEAN←FALSE] = {
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: BOOLEAN] = {
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: BOOLEAN] = 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: BOOLEAN] = {
a: eventTime ActionBody ← [contents: eventTime[eventTime: eventTime]];
RETURN[EnterAction[@a].enteredFull]; };
EnterFullState: PROCEDURE = INLINE {
IF ~EnterFullTime[].enteredFull AND
~ EnterFullKeyState[].enteredFull THEN []←EnterMousePosition[TRUE]; };
-- a procedure to fake actions
-- currently exported to InterminalExtra. Should be exported to Interminal
InsertAction: PUBLIC PROCEDURE [action: ClassIncreek.ActionBody] = {
[] ← EnterAction[@action];
};
EnterAction: PROCEDURE [action: Action] RETURNS [enteredFull: BOOLEAN←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] = {
currentMousePosition↑←[pos.mouseX,pos.mouseY]; };
GetMousePosition: PUBLIC PROC RETURNS [pos: MousePosition] = {
pos←[currentMousePosition.mouseX, display.color, currentMousePosition.mouseY];};
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←0; };
SetCursorPattern: PUBLIC PROC [cursorPattern: CursorArray] = {
IF display.color THEN {
ColorDisplay.SetCursorPattern[cursorPattern];
ColorDisplay.RestoreCursor[]}
ELSE UserTerminal.SetCursorPattern[cursorPattern];
};
GetCursorPattern: PUBLIC PROC RETURNS [cursorPattern: CursorArray] = {
IF display.color THEN RETURN[ColorDisplay.GetCursorPattern[]]
ELSE RETURN[UserTerminal.GetCursorPattern[]];
};
--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: BOOLEAN ← TRUE] = {
color: POINTER TO DisplayRec;
IF hasColor THEN TurnOffColorCursor[];
IF onLeft THEN {display ← right; color ← left}
ELSE {display ← left; color ← right};
color↑ ← [xMin: 0, xMax: ColorDisplay.width-1,
yMin: 0, yMax: ColorDisplay.height-1, color: TRUE];
hasColor ← TRUE;
};
TurnOffColorCursor: PUBLIC PROC = {
IF hasColor THEN {
--set current display to bw
IF display.color THEN {
display ← (IF display=left THEN right ELSE left);
ColorDisplay.HideCursor[];
UserTerminal.SetCursorPattern[ColorDisplay.GetCursorPattern[]];
};
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[];
UserTerminal.WaitForScanLine[0]};
WaitForEnabled: ENTRY PROC = INLINE {
UNTIL terminalHandlerEnabled DO
WAIT enableTerminalHandler;
ENDLOOP};
-- Initialization --
terminalProcess: PROCESS;
StartActionRecorder: PROCEDURE = {
-- <<Implementations of Intime and ClassInscript are assumed to be locked.>> --
KeyboardPriority: P.Priority = 6;
save: P.Priority = P.GetPriority[];
--initialize the display records to reflect the standard display
right ← @rightRep; left ← @leftRep;
right↑ ← left ↑ ← [xMin: 0, xMax: UserTerminal.screenWidth-1,
yMin: 0, yMax: UserTerminal.screenHeight-1, color: FALSE];
display ← right; --most bw displays are to the right of the color monitor
P.SetPriority[KeyboardPriority];
terminalProcess←FORK ActionRecorder[];
P.SetPriority[save];
SpecialSpace.MakeGlobalFrameResident[LOOPHOLE[InterminalImpl, PROGRAM]];
SpecialSpace.MakeCodeResident[LOOPHOLE[InterminalImpl, PROGRAM]];
TerminalMultiplex.RegisterInputController[InscriptController];
CedarSnapshot.Register[c: InscriptCPProc, r: InscriptRBProc];
SetMouseGrain[defaultGrainTicks, defaultGrainDots];
InscriptController[enable]; -- We're it.
};
InscriptController: PROC[action: TerminalMultiplex.InputAction] = {
SELECT action FROM
enable => {
IF terminalHandlerCurrent = 0 THEN EnableTerminalHandler[];
terminalHandlerCurrent ← terminalHandlerCurrent + 1; };
disable => {
terminalHandlerCurrent ← terminalHandlerCurrent - 1;
IF terminalHandlerCurrent = 0 THEN DisableTerminalHandler[]; };
ENDCASE=>ERROR; };
InscriptCPProc: CedarSnapshot.CheckpointProc = {
DisableTerminalHandler[]; };
InscriptRBProc: CedarSnapshot.RollbackProc = {
EnableTerminalHandler[]; };
terminalHandlerCurrent: INTEGER ← 0;
terminalHandlerEnabled: BOOLEAN←FALSE;
enableTerminalHandler: CONDITION;
EnableTerminalHandler: ENTRY PROC = {
-- set things up --
Intime.AdjustEventTime[TRUE];
eventTime ← lastEventTime ← ReadEventTime[];
keyState ← currentKeys↑;
lastKeyState ← keyState;
allUp.keyNames.blank ← keyState.keyNames.blank;
EnterFullState[];
terminalHandlerEnabled←TRUE;
NOTIFY enableTerminalHandler; };
DisableTerminalHandler: ENTRY PROC = {
terminalHandlerEnabled←FALSE; };
StartActionRecorder[];
}.