-- MockingbirdTTY.mesa
-- Derived from MockingbirdTTY.mesa 
-- last edited by Forrest January 6, 1981  7:28 PM 
-- last edited by Levin on 14-Apr-82 18:02:39
-- last edited by McGregor on April 22, 1982 4:23 pm
-- last gutted by Maxwell on May 20, 1982 10:02 am


-- This module also implements non-basic tty features (see end of code).
-- if these are not needed, String and Format may be left unbound

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 [Detach, SetPriority],
  Runtime USING [GetTableBase],
  SpecialSpace USING [
     MakeGlobalFrameResident, --MakeGlobalFrameSwappable,--
     MakeProcedureResident],
  String USING [
    AppendChar, AppendLongNumber, AppendNumber,
    StringToLongNumber, StringToNumber, SubString],
  TerminalMultiplex USING [InputController, RegisterInputController, SelectTerminal],
  Time USING [Packed],
  TTY USING [DateFormat, Handle, LongSubString, NumberFormat],
  UserTerminal USING [
    Coordinate, cursor, GetBitBltTable, keyboard, mouse, screenHeight,
    screenWidth, SetMousePosition, SetState, SetBackground,
    WaitForScanLine];

MockingbirdTTY: MONITOR
  LOCKS m USING m: POINTER TO MONITORLOCK
  IMPORTS
    BitBlt, Format, Inline, Process, Runtime, SpecialSpace,
    String, TerminalMultiplex, UserTerminal
  EXPORTS TTY =

BEGIN OPEN Ascii, BitBlt;

screenLock: MONITORLOCK;
keyboardLock: MONITORLOCK;

shuttingDown: BOOLEAN ← FALSE;
shutDown: CONDITION ← [timeout: 0];
startingUp: BOOLEAN ← FALSE;
startUp: CONDITION ← [timeout: 0];

noTrack: CARDINAL ← 0;

Initialize: PROCEDURE = {
  IF ~TerminalMultiplex.SelectTerminal[alternate] THEN ERROR;
  TerminalMultiplex.RegisterInputController[InputController];
  InputController[enable]};

InputController: TerminalMultiplex.InputController = {
  SELECT action FROM
    enable => {IF useCount = 0 THEN TurnOnTerminal[]; StartTerminal[]};
    disable => TurnOffTerminal[];
    ENDCASE};
   
TurnOnTerminal: PROCEDURE = {
  TurnOnInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = {
    IF ~font.newStyle OR font.indexed OR font.min NOT IN [0C..177C] 
      OR font.max+1 NOT IN [0C..177C] THEN ERROR;
    WHILE shuttingDown DO WAIT shutDown ENDLOOP;
    SpecialSpace.MakeProcedureResident[ProcessKeyboard];
    SpecialSpace.MakeGlobalFrameResident[MockingbirdTTY];
    CDT ← FALSE;
    echo ← TRUE;
    in ← out ← 0;
    [] ← UserTerminal.SetState[on];
    [] ← UserTerminal.SetBackground[white];
    ClearScreen[];	-- to force init of bbPtr (since may have changed)
    Process.Detach[FORK ProcessKeyboard]};
  TurnOnInternal[@keyboardLock]};

StartTerminal: PROCEDURE = {
  StartInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = {
    startingUp ← TRUE; BROADCAST startUp};
  StartInternal[@keyboardLock]};

TurnOffTerminal: PROCEDURE = {
  TurnOffInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = {
    shuttingDown ← TRUE;
    WHILE shuttingDown DO WAIT shutDown ENDLOOP};
    -- Pilot doesn't do this right: SpecialSpace.MakeGlobalFrameSwappable[MockingbirdTTY];
    -- SpecialSpace.MakeProcedureSwappable[ProcessKeyboard];
    -- [] ← UserTerminal.SetState[disconnected]};
  TurnOffInternal[@keyboardLock]};

EnableCursorTracking: PUBLIC PROCEDURE = {
  EnableInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = {
    IF (noTrack ← noTrack - 1) = 0 THEN doBlink ← TRUE};
  EnableInternal[@keyboardLock]};

