326 lines
11 KiB
C
326 lines
11 KiB
C
// Apple IIgs keyboard + mouse input via classic Apple II softswitches.
|
|
//
|
|
// Keyboard: $C000 (data) and $C010 (clear strobe). The Event Manager
|
|
// would be the "modern" approach, but it requires a full ToolBox
|
|
// bring-up that our S16 binary does not perform; calling GetNextEvent
|
|
// uninitialized corrupted KEGS' emulation state. Softswitches have no
|
|
// such dependency: they are live memory-mapped hardware.
|
|
//
|
|
// Tradeoff: $C000 reports the *last* key pressed, not a per-key matrix.
|
|
// Holding multiple non-modifier keys simultaneously cannot be observed;
|
|
// the demo and any game using this port sees one typable key at a time,
|
|
// plus live shift/ctrl/option state from the modifier register at
|
|
// $C025. This matches what every Apple II game ever shipped does, and
|
|
// it is enough for feature parity with the other platforms on typical
|
|
// "press a key, act on it" flows.
|
|
//
|
|
// Held-key state is synthesized via a TTL counter: a fresh strobe on
|
|
// $C000 refreshes the TTL; each halInputPoll decays it; when TTL hits
|
|
// zero we assume the key was released. KEY_TTL is sized to cover the
|
|
// typematic initial delay so that a held key does not flicker.
|
|
//
|
|
// Mouse: $C024 (delta data) and $C027 (status). Each $C024 read
|
|
// returns one signed 7-bit delta; $C027 bit 1 indicates whether the
|
|
// next read will return X (0) or Y (1). On the Y read, $C024 bit 7
|
|
// also encodes inverted button state (0 = pressed). We do exactly two
|
|
// $C024 reads per halInputPoll, accumulating the deltas onto an
|
|
// absolute position which is clamped to the surface rectangle. The
|
|
// IIgs ADB MCU autopolls the mouse and queues fifos behind these
|
|
// softswitches, so the per-frame two-read cadence keeps up with
|
|
// normal motion; bursts may lag by a frame.
|
|
|
|
#include <string.h>
|
|
|
|
#include <types.h>
|
|
|
|
#include "hal.h"
|
|
#include "inputInternal.h"
|
|
#include "joey/surface.h"
|
|
|
|
// ----- Hardware registers -----
|
|
|
|
#define IIGS_KBD ((volatile uint8_t *)0x00C000L)
|
|
#define IIGS_KBDSTRB ((volatile uint8_t *)0x00C010L)
|
|
#define IIGS_MOUSEDATA ((volatile uint8_t *)0x00C024L)
|
|
#define IIGS_MODIFIERS ((volatile uint8_t *)0x00C025L)
|
|
#define IIGS_KMSTATUS ((volatile uint8_t *)0x00C027L)
|
|
|
|
// Joystick / paddle softswitches.
|
|
#define IIGS_BTN0 ((volatile uint8_t *)0x00C061L)
|
|
#define IIGS_BTN1 ((volatile uint8_t *)0x00C062L)
|
|
#define IIGS_PADDLE0 ((volatile uint8_t *)0x00C064L)
|
|
#define IIGS_PADDLE1 ((volatile uint8_t *)0x00C065L)
|
|
#define IIGS_PTRIG ((volatile uint8_t *)0x00C070L)
|
|
#define IIGS_BUTTON_BIT 0x80
|
|
#define IIGS_PADDLE_BUSY 0x80
|
|
#define PADDLE_TIMEOUT 256
|
|
#define PADDLE_LO_THRESHOLD 64
|
|
#define PADDLE_HI_THRESHOLD 192
|
|
|
|
#define KBD_STROBE_BIT 0x80
|
|
#define KBD_ASCII_MASK 0x7F
|
|
|
|
// $C025 layout (IIgs Hardware Reference): bit 0 = shift, bit 1 = ctrl,
|
|
// bit 6 = option (Closed-Apple), bit 7 = command (Open-Apple).
|
|
#define MOD_SHIFT 0x01
|
|
#define MOD_CONTROL 0x02
|
|
#define MOD_OPTION 0x40
|
|
|
|
// $C027 layout (IIgs Hardware Reference / ADB MCU):
|
|
#define KMSTATUS_MOUSE_DATA 0x80 // mouse data available
|
|
#define KMSTATUS_MOUSE_COORD 0x02 // 0 = next $C024 read is X, 1 = Y
|
|
|
|
// $C024 mouse-data layout: bit 7 on Y reads encodes button (0=down).
|
|
// Bit 6 carries the sign of the 7-bit delta; bits 5-0 the magnitude.
|
|
#define MOUSE_DELTA_MASK 0x7F
|
|
#define MOUSE_DELTA_SIGN_BIT 0x40
|
|
#define MOUSE_BUTTON_INV 0x80
|
|
|
|
// Polls a key stays "down" after the last observed strobe. Covers the
|
|
// typematic initial delay so a held key does not flicker off/on between
|
|
// repeats.
|
|
#define KEY_TTL 45
|
|
|
|
#define ASCII_TABLE_SIZE 128
|
|
|
|
// Apple II arrow-key ASCII conventions.
|
|
#define ASCII_LEFT 0x08
|
|
#define ASCII_RIGHT 0x15
|
|
#define ASCII_UP 0x0B
|
|
#define ASCII_DOWN 0x0A
|
|
#define ASCII_RETURN 0x0D
|
|
#define ASCII_TAB 0x09
|
|
#define ASCII_ESCAPE 0x1B
|
|
#define ASCII_DELETE 0x7F
|
|
#define ASCII_SPACE 0x20
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static void buildAsciiTable(void);
|
|
static void pollJoystick(void);
|
|
static void pollMouse(void);
|
|
static void readModifierKeys(void);
|
|
static int8_t signExtend7(uint8_t raw);
|
|
static int8_t thresholdPaddle(uint8_t v);
|
|
|
|
// ----- Module state -----
|
|
|
|
// ASCII -> JoeyKeyE, filled once at halInputInit. ORCA/C is C89 and
|
|
// does not accept designated initializers; runtime fill keeps lookup
|
|
// O(1) instead of a 40-plus-case switch.
|
|
static uint8_t gAsciiToKey[ASCII_TABLE_SIZE];
|
|
static uint8_t gKeyTtl [KEY_COUNT];
|
|
|
|
static int16_t gMouseAbsX = SURFACE_WIDTH / 2;
|
|
static int16_t gMouseAbsY = SURFACE_HEIGHT / 2;
|
|
|
|
// ----- Internal helpers -----
|
|
|
|
static void buildAsciiTable(void) {
|
|
uint16_t i;
|
|
|
|
memset(gAsciiToKey, 0, sizeof(gAsciiToKey));
|
|
|
|
for (i = 'A'; i <= 'Z'; i++) {
|
|
gAsciiToKey[i] = (uint8_t)(KEY_A + (i - 'A'));
|
|
gAsciiToKey[i - 'A' + 'a'] = (uint8_t)(KEY_A + (i - 'A'));
|
|
}
|
|
for (i = '0'; i <= '9'; i++) {
|
|
gAsciiToKey[i] = (uint8_t)(KEY_0 + (i - '0'));
|
|
}
|
|
|
|
gAsciiToKey[ASCII_SPACE] = KEY_SPACE;
|
|
gAsciiToKey[ASCII_ESCAPE] = KEY_ESCAPE;
|
|
gAsciiToKey[ASCII_RETURN] = KEY_RETURN;
|
|
gAsciiToKey[ASCII_TAB] = KEY_TAB;
|
|
gAsciiToKey[ASCII_DELETE] = KEY_BACKSPACE;
|
|
// The left-arrow key produces 0x08, which is also ASCII backspace
|
|
// on a classic Apple II. Prefer the arrow interpretation since
|
|
// there is a dedicated Delete key that reports 0x7F.
|
|
gAsciiToKey[ASCII_LEFT] = KEY_LEFT;
|
|
gAsciiToKey[ASCII_RIGHT] = KEY_RIGHT;
|
|
gAsciiToKey[ASCII_UP] = KEY_UP;
|
|
gAsciiToKey[ASCII_DOWN] = KEY_DOWN;
|
|
}
|
|
|
|
|
|
static void readModifierKeys(void) {
|
|
uint8_t mods;
|
|
|
|
mods = *IIGS_MODIFIERS;
|
|
gKeyState[KEY_LSHIFT] = (mods & MOD_SHIFT) != 0;
|
|
gKeyState[KEY_LCTRL] = (mods & MOD_CONTROL) != 0;
|
|
gKeyState[KEY_LALT] = (mods & MOD_OPTION) != 0;
|
|
}
|
|
|
|
|
|
// Sign-extend a 7-bit two's-complement number stored in bits 0-6.
|
|
static int8_t signExtend7(uint8_t raw) {
|
|
uint8_t v;
|
|
|
|
v = (uint8_t)(raw & MOUSE_DELTA_MASK);
|
|
if (v & MOUSE_DELTA_SIGN_BIT) {
|
|
return (int8_t)(v | 0x80);
|
|
}
|
|
return (int8_t)v;
|
|
}
|
|
|
|
|
|
// Threshold a 0..255 paddle reading into a digital direction so the
|
|
// IIgs analog stick presents the same axis semantics as the digital
|
|
// sticks on ST/Amiga/DOS. Center range is treated as zero.
|
|
static int8_t thresholdPaddle(uint8_t v) {
|
|
if (v < PADDLE_LO_THRESHOLD) {
|
|
return JOYSTICK_AXIS_MIN;
|
|
}
|
|
if (v > PADDLE_HI_THRESHOLD) {
|
|
return JOYSTICK_AXIS_MAX;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Read the Apple IIgs joystick (paddle 0/1 + buttons 0/1). Buttons at
|
|
// $C061/$C062 are tied to the Open-Apple/Closed-Apple keys, so holding
|
|
// either modifier key looks like a fire press -- intentional Apple
|
|
// behavior, accept it. Only one stick is exposed; the IIgs second
|
|
// "stick" wiring (paddles 2/3) is rarely used by retro games.
|
|
//
|
|
// Each paddle read triggers an RC scan via $C070 and then polls the
|
|
// paddle softswitch until bit 7 clears; the iteration count
|
|
// approximates the paddle's 0..255 position (the Apple firmware
|
|
// PREAD routine works the same way). The two reads are inlined here
|
|
// rather than factored into a helper because ORCA/C 2.1 trips over
|
|
// `volatile uint8_t *` function parameters.
|
|
static void pollJoystick(void) {
|
|
uint16_t count;
|
|
uint8_t px;
|
|
uint8_t py;
|
|
uint8_t byte;
|
|
|
|
byte = *IIGS_PTRIG;
|
|
px = 255;
|
|
for (count = 0; count < PADDLE_TIMEOUT; count++) {
|
|
byte = *IIGS_PADDLE0;
|
|
if ((byte & IIGS_PADDLE_BUSY) == 0) {
|
|
px = (uint8_t)count;
|
|
break;
|
|
}
|
|
}
|
|
|
|
byte = *IIGS_PTRIG;
|
|
py = 255;
|
|
for (count = 0; count < PADDLE_TIMEOUT; count++) {
|
|
byte = *IIGS_PADDLE1;
|
|
if ((byte & IIGS_PADDLE_BUSY) == 0) {
|
|
py = (uint8_t)count;
|
|
break;
|
|
}
|
|
}
|
|
|
|
gJoyAxisX[JOYSTICK_0] = thresholdPaddle(px);
|
|
gJoyAxisY[JOYSTICK_0] = thresholdPaddle(py);
|
|
gJoyButtonState[JOYSTICK_0][JOY_BUTTON_0] = (*IIGS_BTN0 & IIGS_BUTTON_BIT) != 0;
|
|
gJoyButtonState[JOYSTICK_0][JOY_BUTTON_1] = (*IIGS_BTN1 & IIGS_BUTTON_BIT) != 0;
|
|
|
|
gJoyConnected[JOYSTICK_0] = true;
|
|
gJoyConnected[JOYSTICK_1] = false;
|
|
}
|
|
|
|
|
|
// Drain one X+Y delta pair from the ADB mouse FIFO. $C027 bit 1 tells
|
|
// us which coordinate the next $C024 read will return; we honor that
|
|
// rather than assuming an order, so we stay in sync even if a stray
|
|
// $C024 read happened between frames. The Y read also carries the
|
|
// inverted button state in bit 7 (0 = pressed).
|
|
static void pollMouse(void) {
|
|
uint8_t status;
|
|
uint8_t data;
|
|
int8_t delta;
|
|
int16_t newPos;
|
|
bool isYRead;
|
|
uint16_t i;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
status = *IIGS_KMSTATUS;
|
|
isYRead = (status & KMSTATUS_MOUSE_COORD) != 0;
|
|
data = *IIGS_MOUSEDATA;
|
|
delta = signExtend7(data);
|
|
|
|
if (isYRead) {
|
|
newPos = (int16_t)(gMouseAbsY + delta);
|
|
if (newPos < 0) { newPos = 0; }
|
|
if (newPos > SURFACE_HEIGHT - 1) { newPos = SURFACE_HEIGHT - 1; }
|
|
gMouseAbsY = newPos;
|
|
// Button bit only meaningful on Y reads. 0 = pressed.
|
|
gMouseButtonState[MOUSE_BUTTON_LEFT] = (data & MOUSE_BUTTON_INV) == 0;
|
|
} else {
|
|
newPos = (int16_t)(gMouseAbsX + delta);
|
|
if (newPos < 0) { newPos = 0; }
|
|
if (newPos > SURFACE_WIDTH - 1) { newPos = SURFACE_WIDTH - 1; }
|
|
gMouseAbsX = newPos;
|
|
}
|
|
}
|
|
|
|
gMouseX = gMouseAbsX;
|
|
gMouseY = gMouseAbsY;
|
|
// The ADB mouse only reports the single physical button; right
|
|
// and middle stay false.
|
|
gMouseButtonState[MOUSE_BUTTON_RIGHT] = false;
|
|
gMouseButtonState[MOUSE_BUTTON_MIDDLE] = false;
|
|
}
|
|
|
|
|
|
// ----- HAL API (alphabetical) -----
|
|
|
|
void halInputInit(void) {
|
|
memset(gKeyState, 0, sizeof(gKeyState));
|
|
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
|
memset(gKeyTtl, 0, sizeof(gKeyTtl));
|
|
buildAsciiTable();
|
|
|
|
gMouseAbsX = SURFACE_WIDTH / 2;
|
|
gMouseAbsY = SURFACE_HEIGHT / 2;
|
|
gMouseX = gMouseAbsX;
|
|
gMouseY = gMouseAbsY;
|
|
|
|
// Clear any pending strobe from before we started.
|
|
(void)*IIGS_KBDSTRB;
|
|
}
|
|
|
|
|
|
void halInputPoll(void) {
|
|
uint8_t kbd;
|
|
uint8_t ascii;
|
|
uint8_t key;
|
|
uint16_t i;
|
|
|
|
for (i = 0; i < KEY_COUNT; i++) {
|
|
if (gKeyTtl[i] > 0) {
|
|
gKeyTtl[i]--;
|
|
if (gKeyTtl[i] == 0) {
|
|
gKeyState[i] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
kbd = *IIGS_KBD;
|
|
if (kbd & KBD_STROBE_BIT) {
|
|
ascii = (uint8_t)(kbd & KBD_ASCII_MASK);
|
|
key = gAsciiToKey[ascii];
|
|
if (key != KEY_NONE) {
|
|
gKeyState[key] = true;
|
|
gKeyTtl[key] = KEY_TTL;
|
|
}
|
|
(void)*IIGS_KBDSTRB;
|
|
}
|
|
|
|
readModifierKeys();
|
|
pollMouse();
|
|
pollJoystick();
|
|
}
|
|
|
|
|
|
void halInputShutdown(void) {
|
|
(void)*IIGS_KBDSTRB;
|
|
}
|