feat: add wired G-series keyboard RGB control#29
Conversation
110612a to
ec1abeb
Compare
There was a problem hiding this comment.
Important
The on-wire path hardcodes a G513-specific feature index, so the broader "G-series" claim in the PR description is unlikely to hold on non-G513 keyboards. A few stale/misleading docstrings to clean up alongside it.
Reviewed changes — initial review of the wired G-series keyboard RGB feature: HID++ enumeration, a per-key 0x8080 write path, a new LightingPanel, a generic non-pointer device_view, refresh hysteresis, multi-row tray, and the diag lighting CLI.
- Discover G-series HID nodes —
enumerate_hidpp_devicesnow also matches the(0xff43, 0x0602)vendor page+usage pair (crates/openlogi-hid/src/transport.rs). - Per-key lighting writer —
set_keyboard_colorsends raw0x12"set group keys" reports + a0x5acommit through a direct HID writer (crates/openlogi-hid/src/write.rs). - Typed
PerKeyLightingV0feature —0x8080wrapper added incrates/openlogi-hid/src/lighting.rsbut never called. diag lightingCLI — solid color command and a--spambrute-force probe over candidate0x8080/0x8070framings.Lightingconfig — per-device{ enabled, color, brightness }persisted inconfig.toml(crates/openlogi-core/src/config.rs).LightingPanel+device_view— swatches, on/off pill, release-only brightness slider; keyboards (and direct-pathUnknown) get lighting, other non-pointer kinds get an "isn't available yet" note (crates/openlogi-gui/src/components/...).AppState::commit_lighting— saves config and pushes viahardware::set_lighting_in_background, which spawns a thread + current-thread tokio runtime per write.- Carousel + refresh hysteresis —
MISS_THRESHOLD = 3keeps a device across a few missed scans;sort_device_listis shared by initial build and merge so order is stable (state.rs,state/devices.rs). - Multi-row tray —
set_device_status→set_device_lineswith up toMAX_DEVICE_ROWS = 8pre-allocatedNSMenuItems (platform/tray.rs,platform/status_item.rs).
⚠️ "G-series" scope vs. hardcoded 0x8080 feature index
The PR description lists G512, G513, G610, G810, G815, G Pro and similar, but the on-wire writer assumes the same 0x8080 feature index (0x0c) on every model. HID++ assigns feature indices per device at enumeration time — the G513 happens to land at 0x0c, other G-series keyboards generally do not. On a mismatched keyboard set_keyboard_color will still appear to succeed (raw output reports don't return an error for an unknown feature index), but no LEDs will change, so the failure mode is silent.
The fix shape that matches what crates/openlogi-hid/src/lighting.rs already gestures at is to resolve the 0x8080 feature index dynamically through the HID++ channel before sending the raw 0x12 frames — same pattern the DPI / SmartShift paths use.
Technical details
# "G-series" scope vs. hardcoded 0x8080 feature index
## Affected sites
- `crates/openlogi-hid/src/write.rs:164-198` — `rep[2] = 0x0c` for both the `0x12 setGroupKeys` frames and the `0x11 commit`. No per-device feature lookup.
- `crates/openlogi-hid/src/lighting.rs:1-86` — `PerKeyLightingV0` wrapper exists (`CreatableFeature::ID = 0x8080`) but is unused; `set_keyboard_color` bypasses the typed channel path entirely.
- PR description in #29 — claims G512/G513/G610/G810/G815/G Pro.
## Required outcome
- `set_keyboard_color` (and `spam_lighting`) resolves the runtime feature index of `0x8080` (and `0x8070` for `spam_lighting`'s `V5`/`V6` variants) per device, instead of using `0x0c` / `0x0d` literals; OR the PR's scope claim is narrowed in the description, code comments and the CLI help text to the models the hardcoded index has actually been validated on.
## Suggested approach
- Open a `HidppChannel` via `transport::open_hidpp_channel` for the route, call the existing feature-table lookup (the same one `dump_features` uses) to resolve `0x8080`'s index, then send the raw `0x12 / 0x11` frames with that index. The typed wrapper in `lighting.rs` can host the resolved index so the raw writes and the typed `set_solid_color` / `commit` stay consistent.
- The fallback for keyboards that expose `0x8070` (`ColorLedEffects`) but not `0x8080` is a follow-up — at minimum, fail loudly (`WriteError::FeatureUnsupported`) when neither feature is present rather than silently writing to a nonexistent index.
## Open questions for the human
- Has this been tested on anything other than a G513? If so, which models, and did they reuse feature index `0x0c`?
- Is the intent to ship the hardcoded path for G513-only first and broaden later, or to claim multi-model support up front? The PR description, the module comment in `lighting.rs`, and the actual implementation currently disagree.Claude Opus | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of ec1abeb against the previously-reviewed 110612a. The delta is cargo fmt only (line wraps in cli/lighting.rs, core/config.rs, gui/components/device_view.rs, gui/components/lighting_panel.rs, gui/platform/tray.rs, gui/state.rs, gui/state/devices.rs, hid/transport.rs, hid/write.rs) — no behavioural changes. Findings from the prior review still stand.
Claude Opus | 𝕏
ec1abeb to
d6ca00f
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of d6ca00f against the previously-reviewed ec1abeb. Two-file delta touching gui/hardware.rs and gui/state.rs; no other code paths moved. The stale set_lighting_in_background docstring is now accurate. Other findings from the initial review (110612a) — hardcoded 0x0c feature index, misleading set_keyboard_color docstring, missing brightness clamp on deserialize — still stand.
- Rewrote
set_lighting_in_backgrounddocstring — drops the "not wired yet" claim and describes the actual HID++ write path (crates/openlogi-gui/src/hardware.rs:120-125). set_lighting_in_backgroundnow borrowsLighting— signature changed fromLightingto&Lighting, eliminating thelighting.clone()previously needed at the call site.- Reordered
commit_lighting— the background HID++ write is now dispatched before the config mutation +save_atomic, so the device sees the new colour without waiting on disk I/O on the main thread (crates/openlogi-gui/src/state.rs:362-367).
Claude Opus | 𝕏
d6ca00f to
78a87b5
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of 78a87b5 against the previously-reviewed d6ca00f. This force-push consolidates the branch into one commit and resolves every open concern from the initial review: dynamic HID++ feature-index resolution, accurate docstring, brightness clamp on deserialize, debug-gated demo keyboard, and deletion of the unused typed wrapper.
- Resolve
0x8080 PerKeyLightingindex per device —set_keyboard_colornow opens anHidppChannel, callsdevice.root().get_feature(0x8080), and uses the returnedinfo.indexfor both the0x12 setGroupKeysframes and the0x11 commit. Also drops the hardcoded0xffdevice index in favour ofroute.device_index()(crates/openlogi-hid/src/write.rs:163-233). - Rewrote
set_keyboard_colordocstring — replaced the stale0x8070 ColorLedEffectsdescription with an accurate0x8080 PerKeyLightingsummary that also notes the runtime feature-index resolution (crates/openlogi-hid/src/write.rs:152-162). - Clamp deserialized brightness —
Lighting::brightnessnow usesdeserialize_with = "deserialize_brightness", which runsu8::deserialize(..)?.min(100)so a hand-editedconfig.tomlcan't feed out-of-range values into thec * brightness / 100scaling (crates/openlogi-core/src/config.rs:131-172). - Gate
OPENLOGI_DEMO_KEYBOARDbehindcfg(debug_assertions)— both the env-var check inbuild_device_listand thedemo_keyboardfn itself are now debug-only, so the dev scaffolding doesn't ship in release builds (crates/openlogi-gui/src/state/devices.rs:56-112). - Deleted the unused
PerKeyLightingV0wrapper —crates/openlogi-hid/src/lighting.rsis gone andpub mod lighting;is removed fromlib.rs, sinceset_keyboard_colornow drives the resolve-then-raw-write flow directly.
Claude Opus | 𝕏
78a87b5 to
d0fa77b
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of d0fa77b against the previously-reviewed 78a87b5. The force-push touches only crates/openlogi-gui/src/state.rs (+23/-2): a new AppState::same_physical_device helper threaded through merge_inventory so a transiently-flaky direct-USB config_key can no longer spawn a duplicate carousel/tray entry for the same physical device.
- Add
same_physical_devicehelper — returnstruewhen twoDeviceRecords share aconfig_key, or when both carry the sameroute(so a momentarily-different config key for the same physical device collapses).crates/openlogi-gui/src/state.rs:308-314. - Route dedup in
merge_inventory— the existing-device loop skips anexistingwhenmergedalready contains the same physical device (collapses pre-existing duplicates), and the "match existing against fresh" lookup as well as the "append unseen fresh" filter both now usesame_physical_deviceinstead of rawconfig_keyequality.crates/openlogi-gui/src/state.rs:265-301.
Route equality is the right discriminator here: DeviceRoute::Bolt { receiver_uid, slot } is unique per pairing, and DeviceRoute::Direct { vendor_id, product_id } already documents "two identical mice on one host are indistinguishable here, so the first match wins (acceptable for v0)" — the new dedup inherits that v0 limitation but doesn't introduce any new ambiguity. Miss-counting still keys by existing.config_key, which is safe because the lookup only fires when the existing record was not found in fresh.
Claude Opus | 𝕏
d0fa77b to
6a86d98
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of 6a86d98 against the previously-reviewed d0fa77b. This force-push rebases the feature commit onto current master (0ca1442), absorbing PR #28's HIDPP_LONG_COLLECTIONS table and PR #37's device_order_key / DeviceStableId. The feature-level deltas are integration-only — no new runtime behavior.
- Joined the new HIDPP collections table —
transport.rsdrops the bespokeHIDPP_G_USAGE_PAGE/HIDPP_G_LONG_USAGE_IDconstants and the hand-rolled filter; the wired G-series interface is now a third row(0xff43, 0x0602, false)inHIDPP_LONG_COLLECTIONS, withlong_only = falsesince wired G keyboards expose both report widths. Tests updated for bothis_hidpp_long_collectionandis_long_only_collection. - Reused master's stable-ordering helper —
state.rsswapssort_device_listfordevice_order_key, andstate/devices.rsdeletes its ownsort_device_list/route_sort_key.DeviceStableIdandDeviceIdentitywidened topub(super)somerge_inventorycan sort by them viamerged.sort_by_key(device_order_key). - Backfilled
DeviceRecordfields on the dev fixture —demo_keyboard()now setsserial_number: Noneandunit_id: [0; 4]to satisfy the new fields master added in PR #37 for identity-based ordering.
Claude Opus | 𝕏
AprilNEA
left a comment
There was a problem hiding this comment.
Thanks for the PR — the overall direction looks good and CI is green, but I think a few correctness/UI issues should be fixed before merging. The main blockers are the lighting slider state drifting from the selected device/config, treating every Unknown non-pointer device as a keyboard, and accepting non-RRGGBB values in the CLI despite the documented contract.
93907bd to
f254373
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of f254373 against the last-reviewed 6a86d98. This force-push incorporates all 5 concerns from AprilNEA's prior review of 93907bd: CLI hex validation, device-view keyboard heuristic, slider/state synchronisation, removal of the spam_lighting probe from the public API, and proper write-error propagation.
- CLI hex validation —
diag lightingnow rejects non-RRGGBBinputs (crates/openlogi-cli/src/cmd/diag/lighting.rs). - DeviceView keyboard heuristic narrowed —
is_keyboardmatchesDeviceKind::Keyboardor(Unknown + DeviceRoute::Direct)instead of blanket-unconditionally treatingUnknownas a keyboard (crates/openlogi-gui/src/components/device_view.rs). - Slider state synced with AppState —
LightingPaneltrackslast_brightnessand re-syncs theSliderStatethumb when the active device's brightness changes without fighting an in-progress drag (crates/openlogi-gui/src/components/lighting_panel.rs). spam_lightingremoved — the brute-force probe and its--spamCLI option are deleted;set_keyboard_coloris the sole lighting write path, with every HID error propagated throughWriteError(crates/openlogi-hid/src/write.rs,crates/openlogi-hid/src/lib.rs).
Big Pickle (free) (credentials for Anthropic not configured) | 𝕏
|
@AprilNEA Thanks for your comments they all should be addressed, please let me know if you find any other issues! |

Adds wired Logitech G-series keyboard support (G513, G512, G610, G810, G815, G Pro…).
0xff43HID++ usage page (Bolt/Unifying was0xff00-only)0x8080, wired to the lighting panel (swatches, on/off, brightness slider)openlogi diag lighting <RRGGBB>Screenshots