HeadsetRGBColor: use shared special_keys.COLORS palette

Drop the ad-hoc 10-color preset list and feed special_keys.COLORS
(the Xorg rgb.txt palette solaar already uses for DPI button color
mapping) directly as the ChoicesValidator choices_universe. Users now
pick from the same named colors other parts of the app expose, and we
stop inventing a one-off naming scheme.

"Off" goes away as a color option — to disable host control, toggle
the separate headset-rgb-hostmode setting instead. Picking `black`
writes (0,0,0) which effectively turns the LEDs off while host mode
stays claimed.

This still isn't the "ideal" solaar pattern (Kind.COLOR with
Gtk.ColorButton via HeteroValidator), but it reuses existing
infrastructure — ChoicesValidator + NamedInts — instead of creating
new patterns just for this setting.
This commit is contained in:
Ken Sanislo 2026-04-17 17:25:29 -07:00
parent 059874c210
commit b90bbf8af3
1 changed files with 45 additions and 60 deletions

View File

@ -1783,38 +1783,24 @@ class HeadsetRGBHostMode(settings.Setting):
validator_class = settings_validator.BooleanValidator
# Preset RGB colors for headset-rgb-color. Name → (R, G, B).
# "Off" disables host mode and returns control to firmware.
_HEADSET_RGB_COLORS = [
("Off", None),
("Red", (0xFF, 0x00, 0x00)),
("Green", (0x00, 0xFF, 0x00)),
("Blue", (0x00, 0x00, 0xFF)),
("White", (0xFF, 0xFF, 0xFF)),
("Yellow", (0xFF, 0xFF, 0x00)),
("Cyan", (0x00, 0xFF, 0xFF)),
("Magenta", (0xFF, 0x00, 0xFF)),
("Orange", (0xFF, 0x80, 0x00)),
("Purple", (0x80, 0x00, 0xFF)),
]
class HeadsetRGBColor(settings.Setting):
"""Set headset RGB zones to a preset color.
"""Pick a color from the shared `special_keys.COLORS` palette and apply it
to all headset RGB zones via HeadsetRGBHostmode (0x0620).
Drives HeadsetRGBHostmode (0x0620). On write, enables host mode, queries
available zones, sets them all to the chosen RGB color, and commits via
FrameEnd. "Off" disables host mode to return control to firmware.
On write: enables host mode, queries zone IDs via GetRGBZoneInfo, sets
them all to the chosen RGB color via SetRgbZonesSingleValue, commits via
FrameEnd. Picking `black` effectively turns the LEDs off; fully disabling
host control is done through the separate headset-rgb-hostmode toggle.
"""
name = "headset-rgb-color"
label = _("Headset RGB Color")
description = _("Set headset LED color (zones set to a single preset).")
description = _("Set headset LED color (all zones, from the shared color palette).")
feature = _F.HEADSET_RGB_HOSTMODE
choices_universe = common.NamedInts(**{name: idx for idx, (name, _rgb) in enumerate(_HEADSET_RGB_COLORS)})
choices_universe = special_keys.COLORS
validator_class = settings_validator.ChoicesValidator
persist = True
# Write-only: we don't read back the current color (host mode just reports on/off).
# Write-only: we don't read back the current color (0x0620 doesn't expose it).
rw_options = {"read_fnid": None, "write_fnid": None}
@classmethod
@ -1832,49 +1818,48 @@ class HeadsetRGBColor(settings.Setting):
def write(self, value, save=True):
if value is None:
return None
idx = int(value)
if not (0 <= idx < len(_HEADSET_RGB_COLORS)):
try:
color_int = int(value)
except (TypeError, ValueError):
return None
name, rgb = _HEADSET_RGB_COLORS[idx]
if color_int not in special_keys.COLORS:
return None
name = str(special_keys.COLORS[color_int])
r = (color_int >> 16) & 0xFF
g = (color_int >> 8) & 0xFF
b = color_int & 0xFF
device = self._device
if not device.online:
logger.info("HeadsetRGBColor: device offline, skipping write of %s", name)
return None
try:
if rgb is None:
# "Off" → disable host mode, return to firmware control.
logger.info("HeadsetRGBColor: disabling host mode (SetHostModeState 0x00)")
resp = device.feature_request(_F.HEADSET_RGB_HOSTMODE, 0x80, b"\x00")
logger.info("HeadsetRGBColor: SetHostModeState resp=%s", resp.hex() if resp else resp)
else:
zone_ids = self._zone_ids(device)
if not zone_ids:
logger.warning("HeadsetRGBColor: no zones available; cannot set color %s", name)
return None
r, g, b = rgb
logger.info(
"HeadsetRGBColor: setting color %s RGB=(%02X,%02X,%02X) on %d zone(s): %s",
name,
r,
g,
b,
len(zone_ids),
[f"0x{z:02X}" for z in zone_ids],
)
# Enable host mode first.
resp = device.feature_request(_F.HEADSET_RGB_HOSTMODE, 0x80, b"\x01")
logger.info("HeadsetRGBColor: SetHostModeState(1) resp=%s", resp.hex() if resp else resp)
# SetRgbZonesSingleValue: [R, G, B, count, zone_ids...]
payload = bytes([r, g, b, len(zone_ids)]) + bytes(zone_ids)
resp = device.feature_request(_F.HEADSET_RGB_HOSTMODE, 0x50, payload)
logger.info(
"HeadsetRGBColor: SetRgbZonesSingleValue payload=%s resp=%s",
payload.hex(),
resp.hex() if resp else resp,
)
# FrameEnd: commit the frame.
resp = device.feature_request(_F.HEADSET_RGB_HOSTMODE, 0x60, b"\x00\x00\x00\x00")
logger.info("HeadsetRGBColor: FrameEnd resp=%s", resp.hex() if resp else resp)
zone_ids = self._zone_ids(device)
if not zone_ids:
logger.warning("HeadsetRGBColor: no zones available; cannot set color %s", name)
return None
logger.info(
"HeadsetRGBColor: setting color %s RGB=(%02X,%02X,%02X) on %d zone(s): %s",
name,
r,
g,
b,
len(zone_ids),
[f"0x{z:02X}" for z in zone_ids],
)
# Enable host mode first.
resp = device.feature_request(_F.HEADSET_RGB_HOSTMODE, 0x80, b"\x01")
logger.info("HeadsetRGBColor: SetHostModeState(1) resp=%s", resp.hex() if resp else resp)
# SetRgbZonesSingleValue: [R, G, B, count, zone_ids...]
payload = bytes([r, g, b, len(zone_ids)]) + bytes(zone_ids)
resp = device.feature_request(_F.HEADSET_RGB_HOSTMODE, 0x50, payload)
logger.info(
"HeadsetRGBColor: SetRgbZonesSingleValue payload=%s resp=%s",
payload.hex(),
resp.hex() if resp else resp,
)
# FrameEnd: commit the frame.
resp = device.feature_request(_F.HEADSET_RGB_HOSTMODE, 0x60, b"\x00\x00\x00\x00")
logger.info("HeadsetRGBColor: FrameEnd resp=%s", resp.hex() if resp else resp)
except Exception as e:
logger.warning("HeadsetRGBColor write failed for %s: %s", name, e)
return None