DisableCursorTracking: PUBLIC PROCEDURE = {
  DisableInternal: ENTRY PROC [m: POINTER TO MONITORLOCK] = {
    IF (noTrack ← noTrack + 1) ~= 0 THEN doBlink ← FALSE};
  DisableInternal[@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 NOT 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)

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Simple TTY Procedures
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

useCount: CARDINAL ← 0;

Create: PUBLIC PROC [STRING] RETURNS [TTY.Handle] = 
  BEGIN
  useCount ← useCount + 1;
  RETURN[LOOPHOLE[100000B]]
  END;

Destroy: PUBLIC PROC [TTY.Handle] = 
  BEGIN
  useCount ← useCount - 1;
  END;

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Keyboard Implementation
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

CDT: BOOLEAN;
charactersAvailable: CONDITION;
echo: BOOLEAN;

-- EXTERNAL PROCEDURES
CtrlChar: PROC [c: CHARACTER] RETURNS [CHARACTER] = INLINE {RETURN[LOOPHOLE[Inline.BITAND[c, 37B]]]};

-- ENTRY PROCEDURES
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 {
    IF shuttingDown 
       THEN {shuttingDown ← FALSE; BROADCAST shutDown; RETURN[TRUE]}
       ELSE RETURN[FALSE]};
  WaitStart: ENTRY PROC [m: POINTER TO MONITORLOCK] = INLINE {
    WHILE ~startingUp DO WAIT startUp; ENDLOOP};
  Process.SetPriority[6];
  new ← kp↑;
  WHILE TRUE DO
    Brdcst: ENTRY PROC [m: POINTER TO MONITORLOCK] =
      INLINE {BROADCAST charactersAvailable};
    charsSeen: BOOLEAN ← FALSE;
    IF GoAway[@keyboardLock] THEN WaitStart[@keyboardLock];
    startingUp ← FALSE;
    old ← new;
    UserTerminal.WaitForScanLine[0];
    new ← kp↑;
    TrackCursor[@keyboardLock];
    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;

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;

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;

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Keyboard RingBuffer
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
in, out: CARDINAL;
buffer: PACKED ARRAY [0..50) OF CHARACTER;

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
  P: ENTRY PROC [m: POINTER TO MONITORLOCK] =
    INLINE BEGIN
    WHILE in=out DO WAIT charactersAvailable ENDLOOP;
    c ← buffer[out];
    IF (out←out+1) = LENGTH[buffer] THEN out ← 0;
    END;
  P[@keyboardLock]
  END;

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;

ResetUserAbort: PUBLIC PROC =
  BEGIN
  CDResetEntry: ENTRY PROC [m: POINTER TO MONITORLOCK] = INLINE {CDT ← FALSE};
  CDResetEntry[@keyboardLock]
  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;

-- perhaps this should be monitored, but since it is a snapshot...
UserAbort: PUBLIC PROC RETURNS [BOOLEAN] = {RETURN[CDT]};

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- DISPLAY
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BBTable: BBTableSpace;
bbPtr: BBptr = AlignedBBTable[@BBTable];
charPos, line, nCharPos, nLines: CARDINAL;
firstLine, thisLine: Environment.BitAddress;
bitsPerTextLine: CARDINAL;  -- = screenWidth*font.height
doBlink: BOOLEAN ← FALSE;

-- EXTERNAL DISPLAY PROCEDURES
GetBitAddress: PROC [p: LONG POINTER, o: CARDINAL] RETURNS [Environment.BitAddress] =
  {RETURN[[p+o/16, 0, o MOD 16]]};

PutBlanks: PUBLIC PROC [h: TTY.Handle, n: CARDINAL] =
  {THROUGH [0..n) DO PutChar[h, ' ] ENDLOOP};

-- ENTRY DISPLAY PROCEDURES
BlinkCursor: PUBLIC 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;

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

-- INTERNAL DISPLAY PROCEDURES
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;

ClearScreen: INTERNAL PROC =
  BEGIN
  zero: CARDINAL ← 0;
  bbPtr↑ ← UserTerminal.GetBitBltTable[];
  bitsPerTextLine ← bbPtr.dstBpl*height;
  firstLine ← thisLine ← GetBitAddress[
    bbPtr.dst.word, bbPtr.dst.bit+8+8*bbPtr.dstBpl];
  charPos ← 0; line ← 1;
  nCharPos ← (bbPtr.width-16)/font.maxwidth;
  nLines ← (bbPtr.height-16)/height;
  bbPtr.src ← [@zero, 0, 0];
  bbPtr.srcDesc ← [gray[[0, 0, 0, 0]]];
  bbPtr.flags ← [gray: TRUE];
  BITBLT[bbPtr];
  -- set up standard arguments for character painting
  bbPtr.dst ← firstLine;
  --bbPtr.dstBpl set
  --bbPtr.src set when proc called
  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
  SELECT c FROM
    IN (SP..'~] =>
      BEGIN
      IF c NOT IN [font.min..font.max] THEN c ← font.max+1;
      bbPtr.src ← GetBitAddress[bitmap, xInSegment[c]];
      BitBlt.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;
    
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;

GetFont: PROC RETURNS [LONG POINTER] =
  BEGIN
  Gacha12Strike: ARRAY [0..1470B) OF WORD = [
    --   0-- 120000B,     0B,   176B,    10B,  1464B,    13B,     3B,     0B,
    --  10--     61B,     0B,     0B,     0B, 10000B,     0B,  2040B,     0B,
    --  20--      0B,     0B,     0B,     0B,     0B,     0B,     0B,     0B,
    --  30--      0B,     0B,     0B,     0B,     0B,     0B,     0B,     0B,
    --  40--      0B,     0B,     0B,     0B,     0B,     0B,     0B,    74B,
    --  50--     74B,     0B,     0B,     0B,     0B,     0B,     0B,     0B,
    --  60--      0B,     0B,     0B,     0B,     0B,     0B,     0B,  7010B,
    --  70--  70000B,     0B,     0B,    10B, 22012B, 34144B, 14010B,  4020B,
    -- 100--   4000B,     0B,     2B, 36010B, 36074B,  6076B, 36176B, 36074B,
    -- 110--      0B,  2000B, 20074B, 14020B, 74034B, 74176B, 77034B, 41076B,
    -- 120--  17102B, 40306B, 41074B, 76074B, 76074B, 77502B, 40501B, 41101B,
    -- 130--  77040B, 20004B,     0B,   100B,     2B,    14B,   100B,  4004B,
    -- 140--  40070B,     0B,     0B,     0B,     0B,     0B,     0B,     0B,
    -- 150--  10010B,  4000B,     0B,     0B,    10B, 22012B, 52244B, 22010B,
    -- 160--  10010B, 25000B,     0B,     2B, 41030B, 41102B,  6040B, 41002B,
    -- 170--  41102B,     0B,  4000B, 10102B, 22020B, 42042B, 42100B, 40042B,
    -- 200--  41010B,  1104B, 40306B, 61102B, 41102B, 41102B,  4102B, 40511B,
    -- 210--  41101B,  1040B, 20004B,     0B,   100B,     2B,    22B,   100B,
    -- 220--   4004B, 40010B,     0B,     0B,     0B,    20B,     0B,     0B,
    -- 230--      0B, 10010B,  4000B,     0B,     0B,    10B, 22024B, 52250B,
    -- 240--  22010B, 10010B, 16010B,     0B,     4B, 41050B, 41102B, 12040B,
    -- 250--  40002B, 41102B,     0B, 10000B,  4102B, 41050B, 42102B, 41100B,
    -- 260--  40102B, 41010B,  1110B, 40252B, 61102B, 41102B, 41102B,  4102B,
    -- 270--  21111B, 22042B,  2040B, 10004B,  4000B,   100B,     2B,    20B,
    -- 300--    100B,     0B, 40010B,     0B,     0B,     0B,    20B,     0B,
    -- 310--      0B,     0B, 10010B,  4000B, 74000B,     0B,    10B, 22076B,
    -- 320--  50310B, 14010B, 20004B, 25010B,     0B,     4B, 43010B,  1002B,
    -- 330--  12174B, 76004B, 41102B,  4010B, 20000B,  2002B, 47050B, 42100B,
    -- 340--  41100B, 40100B, 41010B,  1120B, 40252B, 51102B, 41102B, 41040B,
    -- 350--   4102B, 21111B, 22042B,  4040B, 10004B, 16020B, 34134B, 36072B,
    -- 360--  36174B, 35134B, 34074B, 42010B,167134B, 36134B, 35054B, 36174B,
    -- 370--  41102B,101104B, 41174B, 10010B,  4062B, 74000B,     0B,    10B,
    -- 400--     24B, 34010B, 10000B, 20004B,  4010B,   176B,    10B, 45010B,
    -- 410--   2034B, 22102B, 41004B, 36102B,  4010B, 40176B,  1004B, 51104B,
    -- 420--  76100B, 41174B, 76100B, 77010B,  1160B, 40252B, 51102B, 41102B,
    -- 430--  76030B,  4102B, 21052B, 14024B,  4040B,  4004B, 25040B, 42142B,
    -- 440--  41106B, 41020B, 43142B,  4004B, 44010B,111142B, 41142B, 43062B,
    -- 450--  41020B, 41102B,101104B, 41004B, 10010B,  4132B, 74000B,     0B,
    -- 460--     10B,    50B, 12020B, 24400B, 20004B,   177B,     0B,    10B,
    -- 470--  51010B,  4002B, 22002B, 41010B, 41102B,     0B, 40000B,  1010B,
    -- 500--  51104B, 41100B, 41100B, 40116B, 41010B,  1110B, 40222B, 45102B,
    -- 510--  76102B, 44004B,  4102B, 12052B, 14024B, 10040B,  4004B,  4177B,
    -- 520--   2102B, 40102B, 41020B, 41102B,  4004B, 50010B,111102B, 41102B,
    -- 530--  41040B, 40020B, 41044B,111050B, 21010B, 60010B,  3114B, 74000B,
    -- 540--  35400B,    10B,   174B, 12023B, 45000B, 20004B,    10B,     0B,
    -- 550--     20B, 61010B, 10002B, 42002B, 41010B, 41076B,     0B, 20176B,
    -- 560--   2010B, 46104B, 41100B, 41100B, 40102B, 41010B,  1104B, 40222B,
    -- 570--  45102B, 40102B, 42002B,  4102B, 12052B, 22010B, 20040B,  2004B,
    -- 600--   4040B, 36102B, 40102B, 77020B, 41102B,  4004B, 70010B,111102B,
    -- 610--  41102B, 41040B, 36020B, 41044B,111020B, 22020B, 10010B,  4000B,
    -- 620--  74000B,     0B,     0B,    50B, 52025B, 42000B, 20004B,    10B,
    -- 630--      0B,    20B, 41010B, 20102B, 77102B, 41020B, 41002B,     0B,
    -- 640--  10000B,  4000B, 40376B, 41102B, 41100B, 40102B, 41010B, 41104B,
    -- 650--  40222B, 43102B, 40102B, 42102B,  4102B, 12024B, 22010B, 20040B,
    -- 660--   2004B,  4020B, 42102B, 40102B, 40020B, 41102B,  4004B, 44010B,
    -- 670-- 111102B, 41102B, 41040B,  1020B, 41044B,111050B, 12040B, 10010B,
    -- 700--   4000B, 74000B,     0B,    10B,   120B, 52045B, 42000B, 10010B,
    -- 710--     10B, 14000B,  4040B, 41010B, 40102B,  2102B, 41020B, 41102B,
    -- 720--   4030B,  4000B, 10010B, 21202B, 41042B, 42100B, 40046B, 41010B,
    -- 730--  41102B, 40202B, 43102B, 40102B, 41102B,  4102B,  4024B, 41010B,
    -- 740--  40040B,  1004B,  4000B, 42142B, 41106B, 41020B, 43102B,  4004B,
    -- 750--  42010B,111102B, 41142B, 43040B, 41022B, 43030B,111104B, 14100B,
    -- 760--  10010B,  4000B, 74000B,     0B,    10B,   120B, 34046B, 35400B,
    -- 770--  10010B,     0B,  4000B,  4040B, 36076B, 77074B,  2074B, 36020B,
    --1000--  36074B,  4010B,  2000B, 20010B, 16202B, 76034B, 74176B, 40032B,
    --1010--  41076B, 36102B, 77202B, 41074B, 40074B, 41074B,  4074B,  4024B,
    --1020--  41010B, 77040B,  1004B,     0B, 35134B, 36072B, 36020B, 35102B,
    --1030--   4004B, 41010B,111102B, 36134B, 35040B, 36014B, 35030B, 66104B,
    --1040--   4176B, 10010B,  4000B, 74000B,     0B,     0B,     0B, 10000B,
    --1050--      0B,  4020B,     0B,  4000B,     0B,     0B,     0B,     0B,
    --1060--      0B,     0B,    10B,     0B,     0B,     0B,     0B,     0B,
    --1070--      0B,     0B,     0B,     0B,     0B,    20B,     0B,     0B,
    --1100--      0B,     0B,    40B,     4B,     0B,     0B,     0B,     0B,
    --1110--   1000B,     4B,     0B,     0B,   100B,  1000B,     0B,     0B,
    --1120--      0B,  4000B, 10010B,  4000B, 74000B,     0B,     0B,     0B,
    --1130--      0B,     0B,  2040B,     0B, 10000B,     0B,     0B,     0B,
    --1140--      0B,     0B,     0B,    20B,     0B,     0B,     0B,     0B,
    --1150--      0B,     0B,     0B,     0B,     0B,     0B,    16B,     0B,
    --1160--      0B,     0B,     0B,    74B,    74B,     0B,     0B,     0B,
    --1170--      0B, 41000B,   104B,     0B,     0B,   100B,  1000B,     0B,
    --1200--      0B,     0B, 50000B,  7010B, 70000B, 74000B,   377B,     0B,
    --1210--      0B,     0B,     0B,     0B,     0B,     0B,     0B,     0B,
    --1220--      0B,     0B,     0B,     0B,     0B,     0B,     0B,     0B,
    --1230--      0B,     0B,     0B,     0B,     0B,     0B,     0B,     0B,
    --1240--      0B,     0B,     0B,     0B,     0B,     0B,     0B,     0B,
    --1250--      0B,     0B, 36000B,    70B,     0B,     0B,   100B,  1000B,
    --1260--      0B,     0B,     0B, 20000B,     0B,     0B, 74000B,     0B,
    --1270--     10B,    10B,    10B,    10B,    10B,    10B,    10B,    10B,
    --1300--     10B,    10B,    10B,    10B,    10B,    10B,    10B,    10B,
    --1310--     10B,    10B,    10B,    10B,    10B,    10B,    10B,    10B,
    --1320--     20B,    20B,    20B,    20B,    20B,    20B,    20B,    20B,
    --1330--     30B,    40B,    50B,    60B,    70B,   100B,   110B,   120B,
    --1340--    130B,   140B,   150B,   160B,   170B,   200B,   210B,   220B,
    --1350--    230B,   240B,   250B,   260B,   270B,   300B,   310B,   320B,
    --1360--    330B,   340B,   350B,   360B,   370B,   400B,   410B,   420B,
    --1370--    430B,   440B,   450B,   460B,   470B,   500B,   510B,   520B,
    --1400--    530B,   540B,   550B,   560B,   570B,   600B,   610B,   620B,
    --1410--    630B,   640B,   650B,   660B,   670B,   700B,   710B,   720B,
    --1420--    730B,   740B,   750B,   760B,   770B,  1000B,  1010B,  1020B,
    --1430--   1020B,  1030B,  1040B,  1050B,  1060B,  1070B,  1100B,  1110B,
    --1440--   1120B,  1130B,  1140B,  1150B,  1160B,  1170B,  1200B,  1210B,
    --1450--   1220B,  1230B,  1240B,  1250B,  1260B,  1270B,  1300B,  1310B,
    --1460--   1320B,  1330B,  1340B,  1350B,  1360B,  1370B,  1400B,  1410B];
  p: LONG POINTER TO ARRAY [0..1470B) OF WORD ← Runtime.GetTableBase[LOOPHOLE[MockingbirdTTY]];
  DO
    IF p[0] = Gacha12Strike[0] AND p↑ = Gacha12Strike THEN RETURN[p];
    p ← p+1;
    ENDLOOP;
  END;

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- non-basic tty features
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  BackingStream: PUBLIC PROC [TTY.Handle] RETURNS [never: UNSPECIFIED] =
    {ERROR};

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

  -- extended output procedures
  OutString: PROC [s: STRING] = {PutLongString[LOOPHOLE[0], s]};

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

Initialize[];

END.