Repository: pwr-Solaar/Solaar

Issue:
diverting G keys causes Python exceptions when pressing M-keys
**Information**
- Solaar version (`solaar --version` or `git describe --tags` if cloned from this repository): `solaar 1.1.1`
- Distribution: Arch
- Kernel version (ex. `uname -srmo`): `Linux 5.16.2-arch1-1 x86_64 GNU/Linux`
- Output of `solaar show`:

<details>

```
Solaar version 1.1.1

USB and Bluetooth Devices

  1: G815 Mechanical Keyboard
     Device path  : /dev/hidraw1
     USB id       : 046d:C33F
     Codename     : G815
     Kind         : keyboard
     Protocol     : HID++ 4.2
     Polling rate : 1 ms (1000Hz)
     Serial number: 
     Model ID:      C33F00000000
     Unit ID:       34304708
        Bootloader: BOT 84.00.B0003
          Firmware: U1  31.02.B0018
             Other: 
             Other: 
             Other: 
     Supports 24 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: DEVICE FW VERSION      {0003}   
            Firmware: Bootloader BOT 84.00.B0003 AAEA
            Firmware: Firmware U1  31.02.B0018 C33F
            Firmware: Other   
            Firmware: Other   
            Firmware: Other   
            Unit ID: 34304708  Model ID: C33F00000000  Transport IDs: {'usbid': 'C33F'}
         3: DEVICE NAME            {0005}   
            Name: G815 RGB MECHANICAL GAMING KEYBOARD
            Kind: keyboard
         4: RESET                  {0020}   
         5: DFUCONTROL SIGNED      {00C2}   
         6: DFU                    {00D0}   
         7: REPORT HID USAGE       {1BC0}   
         8: KEYBOARD DISABLE BY USAGE {4522}   
         9: KEYBOARD LAYOUT 2      {4540}   
        10: GKEY                   {8010}   
            Divert G Keys (saved): True
            Divert G Keys        : False
        11: MKEYS                  {8020}   
        12: MR                     {8030}   
        13: BRIGHTNESS CONTROL     {8040}   
        14: REPORT RATE            {8060}   
            Polling Rate (ms): 1
            Polling Rate (ms) (saved): 1
            Polling Rate (ms)        : 1
        15: RGB EFFECTS            {8071}   
        16: PER KEY LIGHTING V2    {8081}   
        17: ONBOARD PROFILES       {8100}   
            Device Mode: Host
        18: unknown:1801           {1801}   internal, hidden
        19: DEVICE RESET           {1802}   internal, hidden
        20: CONFIG DEVICE PROPS    {1806}   internal, hidden
        21: unknown:18B0           {18B0}   internal, hidden
        22: unknown:1E00           {1E00}   hidden
        23: unknown:1EB0           {1EB0}   internal, hidden
     Battery status unavailable.
```
</details>

**Describe the bug**
I cannot bind keys M1 through M3. When running Solaar with the -dd flag I get the following exception logged for both keyup and keydown:

```
11:59:30,798    ERROR [ReceiverListener:hidraw1] logitech_receiver.listener: processing Notification(11,255,0B,00,01000000000000000000000000000000)
Traceback (most recent call last):
  File "/home/gregor/src/Solaar/lib/logitech_receiver/listener.py", line 190, in run
    self._notifications_callback(n)
  File "/home/gregor/src/Solaar/lib/solaar/listener.py", line 192, in _notifications_handler
    _notifications.process(self.receiver, n)
  File "/home/gregor/src/Solaar/lib/logitech_receiver/notifications.py", line 60, in process
    return _process_device_notification(device, status, notification)
  File "/home/gregor/src/Solaar/lib/logitech_receiver/notifications.py", line 197, in _process_device_notification
    return _process_feature_notification(device, status, n, feature)
  File "/home/gregor/src/Solaar/lib/logitech_receiver/notifications.py", line 432, in _process_feature_notification
    _diversion.process_notification(device, status, n, feature)
  File "/home/gregor/src/Solaar/lib/logitech_receiver/diversion.py", line 850, in process_notification
    if new_m_keys_down & (0x01 << (i - 1)) and not m_keys_down & (0x01 << (i - 1)):
TypeError: unsupported operand type(s) for &: 'tuple' and 'int'
```

