Repository: pwr-Solaar/Solaar

Issue:
solaar doesn't apply settings
**Information**
- Solaar version (git describe --tags` if cloned from this repository): 1.0.2-66-gde0894b
- Distribution: Arch Linux
- Kernel version (ex. `uname -srmo`): `Linux 5.7.8-arch1-1 x86_64 GNU/Linux
`
- Output of `solaar -d show`:
```
Unifying Receiver
  Device path  : /dev/hidraw0
  USB id       : 046d:c52b
  Serial       : 28B295ED
    Firmware   : 24.11.B0036
    Bootloader : 02.09
    Other      : AA.AC
  Has 4 paired device(s) out of a maximum of 6.
  Notifications: wireless, software present (0x000900)
  Device activity counters: 1=95, 2=248
15:29:54,906     INFO [MainThread] logitech_receiver.receiver: <UnifyingReceiver(/dev/hidraw0,3)>: found new device 1 (4069)

  1: Wireless Mouse MX Master 2S
     Codename     : MX Master 2S
     Kind         : mouse
     Wireless PID : 4069
     Protocol     : HID++ 4.5
     Polling rate : 8 ms (125Hz)
     Serial number: 493D1134
        Bootloader: BOT 56.01.B0006
          Firmware: MPM 12.01.B0006
          Firmware: MPM 12.01.B0006
             Other: 
     The power switch is located on the base.
     Supports 32 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: DEVICE FW VERSION      {0003}   
         3: DEVICE NAME            {0005}   
         4: WIRELESS DEVICE STATUS {1D4B}   
         5: RESET                  {0020}   
         6: CRYPTO ID              {0021}   
         7: BATTERY STATUS         {1000}   
         8: CONFIG DEVICE PROPS    {1806}   internal, hidden
         9: CHANGE HOST            {1814}   
        10: REPROG CONTROLS V4     {1B04}   
        11: ADJUSTABLE DPI         {2201}   
        12: VERTICAL SCROLLING     {2100}   
            Roller type: 3G
            Ratchet per turn: 24
            Scroll lines: 0
        13: SMART SHIFT            {2110}   
            Smart Shift: 12
        14: HIRES WHEEL            {2121}   
            Multiplier: 8
            Has invert
              Normal wheel motion
            Has ratchet switch
              Normal wheel mode
            Low resolution mode
            HID notification
            High Resolution Wheel Invert: False
            Wheel Resolution: False
        15: GESTURE 2              {6501}   
        16: DFUCONTROL SIGNED      {00C2}   
        17: unknown:1813           {1813}   internal, hidden
        18: unknown:1830           {1830}   internal, hidden
        19: unknown:1890           {1890}   internal, hidden
        20: unknown:1891           {1891}   internal, hidden
        21: unknown:18A1           {18A1}   internal, hidden
        22: unknown:18C0           {18C0}   internal, hidden
        23: unknown:1DF3           {1DF3}   internal, hidden
        24: unknown:1E00           {1E00}   hidden
        25: unknown:1EB0           {1EB0}   internal, hidden
        26: unknown:1803           {1803}   internal, hidden
        27: unknown:1861           {1861}   internal, hidden
        28: unknown:9001           {9001}   internal, hidden
        29: unknown:9200           {9200}   internal, hidden
        30: unknown:9202           {9202}   internal, hidden
        31: OOBSTATE               {1805}   internal, hidden
     Has 8 reprogrammable keys:
         0: Left Button               , default: Left Click                  => Left Button               
             mse, pos:0, group:1, gmask:1
         1: Right Button              , default: Right Click                 => Right Button              
             mse, pos:0, group:1, gmask:1
         2: Middle Button             , default: Mouse Middle Button         => Middle Button             
             mse, reprogrammable, divertable, pos:0, group:3, gmask:7
         3: Back Button               , default: Mouse Back Button           => Back Button               
             mse, reprogrammable, divertable, pos:0, group:2, gmask:3
         4: Forward Button            , default: Mouse Forward Button        => Forward Button            
             mse, reprogrammable, divertable, pos:0, group:2, gmask:3
         5: App Switch Gesture        , default: Gesture Button Navigation   => App Switch Gesture        
             mse, reprogrammable, divertable, pos:0, group:3, gmask:7
         6: Smart Shift               , default: Smart Shift                 => Smart Shift               
             mse, reprogrammable, divertable, pos:0, group:3, gmask:7
         7: Virtual Gesture Button    , default: Virtual Gesture Button      => Virtual Gesture Button    
             divertable, virtual, pos:0, group:4, gmask:0
     Battery: 50%, discharging, next level 20%.
15:29:56,816     INFO [MainThread] logitech_receiver.receiver: <UnifyingReceiver(/dev/hidraw0,3)>: found new device 2 (4072)

  2: Wireless Mobile Mouse MX Anywhere 2
     Codename     : MX Anywhere 2
     Kind         : mouse
     Wireless PID : 4072
     Protocol     : HID++ 4.5
     Polling rate : 8 ms (125Hz)
     Serial number: DDAE1933
        Bootloader: BOT 57.10.B0003
          Firmware: MPM 13.10.B0003
          Firmware: MPM 13.10.B0003
             Other: 
     The power switch is located on the base.
     Supports 28 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: DEVICE FW VERSION      {0003}   
         3: DEVICE NAME            {0005}   
         4: WIRELESS DEVICE STATUS {1D4B}   
         5: RESET                  {0020}   
         6: CRYPTO ID              {0021}   
         7: BATTERY STATUS         {1000}   
         8: CONFIG DEVICE PROPS    {1806}   internal, hidden
         9: CHANGE HOST            {1814}   
        10: REPROG CONTROLS V4     {1B04}   
        11: ADJUSTABLE DPI         {2201}   
        12: VERTICAL SCROLLING     {2100}   
            Roller type: 3G
            Ratchet per turn: 24
            Scroll lines: 0
        13: HIRES WHEEL            {2121}   
            Multiplier: 8
            Has invert
              Normal wheel motion
            Has ratchet switch
              Normal wheel mode
            Low resolution mode
            HID notification
            High Resolution Wheel Invert: False
            Wheel Resolution: False
        14: DFUCONTROL SIGNED      {00C2}   
        15: unknown:1813           {1813}   internal, hidden
        16: unknown:1830           {1830}   internal, hidden
        17: unknown:1890           {1890}   internal, hidden
        18: unknown:1891           {1891}   internal, hidden
        19: unknown:18A1           {18A1}   internal, hidden
        20: unknown:18C0           {18C0}   internal, hidden
        21: unknown:1DF3           {1DF3}   internal, hidden
        22: unknown:1E00           {1E00}   hidden
        23: unknown:1EB0           {1EB0}   internal, hidden
        24: unknown:1803           {1803}   internal, hidden
        25: unknown:1861           {1861}   internal, hidden
        26: unknown:9001           {9001}   internal, hidden
        27: OOBSTATE               {1805}   internal, hidden
     Has 8 reprogrammable keys:
         0: Left Button               , default: Left Click                  => Left Button               
             mse, pos:0, group:1, gmask:1
         1: Right Button              , default: Right Click                 => Right Button              
             mse, pos:0, group:1, gmask:1
         2: Middle Button             , default: Gesture Button Navigation   => Middle Button             
             mse, reprogrammable, divertable, pos:0, group:2, gmask:11
         3: Back Button               , default: Mouse Back Button           => Back Button               
             mse, reprogrammable, divertable, pos:0, group:3, gmask:15
         4: Forward Button            , default: Mouse Forward Button        => Forward Button            
             mse, reprogrammable, divertable, pos:0, group:3, gmask:15
         5: Left Tilt                 , default: Mouse Scroll Left Button    => Left Tilt                 
             mse, reprogrammable, divertable, pos:0, group:3, gmask:15
         6: Right Tilt                , default: Mouse Scroll Right Button   => Right Tilt                
             mse, reprogrammable, divertable, pos:0, group:3, gmask:15
         7: Virtual Gesture Button    , default: Virtual Gesture Button      => Virtual Gesture Button    
             divertable, virtual, pos:0, group:4, gmask:0
     Battery: 90%, discharging, next level 50%.
15:29:59,190     INFO [MainThread] logitech_receiver.receiver: <UnifyingReceiver(/dev/hidraw0,3)>: found new device 3 (4024)

  3: Wireless Touch Keyboard K400
     Codename     : K400
     Kind         : keyboard
     Wireless PID : 4024
     Protocol     : HID++ 2.0
     Polling rate : 8 ms (125Hz)
     Serial number: 25CF31D9
     The power switch is located on the edge of top right corner.
     Battery: unknown (device is offline).
15:29:59,198     INFO [MainThread] logitech_receiver.receiver: <UnifyingReceiver(/dev/hidraw0,3)>: found new device 4 (101B)

  4: Marathon Mouse M705 (M-R0009)
     Codename     : M705 (M-R0009)
     Kind         : mouse
     Wireless PID : 101B
     Protocol     : HID++ 1.0
     Polling rate : 8 ms (125Hz)
     Serial number: 41629AAD
     The power switch is located on the base.
     Battery: unknown (device is offline).
```

**Describe the bug**
Since the implementation of the feature CHANGE_HOST  in 5a4205d, solaar doesn't apply the persistent settings correctly to the mouse (tested with MX Master 2S and MX Anywhere 2) anymore, e.g. the DPI are not set correctly and button remappings are not executed. The GUI shows the correct values, but the mouse does not behave like when set with those values. When changing the settings from the GUI works fine.
Additionally, solaar takes longer to start up than before.There is a delay between showing the GUI and listing the devices, a delay after recognition of each devices and an additional delay until battery info is available.

**To Reproduce**
Steps to reproduce the behavior:
1. Start solaar
2. Notice that the GUI shows the correct settings but the mouse does not behave like it should with those settings

**Screenshots**
Screenshot of the solaar GUI
![grafik](https://user-images.githubusercontent.com/20126509/87551768-1ad68980-c6b1-11ea-8dd1-ab4c89940e7a.png)


**Additional context**
1. output of `solaar  -dd` for g1c2b347 (the last working version): https://gist.github.com/lukasmichel/aa69434d6182685f854a532a3d2f1c17
2. output of `solaar -dd` for gde0894b: https://gist.github.com/lukasmichel/a1686324925a49108e6d97deb42e8211
Please note the delay after [line 181](https://gist.github.com/lukasmichel/a1686324925a49108e6d97deb42e8211#file-solaarshow_current-master-txt-L180) and [line 330](https://gist.github.com/lukasmichel/a1686324925a49108e6d97deb42e8211#file-solaarshow_current-master-txt-L330)
Other than that, I don't know where to look for the described bug.


Discussion:
Line 177 is a CHANGE_HOST request to change hosts (hopefully to the current host).  But it may be that any CHANGE_HOST request causes the device to try to change hosts, which may take a while, so some requests may be lost, which is causing the timeout and may be causing the other problems.
So definitely a bug. The solution is not to write the current value back to the device for CHANGE_HOST.

Resolving patch:
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
@@ -175,8 +175,8 @@ def apply(self):
         if _log.isEnabledFor(_DEBUG):
             _log.debug('%s: apply %s (%s)', self.name, self._value, self._device)
 
-        value = self.read(self.persist)  # Don't use old value if setting doesn't persist
-        if value is not None:
+        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)
 
     def __str__(self):
@@ -656,7 +656,7 @@ class ChoicesValidator(object):
     def __init__(self, choices, bytes_count=None, read_skip_bytes_count=None, write_prefix_bytes=b'', **_ignore):
         assert choices is not None
         assert isinstance(choices, _NamedInts)
-        assert len(choices) > 2
+        assert len(choices) > 1
         self.choices = choices
         self.needs_current_value = False
 
diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py
--- a/lib/logitech_receiver/settings_templates.py
+++ b/lib/logitech_receiver/settings_templates.py
@@ -146,7 +146,7 @@ def feature_choices(name, feature, choices, **kwargs):
     assert choices
     validator = _ChoicesV(choices, **kwargs)
     rw = _FeatureRW(feature, **kwargs)
-    return _Setting(name, rw, validator, kind=_KIND.choice, **kwargs)
+    return _Setting(name, rw, validator, feature=feature, kind=_KIND.choice, **kwargs)
 
 
 def feature_choices_dynamic(name, feature, choices_callback, **kwargs):
@@ -169,7 +169,7 @@ def feature_map_choices(name, feature, choicesmap, **kwargs):
     assert choicesmap
     validator = _ChoicesMapV(choicesmap, **kwargs)
     rw = _FeatureRWMap(feature, **kwargs)
-    return _Settings(name, rw, validator, kind=_KIND.map_choice, **kwargs)
+    return _Settings(name, rw, validator, feature=feature, kind=_KIND.map_choice, **kwargs)
 
 
 def feature_map_choices_dynamic(name, feature, choices_callback, **kwargs):
diff --git a/lib/solaar/ui/config_panel.py b/lib/solaar/ui/config_panel.py
--- a/lib/solaar/ui/config_panel.py
+++ b/lib/solaar/ui/config_panel.py
@@ -238,11 +238,12 @@ def _update_setting_item(sbox, value, is_online=True):
         failed.set_visible(is_online)
         return
 
+    control.set_sensitive(False)
     failed.set_visible(False)
     if isinstance(control, Gtk.Switch):
         control.set_active(value)
     elif isinstance(control, Gtk.ComboBoxText):
-        control.set_active_id(str(value))
+        control.set_active_id(str(int(value)))
     elif isinstance(control, Gtk.Scale):
         control.set_value(int(value))
     elif isinstance(control, Gtk.HBox):


Test patch: