Let a control map listen to any keyboard, and fix the dead arrow keys
The sidescroller's controls did nothing. `akgl_controller_handle_event` matched `event->key.which == curmap->kbid` exactly, with no way to say "whatever keyboard the player is typing on", so the game did the obvious thing and bound `SDL_GetKeyboards()[0]`. That cannot work. The id a key event *carries* is chosen by the video backend and is not required to be an id `SDL_GetKeyboards()` reports. On X11 without XInput2 every key event carries `SDL_GLOBAL_KEYBOARD_ID`, which is 0, while `SDL_AddKeyboard` registers the attached keyboard as `SDL_DEFAULT_KEYBOARD_ID`, which is 1; with XInput2 the events carry the physical slave device's `sourceid` while `SDL_GetKeyboards()[0]` may be the master. Either way the map matched nothing and every key press was dropped. A `kbid` or `jsid` of 0 now matches any device of that kind. 0 is safe to spend: SDL documents `which` as 0 when the source is unknown or virtual, and joystick ids start at 1, so no real device is 0. A non-zero id still matches only that device, which is what keeps two local players on two keyboards apart, and there is a test for that half too. `util/charviewer.c` already passed 0 and depended on the old behaviour by accident; it works under XInput2 now as well. Two reasons this shipped, both closed: - Every test in tests/controller.c dispatched an event whose id equalled the id the map was bound with, so none of them could see it. - The sidescroller's smoke test called the control handlers directly instead of dispatching events, so it passed against a control map that matched nothing. It now pushes synthetic events through akgl_controller_handle_event from a deliberately non-zero device id and fails if the press does not arrive. Verified by breaking the binding and watching the run exit non-zero. `test_controller_wildcard_device_ids` was written first and failed against the unfixed library with "a map bound to keyboard 0 ignored a key press from device 11", which is the whole reason to trust it now that it passes. The controls were documented, in one sentence. Chapter 19 has a proper table of them, and chapter 15 explains why not to reach for SDL_GetKeyboards(). The header said the match was exact and now says what it does. Co-Authored-By: Claude Code <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -87,7 +87,7 @@ 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_KeyboardID kbid; /**< Keyboard this map listens to, or 0 for any. A non-zero id ignores every other keyboard, 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. */
|
||||
@@ -249,10 +249,14 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg
|
||||
* @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.
|
||||
* @param kbid SDL keyboard id to listen to, or **0 for any keyboard**,
|
||||
* which is what a single-player game wants. Do not pass
|
||||
* `SDL_GetKeyboards()[0]`: that reports what is attached,
|
||||
* while the id an event carries is chosen by the video
|
||||
* backend, and the two need not agree. See
|
||||
* akgl_controller_list_keyboards.
|
||||
* @param jsid SDL gamepad id to listen to, or 0 for any gamepad,
|
||||
* 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.
|
||||
|
||||
Reference in New Issue
Block a user