Add RGB lighting persistence via SW control claim for G515/G502
When Solaar claims LED control, properly disable firmware power management via ProfileManagement or OnboardProfiles and claim the LED pipeline with SetSWControl(mode=3, flags=5). This prevents the device from reverting colors to its onboard profile after a few seconds. Adds cleanup handler to restore firmware control on device close, and auto-claims SW control when writing per-key lighting colors. Adds PROFILE_MANAGEMENT (0x8101) feature constant.
This commit is contained in:
parent
3733713d68
commit
65df109e92
|
|
@ -422,6 +422,10 @@ def _process_feature_notification(device: Device, notification: HIDPPNotificatio
|
|||
brightness = struct.unpack("!H", device.feature_request(SupportedFeature.BRIGHTNESS_CONTROL, 0x10)[:2])[0]
|
||||
device.setting_callback(device, settings_templates.BrightnessControl, [brightness])
|
||||
|
||||
elif feature == SupportedFeature.RGB_EFFECTS:
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("%s: RGB_EFFECTS notification addr=%02x: %s", device, notification.address, notification)
|
||||
|
||||
diversion.process_notification(device, notification, feature)
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -1875,6 +1875,57 @@ class RGBControl(settings.Setting):
|
|||
validator_class = settings_validator.ChoicesValidator
|
||||
validator_options = {"choices": choices_universe, "write_prefix_bytes": b"\x01", "read_skip_byte_count": 1}
|
||||
|
||||
def write(self, value, save=True):
|
||||
assert hasattr(self, "_value")
|
||||
assert hasattr(self, "_device")
|
||||
assert value is not None
|
||||
device = self._device
|
||||
if not device.online:
|
||||
return None
|
||||
if self._value != value:
|
||||
self.update(value, save)
|
||||
claiming = int(value) == 1 # Solaar
|
||||
if claiming:
|
||||
self._claim_sw_control(device)
|
||||
else:
|
||||
self._release_sw_control(device)
|
||||
return value
|
||||
|
||||
def _claim_sw_control(self, device):
|
||||
# Disable firmware power management via profile management or onboard profiles
|
||||
if device.features and _F.PROFILE_MANAGEMENT in device.features:
|
||||
device.feature_request(_F.PROFILE_MANAGEMENT, 0x60, b"\x05")
|
||||
elif device.features and _F.ONBOARD_PROFILES in device.features:
|
||||
device.feature_request(_F.ONBOARD_PROFILES, 0x10, b"\x02")
|
||||
# Claim LED pipeline: SetSWControl(mode=3, flags=5)
|
||||
device.feature_request(_F.RGB_EFFECTS, 0x50, b"\x01\x03\x05")
|
||||
# Register cleanup for graceful release on device close
|
||||
if _rgb_cleanup not in device.cleanups:
|
||||
device.cleanups.append(_rgb_cleanup)
|
||||
|
||||
def _release_sw_control(self, device):
|
||||
# Release LED pipeline: SetSWControl(mode=0, flags=0)
|
||||
device.feature_request(_F.RGB_EFFECTS, 0x50, b"\x01\x00\x00")
|
||||
# Restore firmware power management
|
||||
if device.features and _F.PROFILE_MANAGEMENT in device.features:
|
||||
device.feature_request(_F.PROFILE_MANAGEMENT, 0x60, b"\x03")
|
||||
elif device.features and _F.ONBOARD_PROFILES in device.features:
|
||||
device.feature_request(_F.ONBOARD_PROFILES, 0x10, b"\x01")
|
||||
if _rgb_cleanup in device.cleanups:
|
||||
device.cleanups.remove(_rgb_cleanup)
|
||||
|
||||
|
||||
def _rgb_cleanup(device):
|
||||
"""Cleanup handler called when device is closed — restores firmware control."""
|
||||
try:
|
||||
device.feature_request(_F.RGB_EFFECTS, 0x50, b"\x01\x00\x00")
|
||||
if device.features and _F.PROFILE_MANAGEMENT in device.features:
|
||||
device.feature_request(_F.PROFILE_MANAGEMENT, 0x60, b"\x03")
|
||||
elif device.features and _F.ONBOARD_PROFILES in device.features:
|
||||
device.feature_request(_F.ONBOARD_PROFILES, 0x10, b"\x01")
|
||||
except Exception:
|
||||
pass # Device may already be offline
|
||||
|
||||
|
||||
class RGBEffectSetting(LEDZoneSetting):
|
||||
name = "rgb_zone_" # the trailing underscore signals that this setting creates other settings
|
||||
|
|
@ -1895,6 +1946,18 @@ class PerKeyLighting(settings.Settings):
|
|||
keys_universe = special_keys.KEYCODES
|
||||
choices_universe = special_keys.COLORSPLUS
|
||||
|
||||
def _ensure_sw_control(self):
|
||||
"""Ensure SW control is claimed before writing per-key colors."""
|
||||
if getattr(self, "_has_rgb_effects", None) is None:
|
||||
self._has_rgb_effects = bool(self._device.features and _F.RGB_EFFECTS in self._device.features)
|
||||
if not self._has_rgb_effects:
|
||||
return # No autonomous effect engine, no claim needed
|
||||
for s in self._device.settings:
|
||||
if s.name == "rgb_control":
|
||||
if s._value != 1: # Not already claimed by Solaar
|
||||
s.write(1) # Triggers full claim sequence in RGBControl
|
||||
return
|
||||
|
||||
def read(self, cached=True):
|
||||
self._pre_read(cached)
|
||||
if cached and self._value is not None:
|
||||
|
|
@ -1907,6 +1970,7 @@ class PerKeyLighting(settings.Settings):
|
|||
|
||||
def write(self, map, save=True):
|
||||
if self._device.online:
|
||||
self._ensure_sw_control()
|
||||
self.update(map, save)
|
||||
table = {}
|
||||
for key, value in map.items():
|
||||
|
|
@ -1939,6 +2003,7 @@ class PerKeyLighting(settings.Settings):
|
|||
return map
|
||||
|
||||
def write_key_value(self, key, value, save=True):
|
||||
self._ensure_sw_control()
|
||||
if value != special_keys.COLORSPLUS["No change"]: # this signals no change
|
||||
result = super().write_key_value(int(key), value, save)
|
||||
if self._device.online:
|
||||
|
|
|
|||
|
|
@ -387,6 +387,8 @@ class Device:
|
|||
wpid: Optional[str] = "0000"
|
||||
setting_callback: Any = None
|
||||
centurion: bool = False
|
||||
path = None
|
||||
cleanups = None
|
||||
sliding = profiles = _backlight = _keys = _remap_keys = _led_effects = _gestures = None
|
||||
_gestures_lock = threading.Lock()
|
||||
number = "d1"
|
||||
|
|
@ -409,6 +411,7 @@ class Device:
|
|||
self.persister = configuration._DeviceEntry()
|
||||
self.features = hidpp20.FeaturesArray(self)
|
||||
self.settings = []
|
||||
self.cleanups = []
|
||||
self.receiver = []
|
||||
if self.feature is not None:
|
||||
self.features = hidpp20.FeaturesArray(self)
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ simple_tests = [
|
|||
Setup(
|
||||
FeatureTest(settings_templates.RGBControl, 0, 1),
|
||||
fake_hidpp.Response("0000", 0x0450),
|
||||
fake_hidpp.Response("010100", 0x0450, "0101"),
|
||||
fake_hidpp.Response("010305", 0x0450, "010305"),
|
||||
),
|
||||
Setup(
|
||||
FeatureTest(
|
||||
|
|
|
|||
Loading…
Reference in New Issue