From 0ce3b434c04994aaa6c8db8d9ac57dd5c45429ce Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Tue, 30 Jan 2024 00:06:41 -0500 Subject: [PATCH] device: support backlight levels and duration --- lib/logitech_receiver/device.py | 8 ++ lib/logitech_receiver/hidpp20.py | 46 +++++++ lib/logitech_receiver/settings.py | 1 + lib/logitech_receiver/settings_templates.py | 136 +++++++++++++++++++- 4 files changed, 184 insertions(+), 7 deletions(-) diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index e87582669..106964e75 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -79,6 +79,7 @@ def __init__( self._remap_keys = None self._gestures = None self._gestures_lock = _threading.Lock() + self._backlight = None self._registers = None self._settings = None self._feature_settings_checked = False @@ -315,6 +316,13 @@ def gestures(self): self._gestures = _hidpp20.get_gestures(self) or () return self._gestures + @property + def backlight(self): + if self._backlight is None: + if self.online and self.protocol >= 2.0: + self._backlight = _hidpp20.get_backlight(self) + return self._backlight + @property def registers(self): if not self._registers: diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index aaef773bf..8e58aa706 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -1117,6 +1117,45 @@ def set_param(self, param, value): return g.set(self.device, value) if g else None +class Backlight: + """Information about the current settings of x1982 Backlight2 v3, but also works for previous versions""" + + def __init__(self, device): + response = device.feature_request(FEATURE.BACKLIGHT2, 0x00) + if not response: + raise FeatureCallError(msg='No reply from device.') + self.device = device + self.enabled, self.options, supported, effects, self.level, self.dho, self.dhi, self.dpow = _unpack( + '> 3) & 0x03 + if _log.isEnabledFor(_DEBUG): + _log.debug( + 'BACKLIGHT READ %x %x %x %x %x %x %x', self.mode, self.enabled, self.options, self.level, self.dho, self.dhi, + self.dpow + ) + + def write(self): + self.options = (self.options & 0x07) | (self.mode << 3) + if _log.isEnabledFor(_DEBUG): + _log.debug( + 'BACKLIGHT WRITE %x %x %x %x %x %x %x', self.mode, self.enabled, self.options, self.level, self.dho, self.dhi, + self.dpow + ) + level = self.level if self.mode == 0x3 else 0 + data_bytes = _pack(' 1: + return cls(min_value=0, max_value=reply[0] - 1, byte_count=1) + + +class Backlight2Duration(_Setting): + feature = _F.BACKLIGHT2 + min_version = 3 + validator_class = _RangeV + min_value = 1 + max_value = 120 # actual maximum is 2 hours + validator_options = {'byte_count': 2} + + class rw_class: + + def __init__(self, feature, field): + self.feature = feature + self.kind = _FeatureRW.kind + self.field = field + + def read(self, device): + backlight = device.backlight + return _int2bytes(getattr(backlight, self.field), 2) * 5 # use seconds instead of 5-second units def write(self, device, data_bytes): - if self.trail is None: - reply = device.feature_request(_F.BACKLIGHT2, 0x00) - self.trail = reply[1:2] + b'\xff' + reply[5:12] - return super().write(device, data_bytes + self.trail) + backlight = device.backlight + new_duration = (int.from_bytes(data_bytes) + 4) // 5 # use ceiling in 5-second units + if new_duration != getattr(backlight, self.field): + setattr(backlight, self.field, new_duration) + backlight.write() + return True + + +class Backlight2DurationHandsOut(Backlight2Duration): + name = 'backlight_duration_hands_out' + label = _('Backlight Delay Hands Out') + description = _('Delay in seconds until backlight fades out with hands away from keyboard.') + feature = _F.BACKLIGHT2 + validator_class = _RangeV + rw_options = {'field': 'dho'} + + +class Backlight2DurationHandsIn(Backlight2Duration): + name = 'backlight_duration_hands_in' + label = _('Backlight Delay Hands In') + description = _('Delay in seconds until backlight fades out with hands near keyboard.') + feature = _F.BACKLIGHT2 + validator_class = _RangeV + rw_options = {'field': 'dhi'} + + +class Backlight2DurationPowered(Backlight2Duration): + name = 'backlight_duration_powered' + label = _('Backlight Delay Powered') + description = _('Delay in seconds until backlight fades out with external power.') + feature = _F.BACKLIGHT2 + validator_class = _RangeV + rw_options = {'field': 'dpow'} class Backlight3(_Setting): @@ -1290,6 +1406,10 @@ class ADCPower(_Setting): SpeedChange, # Backlight, # not working - disabled temporarily Backlight2, # working + Backlight2Level, + Backlight2DurationHandsOut, + Backlight2DurationHandsIn, + Backlight2DurationPowered, Backlight3, FnSwap, # simple NewFnSwap, # simple @@ -1322,6 +1442,8 @@ class ADCPower(_Setting): def check_feature(device, sclass): if sclass.feature not in device.features: return + if sclass.min_version > device.features.get_feature_version(sclass.feature): + return try: detected = sclass.build(device) if _log.isEnabledFor(_DEBUG):