Repository: pwr-Solaar/Solaar

Issue:
Wireless Touch Keyboard K410 not recognized due to assertion error
Firstly: thanks for writing this software. Being able to toggle Fn key behavior makes my life with this keyboard lot easier.

There is a minor issue with the K410, causing its entry not to be added to the menu. I know it is not in the supported list so it may be expected. But the fun thing is that it is easy to bypass. The assertion in 

```
assert self.device_kind is None or self.device_kind == device.kind
```

fails on a setting where `self.device_kind` is `mouse` and `device.kind` is `keyboard`. This device is a combined keyboard-trackpad.

Traceback:

```
Traceback (most recent call last):
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/listener.py", line 185, in run
    self._notifications_callback(n)
  File "/home/user/upstream/hw/Solaar/lib/solaar/listener.py", line 207, in _notifications_handler
    _notifications.process(dev, n)
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/notifications.py", line 54, in process
    return _process_device_notification(device, status, notification)
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/notifications.py", line 97, in _process_device_notification
    return _process_hidpp10_notification(device, status, n)
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/notifications.py", line 174, in _process_hidpp10_notification
    status.changed(active=link_established)
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/status.py", line 265, in changed
    for s in d.settings:
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/receiver.py", line 254, in settings
    _check_feature_settings(self, self._settings)
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/settings_templates.py", line 268, in check_feature_settings
    check_feature(_SMOOTH_SCROLL[0], _F.HI_RES_SCROLLING)
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/settings_templates.py", line 266, in check_feature
    already_known.append(feature(device))
  File "/home/user/upstream/hw/Solaar/lib/logitech_receiver/settings.py", line 63, in __call__
    assert self.device_kind is None or self.device_kind == device.kind
AssertionError
```

Commenting out the assertion makes it work perfectly, apart from identifying the device as a K400, but the fn switch works!
Version: current master as of ddbc274



Discussion:
`show` output for the device is:

```
  1: Wireless Touch Keyboard K400
     Codename     : K400
     Kind         : keyboard
     Wireless PID : 4024
     Protocol     : HID++ 2.0
     Polling rate : 8 ms (125Hz)
     Serial number: E092B63F
          Firmware: RQK 50.01.B0013
     The power switch is located on the edge of top right corner.
     Supports 22 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: FEATURE INFO           {0002}   
         3: DEVICE FW VERSION      {0003}   
         4: DEVICE NAME            {0005}   
         5: BATTERY STATUS         {1000}   
         6: REPROG CONTROLS        {1B00}   
         7: REPROG CONTROLS V3     {1B03}   
         8: WIRELESS DEVICE STATUS {1D4B}   
         9: unknown:1DF0           {1DF0}   hidden
        10: unknown:1DF3           {1DF3}   internal, hidden
        11: unknown:1E00           {1E00}   hidden
        12: VERTICAL SCROLLING     {2100}   
        13: HI RES SCROLLING       {2120}   
        14: MOUSE POINTER          {2200}   
        15: FN INVERSION           {40A0}   
        16: NEW FN INVERSION       {40A2}   
        17: ENCRYPTION             {4100}   
        18: KEYBOARD LAYOUT        {4520}   
        19: TOUCHPAD FW ITEMS      {6010}   
        20: TOUCHPAD SW ITEMS      {6011}   
        21: TOUCHPAD WIN8 FW ITEMS {6012}   
     Has 18 reprogrammable keys:
         0: SHOW DESKTOP               => ShowDesktop                   is FN, reprogrammable
         1: METRO APPSWITCH            => MetroAppSwitch                is FN, reprogrammable
         2: METRO SEARCH               => MetroSearch                   is FN, reprogrammable
         3: METRO SHARE                => MetroShare                    is FN, reprogrammable
         4: METRO DEVICES              => MetroDevices                  is FN, reprogrammable
         5: METRO SETTINGS             => MetroSettings                 is FN, reprogrammable
         6: Previous                   => Previous                      is FN
         7: Play/Pause                 => Play/Pause                    is FN
         8: Next                       => Next                          is FN
         9: Mute                       => Mute                          is FN
        10: Volume Down                => Volume Down                   is FN
        11: Volume Up                  => Volume Up                     is FN
        12: MEDIA PLAYER               => Music                         nonstandard, reprogrammable
        13: Lock PC                    => WindowsLock                   nonstandard, reprogrammable
        14: SLEEP                      => Sleep                         nonstandard, reprogrammable
        15: LEFT CLICK                 => LeftClick                     mse
        16: LEFT CLICK                 => LeftClick                     mse
        17: RIGHT CLICK                => RightClick                    mse
     Battery: 90%, discharging.
```

Eh, oops the assertion was added in c464e049bf05605f40c7c9b51140f85c35d35c21, but it breaks combo devices like yours. This is slightly annoying, maybe it is better to remove the assert for now... better to show a weird preference than crashing on an assertion failure?

