Logo Search packages:      
Sourcecode: hatari version File versions  Download package

ikbd.c

/*
  Hatari - ikbd.c

  This file is distributed under the GNU Public License, version 2 or at
  your option any later version. Read the file gpl.txt for details.

  The keyboard processor(6301) handles any joystick/mouse task and sends bytes to the ACIA(6850)
  When a byte arrives in the ACIA (which takes just over 7000 CPU cycles) an MFP interrupt is flagged.
  The CPU can now read the byte from the ACIA by reading address $fffc02.
  An annoying bug can be found in Dungeon Master. This, when run, turns off the mouse input - but of course
  then you are unable to play the game! A bodge flag has been added so we need to be told twice to turn off
  the mouse input(although I think this causes errors in other games...)
  Also, the ACIA_CYCLES time is very important for games such as Carrier Command. The keyboard handler
  in this game has a bug in it, which corrupts its own registers if more than one byte is queued up. This
  value was found by a test program on a real ST and has correctly emulated the behaviour.
*/
char IKBD_rcsid[] = "Hatari $Id: ikbd.c,v 1.23 2005/04/05 14:41:26 thothy Exp $";

#include <time.h>

#include "main.h"
#include "gemdos.h"
#include "ikbd.h"
#include "int.h"
#include "ioMem.h"
#include "joy.h"
#include "m68000.h"
#include "memorySnapShot.h"
#include "mfp.h"
#include "misc.h"
#include "screen.h"
#include "video.h"


#define DBL_CLICK_HISTORY  0x07     /* Number of frames since last click to see if need to send one or two clicks */
#define ACIA_CYCLES    7200         /* Cycles (Multiple of 4) between sent to ACIA from keyboard along serial line - 500Hz/64, (approx' 6920-7200cycles from test program) */

#define IKBD_RESET_CYCLES  400000   /* Cycles after RESET before complete */
#define IKBD_INIT_RESET_CYCLES 3000000  /* Cycles after a cold reset before IKBD starts */

#define ABS_X_ONRESET    0          /* Initial XY for absolute mouse position after RESET command */
#define ABS_Y_ONRESET    0
#define ABS_MAX_X_ONRESET  320      /* Initial absolute mouse limits after RESET command */
#define ABS_MAY_Y_ONRESET  200      /* These values are never actually used as user MUST call 'IKBD_Cmd_AbsMouseMode' before ever using them */

#define ABS_PREVBUTTONS  (0x02|0x8) /* Don't report any buttons up on first call to 'IKBD_Cmd_ReadAbsMousePos' */


/* Keyboard state */
KEYBOARD Keyboard;

/* Keyboard processor */
KEYBOARD_PROCESSOR KeyboardProcessor;   /* Keyboard processor details */
BOOL DoubleClickPattern[] = {           /* Pattern of mouse button up/down in ST frames (run off a double-click message) */
 BUTTON_MOUSE,BUTTON_MOUSE,BUTTON_MOUSE,BUTTON_MOUSE,
 0,0,0,0,BUTTON_MOUSE,BUTTON_MOUSE,BUTTON_MOUSE,BUTTON_MOUSE };

BOOL bMouseDisabled, bJoystickDisabled;
BOOL bDuringResetCriticalTime, bBothMouseAndJoy;

/* ACIA */
static Uint8 ACIAControlRegister = 0;
static Uint8 ACIAStatusRegister = ACIA_STATUS_REGISTER__TX_BUFFER_EMPTY;  /* Pass when read 0xfffc00 */
static Uint8 ACIAByte;                      /* When a byte has arrived at the ACIA (from the keyboard) it is stored here */
static BOOL bByteInTransitToACIA = FALSE;   /* Is a byte being sent to the ACIA from the keyboard? */

/*
  6850 ACIA (Asynchronous Communications Inferface Apdater)
  Page 41, ST Internals. Also ST Update Magazine, February 1989 (I glad I kept that!)

  Pins:-
    Vss
    RX DATA Receive Data
    RX CLK Receive Clock
    TX CLK Transmitter Clock
    RTS Request To Send
    TX DATA Transmitter Data
    IRQ Interrupt Request
    CS 0,1,2 Chip Select
    RS Register Select
    Vcc Voltage
    R/W Read/Write
    E Enable
    D0-D7 Data
    DCD Data Carrier Detect
    CTS Clear To Send

  Registers:-
    0xfffc00 Keyboard ACIA Control (write)/Status(read)
    0xfffc02 Keyboard ACIA Data
    0xfffc04 MIDI ACIA Control (write)/Status(read)
    0xfffc06 MIDI ACIA Data

  Control Register (0xfffc00 write):-
    Bits 0,1 - These bits determine by which factor the transmitter and receiver
      clock will be divided. These bits also are joined with a master reset
      function. The 6850 has no separate reset line, so it must be
      accomplished though software.
        0 0    RXCLK/TXCLK without division
        0 1    RXCLK/TXCLK by 16 (MIDI)
        1 0    RXCLK/TXCLK by 64 (Keyboard)
        1 1    Master RESET
    Bits 2,3,4 - These so-called Word Select bits tell whether 7 or 8 data-bits are
      involved; whether 1 or 2 stop-bits are transferred; and the type of parity
    Bits 5,6 - These Transmitter Control bits set the RTS output pin, and allow or prevent
      an interrupt through the ACIA when the send register is emptied. Also, BREAK signals
      can be sent over the serial output by this line. A BREAK signal is nothing more than
      a long seqence of null bits
        0 0    RTS low, transmitter IRQ disabled
        0 1    RTS low, transmitter IRQ enabled
        1 0    RTS high, transmitter IRQ disabled
        1 1    RTS low, transmitter IRQ disabled, BREAK sent
    Bit 7 - The Receiver Interrupt Enable bit determines whether the receiver interrupt
      will be on. An interrupt can be caused by the DCD line chaning from low to high, or
      by the receiver data buffer filling. Besides that, an interrupt can occur from an
      OVERRUN ( a received character isn't properly read from the processior).
        0 Interrupt disabled
        1 Interrupt enabled

  Status Register (0xfffc00 read):-
    Bit 0 - When this bit is high, the RX data register is full. The byte must be read
      before a new character is received (otherwise an OVERRUN happens)
    Bit 1 - This bit reflects the status of the TX data buffer. An empty register
      set the bit.
    Bit 2 - A low-high change in pin DCD sets bit 2. If the receiver interrupt is allowable, the IRQ
      is cancelled. The bit is cleared when the status register and the receiver register are
      read. This also cancels the IRQ. Bit 2 register remains highis the signal on the DCD pin
      is still high; Bit 2 register low if DCD becomes low.
    Bit 3 - This line shows the status of CTS. This signal cannot be altered by a mater reset,
      or by ACIA programming.
    Bit 4 - Shows 'Frame Errors'. Frame errors are when no stop-bit is recognized in receiver
      switching. It can be set with every new character.
    Bit 5 - This bit display the previously mentioned OVERRUN condition. Bit 5 is reset when the
      RX buffer is read.
    Bit 6 - This bit recognizes whether the parity of a received character is correct. The bit is
      set on an error.
    Bit 7 - This signals the state of the IRQ pins; this bit make it possible to switch several
      IRQ lines on one interrupt input. In cases where an interrupt is program-generated, bit 7
      can tell which IC cut off the interrupt.

  ST ACIA:-
    Note CTS,DCD and RTS are not connected! Phew!
    The keyboard ACIA are address 0xfffc000 and 0xfffc02.
    Default parameters are :- 8-bit word, 1 stopbit, no parity, 77812.5 baud; 500KHz/64 (keyboard clock div)
    Default MIDI parameters are are above but :- 31250 baud; 500KHz/16 (MIDI clock div)
*/

/* List of possible keyboard commands, others are seen as NOPs by keyboard processor */
static IKBD_COMMAND_PARAMS KeyboardCommands[] = {
  /* Known messages, counts include command byte */
  { 0x80,2,  IKBD_Cmd_Reset },
  { 0x07,2,  IKBD_Cmd_MouseAction },
  { 0x08,1,  IKBD_Cmd_RelMouseMode },
  { 0x09,5,  IKBD_Cmd_AbsMouseMode },
  { 0x0A,3,  IKBD_Cmd_MouseCursorKeycodes },
  { 0x0B,3,  IKBD_Cmd_SetMouseThreshold },
  { 0x0C,3,  IKBD_Cmd_SetMouseScale },
  { 0x0D,1,  IKBD_Cmd_ReadAbsMousePos },
  { 0x0E,6,  IKBD_Cmd_SetInternalMousePos },
  { 0x0F,1,  IKBD_Cmd_SetYAxisDown },
  { 0x10,1,  IKBD_Cmd_SetYAxisUp },
  { 0x11,1,  IKBD_Cmd_StartKeyboardTransfer },
  { 0x12,1,  IKBD_Cmd_TurnMouseOff },
  { 0x13,1,  IKBD_Cmd_StopKeyboardTransfer },
  { 0x14,1,  IKBD_Cmd_ReturnJoystickAuto },
  { 0x15,1,  IKBD_Cmd_StopJoystick },
  { 0x16,1,  IKBD_Cmd_ReturnJoystick },
  { 0x17,2,  IKBD_Cmd_SetJoystickDuration },
  { 0x18,1,  IKBD_Cmd_SetJoystickFireDuration },
  { 0x19,7,  IKBD_Cmd_SetCursorForJoystick },
  { 0x1A,1,  IKBD_Cmd_DisableJoysticks },
  { 0x1B,7,  IKBD_Cmd_SetClock },
  { 0x1C,1,  IKBD_Cmd_ReadClock },
  { 0x20,4,  IKBD_Cmd_LoadMemory },
  { 0x21,3,  IKBD_Cmd_ReadMemory },
  { 0x22,3,  IKBD_Cmd_Execute },

  /* Report message (top bit set) - ignore for now... */
  { 0x88,1,  IKBD_Cmd_NullFunction },
  { 0x89,1,  IKBD_Cmd_NullFunction },
  { 0x8A,1,  IKBD_Cmd_NullFunction },
  { 0x8B,1,  IKBD_Cmd_NullFunction },
  { 0x8C,1,  IKBD_Cmd_NullFunction },
  { 0x8F,1,  IKBD_Cmd_NullFunction },
  { 0x90,1,  IKBD_Cmd_NullFunction },
  { 0x92,1,  IKBD_Cmd_NullFunction },
  { 0x94,1,  IKBD_Cmd_NullFunction },
  { 0x95,1,  IKBD_Cmd_NullFunction },
  { 0x99,1,  IKBD_Cmd_NullFunction },

  { 0xFF,0,  NULL }  /* Term */
};


/*-----------------------------------------------------------------------*/
/*
  Reset the IKBD processor
*/
void IKBD_Reset(BOOL bCold)
{
  /* Reset internal keyboard processor details */
  if (bCold)
  {
    KeyboardProcessor.bReset = FALSE;
    if (Int_InterruptActive(INTERRUPT_IKBD_RESETTIMER))
      Int_RemovePendingInterrupt(INTERRUPT_IKBD_RESETTIMER);
  }

  KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL;
  KeyboardProcessor.JoystickMode = AUTOMODE_JOYSTICK;

  KeyboardProcessor.Abs.X = ABS_X_ONRESET;  KeyboardProcessor.Abs.Y = ABS_Y_ONRESET;
  KeyboardProcessor.Abs.MaxX = ABS_MAX_X_ONRESET;  KeyboardProcessor.Abs.MaxY = ABS_MAY_Y_ONRESET;
  KeyboardProcessor.Abs.PrevReadAbsMouseButtons = ABS_PREVBUTTONS;

  KeyboardProcessor.Mouse.DeltaX = KeyboardProcessor.Mouse.DeltaY = 0;
  KeyboardProcessor.Mouse.XScale = KeyboardProcessor.Mouse.YScale = 0;
  KeyboardProcessor.Mouse.XThreshold = KeyboardProcessor.Mouse.YThreshold = 1;
  KeyboardProcessor.Mouse.YAxis = 1;          /* Y origin at top */
  KeyboardProcessor.Mouse.Action = 0;

  KeyboardProcessor.Joy.PrevJoyData[0] = KeyboardProcessor.Joy.PrevJoyData[1] = 0;

  /* Reset our ACIA status */
  bByteInTransitToACIA = FALSE;
  ACIAControlRegister = 0;
  ACIAStatusRegister = ACIA_STATUS_REGISTER__TX_BUFFER_EMPTY;
  /* And our keyboard states and clear key state table */
  Keyboard.BufferHead = Keyboard.BufferTail = 0;
  Keyboard.nBytesInInputBuffer = 0;
  memset(Keyboard.KeyStates, 0, sizeof(Keyboard.KeyStates));
  Keyboard.bLButtonDown = BUTTON_NULL;
  Keyboard.bRButtonDown = BUTTON_NULL;
  Keyboard.bOldLButtonDown = Keyboard.bOldRButtonDown = BUTTON_NULL;
  Keyboard.LButtonDblClk = Keyboard.RButtonDblClk = 0;
  Keyboard.LButtonHistory = Keyboard.RButtonHistory = 0;

  /* Store BOOL for when disable mouse or joystick */
  bMouseDisabled = bJoystickDisabled = FALSE;
  /* do emulate hardware 'quirk' where if disable both with 'x' time of a RESET
   * command they are ignored! */
  bDuringResetCriticalTime = bBothMouseAndJoy = FALSE;
}


/*-----------------------------------------------------------------------*/
/*
  Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
*/
void IKBD_MemorySnapShot_Capture(BOOL bSave)
{
  /* Save/Restore details */
  MemorySnapShot_Store(&Keyboard,sizeof(Keyboard));
  MemorySnapShot_Store(&KeyboardProcessor,sizeof(KeyboardProcessor));
  MemorySnapShot_Store(&ACIAControlRegister,sizeof(ACIAControlRegister));
  MemorySnapShot_Store(&ACIAStatusRegister,sizeof(ACIAStatusRegister));
  MemorySnapShot_Store(&ACIAByte,sizeof(ACIAByte));
  MemorySnapShot_Store(&bByteInTransitToACIA,sizeof(bByteInTransitToACIA));
}


/*-----------------------------------------------------------------------*/
/*
  Calculate out 'delta' that mouse has moved by each frame, and add this to our internal keyboard position
*/
static void IKBD_UpdateInternalMousePosition(void)
{

  KeyboardProcessor.Mouse.DeltaX = KeyboardProcessor.Mouse.dx;
  KeyboardProcessor.Mouse.DeltaY = KeyboardProcessor.Mouse.dy;
  KeyboardProcessor.Mouse.dx = 0;
  KeyboardProcessor.Mouse.dy = 0;

  /* Update internal mouse coords - Y axis moves according to YAxis setting(up/down) */
  /* Limit to Max X/Y(inclusive) */
  KeyboardProcessor.Abs.X += KeyboardProcessor.Mouse.DeltaX;
  if (KeyboardProcessor.Abs.X<0)
    KeyboardProcessor.Abs.X = 0;
  if (KeyboardProcessor.Abs.X>KeyboardProcessor.Abs.MaxX)
    KeyboardProcessor.Abs.X = KeyboardProcessor.Abs.MaxX;

  KeyboardProcessor.Abs.Y += KeyboardProcessor.Mouse.DeltaY*KeyboardProcessor.Mouse.YAxis;  /* Needed '+' for Falcon... */
  if (KeyboardProcessor.Abs.Y<0)
    KeyboardProcessor.Abs.Y = 0;
  if (KeyboardProcessor.Abs.Y>KeyboardProcessor.Abs.MaxY)
    KeyboardProcessor.Abs.Y = KeyboardProcessor.Abs.MaxY;

}


