Repository navigation
fix(win32): read a HID pad's buttons with more than 64 of them down - #495
Conversation
siracozmen01
left a comment
There was a problem hiding this comment.
Review at 17bf80a: ready. No findings. Read, not run.
Does 8192 bound what HidP_GetUsages returns?
- A report the raw input path parses is at most
MWIN_WIN32_HID_INPUT_BYTES(1024) less the header, since a device whose input report does not fit is turned away. - The Button page's usages down come from the report's main items. A variable item gives at most one usage per bit. An array item gives one usage per slot, and a slot is at least one bit.
- So no report can list more than 8 × 1024 = 8192, and
MWIN_WIN32_HID_DOWNis that. Thecount < MWIN_WIN32_HID_DOWN ? count : ...clamp guards the call tomwinHidSetButtonsanyway.
If the parser still answers HIDP_STATUS_BUFFER_TOO_SMALL: Parse skips mwinHidSetButtons for that report, so the buttons keep their last state while the axes and hats are still read. That is main's behaviour for every report past 64 down, now reachable only by a report the bound says cannot exist. Nothing reads past hid->down.
Is the 16 KiB only in the context?
USAGE down[8192]is a field ofmwinWin32Hid, which the context holds, andParsenow writes throughhid->downinstead of a 64-entry local. No function takesmwinWin32Hidby value. The test uses astatic s_hid.- One transient to know of:
mwinWin32HidStartresets with*hid = (mwinWin32Hid){.context = context};(win32_hid.c:54). An unoptimized build may build that compound literal on the stack and copy it. That is now 16 KiB larger, harmless on a Windows thread's 1 MB stack. If you want "never on the stack" to hold in Debug too, amemset(hid, 0, sizeof *hid); hid->context = context;does it.
The binary search relies on hid->buttons being ascending and unique. The numbering's own check (fuzz_hid: "each button once and in order") pins that. low/high stay within buttonCount (a uint8_t), and a usage not numbered sets nothing. fuzz_hid now lists up to 255 down and checks each button against the list, so the search is exercised past the old 64.
🤖 I have created a release *beep* *boop* --- ## [0.15.0](v0.14.0...v0.15.0) (2026-11-05) The iOS device check, that a window shows no flash against the launch screen before its first frame, is waived by the owner for this release, which ships on the iOS simulator suite: its iOS changes leave the launch and the first frame alone. ### Changed - **Breaking:** `mwinMessageButtons` is now `mwinMessageChoice`, its constants `mwin_buttonsOk`, `mwin_buttonsOkCancel` and `mwin_buttonsYesNo` are now `mwin_choiceOk`, `mwin_choiceOkCancel` and `mwin_choiceYesNo`, and `mwinMessageBoxDef.buttons` is now `mwinMessageBoxDef.choice`. The type is a choice of one, but its plural name read as a set of flags, as every other plural type in the API is ([#489](#489)). - **Breaking:** the test platform's `mwinGamepad_GetTestRumble` and `mwinGamepad_GetTestTriggerRumble` take `size_t* countOut` instead of `uint32_t* countOut`, as every other count the API reports is a `size_t` ([#490](#490)). - Maul Window builds against Maul Unicode 1.2.0, which gives the results 1.1.2 gave, so nothing a program gets from Maul Window changes. The installed CMake config and pkg-config file ask for Maul Unicode 1.2.0, and a build on a machine with only Maul Unicode 1.1.2 installed fetches 1.2.0 instead of using it ([#493](#493)). ### Fixed - On Windows, a generic HID gamepad with more than 64 buttons down at once, such as a stick or button box of 128 whose toggle switches stay down, reads its buttons. Before, the library asked Windows' parser for the buttons down with room for 64, the parser refused, and the report's buttons were dropped: each button kept the state of the last report that listed 64 or fewer, and a press or a release in between was lost ([#495](#495)).
Problem
On Windows, a generic HID gamepad's report kept none of its buttons when more than 64 of them were down.
Parseinsrc/win32_hid.caskedHidP_GetUsagesfor the Button page's usages down with room for 64. A device with more buttons, such as a stick or button box of 128 whose toggle switches stay down, can have more than 64 down at once. Windows' parser then answersHIDP_STATUS_BUFFER_TOO_SMALL, and the report's buttons were dropped: each button kept the state of the last report that listed 64 or fewer, and a press or release in between was lost without a word to the program (#494).Change
MWIN_WIN32_HID_DOWN, one usage per bit of a raw input read (MWIN_WIN32_HID_INPUT_BYTES * 8, 8192). Every usage down takes at least one bit of the report, and a device whose report does not fit in a raw input read is already turned away, so the list always holds them all. It lives inmwinWin32Hid, made with the context, so nothing is allocated on the event path; it adds 16 KiB there.mwinHidSetButtons(src/hid_controls.c) finds each usage down in the numbering by a binary search, as the numbering keeps the usages in order. Before, it compared each one with every numbered button, which a list of thousands would make slow on every report.docs/internals/gamepads.mdsays how the buttons down are asked for.Verification
test/test_win32_hid.c's stand-in for the parser now answers as Windows' does when the list is too small, and counts every usage down as Wine's does. A new 128-button stick sends buttons 1, 3 and 65 to 128 down. Under Wine (clang 20 for MinGW), the test fails without the change ("the box's buttons read with 66 of them down") and passes with it. The real parser is run only on the Windows CI runner.test/fuzz_hid.chandsmwinHidSetButtonsup to 255 usages and checks each numbered button against the list: down exactly where its usage is listed. It ran 120 seconds (96,067 runs) with no finding.-Werror):linux-gcc-14andweb-wasiin the CI image,linux-clang-20-asan,linux-minimal, the fuzz tree, Windows through clang-20 for MinGW, andandroid-emulator.tools/check.sh changedpasses,hid_controlsincluded.Closes #494