AprilNEA / AprilNEA/OpenLogi

[Bug]: Keyboard lighting reverts to the onboard effect ~20s after a colour is set (G915 X TKL)

Open
#1,000 0 comments 0 reactions 0 assignees View on GitHub
area: hidpp platform: all type: bug
Dominant language
Rust
Stars
21k
Forks
675
Avg merge
2d 5h
Merged PRs (30d)
172

Description

**OpenLogi version:** 0.7.10
**OS:** macOS 15, Apple Silicon
**Device:** Logitech G915 X LIGHTSPEED TKL (`046d:b38b`)
**Connection:** Bluetooth (paired directly, no receiver)
**Affected area:** GUI + agent — lighting

## What happened

Picking a colour in the Lighting panel works, and then stops working about twenty seconds
later: the keyboard returns to the wave effect stored on it, while the panel continues to
show the colour it no longer has. Clicking the swatch again repeats the cycle.

## Steps to reproduce

1. Pair a G915 X TKL over Bluetooth and open its Lighting panel.
2. Turn lighting on and pick any swatch. The keyboard changes colour immediately.
3. Wait ~20 seconds without touching anything.
4. The keyboard resumes its onboard wave effect. The GUI still shows the swatch selected.

## Why it happens

Nothing here is a failed write — I instrumented the exchange and the device answers
correctly throughout. Three behaviours combine:

1. **The write is volatile.** `set_color_effects` passes `Persistence::Volatile`
(`openlogi-device/src/write/lighting.rs`), so the colour lands in the keyboard's RAM
and the effect stored on the device is untouched. That looks deliberate — avoiding
flash wear — and I am not suggesting changing it.

2. **Software never claims the LEDs.** Both `ColorLedEffects` (`0x8070`) and `RgbEffects`
(`0x8071`) expose `setSwControl`, and `rgb_effects`' own module docs say software
"must first take control … before applying effects or power modes, or those calls
return a 'not allowed' error". Grepping the workspace, nothing calls either one — the
functions exist in `openlogi-hidpp` and have no callers. So the firmware still owns the
LEDs and, after its timeout, resumes what it has stored.

3. **Nothing re-asserts it.** `reapply_volatile_settings` runs on device *arrival* and on
the wake path. A keyboard that reverts while it stays connected and online produces
neither event, so the re-apply never fires.

The existing code anticipates part of this — `lighting.rs` notes that on G-series
keyboards the firmware "keeps replaying its stored effect" — but only to justify
preferring `0x8070` over `0x8080`, not to keep the colour afterwards.

## Recommended changes

Roughly in order of how much I would want them, and the first two are alternatives rather
than a sequence:

### 1. Claim the LEDs with `setSwControl` (the real fix)

Take software control before applying an effect, and release it on quit, on device sleep,
and on agent shutdown. This is what stops the firmware reclaiming the LEDs at all, and it
is what Logitech's own stack does.

The risk that needs care: ownership must be released cleanly on quit, crash and sleep, or
the keyboard is left with LEDs that nothing is driving. That is presumably why it has not
been done yet. `0x8071` also refuses effect writes outright without the claim, so the two
features need the claim handled separately.

### 2. Failing that, re-assert the colour on a timer (opt-in)

A per-device flag — call it Sticky Mode — that repeats the same volatile write on an
interval. Measured on this hardware the firmware reclaims at ~20s, so ~15s is inside the
window with margin. It should be **off by default**: it only helps devices that revert,
and on hardware without `0x8070` the repeat is a ~64-frame per-key sweep (see below),
which is real traffic to spend every interval.

I have this implemented and working against the hardware — config flag, agent-side ticker,
GUI toggle with an explanatory tooltip, tests, `PROTOCOL_VERSION` bump with regenerated
wire goldens, and all 22 locale catalogs. Happy to open it as a PR if you want it; equally
happy to attempt (1) instead, which I think is the better change.

### 3. Surface the drop rate, whichever path is taken

On an idle Bluetooth LE link, individual HID++ writes to this keyboard time out fairly
often — 2 of 5 in one sample — and succeed on an immediate retry (0 of 12 failed once
retried). Whatever re-asserts the colour will want a short retry rather than treating one
timeout as a lost cause.

### 4. Two smaller things noticed on the way

- `openlogi diag lighting`'s help text says "wired RGB keyboard", but the code selects on
"no receiver UID", which a Bluetooth-direct device satisfies. The docs are narrower than
the behaviour.
- `#688` is the same family of gap on the diagnostics side for `0x1001`-only devices.

## Hardware detail, in case it is useful

Paired directly over Bluetooth LE, so the HID++ node is the `0xFF43 / 0x0202` collection
and the device index is `0xFF`. The keyboard resolves **`0x8081 PerKeyLightingV2` at index
`0x0a`** and exposes **no `0x8070`** — worth knowing because it makes the per-key sweep the
realistic path on a G915 X, not the fallback. `capabilities.lighting = true` is detected
correctly, and colour changes do apply; they just do not survive.

## Note

Btw, great work on this application! I tried using the Logitech software on mac and ran into a number of issues. Glad there is an open source alternative!

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with openlogi-device/src/write/lighting.rs, the ColorLedEffects and RgbEffects implementations in openlogi-hidpp, and the reapply_volatile_settings wake and arrival paths. Trace the agent and GUI lighting entry points and existing lifecycle tests before choosing between software control and opt-in re-assertion. Done means the G915 X TKL keeps its selected lighting while connected and handles sleep, quit, and retry behavior without leaving stale ownership.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, rust
Domain
desktop, embedded-iot
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.