Opening this before writing code, per CONTRIBUTING, to avoid duplicating
reverse-engineering work and to check where you want the boundaries drawn.
This builds directly on #46, which brought Bolt (046d:C548) support and the
MX Master 3S. Its closing note lists SmartShift, hi-res and thumb wheel, host
switching and button remaps as not yet implemented — all of those are working
here on the MX Master 4, along with haptics, which I have not seen implemented
anywhere.
Device
|
|
| Model |
Logitech MX Master 4 |
| Connection |
Logi Bolt receiver, 046d:C548 |
| Device index |
0x02 on the receiver (not 0x01) |
| Protocol |
HID++ 4.5, 45 features |
| Firmware |
LD 04.00, RBM 27.00 / RBM 27.03, HW 00.00 |
| WPID / model id |
B042 (B04200000000) |
Everything below was read from that device. Where something is inferred rather
than observed, it says so.
What is decoded and working on hardware
0x19B0 HAPTIC — not implemented in any public tool I can find; Solaar
lists the id and nothing else.
- Read fn
0x01 / write fn 0x02, two bytes.
- Byte 1 is strength. Logi Options+ writes exactly
25 / 45 / 60 / 100 for
its Subtle / Low / Medium / High presets, and fn 0x00 reports 60 as the
device default.
- Byte 0 is a flag bitmask — bit 0 haptics on/off, bit 1 battery-saving
mode. Established by flipping each Options+ switch and observing which bit
moved; ten writes for ten toggles, only those two pairs changed anything.
- fn
0x04 plays a haptic effect. Accepts effect ids 0x00–0x0E and
0x1B; everything else answers 0x02 invalid argument. Sixteen accepted ids
and thirteen distinct sensations by hand — 0x00≡0x01 and
0x02≡0x03≡0x04 are indistinguishable. 0x1B sits apart from the
contiguous block and feels like a lighter variant of another effect; I have
not pinned down which. Options+ plays 0x08 after a strength change and
0x00 after re-enabling haptics.
- Byte 1 of a play reply is a motor-busy flag, not a property of the effect:
playing 0x0B from idle returned 0 three times, and 120 ms after the longest
effect it returned 1 three times, for the same id.
0x1815 HOSTS INFO v2 / 0x1814 CHANGE HOST v2 — Easy-Switch.
0x1815 fn 0x00 → [capabilities(2), hostCount, currentHost]. Reading the
counts one byte earlier reports eight slots on a device whose slots 3+ refuse
outright, so the two leading capability bytes matter.
- fn
0x01 [slot] → [slot, status, …, nameLen, maxNameLen], status 1 = paired.
0x1814 fn 0x01 [slot] switches. Verified end to end: the mouse left this
computer, the button underneath brought it back, the panel reconnected.
0x0007 DEVICE FRIENDLY NAME — the editable name, 11 of a maximum 14
characters, holding "MX Master 4". fn 0x00 reports both lengths, fn 0x01
returns the text headed by the requested offset, fn 0x03 writes it back.
Verified by renaming and reading back.
Also verified on this device, and listed as not-yet-implemented in #46:
SmartShift and wheel mode (0x2111), hi-res wheel and invert (0x2121), thumb
wheel invert (0x2150), and button remapping (0x1B04 v6, nine controls).
What I deliberately did not decode
0x19C0 FORCE SENSING BUTTON is dormant config, not a pressure stream.
With a positive control proving the watcher live, it emitted nothing and its
values never moved while the panel was pressed. The Actions Ring panel is an
ordinary 0x1B04 control, cid 0x01A0, which Options+ diverts.
- Easy-Switch host names.
0x1815 fn 0x02 returns six non-text bytes per
slot regardless of the reported name length — an address, not a label — so
the names live on a higher function. I stopped rather than probe further:
those ids are reportedly set-name / move / delete-host, and a blind call to
the last would unpair one of the user's computers.
0x0020 CONFIG CHANGE. fn 0x00 reads a cookie, fn 0x01 writes it.
It is a marker applications write to notice each other's changes, not a
notification from the mouse, so it does not replace polling.
0x1701, 0x1602, 0x00D1 — present and flagged plain, unidentified by
Solaar or anything else I could find. 0x1701 has three functions and answers
01 00 0A; 0x1602 answers all four functions but takes none of them with
empty arguments — two reject the arguments, two return 0x05 logitech
internal; 0x00D1 returns an
identical 00 81 92 01 followed by twelve FF bytes on three separate
functions, which is what unprogrammed memory looks like. Recorded as unknown
rather than guessed at.
- Everything the firmware flags hidden or engineering — 22 of the 45, including
0x1E22 SPI DIRECT ACCESS and 0x1802 DEVICE RESET — was never probed.
Not present on this device, in case it saves someone a lookup: 0x2202,
0x8060/0x8061, 0x8100, 0x1001, 0x1F20, 0x6501.
Method, since the guide asks for evidence
Almost all of it came from watching Logi Options+ rather than guessing. The
Bolt receiver broadcasts replies to every open handle, so a WebHID page sees
the vendor application's own traffic; the low nibble of the function byte is
the requester's software id, and Options+ uses 0x0F. Changing a setting in
Options+ and reading the reply gives the function id and the byte layout
directly. No packet capture or kernel driver involved.
Two corrections worth passing on, both of which cost me a wrong answer first:
- A blind function-id walk cannot be made safe by choosing a low ceiling.
Setters are not always high — 0x0007's is fn 0x03 and 0x0020's is fn
0x01. A walk of mine silently overwrote this device's configuration cookie
and went unnoticed for three runs. It is restored, and the tooling now
snapshots before and after and says loudly when a walk changed something.
- Print raw bytes beside every decode. A decode with nothing to check it
against is how the host count first read as eight slots.
What I would like to contribute
A driver PR here for src/drivers/logitech/, plus the matching OpenMouse PR
against dev for the UI, cross-referenced as the guide asks.
To be clear about the state of it: my working branch predates the protocol
split, so it is written against the old single-file layout. I am not going to
submit that — I have read why #35 and #39 were closed, and porting it into the
codec and driver separation is my job, not the reviewer's. I would rather agree
the shape here first and submit something that fits.
Before I do that, two questions:
- Do you want haptics as its own module under
src/drivers/logitech/, or
folded into hidpp.ts?
- Easy-Switch changes which computer the mouse is on, so a successful write
disconnects the caller and cannot be confirmed by reading back. Is a driver
method that can only report "command sent" acceptable, or would you rather
that stayed out of the protocol package?
Happy to split it smaller if that suits review better.
Opening this before writing code, per CONTRIBUTING, to avoid duplicating
reverse-engineering work and to check where you want the boundaries drawn.
This builds directly on #46, which brought Bolt (
046d:C548) support and theMX Master 3S. Its closing note lists SmartShift, hi-res and thumb wheel, host
switching and button remaps as not yet implemented — all of those are working
here on the MX Master 4, along with haptics, which I have not seen implemented
anywhere.
Device
046d:C5480x02on the receiver (not0x01)LD 04.00,RBM 27.00/RBM 27.03,HW 00.00B042(B04200000000)Everything below was read from that device. Where something is inferred rather
than observed, it says so.
What is decoded and working on hardware
0x19B0HAPTIC — not implemented in any public tool I can find; Solaarlists the id and nothing else.
0x01/ write fn0x02, two bytes.25 / 45 / 60 / 100forits Subtle / Low / Medium / High presets, and fn
0x00reports 60 as thedevice default.
mode. Established by flipping each Options+ switch and observing which bit
moved; ten writes for ten toggles, only those two pairs changed anything.
0x04plays a haptic effect. Accepts effect ids0x00–0x0Eand0x1B; everything else answers0x02invalid argument. Sixteen accepted idsand thirteen distinct sensations by hand —
0x00≡0x01and0x02≡0x03≡0x04are indistinguishable.0x1Bsits apart from thecontiguous block and feels like a lighter variant of another effect; I have
not pinned down which. Options+ plays
0x08after a strength change and0x00after re-enabling haptics.playing
0x0Bfrom idle returned 0 three times, and 120 ms after the longesteffect it returned 1 three times, for the same id.
0x1815HOSTS INFO v2 /0x1814CHANGE HOST v2 — Easy-Switch.0x1815fn0x00→[capabilities(2), hostCount, currentHost]. Reading thecounts one byte earlier reports eight slots on a device whose slots 3+ refuse
outright, so the two leading capability bytes matter.
0x01 [slot]→[slot, status, …, nameLen, maxNameLen], status 1 = paired.0x1814fn0x01 [slot]switches. Verified end to end: the mouse left thiscomputer, the button underneath brought it back, the panel reconnected.
0x0007DEVICE FRIENDLY NAME — the editable name, 11 of a maximum 14characters, holding "MX Master 4". fn
0x00reports both lengths, fn0x01returns the text headed by the requested offset, fn
0x03writes it back.Verified by renaming and reading back.
Also verified on this device, and listed as not-yet-implemented in #46:
SmartShift and wheel mode (
0x2111), hi-res wheel and invert (0x2121), thumbwheel invert (
0x2150), and button remapping (0x1B04v6, nine controls).What I deliberately did not decode
0x19C0FORCE SENSING BUTTON is dormant config, not a pressure stream.With a positive control proving the watcher live, it emitted nothing and its
values never moved while the panel was pressed. The Actions Ring panel is an
ordinary
0x1B04control, cid0x01A0, which Options+ diverts.0x1815fn0x02returns six non-text bytes perslot regardless of the reported name length — an address, not a label — so
the names live on a higher function. I stopped rather than probe further:
those ids are reportedly set-name / move / delete-host, and a blind call to
the last would unpair one of the user's computers.
0x0020CONFIG CHANGE. fn0x00reads a cookie, fn0x01writes it.It is a marker applications write to notice each other's changes, not a
notification from the mouse, so it does not replace polling.
0x1701,0x1602,0x00D1— present and flagged plain, unidentified bySolaar or anything else I could find.
0x1701has three functions and answers01 00 0A;0x1602answers all four functions but takes none of them withempty arguments — two reject the arguments, two return
0x05logitechinternal;
0x00D1returns anidentical
00 81 92 01followed by twelveFFbytes on three separatefunctions, which is what unprogrammed memory looks like. Recorded as unknown
rather than guessed at.
0x1E22 SPI DIRECT ACCESSand0x1802 DEVICE RESET— was never probed.Not present on this device, in case it saves someone a lookup:
0x2202,0x8060/0x8061,0x8100,0x1001,0x1F20,0x6501.Method, since the guide asks for evidence
Almost all of it came from watching Logi Options+ rather than guessing. The
Bolt receiver broadcasts replies to every open handle, so a WebHID page sees
the vendor application's own traffic; the low nibble of the function byte is
the requester's software id, and Options+ uses
0x0F. Changing a setting inOptions+ and reading the reply gives the function id and the byte layout
directly. No packet capture or kernel driver involved.
Two corrections worth passing on, both of which cost me a wrong answer first:
Setters are not always high —
0x0007's is fn0x03and0x0020's is fn0x01. A walk of mine silently overwrote this device's configuration cookieand went unnoticed for three runs. It is restored, and the tooling now
snapshots before and after and says loudly when a walk changed something.
against is how the host count first read as eight slots.
What I would like to contribute
A driver PR here for
src/drivers/logitech/, plus the matching OpenMouse PRagainst
devfor the UI, cross-referenced as the guide asks.To be clear about the state of it: my working branch predates the protocol
split, so it is written against the old single-file layout. I am not going to
submit that — I have read why #35 and #39 were closed, and porting it into the
codec and driver separation is my job, not the reviewer's. I would rather agree
the shape here first and submit something that fits.
Before I do that, two questions:
src/drivers/logitech/, orfolded into
hidpp.ts?disconnects the caller and cannot be confirmed by reading back. Is a driver
method that can only report "command sent" acceptable, or would you rather
that stayed out of the protocol package?
Happy to split it smaller if that suits review better.