Repository: pwr-Solaar/Solaar

Issue:
Solaar doesn't sometime load configs
Hi,
I have problem with my Logitech MX Master 2S mouse and Solaar.
Seems Solaar doesn't load config sometime when I boot my computer. This happen about 1/4 times when I boot.
OS is 64-bit Ubuntu 18.04 Mate edition.

Edit: Looks like it load configs if I wakeup mouse before Ubuntu boot.
If I don't wakeup mouse, I need quit solaar and relaunch it.


Discussion:
This should be fixed with a868b477e1f4cdc13115909a5d4a2915145e29d8
Yes it should but not fixed!
Can confirm. Especially at startup DPI is never set. Also after a wakeup the value is not set again.
Any ideas as to when this issue will be addressed? I'm having the same issue with my Logitech MX Master 2s. The configuration file is sometimes read, but most of the time after suspend / reboots it does not.
@pettert85 I don't have the time to look into it. You can try to fix it yourself :confused:.
Is it possible to start Solaar with the `--debug` option and check if there are any errors?

When the device is reconnected, settings should have been applied.
I am using an MX Master 2S and experience the same issue. I experimented a bit and found out that it makes a differences in which order you start the mouse and Solaar. If is turn on the mosue before is start Solaar, the configuration is loaded (Solaar log: [start_mouse_before_solaar.txt](https://github.com/pwr-Solaar/Solaar/files/4025669/start_mouse_before_solaar.txt)). If I do it the other way round, i.e. I start Solaar before I turn on the mouse, the configuration is not loaded (Solaar log: [start_solaar_before_mouse.txt](https://github.com/pwr-Solaar/Solaar/files/4025670/start_solaar_before_mouse.txt)). I hope this helps you. If I can do anything else, I am happy to help.


It looks as if something screwy is going on in Setting.read in settings.py that is reading from the device when it shouldn't.  Based on the traces, this function should be getting 3400 from self.persister which should be returned but somehow this isn't happening in one case and the function is actually getting the current value from the device, which is then written back by Setting.apply.  As I don't have a device that has dpi I can't check this out, but the thing to do is to print self.persister at the beginning of the Setting.read function and then self._value after the first if statement.  The persister should be a dictionary with 3400 as value for 'dpi' and _value should then be 3400.  
I have added both debug prints. I have again attached solaar's logs (using `-dd`). I have added `>>>>` before the lines I print from `Setting.apply` so that you can identify them more easily.

I have observed the following:
- First turn on mouse, then start solaar ([mouse_off.txt](https://github.com/pwr-Solaar/Solaar/files/4083075/mouse_off.txt)):
    - Every setting is loaded twice
    - The settings are always read correct, i.e. `self.persister` contains the correct values and the correct values are stored to `self._value`
- First start solaar, then turn on the mouse. ([mouse_off.txt](https://github.com/pwr-Solaar/Solaar/files/4083109/mouse_off.txt), mouse is turned on at time 19:12:00,186)
    - The settings are only read once
    - For hires-smooth-invert and hires-smooth-resolution the settings are loaded correctly and `self.persister` contains the correct values for all settings.
    - For dpi and smart-shift, `self.persister` is `None`. This seems to be the problem here. 

I hope this helps. I tried to dig into it myself, but up until now I couldn't even figure out from where these calls to `Settings.read` are made.

Resolving patch:
diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py
--- a/lib/logitech_receiver/descriptors.py
+++ b/lib/logitech_receiver/descriptors.py
@@ -30,12 +30,12 @@
 
 from collections import namedtuple
 _DeviceDescriptor = namedtuple('_DeviceDescriptor',
-				('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings'))
+				('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings', 'persister'))
 del namedtuple
 
 DEVICES = {}
 
-def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, settings=None):
+def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, settings=None, persister=None):
 	assert name
 
 	if kind is None:
@@ -72,7 +72,7 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None,
 
 	device_descriptor = _DeviceDescriptor(name=name, kind=kind,
 					wpid=wpid, codename=codename, protocol=protocol,
-					registers=registers, settings=settings)
+					registers=registers, settings=settings, persister=persister)
 
 	assert codename not in DEVICES, 'duplicate codename in device descriptors: %s' % (DEVICES[codename], )
 	DEVICES[codename] = device_descriptor
diff --git a/lib/logitech_receiver/settings.py b/lib/logitech_receiver/settings.py
--- a/lib/logitech_receiver/settings.py
+++ b/lib/logitech_receiver/settings.py
@@ -42,7 +42,7 @@
 class Setting(object):
 	"""A setting descriptor.
 	Needs to be instantiated for each specific device."""
-	__slots__ = ('name', 'label', 'description', 'kind', 'persister', 'device_kind',
+	__slots__ = ('name', 'label', 'description', 'kind', 'device_kind',
 					'_rw', '_validator', '_device', '_value')
 
 	def __init__(self, name, rw, validator, kind=None, label=None, description=None, device_kind=None):
@@ -57,7 +57,6 @@ def __init__(self, name, rw, validator, kind=None, label=None, description=None,
 
 		assert kind is None or kind & validator.kind != 0
 		self.kind = kind or validator.kind
-		self.persister = None
 
 	def __call__(self, device):
 		assert not hasattr(self, '_value')
@@ -94,26 +93,29 @@ def read(self, cached=True):
 		assert hasattr(self, '_value')
 		assert hasattr(self, '_device')
 
-		if self._value is None and self.persister:
+		if _log.isEnabledFor(_DEBUG):
+			_log.debug("%s: settings read %r from %s", self.name, self._value, self._device)
+
+		if self._value is None and self._device.persister:
 			# We haven't read a value from the device yet,
 			# maybe we have something in the configuration.
-			self._value = self.persister.get(self.name)
+			self._value = self._device.persister.get(self.name)
 
 		if cached and self._value is not None:
-			if self.persister and self.name not in self.persister:
+			if self._device.persister and self.name not in self._device.persister:
 				# If this is a new device (or a new setting for an old device),
 				# make sure to save its current value for the next time.
-				self.persister[self.name] = self._value
+				self._device.persister[self.name] = self._value
 			return self._value
 
 		if self._device.online:
 			reply = self._rw.read(self._device)
 			if reply:
 				self._value = self._validator.validate_read(reply)
-			if self.persister and self.name not in self.persister:
+			if self._device.persister and self.name not in self._device.persister:
 				# Don't update the persister if it already has a value,
 				# otherwise the first read might overwrite the value we wanted.
-				self.persister[self.name] = self._value
+				self._device.persister[self.name] = self._value
 			return self._value
 
 	def write(self, value):
@@ -122,15 +124,15 @@ def write(self, value):
 		assert value is not None
 
 		if _log.isEnabledFor(_DEBUG):
-			_log.debug("%s: write %r to %s", self.name, value, self._device)
+			_log.debug("%s: settings write %r to %s", self.name, value, self._device)
 
 		if self._device.online:
 			# Remember the value we're trying to set, even if the write fails.
 			# This way even if the device is offline or some other error occurs,
 			# the last value we've tried to write is remembered in the configuration.
 			self._value = value
-			if self.persister:
-				self.persister[self.name] = value
+			if self._device.persister:
+				self._device.persister[self.name] = value
 
 			current_value = None
 			if self._validator.needs_current_value:
@@ -140,7 +142,7 @@ def write(self, value):
 			data_bytes = self._validator.prepare_write(value, current_value)
 			if data_bytes is not None:
 				if _log.isEnabledFor(_DEBUG):
-					_log.debug("%s: prepare write(%s) => %r", self.name, value, data_bytes)
+					_log.debug("%s: settings prepare write(%s) => %r", self.name, value, data_bytes)
 
 				reply = self._rw.write(self._device, data_bytes)
 				if not reply:
diff --git a/lib/solaar/configuration.py b/lib/solaar/configuration.py
--- a/lib/solaar/configuration.py
+++ b/lib/solaar/configuration.py
@@ -127,7 +127,4 @@ def attach_to(device):
 		_load()
 
 	persister = _device_entry(device)
-	for s in device.settings:
-		if s.persister is None:
-			s.persister = persister
-		assert s.persister == persister
+	device.persister = persister


Test patch: