-- XMKDriverImpl.mesa
-- Created By Jeff Weinstein on 29-Mar-87 17:16:06

DIRECTORY
  BitBlt,
  Keys,
  Inline,
  Process,
  Runtime,
  System,
  UserTerminal,
  XDefs,
  XEventQ,
  XMKDriver;
  
XMKDriverImpl:PROGRAM IMPORTS Inline, Process, Runtime, System, UserTerminal, XEventQ EXPORTS XMKDriver =
  BEGIN
  
  --Types
  
  buttonState:TYPE = {down, up, waitingRelease, waitingChord};
  buttons: TYPE = MACHINE DEPENDENT {left(1), middle(2), right(3)};

  -- Keyboard vars
  
  lastKeys, newKeys:Keys.KeyBits ← ALL[up];
  keyboard: LONG POINTER TO Keys.KeyBits = LOOPHOLE[UserTerminal.keyboard];
  keyboardEventsEnabled:BOOLEAN ← FALSE;
  
  -- Mouse vars
  lastMouse, newMouse:UserTerminal.Coordinate;
  mouseConstraintBox:XMKDriver.Box ← [0, 0, UserTerminal.screenWidth-1, UserTerminal.screenHeight-1];
  mouseNoInterestBox:XMKDriver.Box ← [UserTerminal.screenWidth, UserTerminal.screenHeight,UserTerminal.screenWidth, UserTerminal.screenHeight];
  mouseEventsEnabled:BOOLEAN ← FALSE;
   
  -- Button vars
  
  buttonStates:ARRAY buttons OF buttonState ← ALL[up];
  chordFlag:BOOLEAN ← FALSE;
  buttonDownTime:System.Pulses;
  buttonDownCoord:UserTerminal.Coordinate;
  chordCounter:INTEGER ← -1;
  
  -- Misc Vars
  
  bitMask:ARRAY [0..7] OF INTEGER ← [128, 64, 32, 16, 8, 4, 2, 1];
  time:System.Pulses;
  lastEventTime:System.Pulses ← [0];
    
  XMKDriverProc:PUBLIC PROCEDURE =
    BEGIN
    
    Process.Detach[Process.GetCurrent[]];
    Process.SetPriority[Process.priorityForeground];
    
    DO
      --Give everyone else a chance to run...
      UserTerminal.WaitForScanLine[0];
      
      --Read the new keyboard and mouse values, constraining the mouse position
      newKeys ← keyboard↑;
      newMouse ← ConstrainMouse[UserTerminal.mouse↑];
      --Get the time in clock pulses
      time ← System.GetClockPulses[];
      
      --Set the mouse and cursor to the constrained position
      UserTerminal.SetMousePosition[newMouse];
      UserTerminal.SetCursorPosition[newMouse];
      
      --Find the events and queue them up
      IF keyboardEventsEnabled THEN
        processKeys[];
      IF mouseEventsEnabled THEN
        BEGIN
        processMouse[];
        processButtons[];
	END;
      
      --Save the new state
      lastKeys ← newKeys;
      lastMouse ← newMouse;
    ENDLOOP;
    END;
    
  ConstrainMouse:PROCEDURE[inCoord:UserTerminal.Coordinate] RETURNS[outCoord:UserTerminal.Coordinate] =
    BEGIN
    outCoord.x ← SELECT inCoord.x FROM
      		   <mouseConstraintBox.x1      	=> mouseConstraintBox.x1,
		   >mouseConstraintBox.x2 	=> mouseConstraintBox.x2,
		   ENDCASE 			=> inCoord.x;
		   
    outCoord.y ← SELECT inCoord.y FROM
      		   <mouseConstraintBox.y1      	=> mouseConstraintBox.y1,
		   >mouseConstraintBox.y2 	=> mouseConstraintBox.y2,
		   ENDCASE 			=> inCoord.y;
    END;
    
  processMouse:PROCEDURE = 
    BEGIN
    event:XEventQ.EventPtr;
    IF newMouse # lastMouse THEN
      BEGIN
      IF NOT ((newMouse.x IN [mouseNoInterestBox.x1..mouseNoInterestBox.x2]) AND
         (newMouse.y IN [mouseNoInterestBox.y1..mouseNoInterestBox.y2]))THEN
	BEGIN
	lastEventTime ← time;
        event ← XEventQ.NewEvent[];
        event↑ ← [x:newMouse.x, y:newMouse.y, time:time, type:MotionNotify, key:0];
        XEventQ.EnQEvent[event];
	END;
      END;
    END;
    
  processKeys:PROCEDURE =
    BEGIN
    size:INTEGER = SIZE[Keys.KeyBits];
    newPtr:LONG POINTER TO ARRAY[0..size) OF WORD;
    lastPtr:LONG POINTER TO ARRAY[0..size) OF WORD;
    nP:LONG POINTER TO Keys.KeyBits ← @newKeys;
    lp:LONG POINTER TO Keys.KeyBits ← @lastKeys;
    
    
    tmpKeys:ARRAY[0..size) OF WORD;
    tP:LONG POINTER TO ARRAY[0..size) OF WORD ← @tmpKeys;
    tmpPtr:LONG POINTER TO Keys.KeyBits ← LOOPHOLE[tP];
    
    loByte, hiByte:INTEGER;
    
    newPtr ← LOOPHOLE[ nP ];
    lastPtr ← LOOPHOLE [ lp ];
    
    FOR i:INTEGER IN [0..size) 
      DO
      tmpKeys[i] ← Inline.BITXOR[newPtr[i], lastPtr[i]];
      ENDLOOP;
    tmpPtr[Point] ← down;
    tmpPtr[Adjust] ← down;
    FOR i:INTEGER IN [0..size)
      DO
      IF tmpKeys[i] # 0 THEN
        BEGIN
	hiByte ← Inline.HighByte[tmpKeys[i]];
	loByte ← Inline.LowByte[tmpKeys[i]];
	IF hiByte # 0 THEN
	  FOR j:INTEGER IN [0..7]
	    DO
	    IF Inline.BITAND[hiByte, bitMask[j]] # 0 THEN
	      sendKeyEvent[i*16+j];
	    ENDLOOP;
	IF loByte # 0 THEN
	  FOR j:INTEGER IN [0..7]
	    DO
	    IF Inline.BITAND[loByte, bitMask[j]] # 0 THEN
	      BEGIN
	      sendKeyEvent[i*16+8+j];
	      IF ( i*16+8+j = 77 ) AND ( newKeys[LOOPHOLE[77]] = down ) THEN
	        Runtime.Interrupt;
	      END
	    ENDLOOP;
	END;
      ENDLOOP;
    END;
    
  sendKeyEvent:PROCEDURE[key:INTEGER] =
    BEGIN
    event:XEventQ.EventPtr;
    type:XDefs.XEventType ← SELECT newKeys[LOOPHOLE[key]] FROM
    			up => KeyRelease,
			down => KeyPress,
			ENDCASE => ERROR;
    event ← XEventQ.NewEvent[];
    IF key < 16 THEN key ← key + 128;
    event↑ ← [x:newMouse.x, y:newMouse.y, time:time, type:type, key:key];
    XEventQ.EnQEvent[event];
    lastEventTime ← time;
    END;
    
    
  processButtonDown:PROCEDURE[button:buttons] =
    BEGIN
    event:XEventQ.EventPtr;
    otherButton:buttons = SELECT button FROM
    				left => right,
				right => left,
				ENDCASE => ERROR;
    SELECT buttonStates[otherButton] FROM
      waitingChord =>
	BEGIN
	event ← XEventQ.NewEvent[];
	event↑ ← [x:newMouse.x, y:newMouse.y, time:time, type:ButtonPress, key:LOOPHOLE[buttons.middle]];
	XEventQ.EnQEvent[event];
	lastEventTime ← time;
	chordFlag ← TRUE;
	chordCounter ← -1;
	buttonStates[button] ← down;
	buttonStates[otherButton] ← down;
	END;
      down, waitingRelease =>
        BEGIN
	event ← XEventQ.NewEvent[];
	event↑ ← [x:newMouse.x, y:newMouse.y, time:time, type:ButtonPress, key:LOOPHOLE[button]];
	XEventQ.EnQEvent[event];
	lastEventTime ← time;
	buttonStates[button] ← down;
	END;
      up =>
        BEGIN
	buttonStates[button] ← waitingChord;
	buttonDownTime ← time;
	buttonDownCoord ← newMouse;
	chordCounter ← 5;
	END;
      ENDCASE => ERROR;
    END;

  processButtons:PROCEDURE =
    BEGIN
    IF newKeys[Point] # lastKeys[Point] THEN
      BEGIN
      SELECT newKeys[Point] FROM
        up	=> processButtonUp[left];
	down	=> processButtonDown[left];
	ENDCASE	=> ERROR;
      END;
    IF newKeys[Adjust] # lastKeys[Adjust] THEN
      SELECT newKeys[Adjust] FROM
        up	=> processButtonUp[right];
	down	=> processButtonDown[right];
	ENDCASE	=> ERROR;
    SELECT chordCounter FROM
      >0	=> chordCounter ← chordCounter - 1;
      =0	=> BEGIN
      		     button:buttons;
		     event:XEventQ.EventPtr;
		     IF buttonStates[left] = waitingChord THEN
		       button ← left
		     ELSE
		       button ← right;
		     event ← XEventQ.NewEvent[];
		     event↑ ← [x:buttonDownCoord.x, y:buttonDownCoord.y, time:buttonDownTime, type:ButtonPress, key:LOOPHOLE[button]];
		     XEventQ.EnQEvent[event];
		     lastEventTime ← time;
		     chordCounter ← -1;
		     buttonStates[button] ← down;
		   END;
      ENDCASE;
		             
    END;
    
  processButtonUp:PROCEDURE[button:buttons] =
    BEGIN
    event:XEventQ.EventPtr;
    otherButton:buttons = SELECT button FROM
    				left => right,
				right => left,
				ENDCASE => ERROR;
    IF chordFlag THEN
      BEGIN
      event ← XEventQ.NewEvent[];
      event↑ ← [x:newMouse.x, y:newMouse.y, time:time, type:ButtonRelease, key:LOOPHOLE[buttons.middle]];
      XEventQ.EnQEvent[event];
      lastEventTime ← time;
      buttonStates[button] ← up;
      chordFlag ← FALSE;
      buttonStates[otherButton] ← waitingRelease;
      END
    ELSE
      SELECT buttonStates[button] FROM
        waitingRelease =>
		BEGIN
		buttonStates[button] ← up;
		END;
	waitingChord =>
		BEGIN
		event ← XEventQ.NewEvent[];
      		event↑ ← [x:buttonDownCoord.x, y:buttonDownCoord.y, time:buttonDownTime, type:ButtonPress, key:LOOPHOLE[button]];
      		XEventQ.EnQEvent[event];
	        event ← XEventQ.NewEvent[];
      		event↑ ← [x:newMouse.x, y:newMouse.y, time:time, type:ButtonRelease, key:LOOPHOLE[button]];
      		XEventQ.EnQEvent[event];
		lastEventTime ← time;
		buttonStates[button] ← up;
		chordCounter ← -1;
		END;
	down =>
		BEGIN
		event ← XEventQ.NewEvent[];
      		event↑ ← [x:newMouse.x, y:newMouse.y, time:time, type:ButtonRelease, key:LOOPHOLE[button]];
      		XEventQ.EnQEvent[event];
		lastEventTime ← time;
		buttonStates[button] ← up;
		END;
	ENDCASE => ERROR;
    END;
  
 
  setMouseInterest:PUBLIC PROCEDURE[box:XMKDriver.Box] =
    BEGIN
    mouseNoInterestBox ← box;
    END;
    
  setMouseConstraint:PUBLIC PROCEDURE[box:XMKDriver.Box] =
    BEGIN
    mouseConstraintBox ← box;
    END;
    
  setCursor:PUBLIC PROCEDURE[cursor:LONG POINTER TO UserTerminal.CursorArray] =
    BEGIN
    UserTerminal.SetCursorPattern[cursor↑];
    END;
    
  setCursorPosition:PUBLIC PROCEDURE[x,y:CARDINAL] =
    BEGIN
    UserTerminal.SetCursorPosition[[x,y]];
    UserTerminal.SetMousePosition[[x,y]];
    END;
    
  getLastEventTime:PUBLIC PROCEDURE RETURNS[time:LONG CARDINAL] =
    BEGIN
    time ← System.PulsesToMicroseconds[lastEventTime];
    END;
    
  setLastEventTime:PUBLIC PROCEDURE [time:LONG CARDINAL] =
    BEGIN
    lastEventTime ← System.MicrosecondsToPulses[time];
    END;
    
  getTimeInMillis:PUBLIC PROCEDURE RETURNS[time:LONG CARDINAL] =
    BEGIN
    time ← System.PulsesToMicroseconds[System.GetClockPulses[]];
    END;
    
  enableMouseEvents:PUBLIC PROCEDURE [enable:BOOLEAN] =
    BEGIN
    mouseEventsEnabled ← enable;
    END;
    
  enableKeyboardEvents:PUBLIC PROCEDURE[enable:BOOLEAN] =
    BEGIN
    keyboardEventsEnabled ← enable;
    END;
    
  displayInit:PUBLIC PROCEDURE [x,y:LONG POINTER TO CARDINAL] RETURNS[addr:LONG POINTER] =
    BEGIN
    bbt:BitBlt.BBTable;
    [] ← UserTerminal.SetState[disconnected];
    [] ← UserTerminal.SetState[on];
    [] ← UserTerminal.SetBackground[black];
--    [] ← UserTerminal.SetState[off];
    bbt ← UserTerminal.GetBitBltTable[];
    addr ← bbt.dst.word;
    x↑ ← bbt.width;
    y↑ ← bbt.height;
    END;
    
  displayOn:PUBLIC PROCEDURE =
    BEGIN
    [] ← UserTerminal.SetState[on];
    END;
    
  displayOff:PUBLIC PROCEDURE =
    BEGIN
    [] ← UserTerminal.SetState[off];
    END;
    
  blinkDisplay:PUBLIC PROCEDURE =
    BEGIN
    UserTerminal.BlinkDisplay[];
    END;
  
  displayClose:PUBLIC PROCEDURE =
    BEGIN
    [] ← UserTerminal.SetState[disconnected];
    END;
  
  --Mainline Code
  
  END.