Skip to content

MX Master 4: button remapping card - #83

Merged
snekxs merged 1 commit into
OpenMouse-Project:devfrom
weltern:feat/logitech-button-remapping
Aug 14, 2026
Merged

snekxs merged 1 commit into
OpenMouse-Project:devfrom
weltern:feat/logitech-button-remapping

Conversation

@weltern

@weltern weltern commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Surfaces feature 0x1B04 (added in OpenMouse-Project/mouse-protocol#11,
which this depends on) as a card in the Buttons tab. Needs that package version
to build.

Follow-up to #82 (merged), which brought the haptics/wheel/name/Easy-Switch
cards. Verified on hardware: MX Master 4 over a Logi Bolt receiver 046d:c548.

The limitation, up front

This remaps control → control only — a button acts as another button the
device already knows. It does not do Options+-style actions ("launch an
app", "switch desktop"); those work by diverting the button and handling it in
vendor software, which the web app has nothing to receive. So the card offers
remapping and a way to hand diverted buttons back, never a way to divert them.

What the card shows

Every reprogrammable control the mouse reports gets a dropdown whose options are
the targets the device's own group mask allows. Two deliberate choices:

  • Left and right click are shown but locked, with a note that the firmware
    is what prevents remapping them (their group mask is zero). Showing them fixed
    reads as a restriction rather than a missing button.
  • Virtual controls are hidden. The MX Master 4's "virtual gesture button" is
    the event its gesture button emits when held and dragged — not something a
    user can press — so a row for it is noise.

Staging

A remap stages like any other write and shares one group, so several apply
together. The controls are not part of MouseStatus, so there is no preview;
the card renders the staged mapping from the snapshot (the same way staged
profile names and rates already are), which is what holds the dropdown on the
picked target until it is flashed and returns it on revert.

Restoring diverted buttons is immediate, not staged

Deliberately. A diversion is state another application left in the mouse, not a
preference this user expressed, and the button does nothing at all until it is
cleared — staging that repair behind a flash step would leave a dead button dead
for no reason. Options+ uses the temporary divert flag, which the mouse clears
itself when Options+ stops, so this is mostly a manual escape hatch.

Reads

The control table is two round-trips per control, too much for the five-second
refresh, so it is read once on connect and re-read after a write — the same
reason the Easy-Switch and friendly-name reads are cached.

Testing

  • npm run check green (51 tests), including availability tests that the
    card appears only when the mouse reports controls and never for a non-Logitech
    device.
  • On hardware (MX Master 4 over Bolt): remapped the gesture button to middle
    click, flashed, confirmed by pressing it, reloaded to confirm it persisted,
    reverted, and confirmed the dropdown holds the staged pick before flashing and
    returns to the device value on revert. Diverted/restore exercised against Logi
    Options+.

Surfaces feature 0x1B04 as a card in the Buttons tab. Depends on the matching
mouse-protocol change and needs that package version to build. Follow-up to OpenMouse-Project#82.

This remaps control to control only, so the card offers remapping and a way to
hand diverted buttons back, never a way to divert them (Options+-style actions
work by diverting the button and handling it in vendor software, which the web
app has nothing to receive).

Every reprogrammable control gets a dropdown whose targets are the ones the
device's own group mask allows. Left and right click are shown but locked, with
a note that the firmware prevents remapping them (their mask is zero). Virtual
controls are hidden: the MX Master 4's "virtual gesture button" is the event
its gesture button emits when held and dragged, not a pressable button.

A remap stages like any other write, sharing one group so several apply
together. The controls are not part of MouseStatus, so there is no preview; the
card renders the staged mapping from the snapshot, which holds the dropdown on
the picked target until it is flashed and returns it on revert.

Restoring diverted buttons is immediate rather than staged: a diversion is
state another application left in the mouse, and the button does nothing until
it is cleared, so staging that repair behind a flash step would leave a dead
button dead for no reason.

The control table is two round-trips per control, so it is read on connect and
after a write rather than on the refresh poll. Verified on an MX Master 4 over
a Logi Bolt receiver.
@weltern

weltern commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

The failing check job is the cross-repo dependency, not a problem with this PR's code.

@openmouse/protocol is pulled from mouse-protocol's default branch, so the button codec it relies on (logitechControlName, LogitechReprogrammableControl, and the driver's readButtons / setButtonMapping / clearButtonDiversion) isn't present until the companion PR merges. Every error in the log traces back to that — including the lone target: any, which only appears because remappableTo loses its number[] type when the import fails.

Merge order: OpenMouse-Project/mouse-protocol#11 first, then a re-run of this PR's check builds green. Same dependency shape as #82 → #9.

@snekxs
snekxs merged commit 1b93bdb into OpenMouse-Project:dev Aug 14, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants