From f938d3430eba086b040d8aa9bb8df7ffde67e408 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Mon, 24 Jan 2022 18:35:34 -0500 Subject: [PATCH] cli: nicer output of settings in solaar show and solaar config --- lib/logitech_receiver/settings.py | 24 +++++++++++++ lib/solaar/cli/config.py | 58 +++++++++++++++++++++++++------ lib/solaar/cli/show.py | 11 +++--- 3 files changed, 77 insertions(+), 16 deletions(-) diff --git a/lib/logitech_receiver/settings.py b/lib/logitech_receiver/settings.py index 24c4d86b..415ea91c 100644 --- a/lib/logitech_receiver/settings.py +++ b/lib/logitech_receiver/settings.py @@ -63,6 +63,10 @@ class Validator: def build(cls, setting_class, device, **kwargs): return cls(**kwargs) + @classmethod + def to_string(cls, value): + return (str(value)) + class BooleanValidator(Validator): __slots__ = ('true_value', 'false_value', 'read_skip_byte_count', 'write_prefix_bytes', 'mask', 'needs_current_value') @@ -222,6 +226,9 @@ class Setting: assert cls.kind is None or cls.kind & validator.kind != 0 return cls(device, rw, validator) + def val_to_string(self, value): + return self._validator.to_string(value) + @property def choices(self): assert hasattr(self, '_value') @@ -745,6 +752,13 @@ class BitFieldValidator(Validator): assert (isinstance(byte_count, int) and byte_count >= self.byte_count) self.byte_count = byte_count + def to_string(self, value): + def element_to_string(key, val): + k = next((k for k in self.options if int(key) == k), None) + return str(k) + ':' + str(val) if k is not None else '?' + + return '{' + ', '.join([element_to_string(k, value[k]) for k in value]) + '}' + def validate_read(self, reply_bytes): r = _bytes2int(reply_bytes[:self.byte_count]) value = {str(int(k)): False for k in self.options} @@ -894,6 +908,9 @@ class ChoicesValidator(Validator): assert self._byte_count + self._read_skip_byte_count <= 14 assert self._byte_count + len(self._write_prefix_bytes) <= 14 + def to_string(self, value): + return str(self.choices[value]) if isinstance(value, int) else str(value) + def validate_read(self, reply_bytes): reply_value = _bytes2int(reply_bytes[self._read_skip_byte_count:self._read_skip_byte_count + self._byte_count]) valid_value = self.choices[reply_value] @@ -974,6 +991,13 @@ class ChoicesMapValidator(ChoicesValidator): assert self._byte_count + self._read_skip_byte_count + self._key_byte_count <= 14 assert self._byte_count + len(self._write_prefix_bytes) + self._key_byte_count <= 14 + def to_string(self, value): + def element_to_string(key, val): + k, c = next(((k, c) for k, c in self.choices.items() if int(key) == k), (None, None)) + return str(k) + ':' + str(c[val]) if k is not None else '?' + + return '{' + ', '.join([element_to_string(k, value[k]) for k in sorted(value)]) + '}' + def validate_read(self, reply_bytes, key): start = self._key_byte_count + self._read_skip_byte_count end = start + self._byte_count diff --git a/lib/solaar/cli/config.py b/lib/solaar/cli/config.py index 96ca29c5..635d6d8d 100644 --- a/lib/solaar/cli/config.py +++ b/lib/solaar/cli/config.py @@ -41,7 +41,36 @@ def _print_setting(s, verbose=True): if value is None: print(s.name, '= ? (failed to read from device)') else: - print(s.name, '= %s' % value) + print(s.name, '=', s.val_to_string(value)) + + +def _print_setting_keyed(s, key, verbose=True): + print('#', s.label) + if verbose: + if s.description: + print('#', s.description.replace('\n', ' ')) + if s.kind == _settings.KIND.multiple_toggle: + k = next((k for k in s._labels if key == k), None) + if k is None: + print(s.name, '=? (key not found)') + else: + print('# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~') + value = s.read(cached=False) + if value is None: + print(s.name, '= ? (failed to read from device)') + else: + print(s.name, s.val_to_string({k: value[str(int(k))]})) + elif s.kind == _settings.KIND.map_choice: + k = next((k for k in s.choices.keys() if key == k), None) + if k is None: + print(s.name, '=? (key not found)') + else: + print('# possible values: one of [', ', '.join(str(v) for v in s.choices[k]), ']') + value = s.read(cached=False) + if value is None: + print(s.name, '= ? (failed to read from device)') + else: + print(s.name, s.val_to_string({k: value[str(int(k))]})) def to_int(s): @@ -66,7 +95,7 @@ def select_choice(value, choices, setting, key): elif lvalue in ('higher', 'lower'): old_value = setting.read() if key is None else setting.read_key(key) if old_value is None: - raise Exception("%s: could not read current value'" % setting.name) + raise Exception('%s: could not read current value' % setting.name) if lvalue == 'lower': lower_values = choices[:old_value] value = lower_values[-1] if lower_values else choices[:][0] @@ -82,9 +111,9 @@ def select_choice(value, choices, setting, key): return value -def select_toggle(value, setting): +def select_toggle(value, setting, key=None): if value.lower() in ('toggle', '~'): - value = not setting.read() + value = not (setting.read() if key is None else setting.read()[str(int(key))]) else: try: value = bool(int(value)) @@ -147,9 +176,10 @@ def run(receivers, args, find_receiver, find_device): return result, message, value = set(dev, setting, args) - print(message) - if result is None: - raise Exception("%s: failed to set value '%s' [%r]" % (setting.name, str(value), value)) + if message is not None: + print(message) + if result is None: + raise Exception("%s: failed to set value '%s' [%r]" % (setting.name, str(value), value)) def set(dev, setting, args): @@ -169,6 +199,9 @@ def set(dev, setting, args): result = setting.write(value) elif setting.kind == _settings.KIND.map_choice: + if args.extra_subkey is None: + _print_setting_keyed(setting, args.value_key) + return (None, None, None) key = args.value_key ikey = to_int(key) k = next((k for k in setting.choices.keys() if key == k), None) @@ -182,6 +215,9 @@ def set(dev, setting, args): result = setting.write_key_value(int(k), value) elif setting.kind == _settings.KIND.multiple_toggle: + if args.extra_subkey is None: + _print_setting_keyed(setting, args.value_key) + return (None, None, None) key = args.value_key all_keys = getattr(setting, 'choices_universe', None) ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, _NamedInts) else to_int(key) @@ -189,18 +225,18 @@ def set(dev, setting, args): if k is None and ikey is not None: k = next((k for k in setting._labels if ikey == k), None) if k is not None: - value = select_toggle(args.extra_subkey, setting) + value = select_toggle(args.extra_subkey, setting, key=k) else: raise Exception("%s: key '%s' not in setting" % (setting.name, key)) message = 'Setting %s key %r to %r' % (setting.name, k, value) - result = setting.write_key_value(int(k), value) + result = setting.write_key_value(str(int(k)), value) elif setting.kind == _settings.KIND.multiple_range: + if args.extra_subkey is None: + raise Exception('%s: setting needs both key and value to set' % (setting.name)) key = args.value_key all_keys = getattr(setting, 'choices_universe', None) ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, _NamedInts) else to_int(key) - if args.extra_subkey is None: - raise Exception('%s: setting needs a subkey' % (setting.name)) if args.extra2 is None or to_int(args.extra2) is None: raise Exception('%s: setting needs an integer value, not %s' % (setting.name, args.extra2)) if not setting._value: # ensure that there are values to look through diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py index 3bb29bbf..85b2f0b3 100644 --- a/lib/solaar/cli/show.py +++ b/lib/solaar/cli/show.py @@ -171,7 +171,7 @@ def _print_device(dev, num=None): print(' Provide vertical tuning, trackball') else: print(' No vertical tuning, standard mice') - if feature == _hidpp20.FEATURE.VERTICAL_SCROLLING: + elif feature == _hidpp20.FEATURE.VERTICAL_SCROLLING: vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev) if vertical_scrolling_info: print(' Roller type: %s' % vertical_scrolling_info['roller']) @@ -231,10 +231,11 @@ def _print_device(dev, num=None): _battery_line(dev) for setting in dev_settings: if setting.feature == feature: - if setting._device and getattr(setting._device, 'persister', - None) and setting._device.persister.get(setting.name) is not None: - print(' %s (saved): %s' % (setting.label, setting._device.persister.get(setting.name))) - v = setting.read(False) + if setting._device and getattr(setting._device, 'persister', None) and \ + setting._device.persister.get(setting.name) is not None: + v = setting.val_to_string(setting._device.persister.get(setting.name)) + print(' %s (saved): %s' % (setting.label, v)) + v = setting.val_to_string(setting.read(False)) print(' %s : %s' % (setting.label, v)) if dev.online and dev.keys: