Combine mouse buttons from every pointing device (#287) - #365
Open
mglushko wants to merge 1 commit into
Open
Conversation
A mouse report carries the full button state of the device that sent it, and whichever device reported last was taken at its word. With two pointing devices in use at once, one of them saying "no buttons" cancelled the other one saying "left down". The setup in hrvach#287 is mouse keys on a keyboard for the buttons and a trackball for the movement. Holding a button and then moving released it, so selecting text and dragging were impossible. The same thing happens with a pointing device plugged into each board. Keyboards already avoid this. combine_kbd_states keeps what each keyboard holds and ORs them into one report. This does the same for mice. Each interface remembers what it is holding, in hid_interface_t rather than in an array indexed by the device number process_mouse_report is handed, because tuh_hid_report_received_cb gives every mouse interface index 1 and two mice would share the slot. Keeping it on the interface also means the memset in tuh_hid_umount_cb drops a device's buttons when it is unplugged. Neither board sees the other's reports, so each announces its own half of the union in a new MOUSE_BUTTONS_MSG whenever that half changes, and holds local | remote. Both boards agreeing also repairs the rule that refuses an output switch while a button is held, which until now could not see a button held on the other board. That announcement only arrives on a change, so the heartbeat carries the same value as a level once a second. Without it, a dropped packet or a board that restarted while a button was held would leave the other side holding a button nobody is pressing. Older firmware leaves the field zero, which is the right answer for a board that never announces buttons, and ignores it in the other direction. Two smaller things in the decode path move with it. The fallback for a device that declares its buttons under a report ID of its own, which the Kensington Expert Mouse does, now reads that interface's own last state rather than the union; reading the union would write another device's buttons into this one's slot, where they would stay held after that device let go. And the button value is narrowed to the byte the outgoing report carries before it is compared or stored, so a device declaring sixteen buttons is not held at full width here only to be truncated on the way out. Fixes hrvach#287
mglushko
force-pushed
the
mouse-button-union
branch
from
August 26, 2026 19:03
cecf5ac to
fe6c40f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two pointing devices used at once cancel each other's buttons. Holding a button on one and
moving with the other lets go of the button, so you cannot select text or drag. Fixes #287.
Root cause
A mouse report carries the full button state of the device that sent it, and the newest
report won:
A trackball reporting movement with nothing pressed says "no buttons" just as clearly as a
keyboard's mouse keys say "left down", so it wins by reporting second. With a pointing
device on each board the report arriving over the link overwrote the state too.
Keyboards already avoid this.
combine_kbd_stateskeeps what each keyboard holds and ORsthem into one report. Mice had no equivalent.
Changes
hid_interface_tgains amouse_buttonsbyte for what that device holds, andprocess_mouse_reportsends the OR across every interface. It sits on the interface ratherthan in an array keyed by the device number, because
tuh_hid_report_received_cbgivesevery mouse interface index 1 and two mice would share the slot. It also means the
memsetalready in
tuh_hid_umount_cbclears a device's buttons when it is unplugged.Neither board sees the other's reports, so each announces its own half in a new
MOUSE_BUTTONS_MSGwhen that half changes, and holdslocal | remote. That also lets therule which refuses an output switch while a button is held see a button held on the other
board.
Since the announcement only arrives on a change, the heartbeat carries the same value as a
level once a second. Without it, a dropped packet or a board that restarted mid-drag would
leave the other side holding a button nobody is pressing. Older firmware leaves the field
zero, which is correct for a board that never announces buttons, and ignores it coming the
other way.
Two smaller decode fixes come with it. The fallback for a device that declares buttons
under their own report ID, as the Kensington Expert Mouse does, now reads that interface's
last state instead of the union, which would otherwise leave one device holding another's
buttons. And the value is narrowed to the byte the outgoing report carries before it is
compared or stored, so a sixteen button device is not kept at full width only to be
truncated later.
No config, webconfig or flash layout changes. One new packet type.
Testing
On hardware with two mice, one in the keyboard port and one in the mouse port: holding a
button on one and moving with the other now works as expected.
Builds clean with no new warnings.
There is also a host side test that links
src/mouse.cagainst stubs under ASan and UBSanand drives two interfaces through
process_mouse_report:test_mouse.c.
It covers the reported case, two devices holding different buttons, unplugging one
mid-hold, a device with more buttons than the report can carry, what the announcement
carries, and the switch being held back by a button on either board.
Note on #357
The packet numbers do not collide. #357 uses 26 and this uses 27, which is why this one
skips 26, so the two can land in either order without renumbering. There may still be a
small textual fixup in the files they both touch, depending on merge order.
They are complementary: #357 fixes the cursor position when a pointing device is attached
to each board, this fixes the buttons for the same setup. Neither depends on the other.