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:
@@ -122,8 +122,29 @@ defaults are already in slots 0–7 and your additions land after them.
|
||||
A match requires **all three** of: the event type equals the binding's `event_on` or
|
||||
`event_off`; the device id on the event equals the map's `kbid` (keyboard events) or `jsid`
|
||||
(gamepad events); and the key or button equals the binding's. The device-id half is what
|
||||
keeps two players on two keyboards apart, and it is exact — `akgl_controller_list_keyboards`
|
||||
logs every attached keyboard and its id, which is how you find the number to pass.
|
||||
keeps two players on two keyboards apart — `akgl_controller_list_keyboards` logs every
|
||||
attached keyboard and its id, which is how you find the number to pass.
|
||||
|
||||
### Bind device 0 unless you mean a specific device
|
||||
|
||||
**A `kbid` or `jsid` of `0` matches every device of that kind**, and it is what a
|
||||
single-player game wants.
|
||||
|
||||
Do not reach for `SDL_GetKeyboards()` to fill it in. That call reports what is *attached*;
|
||||
the id a key event actually *carries* is chosen by the video backend, and the two need not
|
||||
agree. On X11 without XInput2 every key event carries `SDL_GLOBAL_KEYBOARD_ID`, which is
|
||||
`0`, while SDL registers the attached keyboard as `SDL_DEFAULT_KEYBOARD_ID`, which is `1`.
|
||||
With XInput2 the events carry the physical slave device's `sourceid` and
|
||||
`SDL_GetKeyboards()[0]` may be the master. Binding `SDL_GetKeyboards()[0]` therefore gives
|
||||
you a map that matches nothing at all, and controls that silently do nothing.
|
||||
|
||||
That is not hypothetical — the sidescroller in [Chapter 19](19-tutorial-sidescroller.md)
|
||||
shipped exactly that bug, and its smoke test passed anyway because the test called the
|
||||
handlers instead of dispatching events.
|
||||
|
||||
`0` is the right spelling for "any" 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` and nothing is
|
||||
given up by spending it.
|
||||
|
||||
There is no way to remove a binding. `akgl_controller_pushmap` appends and that is the
|
||||
whole API; the only reset is zeroing `akgl_controlmaps[id]` yourself. **Calling
|
||||
|
||||
Reference in New Issue
Block a user