<> <> <> <> <> <> <> <> <> 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[]; <> wordlength: CARDINAL = 16; Internal: ERROR[code: INTEGER] = CODE; -- "can't happen errors" <> mousePosition: Terminal.Position _ terminal.GetMousePosition[]; keyState: KeyState; -- _currentKeys^, see init -- eventTime: EventTime; -- _ReadEventTime[], initialized below <> 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_0; hotY: INTEGER_0; vBWEscape: INTEGER _ 10; -- escape velocity from BW display vColorEscape: INTEGER _ 10; -- escape velocity from color display allZeros: CursorArray _ ALL[0]; <> 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: BOOL_TRUE] ={ hotX_deltaX; hotY_deltaY; doTrack_enableTracking; }; 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_enableTracking; 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; <> 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[]; []_EnterKeyState[] } ELSE DoMousePosition[]; ENDLOOP; }; HardCase: PROC = INLINE { <> <> 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]; <> mouse.y _ Basics.LongDiv[Basics.LongMult[mouse.y,toH],fromH]; setCursorBits[]} ELSE mousePosition.x _ display.xMax }; mouse.x { -- 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]; <> 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; <> IF mousePosition#lastMousePosition THEN { eventTime _ ReadEventTime[]; []_EnterMousePosition[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] = { <> 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] = { <> Kn: PROCEDURE [v: CARDINAL] RETURNS [KeyName] = INLINE {RETURN[LOOPHOLE[v]]}; Kv: PROCEDURE [n: KeyName] RETURNS [CARDINAL] = INLINE {RETURN[LOOPHOLE[n]]}; <> 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 -- <> <> 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]; <> 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 []_EnterMousePosition[TRUE]; }; InsertAction: PUBLIC PROCEDURE [action: ClassIncreek.ActionBody] = { <> <> [] _ EnterAction[@action]; }; EnterAction: PROCEDURE [action: Action] RETURNS [enteredFull: BOOL_FALSE] = { <> <> length: CARDINAL = <> <> -- 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 <> 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_0; }; 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[]]; }; <> <> 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 { <> 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^; <> }; }; <> HasPen: PUBLIC PROC [display: HasPenType] = {}; <> 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] = { -- <> -- KeyboardPriority: Process.Priority = 6; save: Process.Priority = Process.GetPriority[]; <> 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]; }; <> Loader.MakeProcedureResident[--e.g.--ActionRecorder]; Loader.MakeGlobalFrameResident[--e.g.--ActionRecorder]; UserProfile.CallWhenProfileChanges[ProcessProfile]; }.