<> <> <> <> <> <> DIRECTORY Basics USING [LongDiv, LongMult], Booting USING [CheckpointProc, RollbackProc, RegisterProcs], ClassIncreek USING [Action, ActionBody], ClassInscript USING [Inscript, SetWritePage, WriteEntry], Interminal USING [CursorArray, DownUp, HasPenType, KeyName, KeyState, MousePosition, Side], InterminalBackdoor USING [], Intime USING [AdjustEventTime, DeltaDeltaTime, DeltaTime, EventTime, EventTimeDifference, maxDeltaDeltaTime, maxDeltaTime, msPerDeltaTick, MsTicks, MsTicksToDeltaTime, ReadEventTime, SubtractMsTicksFromEventTime], Loader USING [MakeProcedureResident, MakeGlobalFrameResident], 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], TerminalFace USING [refreshRate], UserProfile USING [CallWhenProfileChanges, Number, ProfileChangedProc]; InterminalImpl: MONITOR IMPORTS Basics, Booting, ClassInscript, Intime, Loader, Process, Terminal, TerminalFace, UserProfile EXPORTS Interminal, InterminalBackdoor SHARES Interminal = BEGIN 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 _ [bits[ALL[up]]]; -- _currentKeys^, see init -- eventTime: EventTime; -- _ReadEventTime[], initialized below <> lastMousePosition: Terminal.Position; lastKeyState: KeyState _ [bits[ALL[up]]]; -- _keyState, to start, see init lastEventTime: EventTime; <> <<[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 ~ TerminalFace.refreshRate*2; grainMsPerTick: Intime.MsTicks _ (1000+rR-1)/rR; -- System pulse duration in Ms. SetCursorOffset: PUBLIC SAFE PROC[deltaX, deltaY: INTEGER, enableTracking: BOOL _ TRUE] = CHECKED { hotX _ deltaX; hotY _ deltaY; doTrack _ enableTracking; }; GetCursorOffset: PUBLIC SAFE PROC RETURNS[deltaX, deltaY: INTEGER, trackingEnabled: BOOL] = CHECKED { RETURN[deltaX: hotX, deltaY: hotY, trackingEnabled: doTrack]; }; SetCursorPosition: PUBLIC SAFE PROC[posX, posY: INTEGER, enableTracking: BOOL _ TRUE] = CHECKED { doTrack _ enableTracking; terminal.SetBWCursorPosition[[x: posX, y: posY]]; }; GetCursorPosition: PUBLIC SAFE PROC RETURNS[deltaX, deltaY: INTEGER, trackingEnabled: BOOL] = CHECKED { 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 = { <> 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 _ [bits[terminal.GetKeys[]]]; 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]; }; }; DoMousePosition: PROC = INLINE { <> 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]]]; <> 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}; chordWaits: NAT _ 0; <> MouseClickIndex: NAT = 0; EnterKeyState: PROC [method: KMethod _ action] RETURNS [enteredFull: BOOL_FALSE] = { <> Kn: PROC [v: CARDINAL] RETURNS [KeyName] = INLINE {RETURN[LOOPHOLE[v]]}; Kv: PROC [n: KeyName] RETURNS [CARDINAL] = INLINE {RETURN[LOOPHOLE[n]]}; <> IF mousePosition#lastMousePosition AND EnterMousePosition[FALSE].enteredFull THEN RETURN [TRUE]; DO scanNeeded: BOOL _ FALSE; tempState: KeyState _ keyState; tempTime: EventTime; FOR i: CARDINAL IN [0..SIZE[KeyState]) DO IF keyState.words[i] # lastKeyState.words[i] THEN IF chordWaits # 0 AND i = MouseClickIndex THEN { <> newRed: DownUp _ keyState.bits[Red]; newBlue: DownUp _ keyState.bits[Blue]; oldRed: DownUp _ lastKeyState.bits[Red]; -- Left oldBlue: DownUp _ lastKeyState.bits[Blue]; -- Right oldYellow: DownUp _ lastKeyState.bits[Yellow]; -- Center noChange: BOOL _ FALSE; noEvent: BOOL _ FALSE; THROUGH [0..chordWaits) DO <> < Yellow down or up>> <<(and we clear the observed state of Red and Blue)>> < use Red & Blue as they are>> <<(and we get to go through this whole thing again)>> < use Red & Blue as they are>> SELECT TRUE FROM newRed = newBlue => { <> IF keyState.bits[Yellow] = up THEN { <> tempState.bits[Yellow] _ keyState.bits[Yellow] _ newRed; tempState.bits[Red] _ keyState.bits[Red] _ up; tempState.bits[Blue] _ keyState.bits[Blue] _ up; }; EXIT; }; newRed # oldRed, newBlue # oldBlue => { <> WaitForTick[]; tempState _ [bits[terminal.GetKeys[]]]; FOR kw: CARDINAL IN [0..SIZE[KeyState]) DO IF tempState.words[i] # keyState.words[i] THEN { scanNeeded _ TRUE; EXIT}; ENDLOOP; IF scanNeeded THEN { tempTime _ ReadEventTime[]; newRed _ tempState.bits[Red]; newBlue _ tempState.bits[Blue]; IF newRed # newBlue THEN EXIT; <> IF newRed = oldYellow THEN EXIT; <> }; }; ENDCASE => { <> noChange _ TRUE; EXIT; }; ENDLOOP; IF keyState.words[MouseClickIndex] = lastKeyState.words[MouseClickIndex] THEN { <> noEvent _ TRUE; <> LOOP; }; }; FOR j: KeyName IN [Kn[i*wordlength]..Kn[(i + 1)*wordlength]) DO x: DownUp = keyState.bits[j]; IF x # lastKeyState.bits[j] THEN { aUpDown: keyUp ActionBody _ SELECT TRUE FROM x = up => [contents: keyUp[value: j]], method = action => [contents: keyDown[value: j]], ENDCASE => [contents: keyStillDown[value: j]]; IF EnterAction[@aUpDown].enteredFull THEN RETURN[TRUE]; }; ENDLOOP; ENDLOOP; lastKeyState _ keyState; IF NOT scanNeeded THEN EXIT; keyState _ tempState; eventTime _ tempTime; ENDLOOP; }; EnterFullKeyState: PROC RETURNS [enteredFull: BOOL] = { aAllUp: allUp ActionBody _ [contents: allUp[]]; IF EnterKeyState[].enteredFull THEN RETURN[TRUE]; -- enter any new actions -- lastKeyState _ [bits[ALL[up]]]; 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: PROC 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]; }; <> EnterFullTime: PROC RETURNS [enteredFull: BOOL] = { a: eventTime ActionBody _ [contents: eventTime[eventTime: eventTime]]; RETURN[EnterAction[@a].enteredFull]; }; EnterFullState: PROC = INLINE { IF ~EnterFullTime[].enteredFull AND ~ EnterFullKeyState[].enteredFull THEN []_EnterMousePosition[TRUE]; }; InsertAction: PUBLIC SAFE PROC [action: ClassIncreek.ActionBody] = TRUSTED { <> [] _ EnterAction[@action]; }; EnterAction: PROC [action: Action] RETURNS [enteredFull: BOOL _ FALSE] = { <> <> length: CARDINAL = SELECT action.kind FROM deltaEventTime => SIZE[deltaEventTime ActionBody], keyDown => SIZE[keyDown ActionBody], keyStillDown => SIZE[keyStillDown ActionBody], keyUp => SIZE[keyUp ActionBody], allUp => SIZE[allUp ActionBody], eventTime => SIZE[eventTime ActionBody], deltaMouse => SIZE[deltaMouse ActionBody], mousePosition => SIZE[mousePosition ActionBody], penPosition => SIZE[penPosition ActionBody], timedOut => SIZE[timedOut 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 { <> IF ~SetWritePage[inscript] THEN RETURN[FALSE]; <> EnterFullState[]; RETURN[TRUE]; }; lastEventTime _ eventTime; -- time only changes when something's been entered }; SetMousePosition: PUBLIC SAFE PROC [pos: MousePosition] = CHECKED { <> terminal.SetMousePosition[[x: pos.mouseX, y: pos.mouseY]]; }; GetMousePosition: PUBLIC SAFE PROC RETURNS [pos: MousePosition] = TRUSTED { p: Terminal.Position = terminal.GetMousePosition[]; pos _ [p.x, display.color, p.y]; }; DefaultMouseGrain: PUBLIC SAFE PROC RETURNS [ticks: Intime.MsTicks, dots: INTEGER] = CHECKED { RETURN[defaultGrainTicks, defaultGrainDots]; }; SetMouseGrain: PUBLIC SAFE PROC [ticks: Intime.MsTicks, dots: INTEGER] = CHECKED { grainDots_MIN[defaultGrainDots, dots]; grainMsTicks_MIN[defaultGrainTicks, ticks]; grainTicks_(grainMsTicks+grainMsPerTick-1)/grainMsPerTick; grainTicksLeft_0; }; SetCursorPattern: PUBLIC SAFE PROC [cursorPattern: CursorArray] = TRUSTED { IF display.color THEN terminal.SetColorCursorPattern[cursorPattern] ELSE terminal.SetBWCursorPattern[cursorPattern]; }; GetCursorPattern: PUBLIC SAFE PROC RETURNS [cursorPattern: CursorArray] = TRUSTED { IF display.color THEN RETURN[terminal.GetColorCursorPattern[]] ELSE RETURN[terminal.GetBWCursorPattern[]]; }; TurnOnColorCursor: PUBLIC SAFE PROC [side: Side] = TRUSTED { color: POINTER TO DisplayRec; IF hasColor THEN TurnOffColorCursor[]; IF NOT terminal.hasColorDisplay THEN RETURN; SELECT side FROM left => {display _ right; color _ left}; right => {display _ left; color _ right}; ENDCASE => ERROR; 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 SAFE PROC = TRUSTED { 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 SAFE PROC [display: HasPenType] = CHECKED { }; WaitForTick: PROC = INLINE { WaitForEnabled[]; terminal.WaitForBWVerticalRetrace[]; }; WaitForEnabled: ENTRY PROC = INLINE { UNTIL terminalHandlerEnabled DO WAIT enableTerminalHandler; ENDLOOP; }; terminalProcess: PROCESS; StartActionRecorder: PUBLIC SAFE PROC [scr: ClassInscript.Inscript] = TRUSTED { <<<>>> 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; Intime.AdjustEventTime[TRUE]; eventTime _ lastEventTime _ ReadEventTime[]; keyState _ [bits[terminal.GetKeys[]]]; lastKeyState _ keyState; EnterFullState[]; terminalHandlerEnabled_TRUE; NOTIFY enableTerminalHandler; }; DisableTerminalHandler: ENTRY PROC = {terminalHandlerEnabled _ FALSE}; SetColorDisplaySide: PUBLIC SAFE PROC[side: Side] = CHECKED { IF GetColorDisplaySide[]#side THEN { temp: POINTER TO DisplayRec ~ right; right _ left; left _ temp; }; }; GetColorDisplaySide: PUBLIC SAFE PROC RETURNS[Side] = TRUSTED { RETURN[IF left.color THEN left ELSE right]; }; ProcessProfile: UserProfile.ProfileChangedProc = CHECKED { cw: INT _ UserProfile.Number["Interminal.chordWaits", 0]; SELECT cw FROM > 1000 => cw _ 1000; < 0 => cw _ 0; ENDCASE; chordWaits _ (cw+grainMsPerTick-1)/grainMsPerTick; 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]; END.