-- MMKeyboard.Mesa  Edited by Sandman on June 20, 1979  9:28 AM
-- Edited by Forrest on July 15, 1980  2:49 PM

DIRECTORY
  InlineDefs USING [BITAND, BITOR, BITSHIFT, BITXOR],
  KeyDefs USING [KeyArray, KeyBits, KeyItem, Keys, updown],
  MMInit,
  ProcessDefs USING [
    CV, Detach, DIW, GetPriority, InterruptLevel, Priority, SetPriority],
  StreamDefs USING [
    KeyboardHandle, KeyBufChars, StreamHandle, StreamObject, StreamErrorCode];

MMKeyboard: MONITOR LOCKS monitor
  IMPORTS InlineDefs, ProcessDefs
  EXPORTS StreamDefs, MMInit SHARES StreamDefs =

BEGIN OPEN InlineDefs, KeyDefs, StreamDefs;

StreamError: PUBLIC SIGNAL [stream: StreamHandle, error: StreamErrorCode] = CODE;

monitor: MONITORLOCK;
wait: CONDITION;
charactersAvailable: CONDITION;

-- variables set by KeyStreams
ks: KeyboardHandle;

cursorTracking: BOOLEAN;
continue: BOOLEAN ← TRUE;

-- The Keyboard part:

-- fixed addresses for keyboard and mouse
Keys: POINTER TO KeyArray;

Coordinate: TYPE = RECORD [x,y: INTEGER];
Mouse: POINTER TO Coordinate;
Cursor: POINTER TO Coordinate;
Xmax: CARDINAL = 606-16;
Ymax: CARDINAL = 808-16;

ns, os: KeyArray;
OldState: POINTER TO KeyArray = @os;
NewState: POINTER TO KeyArray = @ns;

ProcessKeyboard: PUBLIC ENTRY PROCEDURE =
  BEGIN
  bitcount, start: [0..15];
  char: [0..377B];
  entry: KeyItem;
  i: [0..SIZE[KeyArray]);
  interruptState: updown ← up;
  newin: CARDINAL;
  StateWord: WORD;
  stroke: POINTER TO KeyBits = LOOPHOLE[NewState];

  WHILE continue DO
  WAIT wait;
  -- first update the cursor
  IF cursorTracking THEN
    BEGIN
    Mouse.x ← Cursor.x ← MAX[MIN[Xmax,Mouse.x], 0];
    Mouse.y ← Cursor.y ← MAX[MIN[Ymax,Mouse.y], 0];
    END;

  NewState↑ ← Keys↑;

  -- The following code checks for down transitions in the keyboard state
  -- and enters characters in the current keystream buffer
  FOR i IN [0..SIZE[KeyArray]) DO
    IF (StateWord ← BITXOR[OldState[i],NewState[i]]) # 0 THEN
      BEGIN -- found one or more transitions
      start ← 0;
      DO
        FOR bitcount IN [start..15] DO
          IF LOOPHOLE[StateWord,INTEGER]<0 THEN EXIT;
          StateWord ← BITSHIFT[StateWord,1];
          ENDLOOP;
        entry ← KeyTable[i*16 + bitcount];
        IF (char ← entry.NormalCode) # 0
        AND BITAND[OldState[i],BITSHIFT[100000B,-bitcount]] # 0 THEN
          BEGIN
          SELECT updown[down] FROM
            stroke.Ctrl => char ← BITAND[char, 37B];
            stroke.LeftShift, stroke.RightShift => char ← entry.ShiftCode;
            stroke.Lock => IF entry.Letter THEN char ← entry.ShiftCode;
            ENDCASE;
          IF (newin←ks.in+1) = KeyBufChars THEN newin ← 0;
          IF newin # ks.out THEN
            BEGIN
            ks.buffer[ks.in] ← LOOPHOLE[char];
            ks.in ← newin;
            BROADCAST charactersAvailable;
            END;
          END;
        IF (StateWord ← BITSHIFT[StateWord,1])=0 THEN EXIT;
        start ← bitcount+1;
        ENDLOOP;
      END;
    ENDLOOP;
  OldState↑ ← NewState↑;
  ENDLOOP;
  END;

ReadChar: PUBLIC ENTRY PROCEDURE [stream: StreamHandle]
  RETURNS [char: UNSPECIFIED] =
  BEGIN
  char ← 0;
  WITH s:stream SELECT FROM
    Keyboard =>
      DO -- until character typed
        IF s.out # s.in THEN
          BEGIN
          char ← s.buffer[s.out];
          s.out ←
            IF s.out = KeyBufChars-1
              THEN 0
              ELSE s.out+1;
          RETURN
          END;
        WAIT charactersAvailable;
        ENDLOOP;
    ENDCASE;
  RETURN;
  END;

