// 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 #include #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; }