A DualSense's buttons all reach the stream, and its Mute button works #380

Merged
enricobuehler merged 1 commits from worktree-dualsense-buttons-and-pad-routing into main 2026-08-23 07:39:09 +00:00
1 Commits
Author SHA1 Message Date
enricobuehler cb07a8f983 fix(clients/android): a DualSense's buttons all reach the stream, and its Mute button works
ci / web (pull_request) Successful in 1m12s
ci / rust-arm64 (pull_request) Successful in 1m25s
ci / bun-nix (pull_request) Successful in 19s
ci / docs-drift (pull_request) Successful in 40s
ci / docs-site (pull_request) Successful in 1m17s
android / android (pull_request) Successful in 5m51s
ci / rust (pull_request) Successful in 5m57s
Three defects reported against a Bluetooth DualSense on a Fire TV Stick 4K Max,
re-implemented from #371's diagnosis. #371 itself should not be merged: all
three problems are real, but each fix lands somewhere that breaks more hardware
than it repairs.

1. Some buttons never reach the stream. Fire OS is reported to tag certain
   DualSense buttons SOURCE_KEYBOARD even though the keycodes are standard
   BUTTON_*, and MainActivity's `event.isFromSource(SOURCE_GAMEPAD)` gate then
   drops them. The event's source class is the platform's per-event guess; the
   DEVICE's is the fact. New `MainActivity.fromPad` widens to the device — but
   ONLY for `KeyEvent.isGamepadButton` keycodes. That exclusion is the whole
   safety of it: DPAD keycodes are a keyboard's arrow keys and BACK is a
   remote's way out of the stream, and both share their keycodes with a pad.
   `Gamepad.isPad` is untouched (source-class only) and no vendor-id or
   device-name matching is added anywhere — the field report records both pads
   being IDENTIFIED correctly; only their button positions were wrong.

2. Touchpad click and Mute were dropped. Both have wire bits (BTN_TOUCHPAD,
   BTN_MISC1) and no Android keycode, so GENERIC_SONY's `0x13d`/`0x13e` rows now
   borrow BUTTON_15/BUTTON_16 to carry them into `buttonBit`. Inside
   GENERIC_SONY and nowhere else: `0x13d`/`0x13e` are BTN_THUMBL/BTN_THUMBR —
   L3 and R3 — in the standard Linux mapping, and they mean touchpad and mute
   only inside the straight-through report order a driverless pad uses. A row in
   SONY_MODERN, or an override above `padMap(dev)`, costs every Xbox pad, Switch
   Pro, 8BitDo, Steam Deck and hid-playstation DualSense both stick clicks.
   `correct()`'s `genericKeyCode` guard stays exactly as it was.

3. Mute toggles the mic — once per press, and only on a pad that has one.
   Edge-triggered through the existing `completesChord` as the one-button chord
   it is: `onButton` still calls `slotButton(down = true)` on auto-repeat, so an
   unguarded check would flap the mic for as long as the button is held. Gated
   on a new `Slot.hasMuteButton`, because BTN_MISC1 is the wire's misc/QAM bit
   and `Sc2Device` puts a Steam Controller 2's QAM button on it — "any MISC1"
   would mute the microphone on every QAM press. Resolved at slot open from what
   each path knows: the report order for an InputDevice, the declared kind for a
   capture link. Under the "local" system-button policy a real mute button is
   exempt from the early return (that policy means the press stays with this
   device, which is exactly what the toggle does) and loses only its wire send;
   every other system button behaves as before.

Tests: `every other pad keeps L3 and R3 on those scancodes` is the regression
that matters and fails on #371's shape (verified by reproducing it). Plus the
rewritten touchpad/mute assertions, the guard's negative path — untested in
either direction until now, because every existing case fed `correct()` the
keycode `Generic.kl` would have produced — and the mute button's edge rule in
GamepadChordTest. `an Xbox pad at the standard positions keeps X, Y and its
shoulders` is kept.

Not yet verified on hardware: no Fire TV Stick 4K Max or DualSense here, and no
adb device attached. §1's premise (the SOURCE_KEYBOARD tagging) is therefore
unconfirmed — the change is a no-op if it does not hold.
2026-08-23 01:07:59 +02:00