/*-----------------------------------------------------------------------*/
/*
  When running in maximum speed the emulation will not see 'double-clicks' of the mouse
  as it is running so fast. In this case, we check for a double-click and pass
  the 'up'/'down' messages in emulation time to simulate the double-click effect!
*/
static void IKBD_CheckForDoubleClicks(void)
{
  /*
    Things get a little complicated when running max speed as a normal
    double-click is a load of 1's, followed by 0's, 1's and 0's - But the ST does
    not see this as a double click as the space in 'ST' time between changes is so great.
    Now, when we see a real double-click in max speed we actually send the down/up/down/up
    in ST time. To get this correct(and not send three clicks) we look in a history buffer
    and start at an index which gives the correct number of clicks! Phew!
  */

  /* Handle double clicks!!! */
  if (Keyboard.LButtonDblClk) {
    if (Keyboard.LButtonDblClk==1) {          /* First pressed! */
      if ((Keyboard.LButtonHistory&0x3f)==0)  /* If not pressed button in long time do full dbl-click pattern */
        Keyboard.LButtonDblClk = 1;
      else {
        Keyboard.LButtonDblClk = 4;           /* Otherwise, check where to begin to give 1111000011110000 pattern */
        if ((Keyboard.LButtonHistory&0x7)==0)
          Keyboard.LButtonDblClk = 8;
        else if ((Keyboard.LButtonHistory&0x3)==0)
          Keyboard.LButtonDblClk = 7;
        else if ((Keyboard.LButtonHistory&0x1)==0)
          Keyboard.LButtonDblClk = 6;
      }
    }

    Keyboard.bLButtonDown = DoubleClickPattern[Keyboard.LButtonDblClk];
    Keyboard.LButtonDblClk++;
    if (Keyboard.LButtonDblClk>=13) {         /* Check for end of sequence */
      Keyboard.LButtonDblClk = 0;
      Keyboard.bLButtonDown = FALSE;
    }
  }
  if (Keyboard.RButtonDblClk) {
    if (Keyboard.RButtonDblClk==1) {          /* First pressed! */
      if ((Keyboard.RButtonHistory&0x3f)==0)  /* If not pressed button in long time do full dbl-click pattern */
        Keyboard.RButtonDblClk = 1;
      else {
        Keyboard.RButtonDblClk = 4;           /* Otherwise, check where to begin to give 1111000011110000 pattern */
        if ((Keyboard.RButtonHistory&0x7)==0)
          Keyboard.RButtonDblClk = 8;
        else if ((Keyboard.RButtonHistory&0x3)==0)
          Keyboard.RButtonDblClk = 7;
        else if ((Keyboard.RButtonHistory&0x1)==0)
          Keyboard.RButtonDblClk = 6;
      }
    }

    Keyboard.bRButtonDown = DoubleClickPattern[Keyboard.RButtonDblClk];
    Keyboard.RButtonDblClk++;
    if (Keyboard.RButtonDblClk>=13) {         /* Check for end of sequence */
      Keyboard.RButtonDblClk = 0;
      Keyboard.bRButtonDown = FALSE;
    }
  }

  /* Store presses into history */
  Keyboard.LButtonHistory = (Keyboard.LButtonHistory<<1);
  if (Keyboard.bLButtonDown)
    Keyboard.LButtonHistory |= 0x1;
  Keyboard.RButtonHistory = (Keyboard.RButtonHistory<<1);
  if (Keyboard.bRButtonDown)
    Keyboard.RButtonHistory |= 0x1;
}


/*-----------------------------------------------------------------------*/
/*
  Convert button to BOOL value
*/
static BOOL IKBD_ButtonBool(int Button)
{
  /* Button pressed? */
  if (Button)
    return(TRUE);
  return(FALSE);
}


/*-----------------------------------------------------------------------*/
/*
  Return TRUE if buttons match, use this as buttons are a mask and not BOOLean
*/
static BOOL IKBD_ButtonsEqual(int Button1,int Button2)
{
  /* Return BOOL compare */
  return(IKBD_ButtonBool(Button1)==IKBD_ButtonBool(Button2));
}


/*-----------------------------------------------------------------------*/
/*
  According to if the mouse if enabled or not the joystick 1 fire button/right mouse button
  will become the same button, ie pressing one will also press the other and vise-versa
*/
static void IKBD_DuplicateMouseFireButtons(void)
{
  /* Don't duplicate fire button when program tries to use both! */
  if(bBothMouseAndJoy)  return;

  /* If mouse is off then joystick fire button goes to joystick */
  if (KeyboardProcessor.MouseMode==AUTOMODE_OFF)
  {
    /* If pressed right mouse button, should go to joystick 1 */
    if (Keyboard.bRButtonDown&BUTTON_MOUSE)
      KeyboardProcessor.Joy.JoyData[1] |= 0x80;
    /* And left mouse button, should go to joystick 0 */
    if (Keyboard.bLButtonDown&BUTTON_MOUSE)
      KeyboardProcessor.Joy.JoyData[0] |= 0x80;
  }
  /* If mouse if on, joystick 1 fire button goes to mouse not to the joystick */
  else
  {
    /* Is fire button pressed? */
    if (KeyboardProcessor.Joy.JoyData[1]&0x80)
    {
      KeyboardProcessor.Joy.JoyData[1] &= 0x7f;  /* Clear fire button bit */
      Keyboard.bRButtonDown |= BUTTON_JOYSTICK;  /* Mimick on mouse right button */
    }
    else
      Keyboard.bRButtonDown &= ~BUTTON_JOYSTICK;
  }
}


/*-----------------------------------------------------------------------*/
/*
  Send 'relative' mouse position
*/
static void IKBD_SendRelMousePacket(void)
{
  int ByteRelX,ByteRelY;
  unsigned char Header;

  if ( (KeyboardProcessor.Mouse.DeltaX!=0) || (KeyboardProcessor.Mouse.DeltaY!=0)
   || (!IKBD_ButtonsEqual(Keyboard.bOldLButtonDown,Keyboard.bLButtonDown)) || (!IKBD_ButtonsEqual(Keyboard.bOldRButtonDown,Keyboard.bRButtonDown)) ) {
    /* Send packet to keyboard process */
    while(TRUE) {
      ByteRelX = KeyboardProcessor.Mouse.DeltaX;
      if (ByteRelX>127)  ByteRelX = 127;
      if (ByteRelX<-128)  ByteRelX = -128;
      ByteRelY = KeyboardProcessor.Mouse.DeltaY;
      if (ByteRelY>127)  ByteRelY = 127;
      if (ByteRelY<-128)  ByteRelY = -128;

      Header = 0xf8;
      if (Keyboard.bLButtonDown)
        Header |= 0x02;
      if (Keyboard.bRButtonDown)
        Header |= 0x01;
      IKBD_AddKeyToKeyboardBuffer(Header);
      IKBD_AddKeyToKeyboardBuffer(ByteRelX);
      IKBD_AddKeyToKeyboardBuffer(ByteRelY*KeyboardProcessor.Mouse.YAxis);

      KeyboardProcessor.Mouse.DeltaX -= ByteRelX;
      KeyboardProcessor.Mouse.DeltaY -= ByteRelY;

      if ( (KeyboardProcessor.Mouse.DeltaX==0) && (KeyboardProcessor.Mouse.DeltaY==0) )
        break;

      /* Store buttons for next time around */
      Keyboard.bOldLButtonDown = Keyboard.bLButtonDown;
      Keyboard.bOldRButtonDown = Keyboard.bRButtonDown;
    }
  }
}


