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:
25
TODO.md
25
TODO.md
@@ -2306,6 +2306,31 @@ The manual documents each of these where a reader would hit it, and points here.
|
||||
(`ACTOR_STATE_ALIVE`) and `velocity_x`/`velocity_y` instead of `speed_x`/`speed_y`.
|
||||
The current loader accepts neither.
|
||||
|
||||
19. **A control map could not say "any keyboard", so binding one was a coin flip.**
|
||||
`akgl_controller_handle_event` matched `event->key.which == curmap->kbid` exactly, and
|
||||
there was no way to express "whatever keyboard the player types on". That is not a
|
||||
theoretical gap, because the id a key event *carries* is chosen by the video backend
|
||||
and does not have to be an id `SDL_GetKeyboards()` reports: on X11 without XInput2
|
||||
every key event carries `SDL_GLOBAL_KEYBOARD_ID` (0) while SDL registers the keyboard
|
||||
as `SDL_DEFAULT_KEYBOARD_ID` (1), and with XInput2 the events carry the physical slave
|
||||
device's `sourceid` while `SDL_GetKeyboards()[0]` may be the master. A game that bound
|
||||
`SDL_GetKeyboards()[0]` -- the obvious thing -- got a map that matched nothing and
|
||||
controls that silently did nothing. `examples/sidescroller` shipped exactly that.
|
||||
|
||||
**Fixed**: `kbid`/`jsid` of 0 now match any device of that kind. 0 is safe to spend
|
||||
because 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 apart. `util/charviewer.c` was already passing 0
|
||||
and depending on the old accidental behaviour, so it works under XInput2 now too.
|
||||
|
||||
The reason this reached a release is worth keeping: **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, and the sidescroller's smoke test called the
|
||||
handlers directly rather than dispatching events, so it passed against a control map
|
||||
that matched nothing. Both are fixed -- `test_controller_wildcard_device_ids` asserts
|
||||
the contract, and the smoke test now drives `akgl_controller_handle_event` from a
|
||||
non-zero device id and fails if the press does not arrive.
|
||||
|
||||
### Header comments that describe code that has changed
|
||||
|
||||
Twenty-seven claims across the public headers were false when checked against `src/`.
|
||||
|
||||
Reference in New Issue
Block a user