PerKeyLighting.write was force-claiming SW control via rgb_control.write(True)
through _ensure_sw_control whenever a saved per-key map was applied. On
startup with rgb_control persisted as False, the apply path would re-enable
LED Control and overwrite the persister with True — making "off" impossible
to keep across restarts.
The fix treats rgb_control as the single gate for LED activity: when it's
off, Solaar performs no SW claim, no per-key/zone wire writes, no SW release
on apply (we never claimed), no profile-management restoration, and no
shutdown-animation trigger on exit. This lets another tool (OpenRGB etc.)
drive the LEDs without Solaar fighting it.
Settings that don't actively change current lighting are still allowed:
NV-config writes for startup/shutdown animations, brightness control (its
own feature, no color push), and persister updates for per-key/zone state
so colors survive an off→on toggle.
UI: _apply_rgb_gates already greys per-key/zone/idle/sleep rows when
rgb_control is off. Fix a race where the async-read completion callback in
_update_setting_item would re-set sensitivity from the user lock-icon flag
alone and undo the grey-out if rgb_control's read happened to complete
first. Extract _gate_blocks as the single source of truth and AND-combine
it into _update_setting_item's set_sensitive call.
The locked-but-applied sensitivity state (False) means the user can't
change the setting from the UI — not that the value should be ignored.
Per-key paint written under sensitivity=False is still on the device's
LEDs and still the dominant layer; gating it out caused the idle dim
ramp to push zone Static over a still-visible per-key buffer, making
the per-key layer vanish instead of fade.
Software-managed LED persistence and power management for devices that
expose RGBEffects (0x8071) — primarily G515 LIGHTSPEED TKL, but the same
infrastructure works on any 0x8071 device that supports SW takeover.
Core mechanism: RGBControl toggle drives a Set SWControl(mode=3, flags)
handshake. While SW control is held, the host owns the LED pipeline: zone
effects, per-key paint, idle/sleep transitions, and the NvConfig boot/exit
animations. On release, the firmware resumes its onboard profile.
Major pieces:
- rgb_power.py — new module hosting the software RGB power manager:
ACTIVE / DIMMING / IDLE / SLEEPING state machine driven by firmware
onUserActivity events, smooth 5-second dim ramp (zone or per-key), idle
Static color snap, software sleep timer, wake handler that re-pushes
saved state. Includes the cleanup hook that runs on device close (and,
optionally, fires the cap 0x0040 shutdown trigger).
- RGBControl (settings_templates) — switch-style render via BooleanValidator
(true_value=3 / false_value=0) plus a full _claim_sw_control /
_release_sw_control pair: profile-management mode, SetSWControl, per-key
flag reset, manager start, cleanup registration, and a post-claim
repaint pass so the device immediately reflects Solaar's saved zone +
per-key state.
- RGBEffectSetting — zone-effect Setting subclass for 0x8071. Handles
per-key/zone coexistence: per-key paint dominates only when zone is
Static and the user has explicitly opted in via the lock icon; under
animations or before opt-in, the zone wire push is the visible layer.
- RGBIdleEffect / RGBIdleTimeout / RGBSleepTimeout — Solaar-managed idle
behavior. Choice list: "No change" → Dim → Static (snap to color) →
device-specific animations. Static idle substitutes the idle color for
unset per-key cells via effective_zone_base_color's state-aware lookup.
- RgbStartupAnimation / RgbShutdownAnimation — toggle-and-color rows for
RGBEffects NvConfig caps 0x0001 and 0x0040, exposed only on devices
that answer the probe. Shutdown trigger fires SetRgbPowerMode(0) at
cleanup time so the firmware plays the configured animation on exit.
- PerKeyLighting — per-key painter improvements: explicit-opt-in
dominance over zone effects, BUSY retry, FrameEnd suppression on
per-cell failure, single-shot prep sequence (SetEffectByIndex into the
out-of-range slot) on mice with firmware effect cards.
- device_quirks.py — small per-model quirks table keyed by device.modelId
(stable across USB/BLE/wireless). Currently used to mark RGBEffects
NvConfig color slots as inert on devices where the firmware accepts
but ignores the bytes (G502 X PLUS startup colors).
- config_panel.py — HeteroKeyControl gains a TOGGLE field kind that
renders as a Gtk.Switch (used by the boot-effect rows). Visual gate
greys out RGB settings whose prerequisites aren't met: rgb_zone_* /
rgb_idle_* / rgb_sleep_timeout require LED Control = Solaar; per-key
additionally requires every zone effect to be Static. Visual-only —
doesn't touch the persister's user lock-icon state.
- tests/test_rgb_power.py — coverage for the power manager state
machine, dim ramp, idle effects, wake path, and per-key/zone
coexistence.
Closespwr-Solaar/Solaar#3149.