521 lines
20 KiB
C
521 lines
20 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.
|
|
//
|
|
// The typed-character queue is fed from the same $C000 drain: the ADB
|
|
// firmware translates shift/caps/layout before the byte reaches the
|
|
// strobe register, so each drained event doubles as a typed character
|
|
// (jlInputCharPush) with no extra keymap work.
|
|
//
|
|
// 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.
|
|
//
|
|
// Release detection uses the IIe-inherited "any key currently down"
|
|
// live flag at $C010 bit 7 (set by the keyboard scanner independently
|
|
// of the strobe). Each jlpInputPoll drains pending strobe events to
|
|
// pick up presses, then samples $C010: bit 7 == 0 means no
|
|
// non-modifier key is physically held, and we wholesale-clear
|
|
// gKeyState. readModifierKeys then re-asserts the modifiers from
|
|
// $C025's live state, so shift/ctrl/option stay accurate. Avoids
|
|
// the inferred-release lag the old TTL-decay scheme had, and works
|
|
// on every IIgs (real or stealth) without ToolBox / ADB Tool init.
|
|
//
|
|
// 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 jlpInputPoll, 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 "port.h"
|
|
#include "inputInternal.h"
|
|
#include "joey/surface.h"
|
|
#include "surfaceInternal.h" // iigsByteFill (hunt-3 rank 5)
|
|
|
|
|
|
// ----- 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
|
|
|
|
// $C010 RDKBDSTRB: reading clears the keyboard strobe at $C000 and
|
|
// returns the live "any key currently held" flag in bit 7 (set by
|
|
// the keyboard scanner / ADB MCU independently of the strobe). Used
|
|
// to drive immediate release detection without an inferred-release
|
|
// TTL counter.
|
|
#define KBD_ANY_KEY_DOWN_BIT 0x80
|
|
|
|
// Cap on the per-poll keyboard-FIFO drain. The IIgs ADB queue is
|
|
// small in practice; this is purely a defensive bound so a stuck
|
|
// strobe can't spin jlpInputPoll forever.
|
|
#define KBD_DRAIN_GUARD 32u
|
|
|
|
// $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
|
|
|
|
#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 -> jlKeyE, filled once at jlpInputInit. Runtime fill keeps
|
|
// lookup O(1) instead of a 40-plus-case switch.
|
|
static uint8_t gAsciiToKey[ASCII_TABLE_SIZE];
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
// Map a raw 0..255 paddle reading to JOYSTICK_AXIS_MIN..MAX, using the
|
|
// stick's calibrated center (captured by jlJoystickReset) and a
|
|
// dead-zone band around it. Returns 0 if reading is within deadZone of
|
|
// the center; otherwise the offset from center, clamped to int8_t.
|
|
static int8_t analogPaddle(uint8_t v, uint8_t center, uint8_t deadZone) {
|
|
int16_t delta;
|
|
|
|
delta = (int16_t)v - (int16_t)center;
|
|
if (delta < 0) {
|
|
if ((-delta) <= (int16_t)deadZone) {
|
|
return 0;
|
|
}
|
|
if (delta < (int16_t)JOYSTICK_AXIS_MIN) {
|
|
return JOYSTICK_AXIS_MIN;
|
|
}
|
|
} else {
|
|
if (delta <= (int16_t)deadZone) {
|
|
return 0;
|
|
}
|
|
if (delta > (int16_t)JOYSTICK_AXIS_MAX) {
|
|
return JOYSTICK_AXIS_MAX;
|
|
}
|
|
}
|
|
return (int8_t)delta;
|
|
}
|
|
|
|
|
|
// 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. Used
|
|
// before jlJoystickReset has been called -- once the app calibrates,
|
|
// we switch to analogPaddle for finer control.
|
|
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 paddle one-shot timer takes ~3 ms to
|
|
// charge at full deflection; if NO joystick is wired up, the BUSY bit
|
|
// stays set forever and the busy-wait runs the full PADDLE_TIMEOUT
|
|
// every frame -- ~3 ms wasted per frame on a stick that isn't there.
|
|
//
|
|
// After JOY_DISCONNECT_THRESHOLD consecutive timeouts we latch the
|
|
// stick as absent and stop polling entirely. The app calls
|
|
// jlJoystickReset to clear the latch and resume polling.
|
|
#define JOY_DISCONNECT_THRESHOLD 60u
|
|
|
|
static uint16_t gJoyConsecutiveTimeouts = 0;
|
|
static bool gJoyDisconnectLatched = false;
|
|
|
|
// Analog calibration: gJoyCenterX/Y hold the raw paddle reading we
|
|
// captured the last time the user called jlJoystickReset. Until
|
|
// that's called, gJoyCenterValid is false and pollJoystick falls back
|
|
// to the digital threshold mapping. gJoyRecalibrate is set by
|
|
// jlpJoystickReset and cleared on the next successful poll, which
|
|
// captures the new center.
|
|
// uint8_t (not bool) so the per-element stride is a known 1 byte.
|
|
// Storage is still 0 or 1 either way.
|
|
static uint8_t gJoyCenterX [JOYSTICK_COUNT];
|
|
static uint8_t gJoyCenterY [JOYSTICK_COUNT];
|
|
static uint8_t gJoyCenterValid [JOYSTICK_COUNT];
|
|
static uint8_t gJoyRecalibrate [JOYSTICK_COUNT];
|
|
|
|
|
|
void jlpJoystickReset(jlJoystickE js) {
|
|
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
|
|
return;
|
|
}
|
|
// Re-enable polling and arm a fresh center capture for the next
|
|
// poll. The dead-zone value lives in core's gJoyDeadZone[js].
|
|
gJoyConsecutiveTimeouts = 0;
|
|
gJoyDisconnectLatched = false;
|
|
gJoyRecalibrate[js] = true;
|
|
}
|
|
|
|
// Asm paddle reader (joeyDraw.s, John Brooks' 1 MHz GetJoyXY). Switches
|
|
// the CPU to 1 MHz for the read so paddle counts match what every other
|
|
// IIgs/Apple II joystick game produces (a busy-wait at 2.8 MHz inflates
|
|
// counts). Returns the paddle read packed as a uint32_t in A:X (register
|
|
// return -- asm writes to a global do not reach the C-read address in
|
|
// this toolchain):
|
|
// resolved = ret & 0xFF (bit0: JoyX valid, bit1: JoyY valid)
|
|
// px = (ret >> 8) & 0xFF (JoyX 0..255)
|
|
// py = (ret >> 16) & 0xFF (JoyY 0..255)
|
|
extern uint32_t iigsPollJoystickInner(void);
|
|
|
|
static void pollJoystick(void) {
|
|
uint32_t result;
|
|
uint8_t px;
|
|
uint8_t py;
|
|
uint8_t resolvedFlags;
|
|
bool xResolved;
|
|
bool yResolved;
|
|
|
|
// Buttons are I/O reads -- always cheap, do them every frame.
|
|
// Indexing through a (uint8_t *) cast collapses each
|
|
// gJoyButtonState[i][j] write to a literal byte offset.
|
|
((uint8_t *)gJoyButtonState)[JOYSTICK_0 * JOY_BUTTON_COUNT + JOY_BUTTON_0]
|
|
= (*IIGS_BTN0 & IIGS_BUTTON_BIT) != 0;
|
|
((uint8_t *)gJoyButtonState)[JOYSTICK_0 * JOY_BUTTON_COUNT + JOY_BUTTON_1]
|
|
= (*IIGS_BTN1 & IIGS_BUTTON_BIT) != 0;
|
|
gJoyConnected[JOYSTICK_1] = false;
|
|
|
|
// Once the stick has been latched as disconnected, only buttons
|
|
// get polled. The app must call jlJoystickReset to resume axis
|
|
// polling (e.g., when the user has just plugged in a stick).
|
|
if (gJoyDisconnectLatched) {
|
|
gJoyAxisX[JOYSTICK_0] = 0;
|
|
gJoyAxisY[JOYSTICK_0] = 0;
|
|
gJoyConnected[JOYSTICK_0] = false;
|
|
return;
|
|
}
|
|
|
|
// Asm read at 1 MHz -- result returned packed in registers (A:X).
|
|
result = iigsPollJoystickInner();
|
|
resolvedFlags = (uint8_t)(result & 0xFFu);
|
|
px = (uint8_t)((result >> 8) & 0xFFu);
|
|
py = (uint8_t)((result >> 16) & 0xFFu);
|
|
xResolved = (resolvedFlags & 0x01) != 0;
|
|
yResolved = (resolvedFlags & 0x02) != 0;
|
|
|
|
gJoyConnected[JOYSTICK_0] = xResolved || yResolved;
|
|
|
|
// Update auto-disconnect counter. Both axes failing => probably no
|
|
// stick. One resolves => stick is present, reset the counter.
|
|
//
|
|
// gJoyConsecutiveTimeouts uses a local-var read-modify-write rather
|
|
// than `++` so the update is DBR-independent: an `inc abs` on the
|
|
// static would depend on DBR pointing at this static's bank, and a
|
|
// cross-segment JSL doesn't update DBR, so a caller in a different
|
|
// load segment could silently mutate the wrong byte. Long-mode
|
|
// lda+sta avoids that.
|
|
if (!xResolved && !yResolved) {
|
|
uint16_t timeouts;
|
|
|
|
timeouts = gJoyConsecutiveTimeouts;
|
|
if (timeouts < 0xFFFFu) {
|
|
timeouts = (uint16_t)(timeouts + 1u);
|
|
gJoyConsecutiveTimeouts = timeouts;
|
|
}
|
|
if (timeouts >= JOY_DISCONNECT_THRESHOLD) {
|
|
gJoyDisconnectLatched = true;
|
|
}
|
|
gJoyAxisX[JOYSTICK_0] = 0;
|
|
gJoyAxisY[JOYSTICK_0] = 0;
|
|
return;
|
|
}
|
|
|
|
gJoyConsecutiveTimeouts = 0;
|
|
|
|
// Capture the resting position on recalibrate (one-shot).
|
|
if (gJoyRecalibrate[JOYSTICK_0]) {
|
|
gJoyCenterX [JOYSTICK_0] = px;
|
|
gJoyCenterY [JOYSTICK_0] = py;
|
|
gJoyCenterValid[JOYSTICK_0] = true;
|
|
gJoyRecalibrate[JOYSTICK_0] = false;
|
|
}
|
|
|
|
// Calibrated => analog axis report (offset from center, dead-zone
|
|
// clamped). Uncalibrated => the legacy 3-state digital threshold,
|
|
// matching how the stick behaved before jlJoystickReset existed.
|
|
if (gJoyCenterValid[JOYSTICK_0]) {
|
|
gJoyAxisX[JOYSTICK_0] = analogPaddle(px,
|
|
gJoyCenterX[JOYSTICK_0],
|
|
gJoyDeadZone[JOYSTICK_0]);
|
|
gJoyAxisY[JOYSTICK_0] = analogPaddle(py,
|
|
gJoyCenterY[JOYSTICK_0],
|
|
gJoyDeadZone[JOYSTICK_0]);
|
|
} else {
|
|
gJoyAxisX[JOYSTICK_0] = thresholdPaddle(px);
|
|
gJoyAxisY[JOYSTICK_0] = thresholdPaddle(py);
|
|
}
|
|
}
|
|
|
|
|
|
// 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 jlpInputInit(void) {
|
|
memset(gKeyState, 0, sizeof(gKeyState));
|
|
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
|
buildAsciiTable();
|
|
|
|
gMouseAttached = true; // the ADB mouse is part of every IIgs
|
|
gMouseAbsX = SURFACE_WIDTH / 2;
|
|
gMouseAbsY = SURFACE_HEIGHT / 2;
|
|
gMouseX = gMouseAbsX;
|
|
gMouseY = gMouseAbsY;
|
|
|
|
// Clear any pending strobe from before we started.
|
|
(void)*IIGS_KBDSTRB;
|
|
}
|
|
|
|
|
|
void jlpInputPoll(void) {
|
|
// Keep the serial soft ring fed across the input scan (multiple I/O-page reads).
|
|
{
|
|
extern void iigsSerialPump(void);
|
|
|
|
iigsSerialPump();
|
|
}
|
|
uint8_t kbd;
|
|
uint8_t ascii;
|
|
uint8_t key;
|
|
uint8_t kbdStrb;
|
|
uint16_t drainGuard;
|
|
bool strobeObserved;
|
|
|
|
// The gKeyState/gKeyPrev/gMouseButtonPrev/gJoyButtonPrev snapshots
|
|
// all happen earlier in jlInputPoll's call to iigsInputSnapshot
|
|
// (asm). We just read the live hardware state here.
|
|
|
|
// Drain the keyboard FIFO, not just the head. The IIgs ADB MCU
|
|
// queues press + autorepeat events; consuming only one per poll
|
|
// would leave queued events waiting to refresh state on later
|
|
// polls. KBD_DRAIN_GUARD bounds the loop in case a stuck strobe
|
|
// ever fails to clear.
|
|
strobeObserved = false;
|
|
for (drainGuard = 0; drainGuard < KBD_DRAIN_GUARD; drainGuard++) {
|
|
{
|
|
extern void iigsSerialPump(void);
|
|
|
|
// Per-iteration pump (the X68000 per-group lesson): the FIFO drain + decode below is a
|
|
// multi-ms deaf window at 9600 and the Z8530 FIFO is 3 bytes - a pump only at poll ENTRY
|
|
// measurably lost a 4-byte clump mid-frame.
|
|
iigsSerialPump();
|
|
}
|
|
kbd = *IIGS_KBD;
|
|
if ((kbd & KBD_STROBE_BIT) == 0) {
|
|
break;
|
|
}
|
|
strobeObserved = true;
|
|
ascii = (uint8_t)(kbd & KBD_ASCII_MASK);
|
|
key = gAsciiToKey[ascii];
|
|
if (key != KEY_NONE) {
|
|
gKeyState[key] = true;
|
|
}
|
|
// Typed-character path: the ADB firmware already applies
|
|
// shift/caps and the keyboard layout, so the strobe byte IS
|
|
// the translated character. The Delete key (0x7F, the IIgs
|
|
// backspace key) is normalized to JL_CHAR_BACKSPACE. A raw
|
|
// 0x08 is the LEFT-ARROW key (Apple II heritage), and arrows
|
|
// are not characters -- suppress it so cursor movement never
|
|
// types a backspace; the other arrows' codes are stopped by
|
|
// jlInputCharPush's filter anyway.
|
|
if (ascii == ASCII_DELETE) {
|
|
jlInputCharPush(JL_CHAR_BACKSPACE);
|
|
} else if (ascii != ASCII_LEFT) {
|
|
jlInputCharPush(ascii);
|
|
}
|
|
(void)*IIGS_KBDSTRB;
|
|
}
|
|
|
|
// $C010 bit 7 is the live "any non-modifier key currently held"
|
|
// flag (IIe-inherited; updated by the keyboard scanner / ADB MCU
|
|
// independently of the strobe). When 0 we know all non-modifier
|
|
// keys are physically released, so wholesale-clear gKeyState and
|
|
// let readModifierKeys re-assert the modifiers from $C025 below.
|
|
//
|
|
// strobeObserved guard: a press that arrived AND was released
|
|
// between two polls would otherwise be set-then-cleared in a
|
|
// single poll, losing the rising edge that jlKeyPressed needs.
|
|
// Holding the press for one poll preserves it; the next poll's
|
|
// bit-7 read will clear normally.
|
|
kbdStrb = *IIGS_KBDSTRB;
|
|
if (!strobeObserved && (kbdStrb & KBD_ANY_KEY_DOWN_BIT) == 0) {
|
|
// iigsByteFill, not memset: llvm-mos lowers memset to a
|
|
// far-call byte loop (finding #79), and this branch runs on
|
|
// nearly EVERY real game frame (idle keyboard). Hunt-3 rank 5.
|
|
iigsByteFill(gKeyState, 0u, (uint16_t)sizeof(gKeyState));
|
|
}
|
|
|
|
readModifierKeys();
|
|
pollMouse();
|
|
pollJoystick();
|
|
}
|
|
|
|
|
|
void jlpInputShutdown(void) {
|
|
(void)*IIGS_KBDSTRB;
|
|
}
|