/*-----------------------------------------------------------------------*/
/*
  Send 'joysticks' bit masks
*/
static void IKBD_SelAutoJoysticks(void)
{
  unsigned char JoyData;

  /* Did joystick 0/mouse change? */
  JoyData = KeyboardProcessor.Joy.JoyData[0];
  if (JoyData!=KeyboardProcessor.Joy.PrevJoyData[0]) {
    IKBD_AddKeyToKeyboardBuffer(0xFE);    /* Joystick 0/Mouse */
    IKBD_AddKeyToKeyboardBuffer(JoyData);

    KeyboardProcessor.Joy.PrevJoyData[0] = JoyData;
  }

  /* Did joystick 1(default) change? */
  JoyData = KeyboardProcessor.Joy.JoyData[1];
  if (JoyData!=KeyboardProcessor.Joy.PrevJoyData[1]) {
    IKBD_AddKeyToKeyboardBuffer(0xFF);    /* Joystick 1 */
    IKBD_AddKeyToKeyboardBuffer(JoyData);

    KeyboardProcessor.Joy.PrevJoyData[1] = JoyData;
  }
}

/*-----------------------------------------------------------------------*/
/*
  Send packets which are generated from the mouse action settings
  If relative mode is on, still generate these packets
*/
static void IKBD_SendOnMouseAction(void)
{
  BOOL bReportPosition = FALSE;

  /* Report buttons as keys? Do in relative/absolute mode */
  if (KeyboardProcessor.Mouse.Action&0x4) {
    /* Left button? */
    if ( (IKBD_ButtonBool(Keyboard.bLButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldLButtonDown))) )
      IKBD_AddKeyToKeyboardBuffer(0x74);    /* Left */
    else if ( (IKBD_ButtonBool(Keyboard.bOldLButtonDown) && (!IKBD_ButtonBool(Keyboard.bLButtonDown))) )
      IKBD_AddKeyToKeyboardBuffer(0x74|0x80);
    /* Right button? */
    if ( (IKBD_ButtonBool(Keyboard.bRButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldRButtonDown))) )
      IKBD_AddKeyToKeyboardBuffer(0x75);    /* Right */
    else if ( (IKBD_ButtonBool(Keyboard.bOldRButtonDown) && (!IKBD_ButtonBool(Keyboard.bRButtonDown))) )
      IKBD_AddKeyToKeyboardBuffer(0x75|0x80);

    /* Ignore bottom two bits, so return now */
    return;
  }

  /* Check MouseAction - report position on press/release */
  /* MUST do this before update relative positions as buttons get reset */
  if (KeyboardProcessor.Mouse.Action&0x3) {
    /* Check for 'press'? */
    if (KeyboardProcessor.Mouse.Action&0x1) {
      /* Did 'press' mouse buttons? */
      if ( (IKBD_ButtonBool(Keyboard.bLButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldLButtonDown))) ) {
        bReportPosition = TRUE;
        KeyboardProcessor.Abs.PrevReadAbsMouseButtons &= ~0x04;
        KeyboardProcessor.Abs.PrevReadAbsMouseButtons |= 0x02;
      }
      if ( (IKBD_ButtonBool(Keyboard.bRButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldRButtonDown))) ) {
        bReportPosition = TRUE;
        KeyboardProcessor.Abs.PrevReadAbsMouseButtons &= ~0x01;
        KeyboardProcessor.Abs.PrevReadAbsMouseButtons |= 0x08;
      }
    }
    /* Check for 'release'? */
    if (KeyboardProcessor.Mouse.Action&0x2) {
      /* Did 'release' mouse buttons? */
      if ( (IKBD_ButtonBool(Keyboard.bOldLButtonDown) && (!IKBD_ButtonBool(Keyboard.bLButtonDown))) ) {
        bReportPosition = TRUE;
        KeyboardProcessor.Abs.PrevReadAbsMouseButtons &= ~0x08;
        KeyboardProcessor.Abs.PrevReadAbsMouseButtons |= 0x01;
      }
      if ( (IKBD_ButtonBool(Keyboard.bOldRButtonDown) && (!IKBD_ButtonBool(Keyboard.bRButtonDown))) ) {
        bReportPosition = TRUE;
        KeyboardProcessor.Abs.PrevReadAbsMouseButtons &= ~0x02;
        KeyboardProcessor.Abs.PrevReadAbsMouseButtons |= 0x04;
      }
    }

    /* Do need to report? */
    if (bReportPosition) {
      /* Only report if mouse in absolute mode */
      if (KeyboardProcessor.MouseMode==AUTOMODE_MOUSEABS) {
#ifdef DEBUG_OUTPUT_IKBD
        Debug_IKBD("Report ABS on MouseAction\n");
#endif
        IKBD_Cmd_ReadAbsMousePos();
      }
    }
  }
}


/*-----------------------------------------------------------------------*/
/*
  Send mouse movements as cursor keys
*/
static void IKBD_SendCursorMousePacket(void)
{
  int i=0;

  /* Run each 'Delta' as cursor presses */
  /* Limit to '10' loops as host mouse cursor might have a VERY poor quality. */
  /* Eg, a single mouse movement on and ST gives delta's of '1', mostly, */
  /* but host mouse might go as high as 20+! */
  while ( (i<10) && ((KeyboardProcessor.Mouse.DeltaX!=0) || (KeyboardProcessor.Mouse.DeltaY!=0)
   || (!IKBD_ButtonsEqual(Keyboard.bOldLButtonDown,Keyboard.bLButtonDown)) || (!IKBD_ButtonsEqual(Keyboard.bOldRButtonDown,Keyboard.bRButtonDown))) ) {
    /* Left? */
    if (KeyboardProcessor.Mouse.DeltaX<0) {
      IKBD_AddKeyToKeyboardBuffer(75);    /* Left cursor */
      IKBD_AddKeyToKeyboardBuffer(75|0x80);
      KeyboardProcessor.Mouse.DeltaX++;
    }
    /* Right? */
    if (KeyboardProcessor.Mouse.DeltaX>0) {
      IKBD_AddKeyToKeyboardBuffer(77);    /* Right cursor */
      IKBD_AddKeyToKeyboardBuffer(77|0x80);
      KeyboardProcessor.Mouse.DeltaX--;
    }
    /* Up? */
    if (KeyboardProcessor.Mouse.DeltaY<0) {
      IKBD_AddKeyToKeyboardBuffer(72);    /* Up cursor */
      IKBD_AddKeyToKeyboardBuffer(72|0x80);
      KeyboardProcessor.Mouse.DeltaY++;
    }
    /* Down? */
    if (KeyboardProcessor.Mouse.DeltaY>0) {
      IKBD_AddKeyToKeyboardBuffer(80);    /* Down cursor */
      IKBD_AddKeyToKeyboardBuffer(80|0x80);
      KeyboardProcessor.Mouse.DeltaY--;
    }

    /* Left button? */
    if ( (IKBD_ButtonBool(Keyboard.bLButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldLButtonDown))) )
      IKBD_AddKeyToKeyboardBuffer(0x74);    /* Left */
    else if ( (IKBD_ButtonBool(Keyboard.bOldLButtonDown) && (!IKBD_ButtonBool(Keyboard.bLButtonDown))) )
      IKBD_AddKeyToKeyboardBuffer(0x74|0x80);
    /* Right button? */
    if ( (IKBD_ButtonBool(Keyboard.bRButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldRButtonDown))) )
      IKBD_AddKeyToKeyboardBuffer(0x75);    /* Right */
    else if ( (IKBD_ButtonBool(Keyboard.bOldRButtonDown) && (!IKBD_ButtonBool(Keyboard.bRButtonDown))) )
      IKBD_AddKeyToKeyboardBuffer(0x75|0x80);

    Keyboard.bOldLButtonDown = Keyboard.bLButtonDown;
    Keyboard.bOldRButtonDown = Keyboard.bRButtonDown;

    /* Count, so exit if try too many times! */
    i++;
  }
}


