Skip to content

Multi-mouse: support any Logitech HID++ mouse, several at once - #1

Merged
dpdev69 merged 14 commits into
mainfrom
multi-mouse
Oct 14, 2026
Merged

dpdev69 merged 14 commits into
mainfrom
multi-mouse

Conversation

@dpdev69

@dpdev69 dpdev69 commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Adds support for any Logitech HID++ 2.0 mouse with programmable buttons (REPROG_CONTROLS_V4), and for several mice at once: any mix of USB, Bluetooth and Unifying/Bolt/Nano receivers, including several mice on one receiver.

Draft: the multi-mouse code has not run on real hardware yet. Waiting on the hardware smoke test and the first CI run.

What changes

  • Kit: support is decided by capability (HID++ ≥ 2.0, pointer kind, configurable controls on 0x1B04), not by model name or PID. Keyboards are excluded, and Lightspeed receivers are never opened. Other new pieces:
    • a device catalog, interface classifier and registry, with the same unit deduplicated across transports;
    • one hub per transport (HIDPPLink) and one EndpointController per mouse, with one request in flight per transport plus an interactive lane;
    • a remap engine that handles input from several mice;
    • mapping schema v3 with per-model tables. v2 data migrates into the MX Vertical model, and the v2 key is left untouched so a downgrade still works;
    • per-device preferences, and a public simulator under Simulation/.
  • App:
    • a device manager that tracks every mouse, with pluggable device discovery (IOKit, or simulated mice for Demo Mode);
    • bindings per model and per-device actions (DPI, Easy-Switch, reconnect, forget);
    • a new Mice pane, a "Configure: [model]" picker in Buttons, one menu-bar row per mouse, and per-device battery alerts.
  • Demo Mode: Upright --demo --export-snapshots <dir> and make snapshots render the real UI with three simulated mice into media/snapshots/. They never touch IOKit or the real defaults domain.
  • Probe: upright-probe --all, read-only unless --divert is passed.
  • Docs: README with a supported-devices table, docs/DEVICES.md, docs/HIDPP.md, CONTRIBUTING, CHANGELOG, and issue templates including a "new mouse" template. Only the MX Vertical is marked as verified on hardware.

Verification

  • 388 UprightKit tests pass locally (Swift 6.4), including a 1000-step fuzz test of routing across several mice.
  • make app (Release, universal) builds; hardened runtime and the macOS 13 deployment target are unchanged.
  • The final audit had two Opus reviewers; each finding was then checked by a separate agent. 8 of the 9 findings are fixed. The ninth is this first CI run.
  • Not verified yet:
    • Linux Swift 6.0 and Xcode 16.2 (this PR's CI);
    • real hardware: see docs/dev/HARDWARE-SMOKE-CHECKLIST.md. Section 4 matters most, because IOHIDManagerOpen is no longer called at launch;
    • models other than the MX Vertical, and two physical mice at once, have only been checked in the simulator.

Known limitations

These are listed in docs/dev/NEXT-STEPS.md under "Audit outcome":

  • scroll settings apply to every mouse wheel;
  • cursor-fallback gestures can't tell which mouse they came from;
  • there are no app-target tests; only Kit tests exist.

🤖 Generated with Claude Code

dpdev69 and others added 13 commits October 13, 2026 22:43
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Gate: 352 tests green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gate, probe --all, end-to-end tests)

Gate: 383 tests green, make app green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/dev/NEXT-STEPS.md describes the remaining waves (app core, UI, demo mode, docs, audit) and the launch video.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Swift 6.0 rejects a default argument that references a main-actor static method from a nonisolated context; the helper is pure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bump MARKETING_VERSION to 1.1.0 (build 2) and date the multi-mouse changelog entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dpdev69
dpdev69 marked this pull request as ready for review October 14, 2026 19:01
testOneRequestInFlightPerTransport timed out on the macOS runner waiting for three mice to connect behind a slot that drops every request (~1.5 s locally, >3 s on CI). The gate assertion itself held.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dpdev69
dpdev69 merged commit 5e9bd35 into main Oct 14, 2026
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.

1 participant