379 lines
12 KiB
C
379 lines
12 KiB
C
// Atari ST keyboard + mouse input: replaces the TOS ikbdsys vector in
|
|
// the KBDVECS table so every byte from the IKBD ACIA comes through our
|
|
// packet-aware handler.
|
|
//
|
|
// The IKBD protocol mixes keyboard scan codes with mouse/joy packets.
|
|
// Bytes 0x00-0x72 (with optional 0x80 break bit -> 0x80..0xF2) are
|
|
// keyboard scan codes; 0xF6-0xFF are packet headers that announce a
|
|
// fixed number of trailing bytes. We dispatch based on header:
|
|
//
|
|
// 0xF8..0xFB Relative mouse packet (3 bytes total). The header's
|
|
// low two bits are the button state (bit 1 = left,
|
|
// bit 0 = right). Next two bytes are signed dx, dy.
|
|
// Other Discarded (joystick / clock / status etc.) -- we just
|
|
// consume the documented byte count to stay in sync.
|
|
//
|
|
// The ISR writes into private gIsrState (keyboard) and gIsrMouse*
|
|
// buffers; jlpInputPoll copies them into the public globals.
|
|
// jlKeyPressed edge detection requires that public gKeyState only
|
|
// advance during jlpInputPoll, never at interrupt time -- jlInputPoll
|
|
// snapshots gKeyState into gKeyPrev before jlpInputPoll runs.
|
|
|
|
#include <string.h>
|
|
|
|
#include <mint/osbind.h>
|
|
#include <mint/ostruct.h>
|
|
|
|
#include "port.h"
|
|
#include "inputInternal.h"
|
|
#include "joey/surface.h"
|
|
|
|
// ----- Constants -----
|
|
|
|
// IKBD ACIA data register; reading also clears the ACIA's interrupt
|
|
// so the MFP ACIA line drops.
|
|
#define ST_ACIA_DATA ((volatile uint8_t *)0xFFFFFC02L)
|
|
|
|
#define SCAN_BREAK_BIT 0x80
|
|
#define SCAN_CODE_MASK 0x7F
|
|
#define SCAN_TABLE_SIZE 128
|
|
|
|
#define PKT_STATUS 0xF6 // + 7 bytes
|
|
#define PKT_ABS_MOUSE 0xF7 // + 5 bytes
|
|
#define PKT_REL_MOUSE_MIN 0xF8 // 0xF8..0xFB + 2 bytes
|
|
#define PKT_REL_MOUSE_MAX 0xFB
|
|
#define PKT_TIME 0xFC // + 6 bytes
|
|
#define PKT_JOY_BOTH 0xFD // + 2 bytes
|
|
#define PKT_JOY0 0xFE // + 1 byte
|
|
#define PKT_JOY1 0xFF // + 1 byte
|
|
|
|
// Bit layout of the IKBD relative-mouse packet header:
|
|
#define MOUSE_HDR_LEFT_BTN 0x02
|
|
#define MOUSE_HDR_RIGHT_BTN 0x01
|
|
|
|
// IKBD joystick byte: bit set = direction held / fire pressed.
|
|
#define JOY_BIT_UP 0x01
|
|
#define JOY_BIT_DOWN 0x02
|
|
#define JOY_BIT_LEFT 0x04
|
|
#define JOY_BIT_RIGHT 0x08
|
|
#define JOY_BIT_FIRE 0x80
|
|
|
|
// Packet kinds for the ISR's small state machine.
|
|
#define PKT_KIND_NONE 0
|
|
#define PKT_KIND_DISCARD 1
|
|
#define PKT_KIND_REL_MOUSE 2
|
|
#define PKT_KIND_JOY0 3 // 0xFE -- 1 byte for ST joy port 0 (mouse port)
|
|
#define PKT_KIND_JOY1 4 // 0xFF -- 1 byte for ST joy port 1 (dedicated)
|
|
#define PKT_KIND_JOY_BOTH 5 // 0xFD -- 2 bytes (joy0 then joy1)
|
|
|
|
// Mapping from ST joystick port -> jlJoystickE. Port 1 is the
|
|
// dedicated stick that retro games use; expose it as the primary
|
|
// JOYSTICK_0. Port 0 (mouse port) becomes JOYSTICK_1.
|
|
#define ST_PORT0_AS_JS JOYSTICK_1
|
|
#define ST_PORT1_AS_JS JOYSTICK_0
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static long patchIkbdVector(void);
|
|
static long restoreIkbdVector(void);
|
|
static long ikbdHandler(void);
|
|
|
|
// ----- Module state -----
|
|
|
|
// ST IKBD scan codes. Identical encoding to the PC AT set-1 for most
|
|
// keys, which is why the table mirrors the DOS scan map.
|
|
static const uint8_t gScanToKey[SCAN_TABLE_SIZE] = {
|
|
[0x01] = KEY_ESCAPE,
|
|
[0x02] = KEY_1,
|
|
[0x03] = KEY_2,
|
|
[0x04] = KEY_3,
|
|
[0x05] = KEY_4,
|
|
[0x06] = KEY_5,
|
|
[0x07] = KEY_6,
|
|
[0x08] = KEY_7,
|
|
[0x09] = KEY_8,
|
|
[0x0A] = KEY_9,
|
|
[0x0B] = KEY_0,
|
|
[0x0E] = KEY_BACKSPACE,
|
|
[0x0F] = KEY_TAB,
|
|
[0x10] = KEY_Q,
|
|
[0x11] = KEY_W,
|
|
[0x12] = KEY_E,
|
|
[0x13] = KEY_R,
|
|
[0x14] = KEY_T,
|
|
[0x15] = KEY_Y,
|
|
[0x16] = KEY_U,
|
|
[0x17] = KEY_I,
|
|
[0x18] = KEY_O,
|
|
[0x19] = KEY_P,
|
|
[0x1C] = KEY_RETURN,
|
|
[0x1D] = KEY_LCTRL,
|
|
[0x1E] = KEY_A,
|
|
[0x1F] = KEY_S,
|
|
[0x20] = KEY_D,
|
|
[0x21] = KEY_F,
|
|
[0x22] = KEY_G,
|
|
[0x23] = KEY_H,
|
|
[0x24] = KEY_J,
|
|
[0x25] = KEY_K,
|
|
[0x26] = KEY_L,
|
|
[0x2A] = KEY_LSHIFT,
|
|
[0x2C] = KEY_Z,
|
|
[0x2D] = KEY_X,
|
|
[0x2E] = KEY_C,
|
|
[0x2F] = KEY_V,
|
|
[0x30] = KEY_B,
|
|
[0x31] = KEY_N,
|
|
[0x32] = KEY_M,
|
|
[0x36] = KEY_RSHIFT,
|
|
[0x38] = KEY_LALT,
|
|
[0x39] = KEY_SPACE,
|
|
[0x3B] = KEY_F1,
|
|
[0x3C] = KEY_F2,
|
|
[0x3D] = KEY_F3,
|
|
[0x3E] = KEY_F4,
|
|
[0x3F] = KEY_F5,
|
|
[0x40] = KEY_F6,
|
|
[0x41] = KEY_F7,
|
|
[0x42] = KEY_F8,
|
|
[0x43] = KEY_F9,
|
|
[0x44] = KEY_F10,
|
|
[0x48] = KEY_UP,
|
|
[0x4B] = KEY_LEFT,
|
|
[0x4D] = KEY_RIGHT,
|
|
[0x50] = KEY_DOWN,
|
|
};
|
|
|
|
static _KBDVECS *gKbdvecs = NULL;
|
|
static long (*gOldIkbdsys)(void) = NULL;
|
|
static volatile uint8_t gPacketRemaining = 0;
|
|
static volatile uint8_t gPacketKind = PKT_KIND_NONE;
|
|
static volatile uint8_t gMousePacketByte = 0; // bytes consumed in current packet
|
|
static bool gHooked = false;
|
|
// uint8_t (not bool) so element size matches gKeyState's. See
|
|
// src/core/inputInternal.h for the full rationale.
|
|
static volatile uint8_t gIsrState[KEY_COUNT];
|
|
|
|
// Mouse delta accumulator. Each ACIA mouse packet adds dx/dy here; the
|
|
// poll routine clamps the running absolute position into the surface
|
|
// rectangle. Buttons are latched in the header byte and stay live in
|
|
// gIsrMouseButtons until the next packet rewrites them.
|
|
static volatile int32_t gIsrMouseDx = 0;
|
|
static volatile int32_t gIsrMouseDy = 0;
|
|
static volatile uint8_t gIsrMouseButtons = 0;
|
|
static int16_t gMouseAbsX = SURFACE_WIDTH / 2;
|
|
static int16_t gMouseAbsY = SURFACE_HEIGHT / 2;
|
|
|
|
// Latched joystick state, written by the IKBD ISR. The byte stays
|
|
// unchanged between packets while a direction or fire is held, so
|
|
// jlpInputPoll can simply read the latest value.
|
|
static volatile uint8_t gIsrJoyByte[JOYSTICK_COUNT];
|
|
|
|
// ----- Internal helpers -----
|
|
|
|
// Runs in MFP ACIA interrupt context. Reads one byte from the ACIA,
|
|
// dispatches to either keyboard handling, mouse-packet capture, or
|
|
// "discard remaining N bytes" for packets we do not yet care about.
|
|
static long ikbdHandler(void) {
|
|
uint8_t byte;
|
|
uint8_t code;
|
|
uint8_t key;
|
|
bool isBreak;
|
|
|
|
byte = *ST_ACIA_DATA;
|
|
|
|
if (gPacketRemaining != 0) {
|
|
switch (gPacketKind) {
|
|
case PKT_KIND_REL_MOUSE:
|
|
// mouse-packet payload: byte 0 = dx, byte 1 = dy
|
|
if (gMousePacketByte == 0) {
|
|
gIsrMouseDx += (int8_t)byte;
|
|
gMousePacketByte = 1;
|
|
} else {
|
|
gIsrMouseDy += (int8_t)byte;
|
|
gMousePacketByte = 0;
|
|
}
|
|
break;
|
|
case PKT_KIND_JOY0:
|
|
gIsrJoyByte[ST_PORT0_AS_JS] = byte;
|
|
break;
|
|
case PKT_KIND_JOY1:
|
|
gIsrJoyByte[ST_PORT1_AS_JS] = byte;
|
|
break;
|
|
case PKT_KIND_JOY_BOTH:
|
|
// 0xFD payload is joy0 then joy1.
|
|
if (gMousePacketByte == 0) {
|
|
gIsrJoyByte[ST_PORT0_AS_JS] = byte;
|
|
gMousePacketByte = 1;
|
|
} else {
|
|
gIsrJoyByte[ST_PORT1_AS_JS] = byte;
|
|
gMousePacketByte = 0;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
gPacketRemaining = (uint8_t)(gPacketRemaining - 1);
|
|
if (gPacketRemaining == 0) {
|
|
gPacketKind = PKT_KIND_NONE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (byte >= PKT_STATUS) {
|
|
gPacketKind = PKT_KIND_DISCARD;
|
|
gMousePacketByte = 0;
|
|
switch (byte) {
|
|
case PKT_STATUS:
|
|
gPacketRemaining = 7;
|
|
break;
|
|
case PKT_ABS_MOUSE:
|
|
gPacketRemaining = 5;
|
|
break;
|
|
case PKT_TIME:
|
|
gPacketRemaining = 6;
|
|
break;
|
|
case PKT_JOY_BOTH:
|
|
gPacketRemaining = 2;
|
|
gPacketKind = PKT_KIND_JOY_BOTH;
|
|
break;
|
|
case PKT_JOY0:
|
|
gPacketRemaining = 1;
|
|
gPacketKind = PKT_KIND_JOY0;
|
|
break;
|
|
case PKT_JOY1:
|
|
gPacketRemaining = 1;
|
|
gPacketKind = PKT_KIND_JOY1;
|
|
break;
|
|
default:
|
|
if (byte >= PKT_REL_MOUSE_MIN && byte <= PKT_REL_MOUSE_MAX) {
|
|
gPacketRemaining = 2;
|
|
gPacketKind = PKT_KIND_REL_MOUSE;
|
|
gIsrMouseButtons = (uint8_t)(byte & (MOUSE_HDR_LEFT_BTN | MOUSE_HDR_RIGHT_BTN));
|
|
} else {
|
|
gPacketKind = PKT_KIND_NONE;
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
isBreak = (byte & SCAN_BREAK_BIT) != 0;
|
|
code = (uint8_t)(byte & SCAN_CODE_MASK);
|
|
key = gScanToKey[code];
|
|
if (key != KEY_NONE) {
|
|
gIsrState[key] = !isBreak;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static long patchIkbdVector(void) {
|
|
gOldIkbdsys = gKbdvecs->ikbdsys;
|
|
gKbdvecs->ikbdsys = ikbdHandler;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static long restoreIkbdVector(void) {
|
|
gKbdvecs->ikbdsys = gOldIkbdsys;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ----- HAL API (alphabetical) -----
|
|
|
|
void jlpJoystickReset(jlJoystickE js) {
|
|
// Atari ST sticks are digital -- no calibration to do.
|
|
(void)js;
|
|
}
|
|
|
|
|
|
void jlpInputInit(void) {
|
|
memset(gKeyState, 0, sizeof(gKeyState));
|
|
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
|
memset((void *)gIsrState, 0, sizeof(gIsrState));
|
|
memset((void *)gIsrJoyByte, 0, sizeof(gIsrJoyByte));
|
|
|
|
gMouseAbsX = SURFACE_WIDTH / 2;
|
|
gMouseAbsY = SURFACE_HEIGHT / 2;
|
|
gMouseX = gMouseAbsX;
|
|
gMouseY = gMouseAbsY;
|
|
gIsrMouseDx = 0;
|
|
gIsrMouseDy = 0;
|
|
gIsrMouseButtons = 0;
|
|
|
|
// Both ports always announce themselves on the ST -- packets
|
|
// arrive even when no stick is plugged in (state stays at zero).
|
|
gJoyConnected[JOYSTICK_0] = true;
|
|
gJoyConnected[JOYSTICK_1] = true;
|
|
|
|
gKbdvecs = (_KBDVECS *)Kbdvbase();
|
|
gPacketRemaining = 0;
|
|
gPacketKind = PKT_KIND_NONE;
|
|
Supexec(patchIkbdVector);
|
|
gHooked = true;
|
|
}
|
|
|
|
|
|
// The ACIA ISR writes gIsrState (keys), gIsrMouse* deltas, and
|
|
// gIsrJoyByte[] at ~100 Hz max; the ~60-byte memcpy is essentially
|
|
// never racing a write. Worst case is a single key or one packet
|
|
// lagging one frame -- well under perceptible.
|
|
void jlpInputPoll(void) {
|
|
int32_t dx;
|
|
int32_t dy;
|
|
int32_t newX;
|
|
int32_t newY;
|
|
uint8_t btn;
|
|
uint8_t joy;
|
|
uint16_t i;
|
|
|
|
memcpy(gKeyState, (const void *)gIsrState, sizeof(gKeyState));
|
|
|
|
// Drain accumulated mouse deltas + latch button state.
|
|
dx = gIsrMouseDx;
|
|
dy = gIsrMouseDy;
|
|
btn = gIsrMouseButtons;
|
|
gIsrMouseDx = 0;
|
|
gIsrMouseDy = 0;
|
|
|
|
newX = (int32_t)gMouseAbsX + dx;
|
|
newY = (int32_t)gMouseAbsY + dy;
|
|
if (newX < 0) { newX = 0; }
|
|
if (newX > SURFACE_WIDTH - 1) { newX = SURFACE_WIDTH - 1; }
|
|
if (newY < 0) { newY = 0; }
|
|
if (newY > SURFACE_HEIGHT - 1) { newY = SURFACE_HEIGHT - 1; }
|
|
gMouseAbsX = (int16_t)newX;
|
|
gMouseAbsY = (int16_t)newY;
|
|
|
|
gMouseX = gMouseAbsX;
|
|
gMouseY = gMouseAbsY;
|
|
|
|
gMouseButtonState[MOUSE_BUTTON_LEFT] = (btn & MOUSE_HDR_LEFT_BTN) != 0;
|
|
gMouseButtonState[MOUSE_BUTTON_RIGHT] = (btn & MOUSE_HDR_RIGHT_BTN) != 0;
|
|
gMouseButtonState[MOUSE_BUTTON_MIDDLE] = false;
|
|
|
|
// Decode latest joystick byte for each stick. Direction is digital
|
|
// on the ST, so axes snap to the JOYSTICK_AXIS_* extremes.
|
|
for (i = 0; i < JOYSTICK_COUNT; i++) {
|
|
joy = gIsrJoyByte[i];
|
|
gJoyAxisX[i] = 0;
|
|
gJoyAxisY[i] = 0;
|
|
if (joy & JOY_BIT_LEFT) { gJoyAxisX[i] = JOYSTICK_AXIS_MIN; }
|
|
if (joy & JOY_BIT_RIGHT) { gJoyAxisX[i] = JOYSTICK_AXIS_MAX; }
|
|
if (joy & JOY_BIT_UP) { gJoyAxisY[i] = JOYSTICK_AXIS_MIN; }
|
|
if (joy & JOY_BIT_DOWN) { gJoyAxisY[i] = JOYSTICK_AXIS_MAX; }
|
|
gJoyButtonState[i][JOY_BUTTON_0] = (joy & JOY_BIT_FIRE) != 0;
|
|
gJoyButtonState[i][JOY_BUTTON_1] = false; // ST has 1 fire button
|
|
}
|
|
}
|
|
|
|
|
|
void jlpInputShutdown(void) {
|
|
if (!gHooked) {
|
|
return;
|
|
}
|
|
Supexec(restoreIkbdVector);
|
|
gHooked = false;
|
|
}
|