cli: nicer output of settings in solaar show and solaar config

This commit is contained in:
Peter F. Patel-Schneider 2022-01-24 18:35:34 -05:00
parent fed9a26cb6
commit f938d3430e
3 changed files with 77 additions and 16 deletions

View File

@ -63,6 +63,10 @@ class Validator:
def build(cls, setting_class, device, **kwargs): def build(cls, setting_class, device, **kwargs):
return cls(**kwargs) return cls(**kwargs)
@classmethod
def to_string(cls, value):
return (str(value))
class BooleanValidator(Validator): class BooleanValidator(Validator):
__slots__ = ('true_value', 'false_value', 'read_skip_byte_count', 'write_prefix_bytes', 'mask', 'needs_current_value') __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 assert cls.kind is None or cls.kind & validator.kind != 0
return cls(device, rw, validator) return cls(device, rw, validator)
def val_to_string(self, value):
return self._validator.to_string(value)
@property @property
def choices(self): def choices(self):
assert hasattr(self, '_value') assert hasattr(self, '_value')
@ -745,6 +752,13 @@ class BitFieldValidator(Validator):
assert (isinstance(byte_count, int) and byte_count >= self.byte_count) assert (isinstance(byte_count, int) and byte_count >= self.byte_count)
self.byte_count = 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): def validate_read(self, reply_bytes):
r = _bytes2int(reply_bytes[:self.byte_count]) r = _bytes2int(reply_bytes[:self.byte_count])
value = {str(int(k)): False for k in self.options} 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 + self._read_skip_byte_count <= 14
assert self._byte_count + len(self._write_prefix_bytes) <= 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): def validate_read(self, reply_bytes):
reply_value = _bytes2int(reply_bytes[self._read_skip_byte_count:self._read_skip_byte_count + self._byte_count]) reply_value = _bytes2int(reply_bytes[self._read_skip_byte_count:self._read_skip_byte_count + self._byte_count])
valid_value = self.choices[reply_value] 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 + self._read_skip_byte_count + self._key_byte_count <= 14
assert self._byte_count + len(self._write_prefix_bytes) + 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): def validate_read(self, reply_bytes, key):
start = self._key_byte_count + self._read_skip_byte_count start = self._key_byte_count + self._read_skip_byte_count
end = start + self._byte_count end = start + self._byte_count

View File

@ -41,7 +41,36 @@ def _print_setting(s, verbose=True):
if value is None: if value is None:
print(s.name, '= ? (failed to read from device)') print(s.name, '= ? (failed to read from device)')
else: 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): def to_int(s):
@ -66,7 +95,7 @@ def select_choice(value, choices, setting, key):
elif lvalue in ('higher', 'lower'): elif lvalue in ('higher', 'lower'):
old_value = setting.read() if key is None else setting.read_key(key) old_value = setting.read() if key is None else setting.read_key(key)
if old_value is None: 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': if lvalue == 'lower':
lower_values = choices[:old_value] lower_values = choices[:old_value]
value = lower_values[-1] if lower_values else choices[:][0] value = lower_values[-1] if lower_values else choices[:][0]
@ -82,9 +111,9 @@ def select_choice(value, choices, setting, key):
return value return value
def select_toggle(value, setting): def select_toggle(value, setting, key=None):
if value.lower() in ('toggle', '~'): if value.lower() in ('toggle', '~'):
value = not setting.read() value = not (setting.read() if key is None else setting.read()[str(int(key))])
else: else:
try: try:
value = bool(int(value)) value = bool(int(value))
@ -147,9 +176,10 @@ def run(receivers, args, find_receiver, find_device):
return return
result, message, value = set(dev, setting, args) result, message, value = set(dev, setting, args)
print(message) if message is not None:
if result is None: print(message)
raise Exception("%s: failed to set value '%s' [%r]" % (setting.name, str(value), value)) if result is None:
raise Exception("%s: failed to set value '%s' [%r]" % (setting.name, str(value), value))
def set(dev, setting, args): def set(dev, setting, args):
@ -169,6 +199,9 @@ def set(dev, setting, args):
result = setting.write(value) result = setting.write(value)
elif setting.kind == _settings.KIND.map_choice: 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 key = args.value_key
ikey = to_int(key) ikey = to_int(key)
k = next((k for k in setting.choices.keys() if key == k), None) 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) result = setting.write_key_value(int(k), value)
elif setting.kind == _settings.KIND.multiple_toggle: 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 key = args.value_key
all_keys = getattr(setting, 'choices_universe', None) 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) 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: if k is None and ikey is not None:
k = next((k for k in setting._labels if ikey == k), None) k = next((k for k in setting._labels if ikey == k), None)
if k is not None: if k is not None:
value = select_toggle(args.extra_subkey, setting) value = select_toggle(args.extra_subkey, setting, key=k)
else: else:
raise Exception("%s: key '%s' not in setting" % (setting.name, key)) raise Exception("%s: key '%s' not in setting" % (setting.name, key))
message = 'Setting %s key %r to %r' % (setting.name, k, value) 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: 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 key = args.value_key
all_keys = getattr(setting, 'choices_universe', None) 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) 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: 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)) 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 if not setting._value: # ensure that there are values to look through

View File

@ -171,7 +171,7 @@ def _print_device(dev, num=None):
print(' Provide vertical tuning, trackball') print(' Provide vertical tuning, trackball')
else: else:
print(' No vertical tuning, standard mice') 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) vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev)
if vertical_scrolling_info: if vertical_scrolling_info:
print(' Roller type: %s' % vertical_scrolling_info['roller']) print(' Roller type: %s' % vertical_scrolling_info['roller'])
@ -231,10 +231,11 @@ def _print_device(dev, num=None):
_battery_line(dev) _battery_line(dev)
for setting in dev_settings: for setting in dev_settings:
if setting.feature == feature: if setting.feature == feature:
if setting._device and getattr(setting._device, 'persister', if setting._device and getattr(setting._device, 'persister', None) and \
None) and setting._device.persister.get(setting.name) is not None: setting._device.persister.get(setting.name) is not None:
print(' %s (saved): %s' % (setting.label, setting._device.persister.get(setting.name))) v = setting.val_to_string(setting._device.persister.get(setting.name))
v = setting.read(False) print(' %s (saved): %s' % (setting.label, v))
v = setting.val_to_string(setting.read(False))
print(' %s : %s' % (setting.label, v)) print(' %s : %s' % (setting.label, v))
if dev.online and dev.keys: if dev.online and dev.keys: