Skip to content

fix(win32): read a HID pad's buttons with more than 64 of them down - #495

Merged
siracozmen01 merged 1 commit into
mainfrom
hid-buttons-down
Nov 5, 2026
Merged

siracozmen01 merged 1 commit into
mainfrom
hid-buttons-down

Conversation

@siracozmen01

Copy link
Copy Markdown
Contributor

Problem

On Windows, a generic HID gamepad's report kept none of its buttons when more than 64 of them were down. Parse in src/win32_hid.c asked HidP_GetUsages for 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 answers HIDP_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

  • The list of usages down has room for 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 in mwinWin32Hid, 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.md says 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.c hands mwinHidSetButtons up 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.
  • These build with no error and no warning (-Werror): linux-gcc-14 and web-wasi in the CI image, linux-clang-20-asan, linux-minimal, the fuzz tree, Windows through clang-20 for MinGW, and android-emulator.
  • tools/check.sh changed passes, hid_controls included.

Closes #494

@siracozmen01 siracozmen01 added the bug Something does not work as documented label Nov 5, 2026

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_DOWN is that. The count < MWIN_WIN32_HID_DOWN ? count : ... clamp guards the call to mwinHidSetButtons anyway.

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 of mwinWin32Hid, which the context holds, and Parse now writes through hid->down instead of a 64-entry local. No function takes mwinWin32Hid by value. The test uses a static s_hid.
  • One transient to know of: mwinWin32HidStart resets 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, a memset(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.

@siracozmen01
siracozmen01 merged commit fadae88 into main Nov 5, 2026
44 checks passed
@siracozmen01
siracozmen01 deleted the hid-buttons-down branch November 5, 2026 14:50
@rawframe-release rawframe-release Bot mentioned this pull request Nov 5, 2026
siracozmen01 pushed a commit that referenced this pull request Nov 5, 2026
🤖 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)).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something does not work as documented

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: a HID pad's buttons are lost when more than 64 are down

1 participant