headset RGB: LED Control as a claim switch + keyboard-style restructure
Rework headset RGB lighting so it mirrors the keyboard/mouse model instead of its own ad-hoc shape. LED Control (0x0620 HostMode) becomes a boolean toggle: whether Solaar holds the headset's live-coloring claim. Off releases the LEDs so another app (e.g. OpenRGB) can drive them; on lets Solaar drive. 0x0620 per-zone painting and the 0x0621 onboard effect are both live LED control, so both are gated on the claim — UI rows grey out and wire writes are skipped (value still persisted) when the claim isn't held, mirroring the keyboard's RGBEffectSetting under rgb_control. 0x0621 HeadsetOnboardEffect is now the primary lighting setting, the headset analog of keyboard 0x8071 zone effects. Its build reads the cluster's supported-effect set so the picker offers only those; effect id 0 is labelled "Static" to match every other Solaar device. The redundant HeadsetLEDsPrimary (0x0620 single-colour host push) is removed — that job is exactly the 0x0621 Static effect. HeadsetPerZoneLighting is the per-key-style overlay: gated on the claim AND the onboard effect being Static, since per-zone painting overlays a Static cluster effect (the analog of keyboard per-key needing rgb_control + zone Static). The 0x0622 signature effects (startup/shutdown/passive colours) are the only stored settings here and stay ungated — editable whether or not Solaar holds the claim. On re-claim HeadsetLEDControl.write reasserts the dominant layer: per-zone painting when the onboard effect is Static, else the 0x0621 effect itself.
This commit is contained in:
parent
936991e0b4
commit
4a7edd75ce
|
|
@ -2330,13 +2330,31 @@ def _headset_setting_by_name(device, name):
|
||||||
|
|
||||||
|
|
||||||
def _headset_primary_color(device, default=0xFFFFFF):
|
def _headset_primary_color(device, default=0xFFFFFF):
|
||||||
"""Resolve the currently-saved Primary color, or `default` if absent."""
|
"""The headset's base color — the 0x0621 onboard Fixed-effect color.
|
||||||
s = _headset_setting_by_name(device, HeadsetLEDsPrimary.name)
|
Per-zone 'No change' cells resolve against this. Returns `default` when
|
||||||
|
the onboard-effect setting is absent or not currently on Fixed."""
|
||||||
|
s = _headset_setting_by_name(device, HeadsetOnboardEffect.name)
|
||||||
|
value = getattr(s, "_value", None) if s is not None else None
|
||||||
|
if value is not None and int(getattr(value, "ID", -1)) == 0:
|
||||||
|
return int(getattr(value, "color1", default))
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _headset_cluster_effect_is_fixed(device):
|
||||||
|
"""True when the 0x0621 onboard effect is Fixed (the Static analog), or
|
||||||
|
when the device has no onboard-effect setting. A non-Fixed cluster
|
||||||
|
animation masks the per-zone buffer, so per-zone writes are suppressed
|
||||||
|
while one runs."""
|
||||||
|
s = _headset_setting_by_name(device, HeadsetOnboardEffect.name)
|
||||||
if s is None:
|
if s is None:
|
||||||
return default
|
return True
|
||||||
value = getattr(s, "_value", None)
|
value = getattr(s, "_value", None)
|
||||||
color = getattr(value, "color", None) if value is not None else None
|
if value is None:
|
||||||
return int(color) if color is not None else default
|
persister = getattr(device, "persister", None)
|
||||||
|
value = persister.get(HeadsetOnboardEffect.name) if persister else None
|
||||||
|
if value is None:
|
||||||
|
return True
|
||||||
|
return int(getattr(value, "ID", 0)) == 0
|
||||||
|
|
||||||
|
|
||||||
def _headset_per_zone_overrides(device):
|
def _headset_per_zone_overrides(device):
|
||||||
|
|
@ -2360,35 +2378,48 @@ def _headset_per_zone_overrides(device):
|
||||||
return overrides
|
return overrides
|
||||||
|
|
||||||
|
|
||||||
class _HeadsetStaticEffectOption:
|
def _headset_led_control_on(device):
|
||||||
"""Minimal stand-in for `hidpp20.LEDEffectInfo`.
|
"""True when the headset LED Control is on (Solaar drives the LEDs).
|
||||||
|
When off, the firmware owns the LEDs and host color writes are
|
||||||
`HeteroValidator` only inspects `.ID` and `.index` on its `options`
|
suppressed — the value is still persisted so it re-applies on switch-on.
|
||||||
list; we don't need the full device-query machinery here because the
|
Reads setting._value first, then the persister; accepts a bool or a
|
||||||
headset wire protocol is handled by `headset_rgb.write_zone_map`.
|
legacy int 0/1 from the old ChoicesValidator era."""
|
||||||
"""
|
s = _headset_setting_by_name(device, HeadsetLEDControl.name)
|
||||||
|
v = getattr(s, "_value", None) if s is not None else None
|
||||||
ID = 0x01 # matches hidpp20.LEDEffects[0x01] = Static
|
if v is None:
|
||||||
index = 0x01
|
persister = getattr(device, "persister", None)
|
||||||
|
v = persister.get(HeadsetLEDControl.name) if persister else None
|
||||||
|
if v is None:
|
||||||
|
return True # unknown — don't suppress
|
||||||
|
return bool(v)
|
||||||
|
|
||||||
|
|
||||||
class HeadsetLEDControl(settings.Setting):
|
class HeadsetLEDControl(settings.Setting):
|
||||||
"""Switch headset LED control between device and Solaar.
|
"""Whether Solaar holds the headset's live-coloring claim.
|
||||||
|
|
||||||
Mirrors the `LEDControl` / `RGBControl` pattern used for keyboards and
|
Mirrors the `RGBControl` pattern for keyboards and mice. On = Solaar
|
||||||
mice. When set to Solaar, the `LEDs Primary` and `Per-zone Lighting`
|
may drive the LEDs — the 0x0621 onboard effect and 0x0620 per-zone
|
||||||
settings drive the LEDs; when set to Device, firmware-driven onboard
|
painting are both live LED control; off = Solaar releases the LEDs so
|
||||||
and signature effects resume.
|
another app (e.g. OpenRGB) can drive them. The 0x0622 signature
|
||||||
|
effects are stored settings (startup/shutdown colors), not live
|
||||||
|
coloring, and stay editable either way.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "headset_led_control"
|
name = "headset_led_control"
|
||||||
label = _("LED Control")
|
label = _("LED Control")
|
||||||
description = _("Switch control of LED zones between device and Solaar")
|
description = _("Allow Solaar to control the headset LED zones.")
|
||||||
feature = _F.HEADSET_RGB_HOSTMODE
|
feature = _F.HEADSET_RGB_HOSTMODE
|
||||||
rw_options = {"read_fnid": 0x70, "write_fnid": 0x80}
|
rw_options = {"read_fnid": 0x70, "write_fnid": 0x80}
|
||||||
choices_universe = common.NamedInts(Device=0, Solaar=1)
|
# Two-state — render as a Gtk.Switch. Wire byte: 1 = Solaar (host) control,
|
||||||
validator_class = settings_validator.ChoicesValidator
|
# 0 = Device (firmware) control.
|
||||||
validator_options = {"choices": choices_universe}
|
validator_class = settings_validator.BooleanValidator
|
||||||
|
validator_options = {"true_value": 1, "false_value": 0}
|
||||||
|
|
||||||
|
def _pre_read(self, cached, key=None):
|
||||||
|
# Migrate legacy int values (0/1 from the old ChoicesValidator) to bool.
|
||||||
|
super()._pre_read(cached, key)
|
||||||
|
if isinstance(self._value, int) and not isinstance(self._value, bool):
|
||||||
|
self._value = self._value != 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, device):
|
def build(cls, device):
|
||||||
|
|
@ -2402,105 +2433,24 @@ class HeadsetLEDControl(settings.Setting):
|
||||||
return super().build(device)
|
return super().build(device)
|
||||||
|
|
||||||
def write(self, value, save=True):
|
def write(self, value, save=True):
|
||||||
# After switching to Solaar control, the firmware drops whatever
|
# On re-claim the firmware drops our colors; reassert the dominant
|
||||||
# colors we'd programmed — so reassert the saved Primary + per-zone
|
# layer — per-zone when the onboard effect is Static, else the effect.
|
||||||
# overrides immediately. Otherwise the LEDs stay on whatever
|
|
||||||
# device-driven effect was last shown until the user edits a color.
|
|
||||||
result = super().write(value, save)
|
result = super().write(value, save)
|
||||||
if result is not None and int(value) == 1 and self._device.online:
|
if result is not None and value and self._device.online:
|
||||||
primary = _headset_primary_color(self._device)
|
if _headset_cluster_effect_is_fixed(self._device):
|
||||||
zones = headset_rgb.discover_zones(self._device)
|
primary = _headset_primary_color(self._device)
|
||||||
if zones:
|
zones = headset_rgb.discover_zones(self._device)
|
||||||
zone_map = {int(z): primary for z in zones}
|
if zones:
|
||||||
zone_map.update(_headset_per_zone_overrides(self._device) or {})
|
zone_map = {int(z): primary for z in zones}
|
||||||
headset_rgb.write_zone_map(self._device, zone_map)
|
zone_map.update(_headset_per_zone_overrides(self._device) or {})
|
||||||
|
headset_rgb.write_zone_map(self._device, zone_map)
|
||||||
|
else:
|
||||||
|
onboard = next((s for s in self._device.settings if s.name == "headset-onboard-effect"), None)
|
||||||
|
if onboard is not None and onboard._value is not None:
|
||||||
|
onboard.write(onboard._value, save=False)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class HeadsetLEDsPrimary(settings.Setting):
|
|
||||||
"""Primary headset LED color, rendered as a GTK color picker.
|
|
||||||
|
|
||||||
Mirrors the `LEDZoneSetting` / `RGBEffectSetting` shape: a
|
|
||||||
`HeteroValidator` with a single "Static" effect whose only visible
|
|
||||||
field is the color. Write applies the chosen color across all zones
|
|
||||||
discovered at build time, then re-applies any per-zone overrides on
|
|
||||||
top so they aren't clobbered.
|
|
||||||
|
|
||||||
Read support is deliberately disabled — the feature exposes no "get
|
|
||||||
current color" function, so we rely on the persister.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "headset_leds_primary"
|
|
||||||
label = _("LEDs") + " " + _("Primary")
|
|
||||||
description = _(
|
|
||||||
"Set the primary color applied to every headset LED zone.\n" "LED Control needs to be set to Solaar to be effective."
|
|
||||||
)
|
|
||||||
feature = _F.HEADSET_RGB_HOSTMODE
|
|
||||||
persist = True
|
|
||||||
rw_options = {"read_fnid": None, "write_fnid": None}
|
|
||||||
|
|
||||||
# HeteroKeyControl renders exactly these fields; ID is hidden
|
|
||||||
# (`label=None`) but kept so setup_visibles can key off it.
|
|
||||||
color_field = {"name": hidpp20.LEDParam.color, "kind": settings.Kind.COLOR, "label": _("Color")}
|
|
||||||
possible_fields = [
|
|
||||||
{
|
|
||||||
"name": "ID",
|
|
||||||
"kind": settings.Kind.CHOICE,
|
|
||||||
"label": None,
|
|
||||||
"choices": [common.NamedInt(0x01, _("Static"))],
|
|
||||||
},
|
|
||||||
color_field,
|
|
||||||
]
|
|
||||||
# HeteroKeyControl.setup_visibles looks up fields_map[effect_id][1] to
|
|
||||||
# decide which fields to show — we only expose the color.
|
|
||||||
fields_map = {0x01: [common.NamedInt(0x01, _("Static")), {hidpp20.LEDParam.color: 0}]}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def build(cls, device):
|
|
||||||
zones = headset_rgb.discover_zones(device)
|
|
||||||
if not zones:
|
|
||||||
return None
|
|
||||||
rw = settings.FeatureRW(cls.feature)
|
|
||||||
validator = settings_validator.HeteroValidator(
|
|
||||||
data_class=hidpp20.LEDEffectSetting,
|
|
||||||
options=[_HeadsetStaticEffectOption()],
|
|
||||||
readable=False,
|
|
||||||
)
|
|
||||||
return cls(device, rw, validator)
|
|
||||||
|
|
||||||
def read(self, cached=True):
|
|
||||||
# Feature 0x0620 doesn't expose a "current primary color" read —
|
|
||||||
# pull from the persister via _pre_read, fall back to white so
|
|
||||||
# the picker opens on a sane starting color.
|
|
||||||
self._pre_read(cached)
|
|
||||||
if self._value is not None:
|
|
||||||
return self._value
|
|
||||||
self._value = hidpp20.LEDEffectSetting(ID=common.NamedInt(0x01, _("Static")), color=0xFFFFFF)
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def write(self, value, save=True):
|
|
||||||
color = getattr(value, "color", None)
|
|
||||||
if color is None:
|
|
||||||
return None
|
|
||||||
device = self._device
|
|
||||||
if not device.online:
|
|
||||||
return None
|
|
||||||
zones = headset_rgb.discover_zones(device)
|
|
||||||
if not zones:
|
|
||||||
return None
|
|
||||||
primary = int(color)
|
|
||||||
zone_map = {int(z): primary for z in zones}
|
|
||||||
# Re-apply any non-"No change" per-zone overrides on top of the
|
|
||||||
# fresh Primary baseline so the user's explicit zone choices stick
|
|
||||||
# when they change the bulk color.
|
|
||||||
overrides = _headset_per_zone_overrides(device) or {}
|
|
||||||
zone_map.update(overrides)
|
|
||||||
if headset_rgb.write_zone_map(device, zone_map):
|
|
||||||
self.update(value, save)
|
|
||||||
return value
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class HeadsetPerZoneLighting(settings.Settings):
|
class HeadsetPerZoneLighting(settings.Settings):
|
||||||
"""Per-zone LED color overrides.
|
"""Per-zone LED color overrides.
|
||||||
|
|
||||||
|
|
@ -2559,19 +2509,24 @@ class HeadsetPerZoneLighting(settings.Settings):
|
||||||
if not device.online:
|
if not device.online:
|
||||||
return None
|
return None
|
||||||
self.update(map_, save)
|
self.update(map_, save)
|
||||||
|
# Gate the wire on both conditions, like keyboard per-key (needs
|
||||||
|
# rgb_control on + zone Static): LED Control on, cluster effect Fixed.
|
||||||
|
if not _headset_led_control_on(device) or not _headset_cluster_effect_is_fixed(device):
|
||||||
|
return map_ # value stored, skip the wire
|
||||||
primary = _headset_primary_color(device)
|
primary = _headset_primary_color(device)
|
||||||
zone_map = self._resolve_zone_map(map_, primary)
|
zone_map = self._resolve_zone_map(map_, primary)
|
||||||
if not zone_map:
|
if not zone_map:
|
||||||
return None
|
|
||||||
if headset_rgb.write_zone_map(device, zone_map):
|
|
||||||
return map_
|
return map_
|
||||||
return None
|
headset_rgb.write_zone_map(device, zone_map)
|
||||||
|
return map_
|
||||||
|
|
||||||
def write_key_value(self, key, value, save=True):
|
def write_key_value(self, key, value, save=True):
|
||||||
result = super().write_key_value(int(key), value, save)
|
result = super().write_key_value(int(key), value, save)
|
||||||
device = self._device
|
device = self._device
|
||||||
if not device.online:
|
if not device.online:
|
||||||
return result
|
return result
|
||||||
|
if not _headset_led_control_on(device) or not _headset_cluster_effect_is_fixed(device):
|
||||||
|
return result # value stored, skip the wire
|
||||||
try:
|
try:
|
||||||
v = int(value)
|
v = int(value)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
|
|
@ -2808,11 +2763,12 @@ yaml.add_representer(_HeadsetOnboardEffect, _HeadsetOnboardEffect.to_yaml)
|
||||||
|
|
||||||
|
|
||||||
class HeadsetOnboardEffect(settings.Setting):
|
class HeadsetOnboardEffect(settings.Setting):
|
||||||
"""The firmware RGB effect a headset runs autonomously on its primary
|
"""The RGB effect the headset shows on its primary lighting cluster
|
||||||
lighting cluster (HEADSET_RGB_ONBOARD_EFFECTS, 0x0621). Build reads the
|
(HEADSET_RGB_ONBOARD_EFFECTS, 0x0621). Build reads the cluster's
|
||||||
cluster's supported-effect set so the picker offers only those. Like the
|
supported-effect set so the picker offers only those. This is live LED
|
||||||
signature/boot effects this is firmware-driven and not gated on host LED
|
control, like per-zone painting — gated on Solaar holding the LED-control
|
||||||
control. Multi-cluster devices (none seen yet) drive only cluster 0."""
|
claim: writes are skipped and the row greys out when the claim is
|
||||||
|
released. Multi-cluster devices (none seen yet) drive only cluster 0."""
|
||||||
|
|
||||||
name = "headset-onboard-effect"
|
name = "headset-onboard-effect"
|
||||||
label = _("Onboard Effect")
|
label = _("Onboard Effect")
|
||||||
|
|
@ -2821,7 +2777,7 @@ class HeadsetOnboardEffect(settings.Setting):
|
||||||
|
|
||||||
_CLUSTER = 0
|
_CLUSTER = 0
|
||||||
_ALL_EFFECTS = (
|
_ALL_EFFECTS = (
|
||||||
("Fixed", 0),
|
("Static", 0),
|
||||||
("Color Cycle", 1),
|
("Color Cycle", 1),
|
||||||
("Color Wave", 2),
|
("Color Wave", 2),
|
||||||
("Breathing", 3),
|
("Breathing", 3),
|
||||||
|
|
@ -2852,7 +2808,11 @@ class HeadsetOnboardEffect(settings.Setting):
|
||||||
return reply[1:10] # strip clusterIndex -> [effectId_u16, 7 param bytes]
|
return reply[1:10] # strip clusterIndex -> [effectId_u16, 7 param bytes]
|
||||||
|
|
||||||
def write(self, device, data_bytes):
|
def write(self, device, data_bytes):
|
||||||
# data_bytes is [effectId_u16, 7 param bytes]; prepend clusterIndex
|
# data_bytes is [effectId_u16, 7 param bytes]; prepend clusterIndex.
|
||||||
|
# The onboard effect is live LED control — skip the wire when Solaar
|
||||||
|
# doesn't hold the claim; the value is still persisted.
|
||||||
|
if not _headset_led_control_on(device):
|
||||||
|
return data_bytes
|
||||||
reply = device.feature_request(self.feature, 0x30, bytes([self._cluster]) + bytes(data_bytes))
|
reply = device.feature_request(self.feature, 0x30, bytes([self._cluster]) + bytes(data_bytes))
|
||||||
return data_bytes if reply is not None else None
|
return data_bytes if reply is not None else None
|
||||||
|
|
||||||
|
|
@ -4463,12 +4423,11 @@ SETTINGS: list[settings.Setting] = [
|
||||||
HeadsetActiveEQPreset,
|
HeadsetActiveEQPreset,
|
||||||
HeadsetAdvancedEQ,
|
HeadsetAdvancedEQ,
|
||||||
HeadsetLEDControl,
|
HeadsetLEDControl,
|
||||||
HeadsetLEDsPrimary,
|
HeadsetOnboardEffect,
|
||||||
HeadsetPerZoneLighting,
|
HeadsetPerZoneLighting,
|
||||||
HeadsetSignatureStartupEffect,
|
HeadsetSignatureStartupEffect,
|
||||||
HeadsetSignatureShutdownEffect,
|
HeadsetSignatureShutdownEffect,
|
||||||
HeadsetSignaturePassiveEffect,
|
HeadsetSignaturePassiveEffect,
|
||||||
HeadsetOnboardEffect,
|
|
||||||
*_LOGIVOICE_SETTINGS,
|
*_LOGIVOICE_SETTINGS,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -816,6 +816,13 @@ _icons_allowables = {v: k for k, v in _allowables_icons.items()}
|
||||||
# the zone index (rgb_zone_1, rgb_zone_2, ...).
|
# the zone index (rgb_zone_1, rgb_zone_2, ...).
|
||||||
_SW_CONTROL_DEPENDENT_NAMES = ("rgb_idle_timeout", "rgb_idle_effect", "rgb_sleep_timeout")
|
_SW_CONTROL_DEPENDENT_NAMES = ("rgb_idle_timeout", "rgb_idle_effect", "rgb_sleep_timeout")
|
||||||
_SW_CONTROL_DEPENDENT_PREFIXES = ("rgb_zone_",)
|
_SW_CONTROL_DEPENDENT_PREFIXES = ("rgb_zone_",)
|
||||||
|
# headset_led_control = whether Solaar holds the live-coloring claim (off lets
|
||||||
|
# another app drive the LEDs). The 0x0620 per-zone painting and the 0x0621
|
||||||
|
# onboard effect are both live LED control, so both need the claim; per-zone
|
||||||
|
# additionally needs the onboard effect on Static (the per-key analog of
|
||||||
|
# needs-rgb_control + zone-Static). The 0x0622 signature effects are stored
|
||||||
|
# settings (startup/shutdown colors) and stay ungated.
|
||||||
|
_HEADSET_LED_DEPENDENT_NAMES = ("headset_per_zone_lighting", "headset-onboard-effect")
|
||||||
|
|
||||||
|
|
||||||
def _sw_control_blocked(device):
|
def _sw_control_blocked(device):
|
||||||
|
|
@ -839,6 +846,43 @@ def _sw_control_blocked(device):
|
||||||
return value not in (True, 3)
|
return value not in (True, 3)
|
||||||
|
|
||||||
|
|
||||||
|
def _headset_led_blocked(device):
|
||||||
|
"""True when the headset's LED Control is off (Device/firmware mode).
|
||||||
|
Reads setting._value first, then the persister; accepts the current bool
|
||||||
|
or a legacy int 0/1 from the old ChoicesValidator persister entries."""
|
||||||
|
persister = getattr(device, "persister", None)
|
||||||
|
if persister is None:
|
||||||
|
return False
|
||||||
|
value = None
|
||||||
|
for s in getattr(device, "settings", []) or []:
|
||||||
|
if s.name == "headset_led_control":
|
||||||
|
value = s._value
|
||||||
|
break
|
||||||
|
if value is None:
|
||||||
|
value = persister.get("headset_led_control")
|
||||||
|
if value is None:
|
||||||
|
return False
|
||||||
|
return value not in (True, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def _cluster_effect_blocks_perzone(device):
|
||||||
|
"""True when the headset's 0x0621 onboard effect is not Fixed — a
|
||||||
|
non-Fixed cluster animation masks the per-zone buffer. Mirrors
|
||||||
|
`_zone_effect_blocks_perkey`. False when the device has no
|
||||||
|
onboard-effect setting (nothing to mask against)."""
|
||||||
|
persister = getattr(device, "persister", None)
|
||||||
|
value = None
|
||||||
|
for s in getattr(device, "settings", []) or []:
|
||||||
|
if s.name == "headset-onboard-effect":
|
||||||
|
value = s._value if s._value is not None else (persister.get(s.name) if persister else None)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if value is None:
|
||||||
|
return False
|
||||||
|
return int(getattr(value, "ID", 0)) != 0
|
||||||
|
|
||||||
|
|
||||||
def _zone_effect_blocks_perkey(device):
|
def _zone_effect_blocks_perkey(device):
|
||||||
"""True when any zone effect's saved ID is not Static (0x01) — zone
|
"""True when any zone effect's saved ID is not Static (0x01) — zone
|
||||||
animations mask the per-key buffer regardless of SW control state."""
|
animations mask the per-key buffer regardless of SW control state."""
|
||||||
|
|
@ -877,6 +921,11 @@ def _gate_blocks(device, name):
|
||||||
return _sw_control_blocked(device)
|
return _sw_control_blocked(device)
|
||||||
if name == "per-key-lighting":
|
if name == "per-key-lighting":
|
||||||
return _sw_control_blocked(device) or _zone_effect_blocks_perkey(device)
|
return _sw_control_blocked(device) or _zone_effect_blocks_perkey(device)
|
||||||
|
if name in _HEADSET_LED_DEPENDENT_NAMES:
|
||||||
|
if _headset_led_blocked(device):
|
||||||
|
return True
|
||||||
|
# Per-zone painting additionally needs the onboard effect on Static.
|
||||||
|
return name == "headset_per_zone_lighting" and _cluster_effect_blocks_perzone(device)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -895,6 +944,7 @@ def _apply_rgb_gates(device):
|
||||||
name in _SW_CONTROL_DEPENDENT_NAMES
|
name in _SW_CONTROL_DEPENDENT_NAMES
|
||||||
or any(name.startswith(p) for p in _SW_CONTROL_DEPENDENT_PREFIXES)
|
or any(name.startswith(p) for p in _SW_CONTROL_DEPENDENT_PREFIXES)
|
||||||
or name == "per-key-lighting"
|
or name == "per-key-lighting"
|
||||||
|
or name in _HEADSET_LED_DEPENDENT_NAMES
|
||||||
):
|
):
|
||||||
_set_row_sensitive(device, name, not _gate_blocks(device, name))
|
_set_row_sensitive(device, name, not _gate_blocks(device, name))
|
||||||
|
|
||||||
|
|
@ -940,10 +990,12 @@ def _change_click(button, sbox):
|
||||||
perkey, has_paint = rgb_power.perkey_has_paint(device)
|
perkey, has_paint = rgb_power.perkey_has_paint(device)
|
||||||
if has_paint:
|
if has_paint:
|
||||||
_write_async(perkey, perkey._value, None)
|
_write_async(perkey, perkey._value, None)
|
||||||
# The lock icon on rgb_control, any zone, or per-key itself can change
|
# The lock icon on rgb_control, any zone, per-key, or headset_led_control
|
||||||
# whether per-key is functional — re-evaluate the gate.
|
# can change whether a dependent row is functional — re-evaluate the gate.
|
||||||
name = sbox.setting.name
|
name = sbox.setting.name
|
||||||
if name == "rgb_control" or name == "per-key-lighting" or name.startswith("rgb_zone_"):
|
if name in ("rgb_control", "per-key-lighting", "headset_led_control", "headset-onboard-effect") or name.startswith(
|
||||||
|
"rgb_zone_"
|
||||||
|
):
|
||||||
_apply_rgb_gates(sbox.setting._device)
|
_apply_rgb_gates(sbox.setting._device)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -1047,8 +1099,9 @@ def _update_setting_item(sbox, value, is_online=True, sensitive=True, null_okay=
|
||||||
logger.warning("%s: error setting control value (%s): %s", sbox.setting.name, sbox.setting._device, repr(e))
|
logger.warning("%s: error setting control value (%s): %s", sbox.setting.name, sbox.setting._device, repr(e))
|
||||||
sbox._control.set_sensitive(sensitive is True and can_function)
|
sbox._control.set_sensitive(sensitive is True and can_function)
|
||||||
_change_icon(sensitive, sbox._change_icon)
|
_change_icon(sensitive, sbox._change_icon)
|
||||||
# rgb_control and rgb_zone_* state gate per-key sensitivity.
|
# rgb_control / rgb_zone_* gate per-key; headset_led_control and the
|
||||||
if name == "rgb_control" or name.startswith("rgb_zone_"):
|
# headset-onboard-effect gate the per-zone row — re-evaluate on a change.
|
||||||
|
if name in ("rgb_control", "headset_led_control", "headset-onboard-effect") or name.startswith("rgb_zone_"):
|
||||||
_apply_rgb_gates(sbox.setting._device)
|
_apply_rgb_gates(sbox.setting._device)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue