-- 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[]; }.