device: support backlight levels and duration

This commit is contained in:
Peter F. Patel-Schneider 2024-01-30 00:06:41 -05:00
parent 0f8e9b3c0f
commit 72c5860a1e
4 changed files with 184 additions and 7 deletions

View File

@ -79,6 +79,7 @@ class Device:
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 @@ class Device:
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:

View File

@ -1117,6 +1117,45 @@ class Gestures:
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(
'<BBBHBHHH', response[:12]
)
self.auto_supported = supported & 0x08
self.temp_supported = supported & 0x10
self.perm_supported = supported & 0x20
self.mode = (self.options >> 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('<BBBBHHH', self.enabled, self.options, 0xFF, level, self.dho, self.dhi, self.dpow)
self.device.feature_request(FEATURE.BACKLIGHT2, 0x00) # for testing - remove later
try: # for testing - remove later
self.device.feature_request(FEATURE.BACKLIGHT2, 0x10, data_bytes)
except Exception as e: # for testing - remove later
self.device.feature_request(FEATURE.BACKLIGHT2, 0x00) # for testing - remove later
raise e # for testing - remove later
self.device.feature_request(FEATURE.BACKLIGHT2, 0x00) # for testing - remove later
#
#
#
@ -1402,6 +1441,13 @@ def get_gestures(device):
return Gestures(device)
def get_backlight(device):
if getattr(device, '_backlight', None) is not None:
return device._backlight
if FEATURE.BACKLIGHT2 in device.features:
return Backlight(device)
def get_mouse_pointer_info(device):
pointer_info = feature_request(device, FEATURE.MOUSE_POINTER)
if pointer_info:

View File

@ -207,6 +207,7 @@ class Setting:
"""A setting descriptor. Needs to be instantiated for each specific device."""
name = label = description = ''
feature = register = kind = None
min_version = 0
persist = True
rw_options = {}
validator_class = BooleanValidator

View File

@ -218,17 +218,133 @@ class Backlight(_Setting):
class Backlight2(_Setting):
name = 'backlight'
label = _('Backlight')
description = _('Turn illumination on or off on keyboard.')
description = _('Illumination level on keyboard. Changes made are only applied in Manual mode.')
feature = _F.BACKLIGHT2
min_version = 0
class rw_class(_FeatureRW):
trail = None
class rw_class:
def __init__(self, feature):
self.feature = feature
self.kind = _FeatureRW.kind
def read(self, device):
backlight = device.backlight
if not backlight.enabled:
return 0xFF
else:
return backlight.mode
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
backlight.enabled = data_bytes[0] != 0xFF
if data_bytes[0] != 0xFF:
backlight.mode = data_bytes[0]
backlight.write()
return True
class validator_class(_ChoicesV):
@classmethod
def build(cls, setting_class, device):
backlight = device.backlight
choices = _NamedInts()
choices[0xFF] = _('Disabled')
if backlight.auto_supported:
choices[0x1] = _('Automatic')
if backlight.perm_supported:
choices[0x3] = _('Manual')
if not (backlight.auto_supported or backlight.temp_supported or backlight.perm_supported):
choices[0x0] = _('Enabled')
return cls(choices=choices, byte_count=1)
class Backlight2Level(_Setting):
name = 'backlight_level'
label = _('Backlight Level')
description = _('Illumination level on keyboard when in Manual mode.')
feature = _F.BACKLIGHT2
min_version = 3
class rw_class:
def __init__(self, feature):
self.feature = feature
self.kind = _FeatureRW.kind
def read(self, device):
backlight = device.backlight
return _int2bytes(backlight.level, 1)
def write(self, device, data_bytes):
if device.backlight.level != _bytes2int(data_bytes):
device.backlight.level = _bytes2int(data_bytes)
device.backlight.write()
return True
class validator_class(_RangeV):
@classmethod
def build(cls, setting_class, device):
reply = device.feature_request(_F.BACKLIGHT2, 0x20)
assert reply, 'Oops, backlight range cannot be retrieved!'
if reply[0] > 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):
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 @@ SETTINGS = [
SpeedChange,
# Backlight, # not working - disabled temporarily
Backlight2, # working
Backlight2Level,
Backlight2DurationHandsOut,
Backlight2DurationHandsIn,
Backlight2DurationPowered,
Backlight3,
FnSwap, # simple
NewFnSwap, # simple
@ -1322,6 +1442,8 @@ SETTINGS = [
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):