> better to show a weird preference than crashing on an assertion failure?

yes, if there is no other way.

Maybe is possible to set a custom device kind based on a lookup table of wireless PID for those combos? Or, instead of lookup table for exceptions, when `self.device_kind == device.kind` isn´t true, check a combination of features supported to determine if this is a combo?  I don´t know if this is possible at this level but might be way to go.
Maybe we could change this into a warning for now, "kind" is a property that is defined in the HID++ protocol, so adding another "combo" kind seems a bit odd. Patches welcome!
Completely agree with  laanwj.  Thank you to every Dev involved in Solaar because this software makes my life so much easier.  Silly Logitech for having a soft Function key toggle that defaults to the non-function key behavior.

Thanks laanwj for helping me fix this by commenting what is now line 64 in settings.py
assert self.device_kind is None or self.device_kind == device.kind

My keyboard/mouse is K400r pid 404B.  Seems like descriptors.py could be a good place to add a unique identifier with cherry picked features.  

Users probably aren't worried about HID++ kinds where self.device_kind is mouse and device.kind is keyboard when it comes to pulling the whole feature set of both into the Solaar GUI.

I only wanted the FN-Swap to have regular function keys.
```
Unifying Receiver
  Device path  : /dev/hidraw1
  USB id       : 046d:c52b
  Serial       : EBF290D7
    Firmware   : 12.03.B0025
    Bootloader : 02.15
    Other      : AA.AA
  Has 1 paired device(s) out of a maximum of 6.
  Notifications: wireless, software present (0x000900)
  Device activity counters: 1=189

  1: Wireless Touch Keyboard K400
     Codename     : K400
     Kind         : keyboard
     Wireless PID : 404B
     Protocol     : HID++ 2.0
     Polling rate : 8 ms (125Hz)
     Serial number: C439E8DA
          Firmware: RQK 61.01.B0023
     The power switch is located on the edge of top right corner.
     Supports 22 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: FEATURE INFO           {0002}   
         3: DEVICE FW VERSION      {0003}   
         4: DEVICE NAME            {0005}   
         5: BATTERY STATUS         {1000}   
         6: REPROG CONTROLS        {1B00}   
         7: REPROG CONTROLS V3     {1B03}   
         8: WIRELESS DEVICE STATUS {1D4B}   
         9: unknown:1DF0           {1DF0}   hidden
        10: unknown:1DF3           {1DF3}   internal, hidden
        11: unknown:1E00           {1E00}   hidden
        12: VERTICAL SCROLLING     {2100}   
        13: HI RES SCROLLING       {2120}   
        14: MOUSE POINTER          {2200}   
        15: FN INVERSION           {40A0}   
        16: NEW FN INVERSION       {40A2}   
        17: ENCRYPTION             {4100}   
        18: KEYBOARD LAYOUT        {4520}   
        19: TOUCHPAD FW ITEMS      {6010}   
        20: TOUCHPAD SW ITEMS      {6011}   
        21: TOUCHPAD WIN8 FW ITEMS {6012}   
     Has 18 reprogrammable keys:
         0: SHOW DESKTOP               => ShowDesktop                   is FN, reprogrammable
         1: METRO APPSWITCH            => MetroAppSwitch                is FN, reprogrammable
         2: METRO SEARCH               => MetroSearch                   is FN, reprogrammable
         3: METRO SHARE                => MetroShare                    is FN, reprogrammable
         4: METRO DEVICES              => MetroDevices                  is FN, reprogrammable
         5: METRO SETTINGS             => MetroSettings                 is FN, reprogrammable
         6: Previous                   => Previous                      is FN
         7: Play/Pause                 => Play/Pause                    is FN
         8: Next                       => Next                          is FN
         9: Mute                       => Mute                          is FN
        10: Volume Down                => Volume Down                   is FN
        11: Volume Up                  => Volume Up                     is FN
        12: MEDIA PLAYER               => Music                         nonstandard, reprogrammable
        13: Lock PC                    => WindowsLock                   nonstandard, reprogrammable
        14: SLEEP                      => Sleep                         nonstandard, reprogrammable
        15: LEFT CLICK                 => LeftClick                     mse
        16: LEFT CLICK                 => LeftClick                     mse
        17: RIGHT CLICK                => RightClick                    mse
     Battery: 90%, discharging.
```
The K400 keyboard should be handled correctly in the version of solaar in this repository.  If you are having problems when using this version of solaar, please add to this issue.
I'll have to check the code later.  I also use PID 404B and that one requires commenting self,device_kind.  Output is from Manjaro Linux latest.

I does appear that K400 Plus, K400 and K830 all show ```FN Swap``` but currently not the K400r

```
Unifying Receiver
  Device path  : /dev/hidraw0
  USB id       : 046d:c52b
  Serial       : PRIVATE
    Firmware   : 12.10.B0032
    Bootloader : 02.15
    Other      : AA.AA
  Has 1 paired device(s) out of a maximum of 6.
  Notifications: wireless, software present (0x000900)
  Device activity counters: 1=104

  1: Wireless Touch Keyboard K400r
     Codename     : K400r
     Kind         : keyboard
     Wireless PID : 404B
     Protocol     : HID++ 2.0
     Polling rate : 8 ms (125Hz)
     Serial number: 44084798
     The power switch is located on the edge of top right corner.
     Battery status unavailable.
```
I use this in ```solaar/lib/logitech_receiver/descriptors.py```:
```
_D('Wireless Touch Keyboard K400r', codename='K400r', protocol=2.0, wpid='404B',
				settings=[
							_FS.new_fn_swap()
						],
				)
```
The output of solaar show above doesn't show the features that the K400r supports, maybe because the keyboard was not online at the time.
Please try again with the keyboard online (i.e., when you are using it). 
You should see a lot more output concerning the K400r. 
This is the result from my custom build with self,device_kind commented out.
```
Unifying Receiver
  Device path  : /dev/hidraw0
  USB id       : 046d:c52b
  Serial       : WITHELD
    Firmware   : 12.10.B0032
    Bootloader : 02.15
    Other      : AA.AA
  Has 1 paired device(s) out of a maximum of 6.
  Notifications: wireless, software present (0x000900)
  Device activity counters: 1=45

  1: Wireless Touch Keyboard K400r
     Codename     : K400r
     Kind         : keyboard
     Wireless PID : 404B
     Protocol     : HID++ 2.0
     Polling rate : 8 ms (125Hz)
     Serial number: WITHELD
     The power switch is located on the edge of top right corner.
     Supports 22 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: FEATURE INFO           {0002}   
         3: DEVICE FW VERSION      {0003}   
         4: DEVICE NAME            {0005}   
         5: BATTERY STATUS         {1000}   
         6: REPROG CONTROLS        {1B00}   
         7: REPROG CONTROLS V3     {1B03}   
         8: WIRELESS DEVICE STATUS {1D4B}   
         9: REMAINING PAIRING      {1DF0}   hidden
        10: unknown:1DF3           {1DF3}   internal, hidden
        11: ENABLE HIDDEN FEATURES {1E00}   hidden
        12: VERTICAL SCROLLING     {2100}   
            Roller type: normal touch pad
            Ratchet per turn: 16
            Scroll lines: 0
        13: HI RES SCROLLING       {2120}   
            Hi-res scrolling disabled
            Hi-res scrolling multiplier: 8
        14: MOUSE POINTER          {2200}   
            DPI: 300
            Acceleration: low
            Override OS ballistics
            No vertical tuning, standard mice
        15: FN INVERSION           {40A0}   
        16: NEW FN INVERSION       {40A2}   
        17: ENCRYPTION             {4100}   
        18: KEYBOARD LAYOUT        {4520}   
        19: TOUCHPAD FW ITEMS      {6010}   
        20: TOUCHPAD SW ITEMS      {6011}   
        21: TOUCHPAD WIN8 FW ITEMS {6012}   
     Has 18 reprogrammable keys:
         0: SHOW DESKTOP               => ShowDesktop                   is FN, reprogrammable
         1: METRO APPSWITCH            => MetroAppSwitch                is FN, reprogrammable
         2: METRO SEARCH               => MetroSearch                   is FN, reprogrammable
         3: METRO SHARE                => MetroShare                    is FN, reprogrammable
         4: METRO DEVICES              => MetroDevices                  is FN, reprogrammable
         5: METRO SETTINGS             => MetroSettings                 is FN, reprogrammable
         6: Previous                   => Previous                      is FN
         7: Play/Pause                 => Play/Pause                    is FN
         8: Next                       => Next                          is FN
         9: Mute                       => Mute                          is FN
        10: Volume Down                => Volume Down                   is FN
        11: Volume Up                  => Volume Up                     is FN
        12: MEDIA PLAYER               => Music                         nonstandard, reprogrammable
        13: Lock PC                    => WindowsLock                   nonstandard, reprogrammable
        14: SLEEP                      => Sleep                         nonstandard, reprogrammable
        15: LEFT CLICK                 => LeftClick                     mse
        16: LEFT CLICK                 => LeftClick                     mse
        17: RIGHT CLICK                => RightClick                    mse
     Battery: 70%, discharging.
```

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
@@ -60,7 +60,8 @@ def __init__(self, name, rw, validator, kind=None, label=None, description=None,
 
 	def __call__(self, device):
 		assert not hasattr(self, '_value')
-		assert self.device_kind is None or device.kind in self.device_kind
+		# combined keyboards and touchpads (e.g., K400) break this assertion so don't use it
+		# assert self.device_kind is None or device.kind in self.device_kind
 		p = device.protocol
 		if p == 1.0:
 			# HID++ 1.0 devices do not support features


Test patch: