-- SpecialTerminalImpl.mesa -- Derived from SimpleTTY.mesa last edited by Forrest January 6, 1981 7:28 PM -- last edited by Levin on November 12, 1982 5:26 pm -- last edited by McGregor on April 22, 1982 4:23 pm DIRECTORY Ascii USING [ BS, ControlA, ControlR, ControlQ, ControlV, ControlW, ControlX, CR, DEL, ESC, FF, SP, TAB], BitBlt USING [AlignedBBTable, BBptr, BBTableSpace, BITBLT], Environment USING [BitAddress], Format USING [ Date, Decimal, LongDecimal, LongNumber, LongOctal, LongSubStringItem, Number, Octal, SubString], Inline USING [BITAND], Process USING [DisableTimeout, Pause, SecondsToTicks, SetPriority, SetTimeout], Runtime USING [GetTableBase], SpecialSpace USING [ MakeGlobalFrameResident, --MakeGlobalFrameSwappable,-- MakeProcedureResident, MakeProcedureSwappable], SpecialTerminal USING [GetProc, PutProc], Streams USING [Destroy, Handle, NewStream, PutChar, Write], String USING [ AppendChar, AppendLongNumber, AppendNumber, StringToLongNumber, StringToNumber, SubString], System USING [GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime], TerminalMultiplex USING [CurrentTerminal, PermitSwaps, PreventSwaps, SelectTerminal, Terminal], Time USING [Append, Packed, Unpack], TTY USING [DateFormat, Handle, LongSubString, NumberFormat], UserTerminal USING [ Coordinate, cursor, GetBitBltTable, keyboard, mouse, screenHeight, screenWidth, SetMousePosition, SetState, SetBackground, WaitForScanLine], Volume USING [GetLabelString, maxNameLength, systemID]; SpecialTerminalImpl: MONITOR LOCKS m USING m: POINTER TO MONITORLOCK IMPORTS BitBlt, Format, Inline, Process, Runtime, SpecialSpace, Streams, String, System, TerminalMultiplex, Time, UserTerminal, Volume EXPORTS TTY, SpecialTerminal = BEGIN OPEN Ascii, BitBlt; screenLock: MONITORLOCK; -- These variables are protected by "screenLock" typescript: Streams.Handle; typescriptEnabled: BOOLEAN ← FALSE; noTrack: CARDINAL ← 0; keyboardLock: MONITORLOCK; -- These variables are protected by "keyboardLock" state: {off, turningOn, on, turningOff} ← off; stateChange: CONDITION ← [timeout: 0]; terminalLock: MONITORLOCK; -- These variables are protected by "terminalLock" originalTerminal: TerminalMultiplex.Terminal; originalLockCount: CARDINAL; keyboardWatcher, timePainter: PROCESS; -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Exports to SpecialTerminal -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ InputTimeout: PUBLIC SIGNAL = CODE; SetInputTimeout: PUBLIC PROC [ticks: CARDINAL] = { SetInputTimeoutInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = { IF ticks = 0 THEN Process.DisableTimeout[@charactersAvailable] ELSE Process.SetTimeout[@charactersAvailable, ticks]}; SetInputTimeoutInternal[@keyboardLock]}; TurnOn: PUBLIC PROC RETURNS [SpecialTerminal.GetProc, SpecialTerminal.PutProc] = { EnsureSpecialVirtualTerminalInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = { OPEN TerminalMultiplex; terminal: TerminalMultiplex.Terminal; lockCount: CARDINAL; [terminal, lockCount] ← CurrentTerminal[]; IF terminal ~= special THEN { originalTerminal ← terminal; originalLockCount ← lockCount; THROUGH [0..lockCount) DO PermitSwaps[] ENDLOOP; IF ~SelectTerminal[terminal: special, lock: TRUE] THEN ERROR} ELSE PreventSwaps[] }; EnsureSpecialVirtualTerminalInternal[@terminalLock]; RETURN [getChar, putChar] }; getChar: PROC RETURNS [CHAR] = {RETURN[GetChar[tty]]}; putChar: PROC [c: CHAR] = {PutChar[tty, c]}; TurnOff: PUBLIC PROC = { EnsureOriginalVirtualTerminalInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = { OPEN TerminalMultiplex; PermitSwaps[]; IF CurrentTerminal[].lockCount = 0 THEN { IF ~SelectTerminal[terminal: originalTerminal, lock: originalLockCount > 0] THEN ERROR; THROUGH [1..originalLockCount) DO PreventSwaps[] ENDLOOP} }; EnsureOriginalVirtualTerminalInternal[@terminalLock]; }; EnableCursorTracking: PUBLIC PROC = { EnableInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = { IF (noTrack ← noTrack - 1) = 0 THEN doBlink ← TRUE}; EnableInternal[@screenLock]}; DisableCursorTracking: PUBLIC PROC = { DisableInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = { IF (noTrack ← noTrack + 1) ~= 0 THEN doBlink ← FALSE}; DisableInternal[@screenLock]}; EnableTypescriptFile: PUBLIC PROC = { EnableTypescriptInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = { IF typescriptEnabled THEN RETURN; typescript ← Streams.NewStream["Init.log"L, Streams.Write]; typescriptEnabled ← TRUE}; EnableTypescriptInternal[@screenLock]}; DisableTypescriptFile: PUBLIC PROC = { DisableTypescriptInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = { IF ~typescriptEnabled THEN RETURN; Streams.Destroy[typescript]; typescriptEnabled ← FALSE}; DisableTypescriptInternal[@screenLock]}; -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Stuff for special clients -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tty: PUBLIC TTY.Handle ← LOOPHOLE[123456B]; -- value is never used TerminalOn: PUBLIC PROC RETURNS [BOOLEAN] = { TerminalOnInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] RETURNS [BOOLEAN] = INLINE { ENABLE UNWIND => NULL; WHILE state ~= off AND state ~= on DO WAIT stateChange ENDLOOP; RETURN [state = on]}; RETURN[TerminalOnInternal[@keyboardLock]] }; TurnOnInternal: PUBLIC PROC [nameStripe: LONG STRING] = { TurnOnInternalA: ENTRY PROC [m: POINTER TO MONITORLOCK] = { ENABLE UNWIND => NULL; WHILE state ~= off DO WAIT stateChange ENDLOOP; state ← turningOn; BROADCAST stateChange}; TurnOnInternalB: ENTRY PROC [m: POINTER TO MONITORLOCK] = { state ← on; BROADCAST stateChange}; IF ~font.newStyle OR font.indexed OR ~(font.min IN [0C..177C]) OR ~(font.max+1 IN [0C..177C]) THEN ERROR; TurnOnInternalA[@keyboardLock]; SpecialSpace.MakeProcedureResident[ProcessKeyboard]; SpecialSpace.MakeGlobalFrameResident[SpecialTerminalImpl]; CDT ← FALSE; echo ← TRUE; in ← out ← 0; InitScreen[nameStripe]; keyboardWatcher ← FORK ProcessKeyboard[]; TurnOnInternalB[@keyboardLock]; }; TurnOffInternal: PUBLIC PROC = { TurnOffInternalA: ENTRY PROC [m: POINTER TO MONITORLOCK] = { ENABLE UNWIND => NULL; WHILE state ~= on DO WAIT stateChange ENDLOOP; state ← turningOff; BROADCAST stateChange}; TurnOffInternalB: ENTRY PROC [m: POINTER TO MONITORLOCK] = { state ← off; BROADCAST stateChange}; TurnOffInternalA[@keyboardLock]; SpecialSpace.MakeProcedureSwappable[ProcessKeyboard]; -- Pilot doesn't do this right: SpecialSpace.MakeGlobalFrameSwappable[SpecialTTY]; [] ← UserTerminal.SetState[disconnected]; JOIN keyboardWatcher; JOIN timePainter; TurnOffInternalB[@keyboardLock];}; -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- FONT Definitions and variables -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ font: LONG POINTER TO MACHINE DEPENDENT RECORD [ newStyle(0:0..0): BOOLEAN, indexed(0:1..1): BOOLEAN, fixed(0:2..2): BOOLEAN, kerned(0:3..3): BOOLEAN, pad(0:4..15): [0..7777B], min(1): CHARACTER, -- limits of chars in font max(2): CHARACTER, -- limits of chars in font maxwidth(3): CARDINAL, length(4): CARDINAL, ascent(5): CARDINAL, descent(6): CARDINAL, xoffset(7): CARDINAL, raster(8): CARDINAL, chars(9:0..63): SELECT OVERLAID * FROM hasBoundingBox => [ boundingBox(9:0..63): RECORD [FontBBox, FontBBoy, FontBBdx, FontBBDy: INTEGER], BBBitmap(13): ARRAY [0..0) OF WORD], noBoundingBox => [bitmap(9): ARRAY [0..0) OF WORD], ENDCASE] = GetFont[]; bitmap: LONG POINTER = IF font.kerned THEN @font.BBBitmap ELSE @font.bitmap; xInSegment: LONG POINTER TO ARRAY CHARACTER [0C..0C) OF CARDINAL = bitmap + font.raster*FontHeight[] - (font.min-0C); height: INTEGER[0..LAST[INTEGER]] = FontHeight[]; CharWidth: PROC [char: CHARACTER] RETURNS [[0..LAST[INTEGER]]] = INLINE BEGIN IF ~(char IN [font.min..font.max]) THEN char ← font.max+1; RETURN[xInSegment[char+1] - xInSegment[char]] END; FontHeight: PROC RETURNS [[0..LAST[INTEGER]]] = INLINE {RETURN[font.ascent+font.descent]}; -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Keyboard Definitions and Constants -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ downUp: TYPE = {down, up}; keycount: CARDINAL = 80; -- must be 0 mod 16 Keyarray: TYPE = MACHINE DEPENDENT RECORD [SELECT OVERLAID * FROM b => [bits: PACKED ARRAY [0..keycount) OF downUp], wds => [wds: ARRAY [0..keycount/16) OF WORD], ENDCASE]; KeyItem: TYPE = RECORD [ Letter: BOOLEAN, ShiftCode: CHARACTER [0C..177C], NormalCode: CHARACTER [0C..377C]]; -- Keyboard Info ctrl: CARDINAL = 52; leftShift: CARDINAL = 57; shiftLock: CARDINAL = 72; rightShift: CARDINAL = 76; spare3: CARDINAL = 77; KeyTable: ARRAY [16..keycount) OF KeyItem = [ -- Index [0..15] mouse, etc -- Index [16..31] [FALSE, 45C, 65C], -- %,5 [FALSE, 44C, 64C], -- $,4 [FALSE, 176C, 66C], -- ~,6 [TRUE, 105C, 145C], -- E [FALSE, 46C, 67C], -- &,7 [TRUE, 104C, 144C], -- D [TRUE, 125C, 165C], -- U [TRUE, 126C, 166C], -- V [FALSE, 51C, 60C], -- ),0 [TRUE, 113C, 153C], -- K [FALSE, 30C, 55C], -- `,- [TRUE, 120C, 160C], -- P [FALSE, 77C, 57C], -- ?,/ [FALSE, 174C, 134C], -- |,\ [FALSE, 12C, 12C], -- LF [FALSE, 10C, 10C], -- BS -- Index [32..47] [FALSE, 43C, 63C], -- #,3 [FALSE, 100C, 62C], -- @,2 [TRUE, 127C, 167C], -- W [TRUE, 121C, 161C], -- Q [TRUE, 123C, 163C], -- S [TRUE, 101C, 141C], -- A [FALSE, 50C, 71C], -- (,9 [TRUE, 111C, 151C], -- I [TRUE, 130C, 170C], -- X [TRUE, 117C, 157C], -- O [TRUE, 114C, 154C], -- L [FALSE, 74C, 54C], -- <,, [FALSE, 42C, 47C], -- ",' [FALSE, 175C, 135C], --},] [FALSE, 0C, 0C], -- SPARE2 [FALSE, 0C, 0C], -- SPARE1 -- Index [48..63] [FALSE, 41C, 61C], -- !,1 [FALSE, 33C, 33C], -- ESCAPE [FALSE, 11C, 11C], -- TAB [TRUE, 106C, 146C], -- F [FALSE, 0C, 0C], -- CONTROL [TRUE, 103C, 143C], -- C [TRUE, 112C, 152C], -- J [TRUE, 102C, 142C], -- B [TRUE, 132C, 172C], -- Z [FALSE, 0C, 0C], -- SHIFT [FALSE, 76C, 56C], -- >,. [FALSE, 72C, 73C], -- :,; [FALSE, 15C, 15C], -- CR [FALSE, 136C, 137C], -- ↑,← [FALSE, 177C, 177C], -- DEL [FALSE, 0C, 0C], -- NOT USED (FL3) -- Index [64..79] [TRUE, 122C, 162C], -- R [TRUE, 124C, 164C], -- T [TRUE, 107C, 147C], -- G [TRUE, 131C, 171C], -- Y [TRUE, 110C, 150C], -- H [FALSE, 52C, 70C], -- *,8 [TRUE, 116C, 156C], -- N [TRUE, 115C, 155C], -- M [FALSE, 0C, 0C], -- LOCK [FALSE, 40C, 40C], -- SPACE [FALSE, 173C, 133C], -- {,[ [FALSE, 53C, 75C], -- +,= [FALSE, 0C, 0C], -- Shift [FALSE, 0C, 0C], -- Spare3 [FALSE, 0C, 0C], -- not user (FR4) [FALSE, 0C, 0C]]; -- not user (FR5) -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Keyboard Implementation -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- These variables are protected by "keyboardLock" CDT: BOOLEAN; charactersAvailable: CONDITION; echo: BOOLEAN; in, out: CARDINAL; buffer: PACKED ARRAY [0..50) OF CHARACTER; -- EXTERNAL PROCS CtrlChar: PROC [c: CHARACTER] RETURNS [CHARACTER] = INLINE {RETURN[LOOPHOLE[Inline.BITAND[c, 37B]]]}; -- ENTRY PROCS ProcessKeyboard: PROC = BEGIN old, new: Keyarray; kp: LONG POINTER TO Keyarray = LOOPHOLE[UserTerminal.keyboard]; blinkCount: CARDINAL ← 33; GoAway: ENTRY PROC [m: POINTER TO MONITORLOCK] RETURNS [BOOLEAN] = INLINE { RETURN[state = turningOff]}; Process.SetPriority[6]; new ← kp↑; UNTIL GoAway[@keyboardLock] DO Brdcst: ENTRY PROC [m: POINTER TO MONITORLOCK] = INLINE {BROADCAST charactersAvailable}; charsSeen: BOOLEAN ← FALSE; old ← new; UserTerminal.WaitForScanLine[0]; new ← kp↑; TrackCursor[@screenLock]; IF (blinkCount←blinkCount-1) = 0 THEN {BlinkCursor[]; blinkCount ← 34}; FOR i: CARDINAL IN [1..keycount/16) DO IF old.wds[i]#new.wds[i] THEN FOR j: CARDINAL IN [i*16..(i+1)*16) DO char: CHARACTER; entry: KeyItem; IF new.bits[j]=up OR old.bits[j]=down THEN LOOP; IF (char ← (entry←KeyTable[j]).NormalCode)#0C THEN BEGIN SELECT TRUE FROM new.bits[ctrl]=down => IF char=177C THEN {CDT ← TRUE; LOOP} ELSE char ← CtrlChar[char]; new.bits[leftShift]=down, new.bits[rightShift]=down => char ← entry.ShiftCode; new.bits[shiftLock]=down AND entry.Letter => char ← entry.ShiftCode; ENDCASE; StuffBuffer[char, @keyboardLock]; charsSeen ← TRUE END; ENDLOOP ENDLOOP; IF charsSeen THEN Brdcst[@keyboardLock]; ENDLOOP END; TrackCursor: ENTRY PROC [m: POINTER TO MONITORLOCK] = INLINE BEGIN mouse: UserTerminal.Coordinate ← UserTerminal.mouse↑; IF noTrack > 0 THEN RETURN; mouse.x ← MIN[MAX[0, mouse.x], UserTerminal.screenWidth]; mouse.y ← MIN[MAX[0, mouse.y], UserTerminal.screenHeight]; UserTerminal.cursor↑ ← mouse; UserTerminal.SetMousePosition[mouse] END; StuffBuffer: ENTRY PROC [c: CHARACTER, m: POINTER TO MONITORLOCK] = INLINE BEGIN newin: CARDINAL; IF (newin←in+1) = LENGTH[buffer] THEN newin ← 0; IF newin#out THEN {buffer[in] ← c; in ← newin}; END; -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- DISPLAY -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- These variables are protected by "screenLock" BBTable: BBTableSpace; bbPtr: BBptr = AlignedBBTable[@BBTable]; charPos, line, nCharPos, nLines: CARDINAL; usableHeight, usableWidth: CARDINAL; firstLine, thisLine: Environment.BitAddress; bitsPerTextLine: CARDINAL; -- = screenWidth*font.height doBlink: BOOLEAN ← FALSE; nameStripeOrg: Environment.BitAddress; nameStripeWidth: CARDINAL; lastTime: System.GreenwichMeanTime; GetBitAddress: PROC [p: LONG POINTER, o: LONG CARDINAL] RETURNS [Environment.BitAddress] = {RETURN[[p+o/16, 0, o MOD 16]]}; BlinkCursor: PROC = BEGIN BlinkCursorEntry: ENTRY PROC [m: POINTER TO MONITORLOCK] = BEGIN blinker: CARDINAL ← 60000B; IF doBlink THEN BEGIN bbPtr.src ← [@blinker, 0, 0]; bbPtr.srcDesc ← [gray[[0, 0, 0, 0]]]; bbPtr.flags ← [gray: TRUE, dstFunc: xor]; BITBLT[bbPtr]; bbPtr.srcDesc ← [srcBpl[font.raster*16]]; bbPtr.flags ← []; END; END; IF doBlink THEN BlinkCursorEntry[@screenLock]; END; Backup: INTERNAL PROC = BEGIN t: CARDINAL = bbPtr.dst.bit+16-font.maxwidth; IF charPos=0 THEN RETURN; charPos ← charPos - 1; bbPtr.dst.word ← bbPtr.dst.word + t/16 - 1; bbPtr.dst.bit ← t MOD 16; END; InitBitmap: PROC = BEGIN zero: CARDINAL ← 0; bbPtr↑ ← UserTerminal.GetBitBltTable[]; -- bbPtr.dst, bbPtr.height, and bbPtr.width are set up bbPtr.src ← [@zero, 0, 0]; bbPtr.srcDesc ← [gray[[0, 0, 0, 0]]]; bbPtr.flags ← [gray: TRUE]; BITBLT[bbPtr]; END; InitScreen: PROC [nameStripe: LONG STRING] = BEGIN borderWidth: CARDINAL = 16; boxWidth: CARDINAL = 1; marginWidth: CARDINAL = 8; org: Environment.BitAddress; InitBitmap: PROC = { zero: CARDINAL ← 0; -- bbPtr.dst, bbPtr.height, and bbPtr.width are set up bbPtr.src ← [@zero, 0, 0]; bbPtr.srcDesc ← [gray[[0, 0, 0, 0]]]; bbPtr.flags ← [gray: TRUE]; BITBLT[bbPtr]}; MakeBorder: PROC [w: CARDINAL] = { stipple: ARRAY [0..3] OF CARDINAL ← [104210B,104210B,021042B,021042B]; -- bbPtr.dstBpl never changes bbPtr.src ← [@stipple, 0, 0]; bbPtr.srcDesc ← [gray[[yOffset: 0, widthMinusOne: 0, heightMinusOne: 3]]]; bbPtr.flags ← [gray: TRUE]; -- top stripe bbPtr.dst ← org; bbPtr.width ← usableWidth; bbPtr.height ← w; BITBLT[bbPtr]; -- bottom stripe bbPtr.dst ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*(usableHeight-w)]; BITBLT[bbPtr]; -- left side bbPtr.dst ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*w]; bbPtr.width ← w; bbPtr.height ← usableHeight-w-w; BITBLT[bbPtr]; -- right side bbPtr.dst ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*w+usableWidth-w]; BITBLT[bbPtr]; org ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*w+w]; usableHeight ← usableHeight - w - w; usableWidth ← usableWidth - w - w; }; MakeBox: PROC [w: CARDINAL] = { black: CARDINAL ← 177777B; bbPtr.src ← [@black, 0, 0]; bbPtr.srcDesc ← [gray[[yOffset: 0, widthMinusOne: 0, heightMinusOne: w-1]]]; bbPtr.flags ← [gray: TRUE]; -- top stripe bbPtr.dst ← org; bbPtr.width ← usableWidth; bbPtr.height ← w; BITBLT[bbPtr]; -- bottom stripe bbPtr.dst ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*(usableHeight-w)]; BITBLT[bbPtr]; -- left side bbPtr.dst ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*w]; bbPtr.width ← w; bbPtr.height ← usableHeight-w-w; BITBLT[bbPtr]; -- right side bbPtr.dst ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*w+usableWidth-w]; BITBLT[bbPtr]; org ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*w+w]; usableHeight ← usableHeight - w - w; usableWidth ← usableWidth - w - w; }; MakeNameStripe: PROC [nameStripe: LONG STRING] = { black: CARDINAL ← 177777B; volumeLabel: STRING ← [Volume.maxNameLength]; Volume.GetLabelString[Volume.systemID, volumeLabel]; nameStripeOrg ← org; nameStripeWidth ← usableWidth; -- paint version text bbPtr.dst ← GetBitAddress[org.word, org.bit+marginWidth]; bbPtr.srcDesc ← [srcBpl[font.raster*16]]; bbPtr.height ← height; bbPtr.width ← font.maxwidth; bbPtr.flags ← []; FOR i: CARDINAL IN [0..nameStripe.length) DO bbPtr.src ← GetBitAddress[bitmap, xInSegment[nameStripe[i]]]; BITBLT[bbPtr]; bbPtr.dst ← GetBitAddress[bbPtr.dst.word, bbPtr.dst.bit+font.maxwidth]; ENDLOOP; -- paint volume name bbPtr.dst ← GetBitAddress[org.word, org.bit+usableWidth-marginWidth-volumeLabel.length*font.maxwidth]; FOR i: CARDINAL IN [0..volumeLabel.length) DO bbPtr.src ← GetBitAddress[bitmap, xInSegment[volumeLabel[i]]]; BITBLT[bbPtr]; bbPtr.dst ← GetBitAddress[bbPtr.dst.word, bbPtr.dst.bit+font.maxwidth]; ENDLOOP; -- overlay with stripe bbPtr.src ← [@black, 0, 0]; bbPtr.srcDesc ← [gray[[yOffset: 0, widthMinusOne: 0, heightMinusOne: 0]]]; bbPtr.flags ← [gray: TRUE, dstFunc: xor]; bbPtr.dst ← org; bbPtr.width ← usableWidth; bbPtr.height ← height; BITBLT[bbPtr]; org ← GetBitAddress[org.word, org.bit+LONG[bbPtr.dstBpl]*height]; usableHeight ← usableHeight - height; lastTime ← System.gmtEpoch; timePainter ← FORK MaintainTime[]; }; [] ← UserTerminal.SetBackground[white]; [] ← UserTerminal.SetState[off]; -- allocate the bitmap, but don't display it yet. bbPtr↑ ← UserTerminal.GetBitBltTable[]; org ← bbPtr.dst; usableHeight ← bbPtr.height; usableWidth ← bbPtr.width; InitBitmap[]; MakeBorder[borderWidth]; MakeBox[boxWidth]; MakeNameStripe[nameStripe]; -- now display the bitmap [] ← UserTerminal.SetState[on]; usableHeight ← usableHeight - 2*marginWidth; usableWidth ← usableWidth - 2*marginWidth; bitsPerTextLine ← bbPtr.dstBpl*height; firstLine ← GetBitAddress[org.word, org.bit+LONG[marginWidth]*bbPtr.dstBpl+marginWidth]; nCharPos ← usableWidth/font.maxwidth; nLines ← usableHeight/height; InitPainting[]; END; MaintainTime: PROC = { GoAway: ENTRY PROC [m: POINTER TO MONITORLOCK] RETURNS [BOOLEAN] = INLINE { RETURN[state = turningOff]}; BBTable: BBTableSpace; bbPtr: BBptr = AlignedBBTable[@BBTable]; secondsOrg: Environment.BitAddress; date: STRING ← [25]; black: CARDINAL ← 177777B; SetToBlacken: PROC = { bbPtr.src ← [@black, 0, 0]; bbPtr.srcDesc ← [gray[[yOffset: 0, widthMinusOne: 0, heightMinusOne: 0]]]; bbPtr.flags ← [gray: TRUE]}; PaintTime: PROC = { current: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[]; IF lastTime/60 = current/60 THEN { seconds: [0..59] ← current MOD 60; SetToBlacken[]; bbPtr.dst ← secondsOrg; bbPtr.width ← 2*font.maxwidth; bbPtr.height ← height; BITBLT[bbPtr]; -- bbPtr.dst & bbPtr.height still OK bbPtr.srcDesc ← [srcBpl[font.raster*16]]; bbPtr.width ← font.maxwidth; bbPtr.flags ← [srcFunc: complement]; bbPtr.src ← GetBitAddress[bitmap, xInSegment['0+seconds/10]]; BITBLT[bbPtr]; bbPtr.dst ← GetBitAddress[bbPtr.dst.word, bbPtr.dst.bit+font.maxwidth]; bbPtr.src ← GetBitAddress[bitmap, xInSegment['0+seconds MOD 10]]; BITBLT[bbPtr]} ELSE { date.length ← 0; Time.Append[date, Time.Unpack[current]]; SetToBlacken[]; bbPtr.dst ← GetBitAddress[nameStripeOrg.word, nameStripeOrg.bit+(nameStripeWidth-date.length*font.maxwidth)/2]; secondsOrg ← GetBitAddress[bbPtr.dst.word, bbPtr.dst.bit+(date.length-2)*font.maxwidth]; bbPtr.width ← date.length*font.maxwidth; bbPtr.height ← height; BITBLT[bbPtr]; -- bbPtr.dst & bbPtr.height still OK bbPtr.srcDesc ← [srcBpl[font.raster*16]]; bbPtr.width ← font.maxwidth; bbPtr.flags ← [srcFunc: complement]; FOR i: CARDINAL IN [0..date.length) DO bbPtr.src ← GetBitAddress[bitmap, xInSegment[date[i]]]; BITBLT[bbPtr]; bbPtr.dst ← GetBitAddress[bbPtr.dst.word, bbPtr.dst.bit+font.maxwidth]; ENDLOOP}; lastTime ← current}; bbPtr↑ ← UserTerminal.GetBitBltTable[]; UNTIL GoAway[@keyboardLock] DO PaintTime[]; Process.Pause[Process.SecondsToTicks[1]]; ENDLOOP}; ClearScreen: PROC = BEGIN zero: CARDINAL ← 0; bbPtr.dst ← firstLine; bbPtr.src ← [@zero, 0, 0]; bbPtr.srcDesc ← [gray[[0, 0, 0, 0]]]; bbPtr.flags ← [gray: TRUE]; bbPtr.height ← usableHeight; bbPtr.width ← usableWidth; BITBLT[bbPtr]; InitPainting[]; END; InitPainting: PROC = BEGIN -- set up standard arguments for character painting charPos ← 0; line ← 1; --bbPtr.dstBpl already set up --bbPtr.src set when proc called bbPtr.dst ← thisLine ← firstLine; bbPtr.srcDesc ← [srcBpl[font.raster*16]]; bbPtr.height ← height; bbPtr.width ← font.maxwidth; bbPtr.flags ← []; END; ClearThisChar: INTERNAL PROC = BEGIN zero: CARDINAL ← 0; bbPtr.src ← [@zero, 0, 0]; bbPtr.srcDesc ← [gray[[0, 0, 0, 0]]]; bbPtr.flags ← [gray: TRUE]; BITBLT[bbPtr]; bbPtr.srcDesc ← [srcBpl[font.raster*16]]; bbPtr.flags ← []; END; DisplayChar: INTERNAL PROC [c: CHARACTER] = BEGIN Newline: INTERNAL PROC = BEGIN IF line<nLines THEN {thisLine ← GetBitAddress[thisLine.word, thisLine.bit+bitsPerTextLine]; line ← line+1} ELSE BEGIN zero: CARDINAL ← 0; sBBTable: BBTableSpace; sbbPtr: BBptr = AlignedBBTable[@sBBTable]; sbbPtr↑ ← [ dst: firstLine, dstBpl: bbPtr.dstBpl, src: GetBitAddress[firstLine.word, firstLine.bit+bitsPerTextLine], srcDesc: [srcBpl[bbPtr.dstBpl]], flags: [direction: forward], width: nCharPos*font.maxwidth, height: height*(nLines-1)]; BITBLT[sbbPtr]; sbbPtr↑ ← [ dst: thisLine, src: [@zero, 0, 0], dstBpl: bbPtr.dstBpl, srcDesc: [gray[[0, 0, 0, 0]]], width: nCharPos*font.maxwidth, height: height, flags: [gray: TRUE]]; BITBLT[sbbPtr]; END; bbPtr.dst ← thisLine; charPos ← 0; END; SELECT c FROM IN (SP..'~] => BEGIN IF ~(c IN [font.min..font.max]) THEN c ← font.max+1; bbPtr.src ← GetBitAddress[bitmap, xInSegment[c]]; BITBLT[bbPtr]; END; SP => NULL; CR => {Newline[]; RETURN}; BS => {Backup[]; ClearThisChar[]; RETURN}; TAB => {UNTIL (charPos MOD 8)=0 DO DisplayChar[SP] ENDLOOP; RETURN}; FF => {ClearScreen[]; RETURN}; IN [0C..SP) => {DisplayChar['↑]; DisplayChar[c+('A-1C)]; RETURN}; ENDCASE => RETURN; IF (charPos←charPos+1)>=nCharPos THEN Newline[] ELSE bbPtr.dst ← GetBitAddress[bbPtr.dst.word, bbPtr.dst.bit+font.maxwidth]; END; GetFont: PROC RETURNS [LONG POINTER] = BEGIN -- Note: Even though it doesn't say so in its strike header, this is a fixed-width -- font. Bitter experience has shown that not all Gacha10.strike font files are, in -- fact, fixed width. You need to painstakingly examine the xInSegment table for -- the truth. Gacha10Strike: ARRAY [0..1142B) OF WORD = [ -- 0-- 100000B, 000040B, 000176B, 000007B, 001136B, 000010B, 000004B, 000000B, -- 10-- 000052B, 000000B, 000001B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 20-- 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 30-- 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 40-- 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 50-- 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 60-- 000000B, 000000B, 017000B, 000040B, 120243B, 106204B, 004010B, 040100B, -- 70-- 000000B, 000002B, 034040B, 160701B, 107616B, 037070B, 070000B, 000200B, -- 100-- 010034B, 034041B, 140707B, 107637B, 016104B, 070162B, 022010B, 110434B, -- 110-- 074161B, 160707B, 144221B, 021104B, 104761B, 141007B, 000000B, 000200B, -- 120-- 000040B, 001600B, 020010B, 010401B, 140000B, 000000B, 000000B, 000000B, -- 130-- 000000B, 000000B, 000060B, 103000B, 017000B, 000040B, 120245B, 052412B, -- 140-- 004020B, 020520B, 000000B, 000002B, 042141B, 011042B, 104021B, 001104B, -- 150-- 104000B, 000400B, 004042B, 042121B, 021044B, 044020B, 021104B, 020022B, -- 160-- 042015B, 114442B, 042211B, 011041B, 004221B, 021104B, 104021B, 001001B, -- 170-- 000000B, 000200B, 000040B, 002000B, 020000B, 000400B, 040000B, 000000B, -- 200-- 000000B, 000400B, 000000B, 000000B, 000100B, 100400B, 017000B, 000040B, -- 210-- 121765B, 054412B, 004020B, 020340B, 100000B, 000004B, 046241B, 010042B, -- 220-- 104020B, 002104B, 104100B, 101000B, 002042B, 056121B, 021044B, 044020B, -- 230-- 021104B, 020022B, 102015B, 114442B, 042211B, 011041B, 004221B, 025050B, -- 240-- 050041B, 000401B, 002010B, 034260B, 160643B, 107415B, 026070B, 070440B, -- 250-- 043313B, 007054B, 032260B, 161704B, 044221B, 021104B, 174100B, 100400B, -- 260-- 017000B, 000040B, 000503B, 001004B, 000040B, 010520B, 100000B, 000004B, -- 270-- 052040B, 020704B, 107436B, 002070B, 104100B, 102017B, 101004B, 052121B, -- 300-- 161004B, 047436B, 020174B, 020023B, 102012B, 112442B, 042211B, 160601B, -- 310-- 004212B, 025020B, 050101B, 000401B, 007020B, 002311B, 011144B, 042023B, -- 320-- 031010B, 010500B, 042514B, 110462B, 046311B, 010404B, 044221B, 012104B, -- 330-- 010100B, 100406B, 057000B, 000040B, 000501B, 101012B, 100040B, 010103B, -- 340-- 160017B, 100010B, 052040B, 040044B, 104221B, 004104B, 074000B, 002000B, -- 350-- 001010B, 052211B, 011004B, 044020B, 027104B, 020022B, 042012B, 112442B, -- 360-- 074211B, 040101B, 004212B, 025020B, 020101B, 000201B, 012476B, 036211B, -- 370-- 001047B, 142021B, 021010B, 010600B, 042510B, 110442B, 042200B, 140404B, -- 400-- 042425B, 004104B, 020100B, 100411B, 117000B, 000000B, 003745B, 042321B, -- 410-- 000040B, 010000B, 100000B, 000010B, 062040B, 100047B, 140221B, 004104B, -- 420-- 004000B, 001017B, 102000B, 056371B, 011044B, 044020B, 021104B, 020422B, -- 430-- 042012B, 111442B, 040211B, 021041B, 004212B, 012050B, 020201B, 000201B, -- 440-- 002020B, 042211B, 001044B, 002021B, 021010B, 010500B, 042510B, 110442B, -- 450-- 042200B, 020404B, 042425B, 004050B, 020600B, 100300B, 017000B, 000040B, -- 460-- 001205B, 042521B, 000040B, 010000B, 101400B, 002020B, 042041B, 001040B, -- 470-- 104221B, 010104B, 104101B, 100400B, 004010B, 040211B, 011044B, 044020B, -- 500-- 021104B, 020422B, 022012B, 111442B, 040211B, 011041B, 004204B, 012104B, -- 510-- 020401B, 000101B, 002010B, 042311B, 011144B, 042023B, 021010B, 010440B, -- 520-- 042510B, 110462B, 046201B, 010444B, 141012B, 012050B, 040100B, 100400B, -- 530-- 017000B, 000040B, 001203B, 104616B, 100040B, 010000B, 000400B, 002020B, -- 540-- 034371B, 170700B, 103416B, 010070B, 070100B, 100200B, 010010B, 036211B, -- 550-- 160707B, 107620B, 016104B, 070342B, 023710B, 110434B, 040161B, 010701B, -- 560-- 003404B, 012104B, 020761B, 000101B, 002000B, 036260B, 160643B, 102015B, -- 570-- 021010B, 010420B, 042510B, 107054B, 032200B, 160303B, 041012B, 021020B, -- 600-- 174100B, 100400B, 000000B, 000000B, 000001B, 000000B, 000020B, 020000B, -- 610-- 000400B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 100000B, -- 620-- 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 630-- 000040B, 000000B, 000000B, 000000B, 000001B, 000001B, 000000B, 000000B, -- 640-- 000000B, 000001B, 000000B, 110000B, 000000B, 000040B, 002000B, 000000B, -- 650-- 000000B, 000020B, 000100B, 100400B, 000000B, 000000B, 000000B, 000000B, -- 660-- 000020B, 020000B, 001000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 670-- 000001B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 700-- 000000B, 000000B, 000030B, 000000B, 000000B, 000000B, 000001B, 000001B, -- 710-- 000000B, 000000B, 000000B, 000016B, 000000B, 060000B, 000000B, 000040B, -- 720-- 002000B, 000000B, 000000B, 000140B, 000100B, 100400B, 000000B, 000000B, -- 730-- 000000B, 000000B, 000010B, 040000B, 000000B, 000000B, 000000B, 000000B, -- 740-- 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 750-- 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 760-- 000001B, 140007B, 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, -- 770-- 000000B, 000000B, 000000B, 000000B, 000000B, 000000B, 000060B, 103000B, --1000-- 012602B, 000000B, 000007B, 000016B, 000025B, 000034B, 000043B, 000052B, --1010-- 000061B, 000070B, 000077B, 000106B, 000115B, 000124B, 000133B, 000142B, --1020-- 000151B, 000160B, 000167B, 000176B, 000205B, 000214B, 000223B, 000232B, --1030-- 000241B, 000250B, 000257B, 000266B, 000275B, 000304B, 000313B, 000322B, --1040-- 000331B, 000340B, 000347B, 000356B, 000365B, 000374B, 000403B, 000412B, --1050-- 000421B, 000430B, 000437B, 000446B, 000455B, 000464B, 000473B, 000502B, --1060-- 000511B, 000520B, 000527B, 000536B, 000545B, 000554B, 000563B, 000572B, --1070-- 000601B, 000610B, 000617B, 000626B, 000635B, 000644B, 000653B, 000662B, --1100-- 000671B, 000700B, 000700B, 000707B, 000716B, 000725B, 000734B, 000743B, --1110-- 000752B, 000761B, 000770B, 000777B, 001006B, 001015B, 001024B, 001033B, --1120-- 001042B, 001051B, 001060B, 001067B, 001076B, 001105B, 001114B, 001123B, --1130-- 001132B, 001141B, 001150B, 001157B, 001166B, 001175B, 001204B, 001213B, --1140-- 001222B, 001230B]; p: LONG POINTER TO ARRAY [0..1142B) OF WORD ← Runtime.GetTableBase[LOOPHOLE[SpecialTerminalImpl]]; DO IF p[0] = Gacha10Strike[0] AND p↑ = Gacha10Strike THEN RETURN[p]; p ← p+1; ENDLOOP; END; --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- TTY Implementation --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create: PUBLIC PROC [STRING] RETURNS [TTY.Handle] = {ERROR}; Destroy: PUBLIC PROC [TTY.Handle] = {ERROR}; BackingStream: PUBLIC PROC [TTY.Handle] RETURNS [never: UNSPECIFIED] = {ERROR}; UserAbort: PUBLIC PROC RETURNS [BOOLEAN] = {RETURN[CDT]}; ResetUserAbort: PUBLIC PROC = BEGIN CDResetEntry: ENTRY PROC [m: POINTER TO MONITORLOCK] = INLINE {CDT ← FALSE}; CDResetEntry[@keyboardLock] END; -- input procedures CharsAvailable: PUBLIC PROC [h: TTY.Handle] RETURNS [CARDINAL] = BEGIN P: ENTRY PROC [m: POINTER TO MONITORLOCK] RETURNS [CARDINAL] = INLINE {RETURN[IF in>=out THEN in-out ELSE in+LENGTH[buffer]-out]}; RETURN[P[@keyboardLock]] END; GetChar: PUBLIC PROC [h: TTY.Handle] RETURNS [c: CHARACTER] = BEGIN GetInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] RETURNS [BOOL] = INLINE { ENABLE UNWIND => NULL; WHILE in=out DO WAIT charactersAvailable; IF in=out THEN RETURN[FALSE]; ENDLOOP; c ← buffer[out]; IF (out←out+1) = LENGTH[buffer] THEN out ← 0; RETURN[TRUE] }; UNTIL GetInternal[@keyboardLock] DO SIGNAL InputTimeout; ENDLOOP; END; GetDecimal: PUBLIC PROC [h: TTY.Handle] RETURNS [INTEGER] = BEGIN s: STRING ← [10]; [] ← GetEditedString[h, s, IsAtom, TRUE]; RETURN[String.StringToNumber[s, 10]] END; GetID: PUBLIC PROC [h: TTY.Handle, s: STRING] = {[] ← GetEditedString[h, s, IsAtom, TRUE]}; GetLine: PUBLIC PROC [h: TTY.Handle, s: STRING] = {[] ← GetEditedString[h, s, IsCR, TRUE]; PutChar[h, CR]}; GetLongDecimal: PUBLIC PROC [h: TTY.Handle] RETURNS [n: LONG INTEGER] = BEGIN s: STRING ← [32]; [] ← GetEditedString[h, s, IsAtom, TRUE]; RETURN[String.StringToLongNumber[s, 10]] END; GetLongNumber: PUBLIC PROC [ h: TTY.Handle, default: LONG UNSPECIFIED, radix: CARDINAL] RETURNS [n: LONG UNSPECIFIED] = BEGIN s: STRING ← [32]; IF radix = 10 AND LOOPHOLE[default, LONG INTEGER] < 0 THEN {s[0] ← '-; s.length ← 1; default ← -default}; String.AppendLongNumber[s, default, radix]; IF radix = 8 THEN String.AppendChar[s, 'B]; [] ← GetEditedString[h, s, IsAtom, TRUE]; RETURN[String.StringToLongNumber[s, radix]]; END; GetLongOctal: PUBLIC PROC [h: TTY.Handle] RETURNS [n: LONG UNSPECIFIED] = BEGIN s: STRING ← [32]; [] ← GetEditedString[h, s, IsAtom, TRUE]; RETURN[String.StringToLongNumber[s, 8]] END; GetNumber: PUBLIC PROC [h: TTY.Handle, default: UNSPECIFIED, radix: CARDINAL] RETURNS [n: UNSPECIFIED] = BEGIN s: STRING ← [10]; IF radix = 10 AND LOOPHOLE[default, INTEGER] < 0 THEN {default ← -default; s[0] ← '-; s.length ← 1}; String.AppendNumber[s, default, radix]; IF radix = 8 THEN String.AppendChar[s, 'B]; [] ← GetEditedString[h, s, IsAtom, TRUE]; RETURN[String.StringToNumber[s, radix]]; END; GetOctal: PUBLIC PROC [h: TTY.Handle] RETURNS [n: UNSPECIFIED] = BEGIN s: STRING ← [10]; [] ← GetEditedString[h, s, IsAtom, TRUE]; RETURN[String.StringToNumber[s, 8]] END; GetString: PUBLIC PROC [ h: TTY.Handle, s: STRING, t: PROC [c: CHARACTER] RETURNS [yes: BOOLEAN]] = {PutChar[h, GetEditedString[h, s, t, TRUE]]}; LineOverflow: PUBLIC SIGNAL [s: STRING] RETURNS [ns: STRING] = CODE; Rubout: PUBLIC SIGNAL = CODE; GetEditedString: PUBLIC PROC [ h: TTY.Handle, s: STRING, t: PROC [c: CHARACTER] RETURNS [yes: BOOLEAN], newstring: BOOLEAN] RETURNS [c: CHARACTER] = BEGIN c ← GetChar[h]; IF newstring THEN IF c = ESC THEN {PutString[h, s]; c ← GetChar[h]} ELSE s.length ← 0; UNTIL t[c] DO SELECT c FROM DEL => SIGNAL Rubout; ControlA, BS => -- backspace IF s.length > 0 THEN {IF echo THEN PutChar[h, BS]; s.length ← s.length - 1}; ControlW, ControlQ => -- backword BEGIN -- text to be backed up is of the form ...<li><v><ti>, the <v> and <ti> are to -- be removed. state: {ti, v, li} ← ti; FOR i: CARDINAL DECREASING IN [0..s.length) DO SELECT s[i] FROM IN ['A..'Z], IN ['a..'z], IN ['0..'9] => IF state = ti THEN state ← v; ENDCASE => IF state = v THEN state ← li; IF state = li THEN GO TO Done; IF echo THEN PutChar[h, BS]; REPEAT Done => s.length ← i + 1; FINISHED => s.length ← 0; ENDLOOP; END; ControlX => -- back everything BEGIN IF echo THEN FOR i: CARDINAL IN [0..s.length) DO PutChar[h, BS] ENDLOOP; s.length ← 0; END; ControlR => -- refresh-- IF echo THEN {PutChar[h, CR]; PutString[h, s]}; ControlV => -- dont parse next char BEGIN WHILE s.length >= s.maxlength DO s ← SIGNAL LineOverflow[s] ENDLOOP; s[s.length] ← c ← GetChar[h]; s.length ← s.length + 1; IF echo THEN PutChar[h, c] END; ENDCASE => BEGIN WHILE s.length >= s.maxlength DO s ← SIGNAL LineOverflow[s] ENDLOOP; s[s.length] ← c; s.length ← s.length + 1; IF echo THEN PutChar[h, c]; END; c ← GetChar[h]; ENDLOOP; END; IsAtom: PROC [c: CHARACTER] RETURNS [BOOLEAN] = {RETURN[c = SP OR c = CR]}; IsCR: PROC [c: CHARACTER] RETURNS [BOOLEAN] = {RETURN[c = CR]}; PutBackChar: PUBLIC PROC [h: TTY.Handle, c: CHARACTER] = BEGIN P: ENTRY PROC [m: POINTER TO MONITORLOCK] = INLINE BEGIN newout: CARDINAL = IF out=0 THEN LENGTH[buffer]-1 ELSE out-1; IF newout#in THEN {buffer[out ← newout] ← c; BROADCAST charactersAvailable}; END; P[@keyboardLock] END; SetEcho: PUBLIC PROC [h: TTY.Handle, new: BOOLEAN] RETURNS [old: BOOLEAN] = BEGIN SetEntry: ENTRY PROC [m: POINTER TO MONITORLOCK] = INLINE {old ← echo; echo ← new}; SetEntry[@keyboardLock] END; -- output procedures NewLine: PUBLIC PROC [TTY.Handle] RETURNS [BOOLEAN] = {RETURN[charPos=0]}; PutChar: PUBLIC PROC [h: TTY.Handle, c: CHARACTER] = BEGIN PutCharEntry: ENTRY PROC [m: POINTER TO MONITORLOCK] = INLINE { doBlink ← FALSE; ClearThisChar[]; DisplayChar[c]; doBlink ← TRUE; IF typescriptEnabled THEN Streams.PutChar[typescript, c]}; PutCharEntry[@screenLock]; END; PutLongString: PUBLIC PROC [h: TTY.Handle, s: LONG STRING] = {IF s#NIL THEN FOR i: CARDINAL IN [0..s.length) DO PutChar[h, s[i]] ENDLOOP}; PutString: PUBLIC PROC [h: TTY.Handle, s: STRING] = {PutLongString[h, s]}; OutString: PROC [s: STRING] = {PutLongString[LOOPHOLE[0], s]}; PutBlanks: PUBLIC PROC [h: TTY.Handle, n: CARDINAL] = {THROUGH [0..n) DO PutChar[h, ' ] ENDLOOP}; PutDate: PUBLIC PROC [h: TTY.Handle, gmt: Time.Packed, format: TTY.DateFormat] = {Format.Date[gmt, format, OutString]}; PutDecimal: PUBLIC PROC [h: TTY.Handle, n: INTEGER] = {Format.Decimal[n, OutString]}; PutLongDecimal: PUBLIC PROC [h: TTY.Handle, n: LONG INTEGER] = {Format.LongDecimal[n, OutString]}; PutLongNumber: PUBLIC PROC [h: TTY.Handle, n: LONG UNSPECIFIED, format: TTY.NumberFormat] = {Format.LongNumber[n, format, OutString]}; PutLongOctal: PUBLIC PROC [h: TTY.Handle, n: LONG UNSPECIFIED] = {Format.LongOctal[n, OutString]}; PutNumber: PUBLIC PROC [h: TTY.Handle, n: UNSPECIFIED, format: TTY.NumberFormat] = {Format.Number[n, format, OutString]}; PutOctal: PUBLIC PROC [h: TTY.Handle, n: UNSPECIFIED] = {Format.Octal[n, OutString]}; PutLongSubString: PUBLIC PROC [h: TTY.Handle, ss: TTY.LongSubString] = {Format.LongSubStringItem[ss, OutString]}; PutSubString: PUBLIC PROC [h: TTY.Handle, ss: String.SubString] = {Format.SubString[ss, OutString]}; END.