Repository navigation
feat: polling, DPI colours, X/Y axes, keyboard combos, buttons UI, packaging and CI - #2
Merged
Merged
Conversation
…ard combos
USB polling rate (register 0x0000, 2-byte record). The codes were first decoded
by m913-ctl from their captures; they are now verified here — all four written,
read back and restored, and with the register at 0x01 the mouse's interrupt
endpoint measured ~1002 Hz, which pins the meaning independently.
Per-stage DPI colours (0x002C): five 4-byte records in the same three captured
shapes as the DPI table. The values read off this mouse match the DC= line of
the official app's Cfg.ini exactly.
Independent X/Y axes: a DPI entry is `X Y 00 ck`, two separate axis bytes the
official app keeps equal. Entries now accept a single value or an (x, y) pair;
existing callers produce byte-identical packets, which the untouched byte-match
tests prove.
The extended slot holds an EVENT LIST, not a fixed pair:
<event count> [ <type> <code lo> <code hi> ] * count <checksum>
Decoded here by assigning Ctrl+C through the official app: the record has four
events (modifier down, key down, modifier up, key up), which is what showed the
first byte counts events rather than naming a type. The codes are standard HID
usages — 0x01 is the left-control bit and 0x06 the keyboard usage for "c".
build_key is confirmed by the same experiment, so its "unverified" warning is
gone; build_combo is new.
Two corrections this decoding forced:
- 0x08 is RGB ON/OFF and 0x07 is the polling switch, not the other way round.
A single-variable test (button 2 set to RGB ON/OFF and nothing else) settled
it. The earlier reading came from a run that changed seven buttons at once,
whose button-to-index attribution turned out to be wrong.
- build_write silently produced packets longer than the 17-byte HID report when
the data exceeded ten bytes; the padding count went negative. It now raises,
and build_events splits a long record across packets the way the app does.
A test now asserts the packet length, which is what the previous byte-match
missed by only comparing the data slice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A third page exposes the 16 button slots. Unknown action bytes are shown as `unknown (09 00 00)` and cannot be selected, so the UI never rewrites a record it cannot interpret — send_button preserves the other fifteen byte for byte and the page must not break that. The DPI page's polling section was decorative and claimed the rate was "not mapped yet". It is now a real control built from protocol.POLLING_RATES, so the UI cannot drift from the protocol, and it distinguishes a silent device from an undecodable code instead of collapsing both into one message. Each DPI stage gains a colour swatch, and an "Independent X / Y axes" toggle reveals a per-axis field. Toggling writes nothing on its own; the rule takes effect on the next edit. The tkinter fallback was advertising "Cycle (coming soon)" and "Breathing (coming soon)" for effects the core has had for a long time, and drawing dead DPI and polling panels. The three colour effects now work, and the DPI page says plainly that this fallback covers lighting only, pointing at the Qt interface and the command line for the rest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
67 new tests. The hidraw layer is replaced by fakes, and _send_feature and the device open are swapped for tripwires that raise, so a test that slipped through fails loudly instead of reaching the mouse. The most valuable one guards the project's central promise: the fixture holds 0xC8 in a DPI stage — above the 0xBD firmware ceiling, so byte -> DPI -> byte is lossy there — then changes a different stage and asserts 0xC8 comes back out of the wire packets untouched. The stage bytes are decoded from the captured packets rather than compared against the builder, so it is an independent check. CI runs the suite on 3.8 through 3.13. The first step runs it with nothing installed, which is the actual proof that the core is stdlib-only; a second job installs the package and checks the entry points resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PEP 621 metadata with the core installing dependency-free and PySide6 behind a `gui` extra. Two entry points: `open-m711pro` for the CLI and `open-m711pro-gui` for the interface. m711pro/__main__.py makes `python -m m711pro` work and holds the dispatch, which main.py at the root now delegates to instead of duplicating. The udev rule uses TAG+="uaccess" rather than MODE="0666". A hidraw node is not inert: reading it sees every button and movement, and writing it reprograms the device — this project maps buttons to arbitrary keystrokes. 0666 grants both to every account permanently; uaccess has logind hand access to the active local session and revoke it on logout. A GROUP fallback ships commented out for systems without logind. install.sh is idempotent, refuses to run as non-root before touching /etc, and has an --uninstall flag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Register 0x0000 was documented as the active DPI stage; it is the USB polling rate, now verified here. The extended slot section is rewritten around the event list model, with the Ctrl+C record annotated event by event. Adds the button-to-index map. The table has 16 slots and this mouse uses eight of them out of order: buttons 1-5 are indices 0-4, but 6, 7 and 8 are indices 10, 11 and 9. Every row was proven by a single-variable experiment; an earlier version of this map, deduced by correlating stage colours with a description, had two of the three wrong. Index 6 holds 09 00 00 from the factory and no button maps to it, so the official app can never be used to learn what it means — recorded so nobody retries it. The 0x0054 block mirrors the M913's main LED register and this mouse holds values byte-identical to their rainbow template. Writes are accepted and read back, but no visible change was ever observed — not a colour change, not switching mode from rainbow to steady, not full brightness. Documented as a dead end so the next person does not spend an afternoon on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Brings the driver to parity with the official Windows app except macros, and
corrects three things we had wrong.
Protocol
0x00000x010x002CCfg.iniDC=line0x000CThe extended slot holds an event list
Assigning Ctrl+C through the official app produced a four-event record, which is
what showed the first byte counts events rather than naming a type. The codes are
standard HID usages:
0x01is the left-control bit,0x06the keyboard usagefor "c".
Three corrections
0x07/0x08were swapped.0x08is RGB ON/OFF,0x07is the pollingswitch — as m913-ctl always said. Our earlier reading came from a run that
changed seven buttons at once; a single-variable test settled it.
The button-to-index map was wrong. Buttons 6, 7 and 8 are indices 10, 11 and
9 — not 9, 10, 11. Every row is now proven by its own experiment.
build_writeproduced oversized packets. With more than ten data bytes thepadding count went negative and the packet came out 21 bytes long, past the
17-byte HID report.
build_combohit exactly that. It now raises, andbuild_eventssplits a long record across packets the way the app does. Theprevious byte-match missed it by comparing only the data slice, never the packet
length; there is a test for that now.
Interface
A Buttons page for the 16 slots. Unknown action bytes render as
unknown (09 00 00)and cannot be selected, so the UI never rewrites a record itcannot interpret. The DPI page's polling section was decorative and claimed the
rate was unmapped; it is a real control now, built from
protocol.POLLING_RATESso it cannot drift. Stage colour swatches and an X/Y toggle.
The tkinter fallback was advertising effects as "coming soon" that the core has
had for a long time, and drawing dead panels. Fixed and made honest about its
scope.
Tests and packaging
43 tests to 112. The device and CLI layers were untested; they now have 67
tests with the hidraw layer replaced by tripwires that raise if anything reaches
the mouse. The one that matters most proves a DPI stage holding
0xC8— abovethe
0xBDceiling, so its round-trip is lossy — survives a change to adifferent stage byte for byte.
CI on 3.8–3.13, running the suite once with nothing installed to prove the core
is stdlib-only.
pip install-able with the GUI behind an extra, a udev ruleusing
uaccessrather than0666, and a desktop entry.How to test
Not included
Macros — the only remaining feature, and it needs a real USB capture. The
0x09action type at index 6 stays unknown: no button maps to that slot, so the
official app cannot be used to learn it. The
0x0054LED block accepts writesbut produces no visible change on this model; documented as a dead end.
🤖 Generated with Claude Code