From 7cb7bc68bc228686e49ac8e2a931cc6f9245545d Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Tue, 12 May 2026 00:08:25 -0700 Subject: [PATCH] solaar show: enumerate LED effects under RGB_EFFECTS / COLOR_LED_EFFECTS Print each zone's effect slot (index, wire ID, name from LEDEffects table, decoded capabilities bits, default period, and the param keys the host would expose as widgets) for any device with 0x8070 or 0x8071. Unknown wire IDs print as 'Unknown(0xXX)' rather than being skipped, so probing surfaces effects the table doesn't yet model. Capabilities are decoded against the documented bits (color, fade, period, direction, fw-handled) with any remaining bits printed as a residual hex value, since LGHUB sets additional bits on newer effects whose semantics aren't yet known. --- lib/solaar/cli/show.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py index ab03a5b9..0dad16b7 100644 --- a/lib/solaar/cli/show.py +++ b/lib/solaar/cli/show.py @@ -112,6 +112,17 @@ def _print_centurion_dongle_features(receiver): print(f" Hardware: model {model_id}" f" rev {hw_rev} product {product_id:04X}") +_LED_CAPS_BITS = ((0x0001, "color"), (0x0002, "fade"), (0x0004, "period"), (0x0010, "direction"), (0xC000, "fw")) + + +def _decode_led_caps(caps): + names = [name for mask, name in _LED_CAPS_BITS if caps & mask] + other = caps & ~sum(m for m, _ in _LED_CAPS_BITS) + if other: + names.append(f"+{other:#06x}") + return f"0x{caps:04x}=" + ("+".join(names) if names else "none") + + def _battery_text(level) -> str: if level is None: return "N/A" @@ -363,6 +374,24 @@ def _print_device(dev, num=None): bands = hidpp20.get_onboard_eq_params(dev) if bands: print(f" EQ: {', '.join(f'{f}Hz:{g:+d}dB' for f, g, _q in bands)}") + elif feature == SupportedFeature.RGB_EFFECTS or feature == SupportedFeature.COLOR_LED_EFFECTS: + try: + infos = dev.led_effects + except Exception as e: + print(f" Effect enumeration failed: {e}") + infos = None + if infos and infos.zones: + for zone in infos.zones: + print(f" Zone {int(zone.index)} ({zone.location}): {len(zone.effects)} effect(s)") + for e in zone.effects: + entry = hidpp20.LEDEffects.get(e.ID) + name = entry[0].name if entry else f"Unknown(0x{e.ID:02x})" + caps = _decode_led_caps(e.capabilities) + params = ", ".join(str(p) for p in entry[1]) if entry and entry[1] else "—" + print( + f" [{e.index}] 0x{e.ID:02x} {name:<14} " + f"caps {caps:<28} default {e.period}ms params: {params}" + ) elif hidpp20.battery_functions.get(feature, None): print("", end=" ") _battery_line(dev)