In addition, the MR key produces no exception, but I cannot bind it either.


Discussion:

Resolving patch:
diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py
--- a/lib/logitech_receiver/diversion.py
+++ b/lib/logitech_receiver/diversion.py
@@ -845,16 +845,16 @@ def process_notification(device, status, notification, feature):
         g_keys_down = new_g_keys_down
     # and also M keys down
     elif feature == _F.MKEYS and notification.address == 0x00:
-        new_m_keys_down = _unpack('!1B', notification.data[:1])
+        new_m_keys_down = _unpack('!1B', notification.data[:1])[0]
         for i in range(1, 9):
             if new_m_keys_down & (0x01 << (i - 1)) and not m_keys_down & (0x01 << (i - 1)):
-                key_down = _CONTROL['M' + str(i + 8 * byte_idx)]
+                key_down = _CONTROL['M' + str(i)]
             if m_keys_down & (0x01 << (i - 1)) and not new_m_keys_down & (0x01 << (i - 1)):
-                key_up = _CONTROL['M' + str(i + 8 * byte_idx)]
+                key_up = _CONTROL['M' + str(i)]
         m_keys_down = new_m_keys_down
     # and also MR key
     elif feature == _F.MR and notification.address == 0x00:
-        new_mr_key_down = _unpack('!1B', notification.data[:1])
+        new_mr_key_down = _unpack('!1B', notification.data[:1])[0]
         if not mr_key_down and new_mr_key_down:
             key_down = _CONTROL['MR']
         if mr_key_down and not new_mr_key_down:
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
@@ -986,6 +986,45 @@ def build(cls, setting_class, device):
             return cls(items, sub_items)
 
 
+class MKeyLEDs(_BitFieldSetting):
+    name = 'm-key-leds'
+    label = _('M-Key LEDs')
+    description = _('Control the M-Key LEDs.')
+    feature = _F.MKEYS
+    choices_universe = _NamedInts()
+    for i in range(8):
+        choices_universe[1 << i] = 'M' + str(i + 1)
+    _labels = {k: (None, _('Lights up the %s key.') % k) for k in choices_universe}
+
+    class rw_class(_FeatureRW):
+        def __init__(self, feature):
+            super().__init__(feature, write_fnid=0x10)
+
+        def read(self, device):  # no way to read, so just assume off
+            return b'\x00'
+
+    class validator_class(_BitFieldV):
+        @classmethod
+        def build(cls, setting_class, device):
+            number = device.feature_request(setting_class.feature, 0x00)[0]
+            options = [setting_class.choices_universe[1 << i] for i in range(number)]
+            return cls(options) if options else None
+
+
+class MRKeyLED(_Setting):
+    name = 'mr-key-led'
+    label = _('MR-Key LED')
+    description = _('Control the MR-Key LED.')
+    feature = _F.MR
+
+    class rw_class(_FeatureRW):
+        def __init__(self, feature):
+            super().__init__(feature, write_fnid=0x00)
+
+        def read(self, device):  # no way to read, so just assume off
+            return b'\x00'
+
+
 SETTINGS = [
     RegisterHandDetection,  # simple
     RegisterSmoothScroll,  # simple
@@ -1015,7 +1054,9 @@ def build(cls, setting_class, device):
     DisableKeyboardKeys,  # working
     DivertCrown,  # working
     CrownSmooth,  # working
-    DivertGkeys,
+    DivertGkeys,  # working
+    MKeyLEDs,  # working
+    MRKeyLED,
     Multiplatform,  # working
     DualPlatform,  # simple
     ChangeHost,  # working


Test patch: