// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // Keyboard, mouse, and joystick input polling. // // Call jlInputPoll() once per frame (typically right before // drawing) to refresh keyboard, mouse, and joystick state. After // polling, the jlKey* predicates return the current state of every // key: // // jlKeyDown(k) -- is key k held down right now // jlKeyPressed(k) -- rising edge since the previous poll // jlKeyReleased(k) -- falling edge since the previous poll // // Typed-character input (text entry) is a separate path: // // jlInputGetChar() -- pop the next typed character, or -1 // // The key predicates are the right shape for games (is the fire key // held?); the character queue is the right shape for text fields // (which character did the user type, with shift and the machine's // keyboard layout already applied). Both are refreshed by the same // jlInputPoll() and coexist freely. // // The mouse predicates return the pointer state: // // jlMouseX/Y() -- pointer position in surface // coords (0..JL_SURFACE_WIDTH-1, // 0..JL_SURFACE_HEIGHT-1) // jlMouseDown(b) -- is button b held right now // jlMousePressed(b) -- rising edge since last poll // jlMouseReleased(b) -- falling edge since last poll // // The joystick predicates return per-stick state: // // jlJoystickConnected(js) -- true if the platform reports a // stick on this port // jlJoystickX/Y(js) -- axis values, signed -127..+127. // Digital sticks snap to the // extremes; analog sticks return // their offset from centre, scaled, // dead zone applied. // jlJoyDown(js, b) -- is button b held on stick js // jlJoyPressed(js, b) -- rising edge since last poll // jlJoyReleased(js, b) -- falling edge since last poll // // Edge predicates are one-shot: they return true only in the // frame the transition occurred and false thereafter. #ifndef JOEYLIB_INPUT_H #define JOEYLIB_INPUT_H #include "platform.h" #include "types.h" typedef enum { JL_KEY_NONE = 0, JL_KEY_A, JL_KEY_B, JL_KEY_C, JL_KEY_D, JL_KEY_E, JL_KEY_F, JL_KEY_G, JL_KEY_H, JL_KEY_I, JL_KEY_J, JL_KEY_K, JL_KEY_L, JL_KEY_M, JL_KEY_N, JL_KEY_O, JL_KEY_P, JL_KEY_Q, JL_KEY_R, JL_KEY_S, JL_KEY_T, JL_KEY_U, JL_KEY_V, JL_KEY_W, JL_KEY_X, JL_KEY_Y, JL_KEY_Z, JL_KEY_0, JL_KEY_1, JL_KEY_2, JL_KEY_3, JL_KEY_4, JL_KEY_5, JL_KEY_6, JL_KEY_7, JL_KEY_8, JL_KEY_9, JL_KEY_SPACE, JL_KEY_ESCAPE, JL_KEY_RETURN, JL_KEY_TAB, JL_KEY_BACKSPACE, JL_KEY_UP, JL_KEY_DOWN, JL_KEY_LEFT, JL_KEY_RIGHT, JL_KEY_LSHIFT, JL_KEY_RSHIFT, JL_KEY_LCTRL, JL_KEY_LALT, JL_KEY_F1, JL_KEY_F2, JL_KEY_F3, JL_KEY_F4, JL_KEY_F5, JL_KEY_F6, JL_KEY_F7, JL_KEY_F8, JL_KEY_F9, JL_KEY_F10, JL_KEY_COUNT } jlKeyE; typedef enum { JL_MOUSE_BUTTON_NONE = 0, JL_MOUSE_BUTTON_LEFT, JL_MOUSE_BUTTON_RIGHT, JL_MOUSE_BUTTON_MIDDLE, JL_MOUSE_BUTTON_COUNT } jlMouseButtonE; typedef enum { JL_JOYSTICK_0 = 0, JL_JOYSTICK_1, JL_JOYSTICK_COUNT } jlJoystickE; typedef enum { JL_JOY_BUTTON_0 = 0, JL_JOY_BUTTON_1, JL_JOY_BUTTON_COUNT } jlJoyButtonE; #define JL_JOYSTICK_AXIS_MAX 127 #define JL_JOYSTICK_AXIS_MIN (-127) // Control characters delivered through jlInputGetChar alongside // printable ASCII. Everything else below 0x20, and everything above // 0x7F, is filtered out of the queue. #define JL_CHAR_BACKSPACE 0x08 #define JL_CHAR_TAB 0x09 #define JL_CHAR_RETURN 0x0D #define JL_CHAR_ESCAPE 0x1B // The FORWARD delete (the key labelled Del/Delete on a PC, ST or Amiga keyboard - not backspace). A text // editor needs both: backspace removes the character BEFORE the caret, this one removes the character // UNDER it. 0x7F is ASCII DEL. The IIgs never delivers it: its Delete key is backspace. #define JL_CHAR_DELETE 0x7F // Typed-character queue capacity (ring buffer; one slot stays empty, // so JL_CHAR_QUEUE_SIZE - 1 characters can be pending). Must be a // power of two. #define JL_CHAR_QUEUE_SIZE 32 void jlInputPoll(void); // Pop the next typed character as a 7-bit code, or -1 if the queue // is empty. Codes: printable ASCII 0x20..0x7E, plus JL_CHAR_BACKSPACE, // JL_CHAR_TAB, JL_CHAR_RETURN, JL_CHAR_ESCAPE, JL_CHAR_DELETE. Shift, caps lock, and // the keyboard layout are already applied by the backend, so // punctuation and shifted symbols arrive correctly on every port. // (The layout is the machine's own on IIgs/Amiga/ST/X68000, DVX's on // DVX, and the OS's on the hosted ports; DOS and the PSP's on-screen // keyboard use a fixed US layout. On the ST a key held with Alt types // nothing.) // // The queue is refilled by jlInputPoll() -- no extra bring-up is // needed. It holds JL_CHAR_QUEUE_SIZE - 1 characters; when full, // further characters are dropped (drop-newest) until the app pops. // A held key repeats its character on every port (the caller manages // repeat policy). // A chord types nothing on any port: Ctrl+M is not Return. // 7-bit ASCII only; no Unicode / IME. Arrow keys are not characters // and stay on the predicate API (jlKeyDown(JL_KEY_UP) etc.). // jlWaitForAnyKey empties the queue when it returns -- its dismissing // keystroke is consumed by the wait, not delivered as text. // // Returns int (not a stdint type) deliberately: getchar()-style // "byte or -1" is the interface contract consumers expect. int jlInputGetChar(void); // Block until the user presses a key: one with a jlKeyE, or one that types // a character (punctuation, which has no jlKeyE). Mouse and joystick // buttons do not end it, and a key held when the wait starts must be let go // and pressed again. // Internally polls via jlInputPoll, so per-port jlpInputPoll machinery // (including audio-friendly IRQ-driven samplers) keeps working while // the wait loop runs. void jlWaitForAnyKey(void); // A key two physical keys give (Return and the keypad's Enter, an arrow and // the keypad's 8, 2, 4, or 6, the left and right Ctrl or Alt) is down while // either is held. bool jlKeyDown(jlKeyE key); bool jlKeyPressed(jlKeyE key); bool jlKeyReleased(jlKeyE key); // True when the platform confirmed an attached mouse at init (DOS: the INT 33h driver reset; // Amiga: JoeyLib's input window opened; Win32/Win16: Windows reports one; SDL3/web: SDL reports one; // IIgs/ST/X68000/DVX/Haiku/BeOS: always; PSP: never). The IIgs mouse has one button, the ST's and // X68000's two. bool jlMousePresent(void); int16_t jlMouseX(void); int16_t jlMouseY(void); bool jlMouseDown(jlMouseButtonE button); bool jlMousePressed(jlMouseButtonE button); bool jlMouseReleased(jlMouseButtonE button); // Every stick has two buttons. On the ST's own joystick ports, and on // the Amiga until a stick's second button is first pressed, a stick is // taken to have one: a quick press of it is JL_JOY_BUTTON_0, down for // the one poll after it is let go, and a press held for half a second // is JL_JOY_BUTTON_1, down from then until it is let go: act on button // 0 with jlJoyPressed, not jlJoyDown. bool jlJoystickConnected(jlJoystickE js); int8_t jlJoystickX(jlJoystickE js); int8_t jlJoystickY(jlJoystickE js); bool jlJoyDown(jlJoystickE js, jlJoyButtonE button); bool jlJoyPressed(jlJoystickE js, jlJoyButtonE button); bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button); // Set the stick's dead zone (8 at jlInit): axis values within `deadZone` of 0 (on the // -127..127 scale) read 0 on the ports with analog sticks (IIgs, DOS, // DVX, hosted); use 0 to disable it. On the IIgs and DOS the call also // re-centres the stick: the next reading after it is taken as the // centre, so the user must hold the stick centred when calling. The // IIgs port stops polling after about a second (60 polls) with no stick // seen (an absent stick costs ~1 ms a frame of busy-wait) and does NOT // re-probe by itself: this call resumes polling. DOS probes the game // port again; the Amiga hands the port back to lowlevel.library's own // detection. Elsewhere only the dead zone changes. void jlJoystickReset(jlJoystickE js, uint8_t deadZone); #endif