feat(openlogi-hid): route HID++ writes to directly-attached devices#5
Merged
Conversation
Introduce DeviceRoute { Bolt | Direct } so DPI, SmartShift, and control
capture work for devices attached over a USB cable or Bluetooth, not just
ones paired to a Bolt receiver. open_route_channel resolves either kind in
one place, replacing the Bolt-only assumption baked into the write and
capture paths.
Relax the inventory's direct-device discriminator from "must report a
battery" to "battery OR a control feature (adjustable DPI / reprogrammable
buttons)", so wired mice — which have no battery, e.g. a corded G502 — are
listed instead of dropped as a receiver's secondary interface.
The GUI builds a Bolt or Direct route per device (direct ones keyed by
vendor/product id at HID++ self-index 0xff), and the CLI diag commands plus
SharedChannel reuse follow the same route. DpiTarget/GestureTarget collapse
into DeviceRoute.
There was a problem hiding this comment.
ℹ️ Minor suggestion only — design and refactor look solid.
Reviewed changes — initial review of the DpiTarget/GestureTarget → DeviceRoute consolidation, the new open_route_channel single-source resolver, and the hybrid peripheral discriminator that admits wired devices.
- Introduce
DeviceRoute+open_route_channel— single Bolt-vs-direct resolver shared bywrite::with_routeandgesture::run_capture_session;DpiTarget/GestureTargetremoved;WriteError/GestureErrorcollapseReceiverNotFoundintoDeviceNotFoundand re-keyDeviceUnreachableonindexrather thanslot. - Hybrid
probe_directdiscriminator — keep a slot-0xffdevice if it reports a battery OR exposes0x2201/0x2202/0x1b04via root feature lookup; phantom-receiver guard still holds because a Bolt secondary interface exposes neither. - GUI plumbing —
DeviceRecord::routereplacesdpi_target;state/devices.rs::device_routebuilds Bolt or Direct from inventory (Bolt with no UID →Noneso writes are skipped, not mis-routed); DPI cycle / SmartShift / gesture capture all flow through the route. - CLI
diag—first_online_devicereturns(DeviceRoute, String);dpi/features/smartshifttake&DeviceRoute.
Claude Opus | 𝕏
Address PR review: a direct route's vendor/product id lives on the unopened async_hid DeviceInfo, so filter candidates by vid/pid before open_hidpp_channel rather than opening every node (~100ms each) first. On a host with a Bolt receiver plus a direct mouse, this stops every non-reused direct write from opening the receiver's channel first.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of the perf follow-up to the prior open_route_channel thread.
- Pre-filter
Directcandidates by vid/pid beforeopen_hidpp_channel— incrates/openlogi-hid/src/route.rs, the candidate loop nowcontinues on a vid/pid miss forDeviceRoute::Directbefore paying the ~100ms channel-open cost; the innerDeviceRoute::Direct { .. }match arm collapses toreturn Ok(Some(channel))because the pre-filter already guarantees the match. Bolt branch unchanged — still opens the channel forreceiver::detectandbolt.get_unique_id().
The prior review's only thread (route.rs:80-102) is addressed exactly as suggested and has been resolved.
Claude Opus | 𝕏
…ice-routing # Conflicts: # crates/openlogi-hid/src/gesture.rs
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.

What & why
Today every control path assumes a device hangs off a Logi Bolt receiver at a pairing slot. Directly-attached devices (USB cable or Bluetooth) are second-class: Bluetooth ones list but are read-only, and wired ones (no battery — e.g. a corded G502) are dropped entirely. This makes them first-class.
Three blockers removed:
probe_directrequired a battery → wired mice dropped0x2201/0x2202or ReprogControls0x1b04). A receiver's secondary0xffinterface exposes neither, so the phantom-entry guard still holds.with_device/open_target_channelbailed unlessReceiver::BoltDeviceRoute { Bolt { receiver_uid, slot } | Direct { vendor_id, product_id } };open_route_channelresolves both in one place. Direct devices are addressed at HID++ self-index0xffand re-found by vid/pid.DpiTargetrequired areceiver_uid→ direct devices gotdpi_target = None, so DPI/SmartShift/capture were never wired upBoltorDirectroute per device; DPI, SmartShift, and gesture/button capture all route through it.DpiTarget/GestureTargetcollapse intoDeviceRoute. The CLIdiagcommands andSharedChannelreuse follow the same route, soopenlogi diag {features,dpi,smartshift}work on direct devices too.Test plan
Run in the project devenv (real-Xcode Metal env for the GUI):
cargo clippy --workspace --all-targets -- -D warnings→ cleancargo fmt --check→ cleancargo test→ non-GUI 14 passed, GUI 6 passed, 0 failedeb2223c0) unchangedStill needs real-hardware verification (no G502 on hand)
transport.rs): only Logitech VID + usage page0xff00/ usage0x0002are enumerated — unchanged here. If a wired G502 exposes HID++ on a different vendor usage page (some G-series use0xff43), it won't appear at all. This is the first thing to confirm withopenlogi diag features.0x2201/0x2202/0x1b04. A variant speaking only legacy HID++ 1.0 registers would be skipped.