From 97b6b958c8f642c94f9b86e5b94e3567a29bdafa Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Sun, 2 Nov 2025 14:33:10 +0900 Subject: [PATCH] settings: expand new settings type --- lib/logitech_receiver/hidpp20.py | 19 +++++- lib/logitech_receiver/settings_new.py | 76 +++++++++++++++------ lib/logitech_receiver/settings_templates.py | 17 ++++- lib/solaar/ui/config_panel.py | 5 ++ 4 files changed, 95 insertions(+), 22 deletions(-) diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index ec0e2527..038ea094 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -2100,5 +2100,22 @@ class ForceSensingButtonArray(UserDict): self[index] = button return button - def acceptable(self, index: int, value: int) -> bool: + def query(self): + for index in self: + button = ForceSensingButton.create(self.device, index) + if button: + self[index] = button + return self + + # interface for single force button + def get_current(self): + return self[0].get_current() + + def set_current(self, current: int) -> None: + self[0].set_current(current) + + def acceptable(self, value: int) -> bool: + return self[0].acceptable(value) + + def acceptable_current_key(self, index: int, value: int) -> bool: return self[index].acceptable(value) diff --git a/lib/logitech_receiver/settings_new.py b/lib/logitech_receiver/settings_new.py index 46c0b57c..bc0e807b 100644 --- a/lib/logitech_receiver/settings_new.py +++ b/lib/logitech_receiver/settings_new.py @@ -44,6 +44,18 @@ class Setting: _device_object = None # The object that interacts with the feature for the device _value = None # Stored value as maintained by Solaar, used for persistence + def __init__(self, device, device_object): + self._device = device + self._device_object = device_object + + @classmethod + def build(cls, device): + cls.check_properties(cls) + device_object = getattr(device, cls.setup)() + if device_object: + setting = cls(device, device_object) + return setting + @classmethod def check_properties(cl, cls): assert cls.name and cls.label and cls.description, "New settings require a name, label, and description" @@ -51,10 +63,20 @@ class Setting: assert cls.setup, "New settings require a setup device method" assert cls.get and cls.set and cls.acceptable, "New settings require get, set, and acceptable methods" - @classmethod - def build(cls, device): - """Create the setting.""" - pass + def setup_from_class(self, clss): + """Copy settings methods for a new setting from a settting class""" + self.name = clss.name + self.label = clss.label + self.description = clss.description + self.feature = clss.feature + self.min_version = clss.min_version + self.setup = clss.setup + self.get = clss.get + self.set = clss.set + self.acceptable = clss.acceptable + self.choices_universe = clss.choices_universe + self.kind = clss.kind + self.persist = clss.persist def _pre_read(self, cached): """Get information from and save information to the persister""" @@ -68,11 +90,21 @@ class Setting: def read(self, cached=True): """Get all the data for the setting. If cached is True the data in the _value can be used.""" - pass + self._pre_read(cached) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: setting read %r from %s", self.name, self._value, self._device) + if cached and self._value is not None: + return self._value + if cached: + self._value = getattr(self._device_object, self.get)() + return self._value + if self._device.online: + self._value = getattr(self._device_object.query(), self.get)() + return self._value def write(self, value, save=True): """Write the value to the device. If saved is True also save in the persister""" - pass + pass ## fill out def apply(self): """Write saved data to the device, using persisted data if available""" @@ -87,6 +119,11 @@ class Setting: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: error applying %s so ignore it (%s): %s", self.name, value, self._device, repr(e)) + @property + def range(self): + if self.kind == Kind.RANGE: + return self.min_value, self.max_value + def val_to_string(self, value): return str(value) @@ -100,18 +137,12 @@ class Settings(Setting): Picks out a field from the mapped device feature objects.""" # setup creates a dictionary with entries for all the keys - # get, set, and acceptable are methods of dict value objects, not of the device object itself + # _value is a map from keys to values + # get, set, and acceptable are methods of dict value objects, not of the device object itself #### FIX THIS! MAYBE?? - @classmethod - def build(cls, device): - cls.check_properties(cls) - _device_object = getattr(device, cls.setup)() - if _device_object: - setting = cls() - setting._device = device - setting._device_object = _device_object - setting._value = {} - return setting + def __init__(self, device, device_object): + super().__init__(device, device_object) + self._value = {} def read(self, cached=True): self._pre_read(cached) @@ -122,7 +153,7 @@ class Settings(Setting): return self._value def read_key(self, key, cached=True): - """Get the data for the key. If cached is True the data in the _device_object can be used.""" + """Get the data for the key. If cached is True the data in the device_object can be used.""" self._pre_read(cached) if key not in self._device_object: logger.error("%s: settings illegal read key %r for %s", self.name, key, self._device) @@ -143,8 +174,13 @@ class Settings(Setting): def write(self, value, save=True): if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) - for key, val in value.items(): - self.write_key_value(key, val, save) + if isinstance(value, dict): + for key, val in value.items(): + self.write_key_value(key, val, save) + else: # to mimic interface for non-dict setting + key = next(iter(self._device_object)) + self.write_key_value(key, value, save) + return value def write_key_value(self, key, value, save=True): """Write the data for the key. If saved is True also save in the persister""" diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index d529eccd..2cd5b327 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -1789,10 +1789,25 @@ class ForceSensing(settings_new.Settings): setup = "force_buttons" get = "get_current" set = "set_current" - acceptable = "acceptable_current" + acceptable = "acceptable_current_key" choices_universe = list(range(0, 256)) kind = settings.Kind.MAP_RANGE + @classmethod + def build(cls, device): + cls.check_properties(cls) + device_object = getattr(device, cls.setup)() + if device_object: + setting = cls(device, device_object) + if setting and len(device_object) == 1: + ## If there is only one force button a simpler interface can be used + setting.label = _("Force Sensing Button") + setting.acceptable = "acceptable_current" + setting.min_value = device_object[0].min_value + setting.max_value = device_object[0].max_value + setting.kind = settings.Kind.RANGE + return setting + SETTINGS: list[settings.Setting] = [ RegisterHandDetection, # simple diff --git a/lib/solaar/ui/config_panel.py b/lib/solaar/ui/config_panel.py index 45dcc830..71443bdc 100644 --- a/lib/solaar/ui/config_panel.py +++ b/lib/solaar/ui/config_panel.py @@ -147,6 +147,11 @@ class SliderControl(Gtk.Scale, Control): self.set_increments(1, 5) self.connect(GtkSignal.VALUE_CHANGED.value, self.changed) + def set_value(self, value): + if isinstance(value, dict): + value = next(iter(value.values())) + return super().set_value(value) + def get_value(self): return int(super().get_value())