diff --git a/lib/logitech_receiver/settings.py b/lib/logitech_receiver/settings.py index 59a54aeb..5ff8d025 100644 --- a/lib/logitech_receiver/settings.py +++ b/lib/logitech_receiver/settings.py @@ -443,7 +443,6 @@ class Settings(Setting): _log.debug('%s: settings prepare key value write(%s,%s) => %r', self.name, key, value, data_bytes) reply = self._rw.write(self._device, int(key), data_bytes) if not reply: - # tell whomever is calling that the write failed return None return value @@ -978,8 +977,9 @@ class ChoicesMapValidator(ChoicesValidator): def __init__( self, choices_map, - key_byte_count=None, - byte_count=None, + key_byte_count=0, + key_postfix_bytes=b'', + byte_count=0, read_skip_byte_count=0, write_prefix_bytes=b'', extra_default=None, @@ -1005,10 +1005,12 @@ class ChoicesMapValidator(ChoicesValidator): if byte_count: assert self._byte_count <= byte_count self._byte_count = byte_count + self.choices = choices_map self.needs_current_value = False self.extra_default = extra_default - self._read_skip_byte_count = read_skip_byte_count + self._key_postfix_bytes = key_postfix_bytes + self._read_skip_byte_count = read_skip_byte_count if read_skip_byte_count else 0 self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b'' self.activate = activate self.mask = mask @@ -1029,10 +1031,14 @@ class ChoicesMapValidator(ChoicesValidator): # reprogrammable keys starts out as 0, which is not a choice, so don't use assert here if self.extra_default is not None and self.extra_default == reply_value: return int(self.choices[key][0]) - assert reply_value in self.choices[ - key], '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) + if reply_value not in self.choices[key]: + assert reply_value in self.choices[ + key], '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) return reply_value + def prepare_key(self, key): + return key.to_bytes(self._key_byte_count, 'big') + self._key_postfix_bytes + def prepare_write(self, key, new_value): choices = self.choices.get(key) if choices is None or (new_value not in choices and new_value != self.extra_default): diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 429bb825..4167288c 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -449,9 +449,7 @@ class ReprogrammableKeys(_Settings): tgts = k.remappable_to if len(tgts) > 1: choices[k.key] = tgts - if not choices: - return None - return cls(choices, key_byte_count=2, byte_count=2, extra_default=0) + return cls(choices, key_byte_count=2, byte_count=2, extra_default=0) if choices else None class DivertKeys(_Settings): @@ -1044,6 +1042,48 @@ class MRKeyLED(_Setting): return b'\x00' + +## Only implemented for devices that can produce HID and Consumer Key Codes +## Only interested in current host, so use 0xFF for it +class PersistentRemappableAction(_Settings): + name = 'persistent-remappable-keys' + label = _('Persistent Key/Button Mapping') + description = ( + _('Permanently change the mapping for the key or button.') + '\n' + + _('Changing important keys or buttons (such as for the left mouse button) can result in an unusable system.') + ) + persist = False # This setting is persistent in the device so no need to persist it here + feature = _F.PERSISTENT_REMAPPABLE_ACTION + keys_universe = _special_keys.CONTROL + choices_universe = _special_keys.KEYS + + class rw_class: + def __init__(self, feature): + self.feature = feature + self.kind = _FeatureRW.kind + + def read(self, device, key): + ks = device.remap_keys[device.remap_keys.index(key)] + return b'\x00\x00' + ks.data_bytes + + def write(self, device, key, data_bytes): + ks = device.remap_keys[device.remap_keys.index(key)] + v = ks.remap(data_bytes) + return v + + class validator_class(_ChoicesMapV): + @classmethod + def build(cls, setting_class, device): + remap_keys = device.remap_keys + if not remap_keys or not device.remap_keys.capabilities & 0x0041 == 0x0041: # HID and Consumer Key Codes + return None + choices = {} + for k in remap_keys: + key = _special_keys.CONTROL[k.key] + choices[key] = _special_keys.KEYS + return cls(choices, key_byte_count=2, byte_count=4) if choices else None + + SETTINGS = [ RegisterHandDetection, # simple RegisterSmoothScroll, # simple @@ -1071,13 +1111,14 @@ SETTINGS = [ NewFnSwap, # simple K375sFnSwap, # working ReprogrammableKeys, # working + PersistentRemappableAction, DivertKeys, # working DisableKeyboardKeys, # working DivertCrown, # working CrownSmooth, # working DivertGkeys, # working MKeyLEDs, # working - MRKeyLED, + MRKeyLED, # working Multiplatform, # working DualPlatform, # simple ChangeHost, # working diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py index e721325e..ac6eaddd 100644 --- a/lib/solaar/cli/show.py +++ b/lib/solaar/cli/show.py @@ -255,10 +255,7 @@ def _print_device(dev, num=None): if dev.online and dev.remap_keys: print(' Has %d persistent remappable keys:' % len(dev.remap_keys)) for k in dev.remap_keys: - print( - ' %2d: %4d %-26s => %s%s' % - (k.index, int(k.key), k.key, k.action, ' (remapped)' if k.cidStatus else '') - ) + print(' %2d: %-26s => %s%s' % (k.index, k.key, k.action, ' (remapped)' if k.cidStatus else '')) if dev.online and dev.gestures: print( ' Has %d gesture(s), %d param(s) and %d spec(s):' %