/*-----------------------------------------------------------------------*/
/*
  Return packets from keyboard for auto, rel mouse, joystick etc...
*/
void IKBD_SendAutoKeyboardCommands(void)
{
  /* Don't do anything until processor is first reset */
  if (!KeyboardProcessor.bReset)
    return;

  /* Read joysticks for this frame */
  KeyboardProcessor.Joy.JoyData[1] = Joy_GetStickData(1);
  /* If mouse is on, joystick 0 is not connected */
  if (KeyboardProcessor.MouseMode==AUTOMODE_OFF
      || (bBothMouseAndJoy && KeyboardProcessor.MouseMode==AUTOMODE_MOUSEREL))
    KeyboardProcessor.Joy.JoyData[0] = Joy_GetStickData(0);
  else
    KeyboardProcessor.Joy.JoyData[0] = 0x00;

  /* Check for double-clicks in maximum speed mode */
  IKBD_CheckForDoubleClicks();

  /* Handle Joystick/Mouse fire buttons */
  IKBD_DuplicateMouseFireButtons();

  /* Send any packets which are to be reported by mouse action */
  IKBD_SendOnMouseAction();

  /* Update internal mouse absolute position by find 'delta' of mouse movement */
  IKBD_UpdateInternalMousePosition();

  /* Send automatic joystick packets */
  if (KeyboardProcessor.JoystickMode==AUTOMODE_JOYSTICK)
    IKBD_SelAutoJoysticks();
  /* Send automatic relative mouse positions(absolute are not send automatically) */
  if (KeyboardProcessor.MouseMode==AUTOMODE_MOUSEREL)
    IKBD_SendRelMousePacket();
  /* Send cursor key directions for movements */
  else if (KeyboardProcessor.MouseMode==AUTOMODE_MOUSECURSOR)
    IKBD_SendCursorMousePacket();

  /* Store buttons for next time around */
  Keyboard.bOldLButtonDown = Keyboard.bLButtonDown;
  Keyboard.bOldRButtonDown = Keyboard.bRButtonDown;

  /* Send joystick button '2' as 'Space bar' key - MUST do here so does not get mixed up in middle of joystick packets! */
  if (JoystickSpaceBar) {
    /* As we simulating space bar? */
    if (JoystickSpaceBar==JOYSTICK_SPACE_DOWN) {
      IKBD_PressSTKey(57,TRUE);         /* Press */
      JoystickSpaceBar = JOYSTICK_SPACE_UP;
    }
    else { //if (JoystickSpaceBar==JOYSTICK_SPACE_UP) {
      IKBD_PressSTKey(57,FALSE);        /* Release */
      JoystickSpaceBar = FALSE;         /* Complete */
    }
  }
}


/*-----------------------------------------------------------------------*/
/*
  On ST if disable Mouse AND Joystick with a set time of a RESET command they are
  actually turned back on! (A number of games do this so can get mouse and joystick
  packets at the same time)
*/
static void IKBD_CheckResetDisableBug(void)
{
  /* Have disabled BOTH mouse and joystick? */
  if (bMouseDisabled && bJoystickDisabled)
  {
    /* And in critical time? */
    if (bDuringResetCriticalTime)
    {
      /* Emulate relative mouse and joystick reports being turned back on */
      KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL;
      KeyboardProcessor.JoystickMode = AUTOMODE_JOYSTICK;
      bBothMouseAndJoy = TRUE;

#ifdef DEBUG_OUTPUT_IKBD
      Debug_IKBD("IKBD Mouse+Joystick disabled during RESET. Revert.\n");
      Debugger_TabIKBD_AddListViewItem("( Mouse+Joystick disabled during RESET. Revert. )");
#endif
    }
  }
}


/*-----------------------------------------------------------------------*/
/*
  Start timer after keyboard RESET command to emulate 'quirk'
  If some IKBD commands are sent during time after a RESET they may be ignored
*/
void IKBD_InterruptHandler_ResetTimer(void)
{
  /* Remove this interrupt from list and re-order */
  Int_AcknowledgeInterrupt();

  /* Turn processor on; can now process commands */
  KeyboardProcessor.bReset = TRUE;

  /* Critical timer is over */
  bDuringResetCriticalTime = FALSE;
}



/*-----------------------------------------------------------------------*/
/*
  List of keyboard commands
*/


