Repository navigation
MX Master 4: button remapping (0x1B04) - #11
Merged
snekxs merged 1 commit intoAug 14, 2026
Merged
Conversation
Adds feature 0x1B04 (REPROG CONTROLS V4): read the control table, point one control at another, and hand diverted controls back to the hardware. Codec in src/logitech/controls.ts, driver methods on LogitechHidppClient. Follow-up to OpenMouse-Project#9, whose scope held button remapping back to stay reviewable. This remaps control to control only. It does not implement Options+-style actions like launching an app; Options+ does those by diverting the button and handling it in its own software, which nothing here receives. So diversion is exposed only to be cleared, never set. The device decides what is legal: every control reports a group and a mask of the groups it accepts, and the primary buttons report a mask of zero, so the firmware refuses to move them rather than any rule here. They still appear as targets for other controls, because any control in an accepted group is a legal destination even when it is not a legal source. Two decode details. The mapping flags are one 16-bit field split across two non-adjacent bytes (low at payload[2], high at payload[5], with the remap target between), so reading them as a contiguous pair mistakes a button remapped to a high control id for a diverted one. And each mapping flag is a value plus a valid bit one position higher, so a pure remap writes a zero flags byte and leaves diversion untouched; clearing diversion sets only the valid bits, so it can only ever turn diversion off. A control merges getControlIdInfo (key flags) with getControlIdReporting (the 16-bit mapping field); those are named apart (flags vs mappingFlags) so neither overwrites the other, and virtual/reprogrammable are exposed as booleans. Verified on an MX Master 4 over a Logi Bolt receiver. Codec tests cover every byte layout above; fake-HID driver tests cover read-back, refusal of illegal or absent controls, the acknowledge-but-ignore path, and restoring diversion without disturbing an existing mapping.
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.
Adds feature
0x1B04(REPROG CONTROLS V4): read the control table, point onecontrol at another, and hand diverted controls back to the hardware. Codec in
src/logitech/controls.ts, driver methods onLogitechHidppClient.Follow-up to #9 (merged), whose scope deliberately held button remapping
back so that change stayed reviewable. Companion OpenMouse PR: OpenMouse-Project/openmouse#83
(the buttons-tab card).
Verified on hardware: MX Master 4, WPID
B042, firmwareLD 04.00, over a LogiBolt receiver
046d:c548, pairing slot 2.The limitation, up front
This remaps control → control only: a button can be made to act as another
button the device already knows (middle click, back, forward, etc.). It does
not implement Options+-style actions like "launch an app" or "switch
desktop". Options+ does those by diverting the button — routing its events to
HID++ — and handling them in its own software, which a driver with nothing
listening cannot do. So diversion is exposed only to be cleared, never set.
Evidence
As with #9, almost all of it came from watching Logi Options+ rather than
guessing: the Bolt receiver broadcasts replies to every open handle, and the
low nibble of the function byte carries the requester's software id (Options+
uses
0x0F). Changing a mapping in the vendor app and reading the reply givesthe function id and byte layout directly. No packet capture.
What the device decides, not this code
Every control reports a group and a bitmask of the groups it accepts as remap
targets. Left and right click report a mask of zero — the firmware itself
refuses to move the primary buttons; there is no rule here that excludes them.
They still appear as targets for other controls, because any control in an
accepted group is a legal destination even when it is not a legal source.
Two decode details worth flagging
bytes — low at
payload[2], high atpayload[5], with the remap target at[3..4]between them. Read as a contiguous pair, the target's high byte landsin the flags, and any button remapped to a high control id (e.g. the Actions
Ring,
0x01A0) reads as diverted. Decoded from the split field it does not.higher — and the device ignores any flag whose valid bit is clear. A pure
remap therefore writes a zero flags byte, which changes no flag at all and
leaves diversion exactly as it was. Clearing diversion sets only the valid
bits, with the value bits clear, so it can only ever turn diversion off.
A control merges
getControlIdInfo(key/capability flags) withgetControlIdReporting(the 16-bit mapping field). Those were named apart(
flagsvsmappingFlags) so the merge keeps both;virtualandreprogrammableare exposed as their own booleans so consumers never read rawbits.
Deliberately not here
0x04). Options+ uses the temporary flag (0x01),which the device clears itself when the owner stops — so quitting Options+
does not leave buttons dead. The persistent flag would, so it is neither set
nor offered.
Testing
npm run checkgreen: 384 tests, including codec tests for every bytelayout above and fake-HID driver tests for read-back, refusal of illegal or
absent controls, the acknowledge-but-ignore path, and restoring diversion
without disturbing an existing mapping.
group > 0guard no 8-bit mask can distinguish, kept as documentation).physical button, persisted across a reload, restored to default, and the
diverted/restore path exercised against Logi Options+.
Its predecessor PR #46 (MX Master 3S) closes by listing button remapping as
not yet implemented; this is that piece.