Files
Solaar/lib/logitech_receiver/settings_new.py
2025-11-12 14:50:46 -05:00

207 lines
9.3 KiB
Python

## Copyright (C) 2025 Solaar contributors
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
## A new way of supporting settings, using a feature-specifi device class to store, read, and write relevant information
## The setting uses the device class to interact with the device feature.
## The setting uses a persist class to keep track of the setting.
## Interface:
import logging
from .settings import Kind
logger = logging.getLogger(__name__)
class Setting:
name = None # Solaar internal name for the setting
label = None # Solaar user name for the setting (translatable)
description = None # Solaar extra desciption for the setting (translatable)
feature = None # Logitech feature that the setting uses
min_version = 0 # Minimum version of the feature needed
setup = None # method name on Device class to get the device object
get = None # method name on the device object to get the setting value
set = None # method name on the device object to set the setting value
acceptable = None # method name on the device object to check for acceptable values
choices_universe = None # All possible acceptable keys, for settings with keys
kind = Kind.NONE # What GUI interface to use
persist = True # Whether to remember the setting
display = True # display setting in UI
_device = None # The device that this setting is for
_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"
assert cls.feature, "New settings require a feature"
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"
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"""
# Get the persister map if available and not done already
if self.persist and self._value is None and getattr(self._device, "persister", None):
self._value = self._device.persister.get(self.name)
# If this is new save its current value for the next time
if cached and self._value is not None:
if getattr(self._device, "persister", None) and self.name not in self._device.persister:
self._device.persister[self.name] = self._value if self.persist else None
def read(self, cached=True):
"""Get all the data for the setting. If cached is True the data in the _value can be used."""
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 ## fill out
def apply(self):
"""Write saved data to the device, using persisted data if available"""
if logger.isEnabledFor(logging.DEBUG):
logger.debug("%s: apply (%s)", self.name, self._device)
value = None
try:
value = self.read(self.persist) # Don't use persisted value if setting doesn't persist
if self.persist and value is not None: # If setting doesn't persist no need to write value just read
self.write(value, save=False)
except Exception as e:
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)
## key mapping from symbols to values????
class Settings(Setting):
"""A setting descriptor for multiple keys.
Supported by a class that provides the interface to the device, see ForceSensingButtonArray in hidpp20.py
Picks out a field from the mapped device feature objects."""
# setup creates a dictionary with entries for all the keys
# _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??
def __init__(self, device, device_object):
super().__init__(device, device_object)
self._value = {}
def read(self, cached=True):
self._pre_read(cached)
if logger.isEnabledFor(logging.DEBUG):
logger.debug("%s: settings read %r from %s", self.name, self._value, self._device)
for key in self._device_object:
self.read_key(key, cached)
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."""
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)
return None
if logger.isEnabledFor(logging.DEBUG):
logger.debug("%s: settings key %r read %r from %s", self.name, key, self._value, self._device)
if cached and key in self._value and self._value[key] is not None:
return self._value[key]
if cached:
data = self._device_object[key]
self._value[key] = getattr(data, self.get)()
return self._value[key]
if self._device.online:
data = self._device_object.query_key(key)
self._value[key] = getattr(data, self.get)()
return self._value[key]
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)
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"""
if key not in self._device_object:
logger.error("%s: settings illegal write key %r for %s", self.name, key, self._device)
return None
if logger.isEnabledFor(logging.DEBUG):
logger.debug("%s: settings write key %r value %r to %s", self.name, key, value, self._device)
if self._device.online:
if self._device_object[key] is None:
self.read_key(key)
if self._device_object[key] is None:
logger.error("%s: settings illegal write key %r for %s", self.name, key, self._device)
return None
if not getattr(self._device_object[key], self.acceptable)(value):
logger.error("%s: settings illegal write key %r value %r for %s", self.name, key, value, self._device)
return None
self._value[key] = value
if self._device.persister and self.persist and save:
self._device.persister[self.name][key] = value
getattr(self._device_object[key], self.set)(value)
return value