/*-----------------------------------------------------------------------*/
/*
  Blank function for some keyboard commands - this can be used to find errors
*/
void IKBD_Cmd_NullFunction(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_NullFunction\n");
  Debugger_TabIKBD_AddListViewItem("( NullFunction )");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  RESET

  0x80
  0x01

  Performs self test and checks for stuck (closed) keys, if OK returns 0xF0. Otherwise
  returns break codes for keys
*/
void IKBD_Cmd_Reset(void)
{
  /* Check for error series of bytes, eg 0x80,0x01 */
  if (Keyboard.InputBuffer[1] == 0x01)
  {
#ifdef DEBUG_OUTPUT_IKBD
    Debug_IKBD("KEYBOARD ON\n");
#endif

    /* Set defaults */
    KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL;
    KeyboardProcessor.JoystickMode = AUTOMODE_JOYSTICK;
    KeyboardProcessor.Abs.X = ABS_X_ONRESET;  KeyboardProcessor.Abs.Y = ABS_Y_ONRESET;
    KeyboardProcessor.Abs.MaxX = ABS_MAX_X_ONRESET;  KeyboardProcessor.Abs.MaxY = ABS_MAY_Y_ONRESET;
    KeyboardProcessor.Abs.PrevReadAbsMouseButtons = ABS_PREVBUTTONS;

    IKBD_AddKeyToKeyboardBuffer(0xF0);    /* Assume OK, return correct code */

    /* Start timer - some commands are send during this time they may be ignored (see real ST!) */
    if (!KeyboardProcessor.bReset)
      Int_AddRelativeInterrupt(IKBD_INIT_RESET_CYCLES, INTERRUPT_IKBD_RESETTIMER);
    else
      Int_AddRelativeInterrupt(IKBD_RESET_CYCLES, INTERRUPT_IKBD_RESETTIMER);

    /* Set this 'critical' flag, gets reset when timer expires */
    bDuringResetCriticalTime = TRUE;
    bMouseDisabled = bJoystickDisabled = FALSE;
    bBothMouseAndJoy = FALSE;
  }
  /* else if not 0x80,0x01 just ignore */
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_Reset\n");
  Debugger_TabIKBD_AddListViewItem("RESET");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET MOUSE BUTTON ACTION

  0x07
  %00000mss  ; mouse button action
        ;  (m is presumed =1 when in MOUSE KEYCODE mode)
        ; mss=0xy, mouse button press or release causes mouse
        ;  position report
        ;  where y=1, mouse key press causes absolute report
        ;  and x=1, mouse key release causes absolute report
        ; mss=100, mouse buttons act like keys
*/
void IKBD_Cmd_MouseAction(void)
{
  KeyboardProcessor.Mouse.Action = Keyboard.InputBuffer[1];
  KeyboardProcessor.Abs.PrevReadAbsMouseButtons = ABS_PREVBUTTONS;
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_MouseAction %d\n",(unsigned int)KeyboardProcessor.Mouse.Action);
  Debugger_TabIKBD_AddListViewItem("MouseAction %d",(unsigned int)KeyboardProcessor.Mouse.Action);
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET RELATIVE MOUSE POSITION REPORTING

  0x08
*/
void IKBD_Cmd_RelMouseMode(void)
{
  KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL;
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_RelMouseMode\n");
  Debugger_TabIKBD_AddListViewItem("RelMouseMode");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET ABSOLUTE MOUSE POSITIONING

  0x09
  XMSB      ;X maximum (in scaled mouse clicks)
  XLSB
  YMSB      ;Y maximum (in scaled mouse clicks)
  YLSB
*/
void IKBD_Cmd_AbsMouseMode(void)
{
  /* These maximums are 'inclusive' */
  KeyboardProcessor.MouseMode = AUTOMODE_MOUSEABS;
  KeyboardProcessor.Abs.MaxX = (((unsigned int)Keyboard.InputBuffer[1])<<8) | (unsigned int)Keyboard.InputBuffer[2];
  KeyboardProcessor.Abs.MaxY = (((unsigned int)Keyboard.InputBuffer[3])<<8) | (unsigned int)Keyboard.InputBuffer[4];
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_AbsMouseMode %d,%d\n",KeyboardProcessor.Abs.MaxX,KeyboardProcessor.Abs.MaxY);
  Debugger_TabIKBD_AddListViewItem("AbsMouseMode %d,%d",KeyboardProcessor.Abs.MaxX,KeyboardProcessor.Abs.MaxY);
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET MOUSE KEYCODE MODE

  0x0A
  deltax      ; distance in X clicks to return (LEFT) or (RIGHT)
  deltay      ; distance in Y clicks to return (UP) or (DOWN)
*/
void IKBD_Cmd_MouseCursorKeycodes(void)
{
  KeyboardProcessor.MouseMode = AUTOMODE_MOUSECURSOR;
  KeyboardProcessor.Mouse.KeyCodeDeltaX = Keyboard.InputBuffer[1];
  KeyboardProcessor.Mouse.KeyCodeDeltaY = Keyboard.InputBuffer[2];
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_MouseCursorKeycodes %d,%d\n",(int)KeyboardProcessor.Mouse.KeyCodeDeltaX,(int)KeyboardProcessor.Mouse.KeyCodeDeltaY);
  Debugger_TabIKBD_AddListViewItem("MouseCursorKeycodes %d,%d",(int)KeyboardProcessor.Mouse.KeyCodeDeltaX,(int)KeyboardProcessor.Mouse.KeyCodeDeltaY);
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET MOUSE THRESHOLD

  0x0B
  X      ; x threshold in mouse ticks (positive integers)
  Y      ; y threshold in mouse ticks (positive integers)
*/
void IKBD_Cmd_SetMouseThreshold(void)
{
  KeyboardProcessor.Mouse.XThreshold = (unsigned int)Keyboard.InputBuffer[1];
  KeyboardProcessor.Mouse.YThreshold = (unsigned int)Keyboard.InputBuffer[2];
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetMouseThreshold %d,%d\n",KeyboardProcessor.Mouse.XThreshold,KeyboardProcessor.Mouse.YThreshold);
  Debugger_TabIKBD_AddListViewItem("SetMouseThreshold %d,%d",KeyboardProcessor.Mouse.XThreshold,KeyboardProcessor.Mouse.YThreshold);
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET MOUSE SCALE

  0x0C
  X      ; horizontal mouse ticks per internel X
  Y      ; vertical mouse ticks per internel Y
*/
void IKBD_Cmd_SetMouseScale(void)
{
  KeyboardProcessor.Mouse.XScale = (unsigned int)Keyboard.InputBuffer[1];
  KeyboardProcessor.Mouse.YScale = (unsigned int)Keyboard.InputBuffer[2];
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetMouseScale %d,%d\n",KeyboardProcessor.Mouse.XScale,KeyboardProcessor.Mouse.YScale);
  Debugger_TabIKBD_AddListViewItem("SetMouseScale %d,%d",KeyboardProcessor.Mouse.XScale,KeyboardProcessor.Mouse.YScale);
#endif
}

/*-----------------------------------------------------------------------*/
/*
  INTERROGATE MOUSE POSITION

  0x0D
    Returns:  0xF7  ; absolute mouse position header
      BUTTONS
        0000dcba
        where a is right button down since last interrogation
        b is right button up since last
        c is left button down since last
        d is left button up since last
      XMSB      ; X coordinate
      XLSB
      YMSB      ; Y coordinate
      YLSB
*/
void IKBD_Cmd_ReadAbsMousePos(void)
{
  unsigned char Buttons,PrevButtons;

  /* Test buttons */
  Buttons = 0;
  /* Set buttons to show if up/down */
  if (Keyboard.bRButtonDown)
    Buttons |= 0x01;
  else
    Buttons |= 0x02;
  if (Keyboard.bLButtonDown)
    Buttons |= 0x04;
  else
    Buttons |= 0x08;
  /* Mask off it didn't send last time */
  PrevButtons = KeyboardProcessor.Abs.PrevReadAbsMouseButtons;
  KeyboardProcessor.Abs.PrevReadAbsMouseButtons = Buttons;
  Buttons &= ~PrevButtons;

  /* And send packet */
  IKBD_AddKeyToKeyboardBuffer(0xf7);
  IKBD_AddKeyToKeyboardBuffer(Buttons);
  IKBD_AddKeyToKeyboardBuffer((unsigned int)KeyboardProcessor.Abs.X>>8);
  IKBD_AddKeyToKeyboardBuffer((unsigned int)KeyboardProcessor.Abs.X&0xff);
  IKBD_AddKeyToKeyboardBuffer((unsigned int)KeyboardProcessor.Abs.Y>>8);
  IKBD_AddKeyToKeyboardBuffer((unsigned int)KeyboardProcessor.Abs.Y&0xff);

#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_ReadAbsMousePos %d,%d 0x%X\n",KeyboardProcessor.Abs.X,KeyboardProcessor.Abs.Y,Buttons);
  Debugger_TabIKBD_AddListViewItem("ReadAbsMousePos %d,%d 0x%X",KeyboardProcessor.Abs.X,KeyboardProcessor.Abs.Y,Buttons);
#endif
}

/*-----------------------------------------------------------------------*/
/*
  LOAD MOUSE POSITION

  0x0E
  0x00      ; filler
  XMSB      ; X coordinate
  XLSB      ; (in scaled coordinate system)
  YMSB      ; Y coordinate
  YLSB
*/
void IKBD_Cmd_SetInternalMousePos(void)
{
  /* Setting these do not clip internal position(this happens on next update) */
  KeyboardProcessor.Abs.X = (((unsigned int)Keyboard.InputBuffer[2])<<8) | (unsigned int)Keyboard.InputBuffer[3];
  KeyboardProcessor.Abs.Y = (((unsigned int)Keyboard.InputBuffer[4])<<8) | (unsigned int)Keyboard.InputBuffer[5];
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetInternalMousePos %d,%d\n",KeyboardProcessor.Abs.X,KeyboardProcessor.Abs.Y);
  Debugger_TabIKBD_AddListViewItem("SetInternalMousePos %d,%d",KeyboardProcessor.Abs.X,KeyboardProcessor.Abs.Y);
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET Y=0 AT BOTTOM

  0x0F
*/
void IKBD_Cmd_SetYAxisDown(void)
{
  KeyboardProcessor.Mouse.YAxis = -1;
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetYAxisDown\n");
  Debugger_TabIKBD_AddListViewItem("SetYAxisDown");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET Y=0 AT TOP

  0x10
*/
void IKBD_Cmd_SetYAxisUp(void)
{
  KeyboardProcessor.Mouse.YAxis = 1;
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetYAxisUp\n");
  Debugger_TabIKBD_AddListViewItem("SetYAxisUp");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  RESUME

  0x11
*/
void IKBD_Cmd_StartKeyboardTransfer(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_StartKeyboardTransfer\n");
  Debugger_TabIKBD_AddListViewItem("StartKeyboardTransfer");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  DISABLE MOUSE

  0x12
*/
void IKBD_Cmd_TurnMouseOff(void)
{
  KeyboardProcessor.MouseMode = AUTOMODE_OFF;
  bMouseDisabled = TRUE;
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_TurnMouseOff\n");
  Debugger_TabIKBD_AddListViewItem("TurnMouseOff");
#endif

  IKBD_CheckResetDisableBug();
}


/*-----------------------------------------------------------------------*/
/*
  PAUSE OUTPUT

  0x13
*/
void IKBD_Cmd_StopKeyboardTransfer(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_StopKeyboardTransfer\n");
  Debugger_TabIKBD_AddListViewItem("StopKeyboardTransfer");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET JOYSTICK EVENT REPORTING

  0x14
*/
void IKBD_Cmd_ReturnJoystickAuto(void)
{
  KeyboardProcessor.JoystickMode = AUTOMODE_JOYSTICK;
  KeyboardProcessor.MouseMode = AUTOMODE_OFF;

  /* Again, if try to disable mouse within time of a reset it isn't disabled! */
  if (bDuringResetCriticalTime)
  {
    KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL;
    bBothMouseAndJoy = TRUE;
  }

#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_ReturnJoystickAuto\n");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET JOYSTICK INTERROGATION MODE

  0x15
*/
void IKBD_Cmd_StopJoystick(void)
{
  KeyboardProcessor.JoystickMode = AUTOMODE_OFF;
//  Debug_IKBD("IKBD_Cmd_StopJoystick\n");
}


/*-----------------------------------------------------------------------*/
/*
  JOYSTICK INTERROGATE

  0x16
*/
void IKBD_Cmd_ReturnJoystick(void)
{
  IKBD_AddKeyToKeyboardBuffer(0xFD);
  IKBD_AddKeyToKeyboardBuffer(Joy_GetStickData(0));
  IKBD_AddKeyToKeyboardBuffer(Joy_GetStickData(1));
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_ReturnJoystick\n");
  Debugger_TabIKBD_AddListViewItem("ReturnJoystick");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET JOYSTICK MONITORING

  0x17
  rate      ; time between samples in hundreths of a second
    Returns: (in packets of two as long as in mode)
      %000000xy  where y is JOYSTICK1 Fire button
          and x is JOYSTICK0 Fire button
      %nnnnmmmm  where m is JOYSTICK1 state
          and n is JOYSTICK0 state
*/
void IKBD_Cmd_SetJoystickDuration(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetJoystickDuration\n");
  Debugger_TabIKBD_AddListViewItem("SetJoystickDuration");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET FIRE BUTTON MONITORING

  0x18
    Returns: (as long as in mode)
      %bbbbbbbb  ; state of the JOYSTICK1 fire button packed
            ; 8 bits per byte, the first sample if the MSB
*/
void IKBD_Cmd_SetJoystickFireDuration(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetJoystickFireDuration\n");
  Debugger_TabIKBD_AddListViewItem("SetJoystickFireDuration");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  SET JOYSTICK KEYCODE MODE

  0x19
  RX        ; length of time (in tenths of seconds) until
          ; horizontal velocity breakpoint is reached
  RY        ; length of time (in tenths of seconds) until
          ; vertical velocity breakpoint is reached
  TX        ; length (in tenths of seconds) of joystick closure
          ; until horizontal cursor key is generated before RX
          ; has elapsed
  TY        ; length (in tenths of seconds) of joystick closure
          ; until vertical cursor key is generated before RY
          ; has elapsed
  VX        ; length (in tenths of seconds) of joystick closure
          ; until horizontal cursor keystokes are generated after RX
          ; has elapsed
  VY        ; length (in tenths of seconds) of joystick closure
          ; until vertical cursor keystokes are generated after RY
          ; has elapsed
*/
void IKBD_Cmd_SetCursorForJoystick(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetCursorForJoystick\n");
  Debugger_TabIKBD_AddListViewItem("SetCursorForJoystick");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  DISABLE JOYSTICKS

  0x1A
*/
void IKBD_Cmd_DisableJoysticks(void)
{
  KeyboardProcessor.JoystickMode = AUTOMODE_OFF;
  bJoystickDisabled = TRUE;
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_DisableJoysticks\n");
  Debugger_TabIKBD_AddListViewItem("DisableJoysticks");
#endif

  IKBD_CheckResetDisableBug();
}


/*-----------------------------------------------------------------------*/
/*
  TIME-OF-DAY CLOCK SET

  0x1B
  YY        ; year (2 least significant digits)
  MM        ; month
  DD        ; day
  hh        ; hour
  mm        ; minute
  ss        ; second
*/
void IKBD_Cmd_SetClock(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_SetClock\n");
  Debugger_TabIKBD_AddListViewItem("SetClock");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  INTERROGATE TIME-OF-DAT CLOCK

  0x1C
    Returns:
      0xFC  ; time-of-day event header
      YY    ; year (2 least significant digits)
      There seems to be a problem with the bcd conversion of the year
      when year/10 >= 10. So the bcd conversion keeps the part > 10.
      If you put year%100 here (as says the doc), and put a real bcd
      conversion function in misc.c, then you end up with year 2031
      instead of 2003...

      MM    ; month
      DD    ; day
      hh    ; hour
      mm    ; minute
      ss    ; second
*/
void IKBD_Cmd_ReadClock(void)
{
  struct tm *SystemTime;
  time_t nTimeTicks;

  /* Get system time */
  nTimeTicks = time(NULL);
  SystemTime = localtime(&nTimeTicks);

  /* Return packet */
  IKBD_AddKeyToKeyboardBuffer(0xFC);
  /* Return time-of-day clock as yy-mm-dd-hh-mm-ss as BCD */
  IKBD_AddKeyToKeyboardBuffer(Misc_ConvertToBCD(SystemTime->tm_year)); /* yy - year (2 least significant digits) */
  IKBD_AddKeyToKeyboardBuffer(Misc_ConvertToBCD(SystemTime->tm_mon+1));    /* mm - Month */
  IKBD_AddKeyToKeyboardBuffer(Misc_ConvertToBCD(SystemTime->tm_mday));     /* dd - Day */
  IKBD_AddKeyToKeyboardBuffer(Misc_ConvertToBCD(SystemTime->tm_hour));     /* hh - Hour */
  IKBD_AddKeyToKeyboardBuffer(Misc_ConvertToBCD(SystemTime->tm_min));      /* mm - Minute */
  IKBD_AddKeyToKeyboardBuffer(Misc_ConvertToBCD(SystemTime->tm_sec));      /* ss - Second */
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_ReadClock\n");
  Debugger_TabIKBD_AddListViewItem("ReadClock");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  MEMORY LOAD

  0x20
  ADRMSB      ; address in controller
  ADRLSB      ; memory to be loaded
  NUM        ; number of bytes (0-128)
  { data }
*/
void IKBD_Cmd_LoadMemory(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_LoadMemory\n");
  Debugger_TabIKBD_AddListViewItem("LoadMemory");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  MEMORY READ

  0x21
  ADRMSB        ; address in controller
  ADRLSB        ; memory to be read
    Returns:
      0xF6    ; status header
      0x20    ; memory access
      { data }  ; 6 data bytes starting at ADR
*/
void IKBD_Cmd_ReadMemory(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_ReadMemory\n");
  Debugger_TabIKBD_AddListViewItem("ReadMemory");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  CONTROLLER EXECUTE

  0x22
  ADRMSB      ; address of subroutine in
  ADRLSB      ; controller memory to be called
*/
void IKBD_Cmd_Execute(void)
{
#ifdef DEBUG_OUTPUT_IKBD
  Debug_IKBD("IKBD_Cmd_Execute\n");
  Debugger_TabIKBD_AddListViewItem("Execute");
#endif
}


/*-----------------------------------------------------------------------*/
/*
  Send data to keyboard processor via ACIA by writing to address 0xfffc02.
  For our emulation we bypass the ACIA (I've yet to see anything check for this)
  and add the byte directly into the keyboard input buffer.
*/
static void IKBD_RunKeyboardCommand(unsigned short aciabyte)
{
  int i=0;

  /* Write into our keyboard input buffer */
  Keyboard.InputBuffer[Keyboard.nBytesInInputBuffer++] = aciabyte;

  /* Now check bytes to see if we have a valid/in-valid command string set */
  while(KeyboardCommands[i].Command!=0xff) {
    /* Found command? */
    if (KeyboardCommands[i].Command==Keyboard.InputBuffer[0]) {
      /* Is string complete, then can execute? */
      if (KeyboardCommands[i].NumParameters==Keyboard.nBytesInInputBuffer) {
        CALL_VAR(KeyboardCommands[i].pCallFunction);
        Keyboard.nBytesInInputBuffer = 0;
      }

      return;
    }

    i++;
  }

  /* Command not known, reset buffer(IKBD assumes a NOP) */
  Keyboard.nBytesInInputBuffer = 0;
}


/*-----------------------------------------------------------------------*/
/*
  Send byte to our keyboard processor, and execute
*/
void IKBD_SendByteToKeyboardProcessor(unsigned short bl)
{
  IKBD_RunKeyboardCommand(bl);  /* And send */
}


/*-----------------------------------------------------------------------*/
/*
  The byte stored in the ACIA 'ACIAByte' has been read by the CPU by reading from
  address $fffc02. We clear the status flag and set the GPIP register to signal read.
*/
unsigned short IKBD_GetByteFromACIA(void)
{
  /* ACIA is now reset */
  ACIAStatusRegister &= ~(ACIA_STATUS_REGISTER__RX_BUFFER_FULL | ACIA_STATUS_REGISTER__INTERRUPT_REQUEST | ACIA_STATUS_REGISTER__OVERRUN_ERROR);

  /* GPIP I4 - General Purpose Pin Keyboard/MIDI interrupt */
  MFP_GPIP |= 0x10;
  return ACIAByte;  /* Return byte from keyboard */
}


/*-----------------------------------------------------------------------*/
/*
  Byte received in the ACIA from the keyboard processor. Store byte for read from $fffc02
  and clear the GPIP I4 register. This register will be remain low until byte has been
  read from ACIA.
*/
void IKBD_InterruptHandler_ACIA(void)
{
  /* Remove this interrupt from list and re-order */
  Int_AcknowledgeInterrupt();

  /* Copy keyboard byte, ready for read from $fffc02 */
  ACIAByte = Keyboard.Buffer[Keyboard.BufferHead++];
  Keyboard.BufferHead &= KEYBOARD_BUFFER_MASK;

  /* Did we get an over-run? Ie byte has arrived from keyboard processor BEFORE CPU has read previous one from ACIA */
  if (ACIAStatusRegister&ACIA_STATUS_REGISTER__RX_BUFFER_FULL)
    ACIAStatusRegister |= ACIA_STATUS_REGISTER__OVERRUN_ERROR;  /* Set over-run */

  /* ACIA buffer is now full */
  ACIAStatusRegister |= ACIA_STATUS_REGISTER__RX_BUFFER_FULL;
  /* Signal interrupt pending */
  ACIAStatusRegister |= ACIA_STATUS_REGISTER__INTERRUPT_REQUEST;
  /* GPIP I4 - General Purpose Pin Keyboard/MIDI interrupt */
  /* NOTE: GPIP will remain low(0) until keyboard data is read from $fffc02. */
  MFP_GPIP &= ~0x10;

  /* Acknowledge in MFP circuit, pass bit,enable,pending */
  MFP_InputOnChannel(MFP_ACIA_BIT, MFP_IERB, &MFP_IPRB);

  /* Clear flag so can allow another byte to be sent along serial line */
  bByteInTransitToACIA = FALSE;
  /* If another key is waiting, start sending from keyboard processor now */
  if (Keyboard.BufferHead!=Keyboard.BufferTail)
    IKBD_SendByteToACIA();
}


/*-----------------------------------------------------------------------*/
/*
  Send a byte from the keyboard buffer to the ACIA. On a real ST this takes some time to send
  so we must be as accurate in the timing as possible - bytes do not appear to the 68000 instantly!
  We do this via an internal interrupt - neat!
*/
void IKBD_SendByteToACIA(void)
{
  /* Transmit byte from keyboard processor to ACIA. This takes approx ACIA_CYCLES CPU clock cycles to complete */
  if (!bByteInTransitToACIA) {
    /* Send byte to ACIA */
    Int_AddRelativeInterrupt(ACIA_CYCLES,INTERRUPT_IKBD_ACIA);
    /* Set flag so only transmit one byte at a time */
    bByteInTransitToACIA = TRUE;
  }
}


/*-----------------------------------------------------------------------*/
/*
  Add characer our internal keyboard buffer. These bytes are then sent one at a time to the ACIA.
  This is done via a delay to mimick the STs internal workings, as this is needed for games such
  as Carrier Command.
*/
void IKBD_AddKeyToKeyboardBuffer(unsigned char Data)
{
  /* Is keyboard initialised yet? Ignore any bytes until it is */
  if (!KeyboardProcessor.bReset)
    return;

  /* Check we have space to add byte */
  if (Keyboard.BufferHead!=((Keyboard.BufferTail+1)&KEYBOARD_BUFFER_MASK)) {
    /* Add byte to our buffer */
    Keyboard.Buffer[Keyboard.BufferTail++] = Data;
    Keyboard.BufferTail &= KEYBOARD_BUFFER_MASK;

    /* We have character ready to transmit from the ACIA - see if can send it now */
    IKBD_SendByteToACIA();
  }
}


/*-----------------------------------------------------------------------*/
/*
  When press/release key under host OS, execute this function.
*/
void IKBD_PressSTKey(unsigned char ScanCode,BOOL bPress)
{
  if (!bPress)
    ScanCode |= 0x80;    /* Set top bit if released key */
  IKBD_AddKeyToKeyboardBuffer(ScanCode);  /* And send to keyboard processor */
}


/*-----------------------------------------------------------------------*/
/*
  Handle read from keyboard control ACIA register (0xfffc00)
*/
void IKBD_KeyboardControl_ReadByte(void)
{
      /* For our emulation send is immediate so acknowledge buffer is empty */
      IoMem[0xfffc00] = ACIAStatusRegister | ACIA_STATUS_REGISTER__TX_BUFFER_EMPTY;
}

/*-----------------------------------------------------------------------*/
/*
  Handle read from keyboard data ACIA register (0xfffc02)
*/
void IKBD_KeyboardData_ReadByte(void)
{
      IoMem[0xfffc02] = IKBD_GetByteFromACIA();  /* Return our byte from keyboard processor */
}


/*-----------------------------------------------------------------------*/
/*
  Handle write to keyboard control ACIA register (0xfffc00)
*/
void IKBD_KeyboardControl_WriteByte(void)
{
      /* Nothing... */
}

/*-----------------------------------------------------------------------*/
/*
  Handle write to keyboard data ACIA register (0xfffc02)
*/
void IKBD_KeyboardData_WriteByte(void)
{
      IKBD_SendByteToKeyboardProcessor(IoMem[0xfffc02]);  /* Pass our byte to the keyboard processor */
}

Generated by  Doxygen 1.6.0   Back to index