InputBufferEmpty: PUBLIC ENTRY PROCEDURE [stream:StreamHandle]
  RETURNS [empty: BOOLEAN] =
  BEGIN
  empty ← TRUE;
  WITH s:stream SELECT FROM
    Keyboard => IF s.in # s.out THEN empty ← FALSE;
    ENDCASE;
  RETURN
  END;

KS: Keyboard StreamObject ← StreamObject [
  reset: ClearInputBuffer, get: ReadChar, putback: PutBackChar,
  put: WriteChar, endof: InputBufferEmpty, destroy: DestroyKey, link: NIL,
  body: Keyboard[0,0,]];

GetDefaultKey, GetCurrentKey: PUBLIC PROCEDURE RETURNS [KeyboardHandle] =
  BEGIN
  RETURN[@KS];
  END;

OpenKeyStream: PUBLIC ENTRY PROCEDURE [stream:StreamHandle] =
  BEGIN
  WITH s:stream SELECT FROM
    Keyboard => ks ← @s;
    ENDCASE => SIGNAL StreamError[stream,StreamType ! UNWIND => NULL];
  RETURN;
  END;

ClearInputBuffer: ENTRY PROCEDURE [stream:StreamHandle] =
  BEGIN
  WITH s:stream SELECT FROM
    Keyboard => s.in ← s.out ← 0;
    ENDCASE => SIGNAL StreamError[stream,StreamType ! UNWIND => NULL];
  RETURN;
  END;

PutBackChar: ENTRY PROCEDURE [stream:StreamHandle, char:UNSPECIFIED] =
  BEGIN
  newout: CARDINAL;   
  WITH s:stream SELECT FROM
    Keyboard =>
      BEGIN
      newout ← s.out;
      newout ← IF newout = 0 THEN KeyBufChars-1 ELSE newout-1;
      IF newout # s.in THEN
        BEGIN
        s.out ← newout;
        s.buffer[s.out] ← char;
        BROADCAST charactersAvailable;
	END;
      END;
    ENDCASE => SIGNAL StreamError[stream,StreamType !  UNWIND => NULL];
  RETURN;
  END;

WriteChar: PROCEDURE [stream:StreamHandle, char:UNSPECIFIED] =
  BEGIN
  SIGNAL StreamError[stream,StreamAccess];
  RETURN
  END;

CloseKeyStream: PUBLIC ENTRY PROCEDURE [stream:StreamHandle] = BEGIN END;

DestroyKey: --ENTRY-- PROCEDURE [stream:StreamHandle] =
  BEGIN continue ← FALSE; ProcessDefs.CV[KeyboardLevel] ← LOOPHOLE[0] END;

CursorTrack: PUBLIC PROCEDURE [b: BOOLEAN] =
  BEGIN
  cursorTracking ← b;
  RETURN
  END;

KeyItem: TYPE = KeyDefs.KeyItem;

KeyTable: ARRAY [0..80) OF KeyItem = [
  -- MEMORY[177033B]	Index [0..15]
  KeyItem[FALSE, 0, 0],  -- UNUSED
  KeyItem[FALSE, 0, 0],  -- UNUSED
  KeyItem[FALSE, 0, 0],  -- UNUSED
  KeyItem[FALSE, 0, 0],  -- UNUSED
  KeyItem[FALSE, 0, 0],  -- UNUSED
  KeyItem[FALSE, 0, 0],  -- UNUSED
  KeyItem[FALSE, 0, 0],  -- UNUSED
  KeyItem[FALSE, 0, 0],  -- UNUSED
  KeyItem[FALSE, 0, 0],  -- KeyItemset1
  KeyItem[FALSE, 0, 0],  -- KeyItemset2
  KeyItem[FALSE, 0, 0],  -- KeyItemset3
  KeyItem[FALSE, 0, 0],  -- KeyItemset4
  KeyItem[FALSE, 0, 0],  -- KeyItemset5
  KeyItem[FALSE, 0, 0],  -- Red
  KeyItem[FALSE, 0, 0],  -- Blue
  KeyItem[FALSE, 0, 0],  -- Yellow
  -- MEMORY[177034B]	Index [16..31]
  KeyItem[FALSE,  45B,  65B],  -- %,5
  KeyItem[FALSE,  44B,  64B],  -- $,4
  KeyItem[FALSE, 176B,  66B],  -- ~,6
  KeyItem[TRUE,  105B, 145B],  -- E
  KeyItem[FALSE,  46B,  67B],  -- &,7
  KeyItem[TRUE,  104B, 144B],  -- D
  KeyItem[TRUE,  125B, 165B],  -- U
  KeyItem[TRUE,  126B, 166B],  -- V
  KeyItem[FALSE,  51B,  60B],  -- ),0
  KeyItem[TRUE,  113B, 153B],  -- K
  KeyItem[FALSE,  30B,  55B],  -- `,-
  KeyItem[TRUE,  120B, 160B],  -- P
  KeyItem[FALSE,  77B,  57B],  -- ?,/
  KeyItem[FALSE, 174B, 134B],  -- |,\
  KeyItem[FALSE,  12B,  12B],  -- LF
  KeyItem[FALSE,  10B,  10B],  -- BS
  -- MEMORY[177035B]	Index [32..47]
  KeyItem[FALSE,  43B,  63B],  -- #,3
  KeyItem[FALSE, 100B,  62B],  -- @,2
  KeyItem[TRUE,  127B, 167B],  -- W
  KeyItem[TRUE,  121B, 161B],  -- Q
  KeyItem[TRUE,  123B, 163B],  -- S
  KeyItem[TRUE,  101B, 141B],  -- A
  KeyItem[FALSE,  50B,  71B],  -- (,9
  KeyItem[TRUE,  111B, 151B],  -- I
  KeyItem[TRUE,  130B, 170B],  -- X
  KeyItem[TRUE,  117B, 157B],  -- O
  KeyItem[TRUE,  114B, 154B],  -- L
  KeyItem[FALSE,  74B,  54B],  -- <,,
  KeyItem[FALSE,  42B,  47B],  -- ",'
  KeyItem[FALSE, 175B, 135B],  -- },]
  KeyItem[FALSE,   0B,   0B],  -- SPARE2
  KeyItem[FALSE,   0B,   0B],  -- SPARE1
  -- MEMORY[177036B]	Index [48..63]
  KeyItem[FALSE,  41B,  61B],  -- !,1
  KeyItem[FALSE,  33B,  33B],  -- ESCAPE
  KeyItem[FALSE,  11B,  11B],  -- TAB
  KeyItem[TRUE,  106B, 146B],  -- F
  KeyItem[FALSE,    0B,  0B],  -- CONTROL
  KeyItem[TRUE,  103B, 143B],  -- C
  KeyItem[TRUE,  112B, 152B],  -- J
  KeyItem[TRUE,  102B, 142B],  -- B
  KeyItem[TRUE,  132B, 172B],  -- Z
  KeyItem[FALSE,    0B,  0B],  -- SHIFT
  KeyItem[FALSE,  76B,  56B],  -- >,.
  KeyItem[FALSE,  72B,  73B],  -- :,;
  KeyItem[FALSE,  15B,  15B],  -- CR
  KeyItem[FALSE, 136B, 137B],  -- ↑,←
  KeyItem[FALSE, 177B, 177B],  -- DEL
  KeyItem[FALSE,    0B,  0B],  -- NOT USED (FL3)
  -- MEMORY[177037B]	Index [64..79]
  KeyItem[TRUE,  122B, 162B],  -- R
  KeyItem[TRUE,  124B, 164B],  -- T
  KeyItem[TRUE,  107B, 147B],  -- G
  KeyItem[TRUE,  131B, 171B],  -- Y
  KeyItem[TRUE,  110B, 150B],  -- H
  KeyItem[FALSE,  52B,  70B],  -- *,8
  KeyItem[TRUE,  116B, 156B],  -- N
  KeyItem[TRUE,  115B, 155B],  -- M
  KeyItem[FALSE,   0B,   0B],  -- LOCK
  KeyItem[FALSE,  40B,  40B],  -- SPACE
  KeyItem[FALSE, 173B, 133B],  -- {,[
  KeyItem[FALSE,  53B,  75B],  -- +,=
  KeyItem[FALSE,   0B,   0B],  -- SHIFT
  KeyItem[FALSE,   0B,   0B],  -- SPARE3
  KeyItem[FALSE,   0B,   0B],  -- NOT USED (FL4)
  KeyItem[FALSE,   0B,   0B]]; -- NOT USED (FR5)


-- The Process (and initialization) part

KeyboardLevel: ProcessDefs.InterruptLevel = 7;

Init: PROCEDURE =
  BEGIN OPEN ProcessDefs;
  KeyboardBit: WORD = InlineDefs.BITSHIFT[1,KeyboardLevel];
  save: Priority = GetPriority[];
  KeyboardPriority: Priority = 6;
  OldState↑ ← Keys↑;
  CursorTrack[TRUE];
  Keys ← LOOPHOLE[KeyDefs.Keys];
  ks ← @KS;
  Mouse ← LOOPHOLE[424B];
  Cursor ← LOOPHOLE[426B];
  SetPriority[KeyboardPriority];
  Detach[FORK ProcessKeyboard];
  SetPriority[save];
  CV[KeyboardLevel] ← @wait;
  DIW↑ ← InlineDefs.BITOR[DIW↑,KeyboardBit];
  END;

Init[];

END.