diff --git a/lib/logitech/unifying_receiver/settings.py b/lib/logitech/unifying_receiver/settings.py index 1a1dfd8f..6c34a78d 100644 --- a/lib/logitech/unifying_receiver/settings.py +++ b/lib/logitech/unifying_receiver/settings.py @@ -33,6 +33,7 @@ class _Setting(object): self.persister = None def __call__(self, device): + assert not hasattr(self, '_value') o = _copy(self) o._value = None o._device = device # _proxy(device) @@ -43,12 +44,15 @@ class _Setting(object): return self._validator.choices if self._validator.kind & KIND.choice else None def read(self, cached=True): + if self._value is None and self.persister: + self._value = self.persister.get(self.name) + if cached and self._value is not None: if self.persister and self.name not in self.persister: self.persister[self.name] = self._value return self._value - if self._device: + if self._device.online: reply = self._rw.read(self._device) if reply: self._value = self._validator.validate_read(reply) @@ -66,6 +70,10 @@ class _Setting(object): self.persister[self.name] = self._value return self._value + def apply(self): + if self._value is not None: + self.write(self._value) + def __str__(self): if hasattr(self, '_value'): assert hasattr(self, '_device') @@ -73,6 +81,9 @@ class _Setting(object): return '' % (self._rw.kind, self._validator.kind, self.name) __unicode__ = __repr__ = __str__ +# +# read/write low-level operators +# class _RegisterRW(object): __slots__ = ['register'] @@ -111,6 +122,10 @@ class _FeatureRW(object): assert self.feature is not None return device.feature_request(self.feature, self.write_fnid, data_bytes) +# +# value validators +# handle the conversion from read bytes, to setting value, and back +# class _BooleanValidator(object): __slots__ = ['true_value', 'false_value', 'mask', 'write_returns_value'] @@ -118,6 +133,7 @@ class _BooleanValidator(object): kind = KIND.toggle default_true = 0x01 default_false = 0x00 + # mask specifies all the affected bits in the value default_mask = 0xFF def __init__(self, true_value=default_true, false_value=default_false, mask=default_mask, write_returns_value=False): @@ -191,7 +207,7 @@ class _ChoicesValidator(object): return self.choices[value] # -# +# pre-defined basic setting descriptors # def register_toggle(name, register, diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index 4c4749dc..a91d3d25 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -136,9 +136,6 @@ class DeviceStatus(dict): # timestamp of when this status object was last updated self.updated = 0 - # optional object able to persist device settings - self.configuration = None - def __str__(self): def _item(name, format): value = self.get(name) @@ -242,8 +239,11 @@ class DeviceStatus(dict): # get cleared when the device is turned off (but not when the device # goes idle, and we can't tell the difference right now). self[KEYS.NOTIFICATION_FLAGS] = d.enable_notifications() - if self.configuration: - self.configuration.attach_to(d) + + # Devices lose configuration when they are turned off, + # make sure they're up-to-date. + for s in self._device.settings: + s.apply() else: if was_active: battery = self.get(KEYS.BATTERY_LEVEL) diff --git a/lib/solaar/configuration.py b/lib/solaar/configuration.py index 37ce02b8..16261f26 100644 --- a/lib/solaar/configuration.py +++ b/lib/solaar/configuration.py @@ -105,7 +105,3 @@ def attach_to(device): if s.persister is None: s.persister = persister assert s.persister == persister - if s.name in persister: - s.write(persister[s.name]) - else: - persister[s.name] = s.read(cached=False) diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index c5ce6504..3f29c3ad 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -171,8 +171,10 @@ class ReceiverListener(_listener.EventsListener): if not already_known: _log.info("%s triggered new device %s (%s)", n, dev, dev.kind) + # If there are saved configs, bring the device's settings up-to-date. + # They will be applied when the device is marked as online. + configuration.attach_to(dev) dev.status = _status.DeviceStatus(dev, self._status_changed) - dev.status.configuration = configuration # the receiver changed status as well self._status_changed(self.receiver) diff --git a/lib/solaar/ui/config_panel.py b/lib/solaar/ui/config_panel.py index 0fc14312..f0e03d25 100644 --- a/lib/solaar/ui/config_panel.py +++ b/lib/solaar/ui/config_panel.py @@ -9,7 +9,8 @@ from gi.repository import Gtk, GLib from logitech.unifying_receiver import settings as _settings # -# +# a separate thread is used to read/write from the device +# so as not to block the main (GUI) thread # try: @@ -29,15 +30,16 @@ def _process_apply_queue(): while True: task = _apply_queue.get() assert isinstance(task, tuple) + device_is_online = True # print ("task", *task) if task[0] == 'write': _, setting, value, sbox = task GLib.idle_add(_write_start, sbox, priority=0) value = setting.write(value) elif task[0] == 'read': - _, setting, force_read, sbox = task + _, setting, force_read, sbox, device_is_online = task value = setting.read(not force_read) - GLib.idle_add(_update_setting_item, sbox, value, priority=99) + GLib.idle_add(_update_setting_item, sbox, value, device_is_online, priority=99) from threading import Thread as _Thread _queue_processor = _Thread(name='SettingsProcessor', target=_process_apply_queue) @@ -190,10 +192,7 @@ def update(device, is_online=None): sbox = _items[k] = _create_sbox(s) _box.pack_start(sbox, False, False, 0) - if is_online: - _apply_queue.put(('read', s, False, sbox)) - else: - _update_setting_item(sbox, None, False) + _apply_queue.put(('read', s, False, sbox, is_online)) _box.set_visible(True)