/** * @file controller.h * @brief Input binding: SDL events in, actor state changes out. * * A control map ties one actor to one keyboard and one gamepad, and holds up to * #AKGL_MAX_CONTROLS bindings. A binding says "when event X arrives from device * Y carrying button or key Z, call this handler". Eight maps means up to eight * locally controlled players, each on its own device. * * The host pumps SDL events into akgl_controller_handle_event(), which scans the * maps in order and stops at the first binding that claims the event -- so a key * bound in two maps only fires once, in the lower-numbered one. * * Alongside that, and independent of it, every key press is pushed into a small * ring buffer that akgl_controller_poll_key() and akgl_controller_poll_keystroke() * drain. That serves a caller that wants "is there a key waiting" without owning * an event loop, and it sees keys whether or not a control map also claimed them. * * The two pollers read the same buffer and differ in what they hand back. A game * asking "was the up arrow pressed" wants a keycode and nothing else, which is * akgl_controller_poll_key(). A line editor asking "what did the user type" needs * the modifier state and the character SDL composed -- there is no `"` keycode, * and no keycode at all for a dead key or an IME commit -- which is * akgl_controller_poll_keystroke(). */ #ifndef _CONTROLLER_H_ #define _CONTROLLER_H_ #include #include #include "types.h" // The binding handlers below take an akgl_Actor *, so this header cannot be // included before akgl/actor.h without one. akgl_Actor is a typedef of a named // struct, and repeating a typedef is not C99, so it is included rather than // forward-declared. actor.h reaches only types.h and character.h, so this does // not close a cycle. #include /** @brief How many control maps exist -- effectively the local player limit. */ #define AKGL_MAX_CONTROL_MAPS 8 /** @brief Bindings per control map. The default map installed by akgl_controller_default uses 8 of them. */ #define AKGL_MAX_CONTROLS 32 /** * @brief Keystrokes akgl_controller_handle_event() will hold for a poller. * * The Commodore keyboard buffer this serves held ten. Thirty-two is enough that * a program which polls once per frame never loses a key to a fast typist, and * small enough that the buffer stays a fixed-size object in the library's data * segment. */ #define AKGL_CONTROLLER_KEY_BUFFER 32 /** * @brief Bytes of composed text one buffered keystroke can carry, NUL included. * * A UTF-8 code point is at most four bytes, so this holds one character and its * terminator with room to spare. An input event carrying more than fits -- an * IME committing a whole word at once -- is truncated on a code point boundary * rather than split through the middle of one. */ #define AKGL_CONTROLLER_KEYSTROKE_TEXT 8 /** @brief One buffered keystroke: which key, which modifiers, and what it typed. */ typedef struct { SDL_Keycode key; /**< The keycode, or 0 for an entry that carries only composed text -- an IME commit, or a character finished by a dead key. */ SDL_Keymod mod; /**< Modifier state when the key went down. 0 on a text-only entry, whose text already reflects them. */ char text[AKGL_CONTROLLER_KEYSTROKE_TEXT]; /**< What the keystroke composed to, UTF-8. The empty string for a key that produces no character, such as an arrow or a bare modifier. */ } akgl_Keystroke; /** @brief Maps one SDL input to pressed and released callbacks. */ typedef struct { uint32_t event_on; /**< SDL event type that fires `handler_on`, e.g. `SDL_EVENT_KEY_DOWN`. */ uint32_t event_off; /**< SDL event type that fires `handler_off`, e.g. `SDL_EVENT_KEY_UP`. */ uint8_t button; /**< Gamepad button (`SDL_GamepadButton`) this binding is for. Only consulted for gamepad events. */ SDL_Keycode key; /**< Keycode this binding is for. Only consulted for keyboard events. */ uint8_t axis; /**< Analogue axis. Declared but not yet consulted by akgl_controller_handle_event. */ uint8_t axis_range_min; /**< Low end of the axis range that counts as "on". Not yet consulted. */ uint8_t axis_range_max; /**< High end of that range. Not yet consulted. */ akerr_ErrorContext AKERR_NOIGNORE *(*handler_on)(akgl_Actor *obj, SDL_Event *event); /**< Called with the map's target on `event_on`. Required if `event_on` can fire. */ akerr_ErrorContext AKERR_NOIGNORE *(*handler_off)(akgl_Actor *obj, SDL_Event *event); /**< Called with the map's target on `event_off`. */ } akgl_Control; /** @brief Groups input bindings for one actor and its input devices. */ typedef struct { akgl_Actor *target; /**< The actor these bindings drive. A `NULL` target makes the whole map inert, which is how an unused slot is spelled. */ uint16_t nextMap; /**< Number of bindings in use; the index akgl_controller_pushmap writes to next. */ akgl_Control controls[AKGL_MAX_CONTROLS]; /**< The bindings, scanned in order. */ SDL_KeyboardID kbid; /**< Keyboard this map listens to. A keyboard event from any other id is ignored, which is what keeps two players on two keyboards apart. */ SDL_JoystickID jsid; /**< Gamepad this map listens to, matched the same way. */ SDL_MouseID mouseid; /**< Mouse this map listens to. Declared but not yet consulted. */ SDL_PenID penid; /**< Pen this map listens to. Declared but not yet consulted. */ } akgl_ControlMap; /** @brief Every control map. Zeroed by akgl_game_init; index it by the same id the functions below take. */ extern akgl_ControlMap GAME_ControlMaps[AKGL_MAX_CONTROL_MAPS]; /** * @brief Log every attached keyboard and its SDL id. * * A diagnostic, not a query: it writes to the SDL log rather than returning * anything. Its use is finding the `kbid` to hand akgl_controller_default when * more than one keyboard is attached. * * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If SDL cannot enumerate keyboards. The message * carries `SDL_GetError()`; note that "no keyboards attached" is * reported by SDL as an empty list, not as a failure. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void); /** * @brief Dispatch one SDL event to whichever control map binds it. * * The entry point for the whole subsystem -- call it for every event the host * pumps. A key press is recorded in the poll buffer first, whether or not any * map wants it, and an `SDL_EVENT_TEXT_INPUT` is attached to the press it * belongs to; then the maps are scanned in index order and, within a map, * bindings in the order they were pushed. The **first** binding whose event type * and device id and button/key all match wins, and the scan stops there. * * An event nothing binds is not an error: it returns success having done * nothing, which is what lets a host pass every event through unconditionally. * * @param appstate Passed through from SDL's callback. Required -- but only as a * non-`NULL` token: nothing here reads it. Pass any non-`NULL` * pointer if your program has no app state. * @param event The event to dispatch. Required. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`. * @throws AKERR_* Whatever the matched binding's handler raises. * * @warning A matched binding's handler pointer is not checked, so a control * pushed with a `NULL` `handler_on` or `handler_off` crashes when its * event arrives. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_event(void *appstate, SDL_Event *event); /** * @brief Declared but not defined under this name. Do not call. * * The implementation exists as `gamepad_handle_button_down` in `src/controller.c` * and is `static`-in-spirit -- it is not declared anywhere -- so a caller that * uses this declaration compiles and then fails to link. TODO.md, "Known and * still open" item 10. * * @param appstate Application state supplied by SDL. * @param event SDL input event to process. * @return Nothing; it cannot be called. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_down(void *appstate, SDL_Event *event); /** * @brief Declared but not defined under this name. Do not call. * * See akgl_controller_handle_button_down. The implementation is * `gamepad_handle_button_up`. * * @param appstate Application state supplied by SDL. * @param event SDL input event to process. * @return Nothing; it cannot be called. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_up(void *appstate, SDL_Event *event); /** * @brief Declared but not defined under this name. Do not call. * * See akgl_controller_handle_button_down. The implementation is * `gamepad_handle_added`, which opens a newly plugged-in gamepad. * * @param appstate Application state supplied by SDL. * @param event SDL input event to process. * @return Nothing; it cannot be called. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_added(void *appstate, SDL_Event *event); /** * @brief Declared but not defined under this name. Do not call. * * See akgl_controller_handle_button_down. The implementation is * `gamepad_handle_removed`, which closes an unplugged gamepad. * * @param appstate Application state supplied by SDL. * @param event SDL input event to process. * @return Nothing; it cannot be called. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate, SDL_Event *event); /** * @brief Append a binding to a control map. * * The binding is copied, so the caller's `akgl_Control` can be a stack local * reused across pushes -- which is exactly what akgl_controller_default does. * Bindings can only be appended; there is no remove, and no way to reset a map * short of zeroing it in ::GAME_ControlMaps directly. * * @param controlmapid Which map to append to, 0 through * #AKGL_MAX_CONTROL_MAPS - 1. * @param control The binding to copy in. Required. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p control is `NULL`. * @throws AKERR_OUTOFBOUNDS If @p controlmapid is at or above * #AKGL_MAX_CONTROL_MAPS, or if the map already holds * #AKGL_MAX_CONTROLS bindings. * * @warning A **negative** @p controlmapid is not rejected -- only the upper * bound is checked -- and indexes before the start of * ::GAME_ControlMaps. TODO.md, "Known and still open" item 11. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akgl_Control *control); /** * @brief Bind an actor to the arrow keys and the D-pad, in one call. * * Points the map at the named actor and pushes eight bindings: the four arrow * keys and the four D-pad directions, each wired to the matching * `akgl_Actor_cmhf_*_on`/`_off` pair. It is the "just give me something that * works" path -- a game wanting different keys builds its own bindings with * akgl_controller_pushmap. * * @param controlmapid Which map to configure, 0 through * #AKGL_MAX_CONTROL_MAPS - 1. * @param actorname Registry name of the actor to drive. Required in practice, * though a `NULL` is reported as "not found" rather than as * a null pointer. * @param kbid SDL keyboard id to listen to. Only events from this * keyboard match; see akgl_controller_list_keyboards for * how to find it. * @param jsid SDL gamepad id to listen to, matched the same way. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_OUTOFBOUNDS If @p controlmapid is at or above * #AKGL_MAX_CONTROL_MAPS, or if the map cannot hold eight more bindings. * @throws AKGL_ERR_REGISTRY If @p actorname is not in #AKGL_REGISTRY_ACTOR -- * usually because the actor has not been created yet. * * @warning A negative @p controlmapid is not rejected. See * akgl_controller_pushmap. * @note It appends rather than replaces, so calling it twice on the same map * leaves sixteen bindings and the first eight are the ones that fire. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, char *actorname, int kbid, int jsid); /** * @brief Open every gamepad currently attached. * * SDL will not deliver button events from a gamepad nobody has opened, so this * runs once at startup -- akgl_game_init calls it. Devices plugged in later are * SDL_EVENT_GAMEPAD_ADDED events, handled elsewhere. * * No gamepads attached is success, not an error. * * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If SDL reports gamepads present but cannot * enumerate them, or if one of them cannot be opened. The message * carries `SDL_GetError()`. * * @note The enumeration array is only freed on the success path, so a failure * part-way through leaks it. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_open_gamepads(void); /** * @brief Take the oldest waiting keystroke, if there is one. * * The rest of this header is built around the host pumping SDL events into * akgl_controller_handle_event(), which suits a game loop and does not suit an * embedded interpreter asking "is there a key waiting, yes or no" without * owning the event loop itself. Every key press that reaches * akgl_controller_handle_event() is recorded in a fixed ring buffer first, * whether or not a control map claims it, and this drains that buffer one * keystroke per call. * * When no key is waiting the call still succeeds: @p available is set to * `false` and @p keycode to 0. The caller polls, it does not block. * * A full buffer drops the *newest* keystroke rather than the oldest, so what * was typed first is what is read first. This runs on whichever thread pumps * events; it is not synchronized. * * Entries that carry only composed text and no keycode are discarded on the way * past rather than reported as keycode 0: this form is for a caller that acts on * keys, and there is no key to report. A caller that wants those characters uses * akgl_controller_poll_keystroke() instead. * * @param keycode Receives the SDL keycode, or 0 when nothing was waiting. * Required -- the return value is the error context. * @param available Receives `true` when a keystroke was taken, `false` when the * buffer was empty. Required. Check this rather than testing * @p keycode against 0. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p keycode or @p available is `NULL`. Both are * required; there is no "I only want to know whether one is waiting" * form. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *available); /** * @brief Take the oldest waiting keystroke whole: key, modifiers and text. * * akgl_controller_poll_key() with nothing thrown away. A line editor needs all * three: the keycode to recognise Backspace and Return, the modifier state to * tell Ctrl-C from a `c`, and the composed text because a keycode cannot express * what a shifted key produces on the user's own layout. `"`, `!`, `(`, `)`, `:` * and `;` are all unreachable from a keycode alone, and so is every lower-case * letter. * * The text is what SDL composed, taken from the `SDL_EVENT_TEXT_INPUT` that * follows the key press -- the only correct way to get a character out of SDL, * and what makes a keyboard layout, a compose key and a dead key work. **SDL * only sends those events while text input is started**, so a host that wants * the `text` field populated calls `SDL_StartTextInput()` on its window first. * Without it, `key` and `mod` still arrive and `text` is always empty. * * The two pollers drain the same buffer, so a keystroke taken by one is not * waiting for the other. * * @param dest Receives the keystroke. Required. Written only when a * keystroke was waiting; zeroed otherwise, so `key` is 0 and * `text` is the empty string. * @param available Receives `true` when a keystroke was taken, `false` when the * buffer was empty. Required. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p dest or @p available is `NULL`. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_keystroke(akgl_Keystroke *dest, bool *available); /** * @brief Discard every keystroke waiting in the buffer. * * For a caller that has been ignoring input and does not want a backlog acted * on the moment it starts polling again. * * This empties only the polling buffer. Keys already dispatched to control maps * have had their effect and cannot be taken back. * * @return `NULL`. There is no failure path -- it resets two counters. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void); #endif // _CONTROLLER_H_