Repository: pwr-Solaar/Solaar

Issue:
multiple buttons initiating mouse gestures
**Information**
<!-- Please update to Solaar from this repository before asking for a new feature. -->
- Solaar version (`solaar --version` and `git describe --tags`): 1.1.3
- Distribution: Manjaro
- Kernel version (ex. `uname -srmo`): Linux 5.15.50-1-MANJARO
- Output of `solaar show` for the target device (if applicable):

<details>

```
Solaar version 1.1.3

USB and Bluetooth Devices

  1: Wireless Mouse MX Master 2S
     Device path  : /dev/hidraw3
     USB id       : 046d:B019
     Codename     : MX Master 2S
     Kind         : mouse
     Protocol     : HID++ 4.5
     Serial number: 
     Model ID:      B01940690000
     Unit ID:       D34FD133
        Bootloader: BOT 56.01.B0006
          Firmware: MPM 12.01.B0006
          Firmware: MPM 12.01.B0006
             Other: 
     Supports 29 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: DEVICE FW VERSION      {0003}   
            Firmware: Bootloader BOT 56.01.B0006 406916A9FC5401
            Firmware: Firmware MPM 12.01.B0006 B01916A9FC5401
            Firmware: Firmware MPM 12.01.B0006 406916A9FC5401
            Firmware: Other   
            Unit ID: D34FD133  Model ID: B01940690000  Transport IDs: {'btleid': 'B019', 'wpid': '4069'}
         3: DEVICE NAME            {0005}   
            Name: Wireless Mouse MX Master 2S
            Kind: mouse
         4: WIRELESS DEVICE STATUS {1D4B}   
         5: RESET                  {0020}   
         6: CRYPTO ID              {0021}   
         7: BATTERY STATUS         {1000}   
            Battery: 50%, discharging, next level 20%.
         8: CONFIG DEVICE PROPS    {1806}   internal, hidden
         9: CHANGE HOST            {1814}   
            改变主机        : 1:yan-desktop
        10: REPROG CONTROLS V4     {1B04}   
            DPI 滑动调节 (saved): 关
            DPI 滑动调节        : 关
            鼠标手势 (saved): Mouse Gesture Button
            鼠标手势        : 关
            按键/按钮操作 (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift}
            按键/按钮操作        : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift}
            按键/按钮改道 (saved): {Middle Button:常规, Back Button:改道, Forward Button:改道, Mouse Gesture Button:改道, Smart Shift:常规}
            按键/按钮改道        : {Middle Button:常规, Back Button:改道, Forward Button:改道, Mouse Gesture Button:改道, Smart Shift:常规}
        11: ADJUSTABLE DPI         {2201}   
            灵敏度 (DPI) (saved): 1000
            灵敏度 (DPI)        : 1000
        12: VERTICAL SCROLLING     {2100}   
            Roller type: 3G
            Ratchet per turn: 24
            Scroll lines: 0
        13: SMART SHIFT            {2110}   
            滚轮分段模式 (saved): 16
            滚轮分段模式        : 16
        14: HIRES WHEEL            {2121}   
            Multiplier: 8
            Has invert: Normal wheel motion
            Has ratchet switch: Normal wheel mode
            Low resolution mode
            HID notification
            滚轮方向 (saved): False
            滚轮方向        : False
            滚轮分辨率 (saved): False
            滚轮分辨率        : False
            滚轮改道 (saved): False
            滚轮改道        : False
        15: GESTURE 2              {6501}   
            手势 (saved): {45: True, 46: True}
            手势        : {46: True, 45: True}
            手势改道 (saved): {46: False}
            手势改道        : {46: False}
        16: unknown:1813           {1813}   internal, hidden
        17: unknown:1830           {1830}   internal, hidden
        18: unknown:18A1           {18A1}   internal, hidden
        19: unknown:18C0           {18C0}   internal, hidden
        20: unknown:1DF3           {1DF3}   internal, hidden
        21: unknown:1E00           {1E00}   hidden
        22: unknown:1EB0           {1EB0}   internal, hidden
        23: unknown:1803           {1803}   internal, hidden
        24: unknown:1861           {1861}   internal, hidden
        25: unknown:9001           {9001}   internal, hidden
        26: unknown:9200           {9200}   internal, hidden
        27: unknown:9202           {9202}   internal, hidden
        28: OOBSTATE               {1805}   internal, hidden
     Has 8 reprogrammable keys:
         0: Left Button               , default: Left Click                  => Left Click                
             mse, pos:0, group:1, group mask:g1
             reporting: default
         1: Right Button              , default: Right Click                 => Right Click               
             mse, pos:0, group:1, group mask:g1
             reporting: default
         2: Middle Button             , default: Mouse Middle Button         => Mouse Middle Button       
             mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3
             reporting: default
         3: Back Button               , default: Mouse Back Button           => Mouse Back Button         
             mse, reprogrammable, divertable, raw XY, pos:0, group:2, group mask:g1,g2
             reporting: diverted
         4: Forward Button            , default: Mouse Forward Button        => Mouse Forward Button      
             mse, reprogrammable, divertable, raw XY, pos:0, group:2, group mask:g1,g2
             reporting: diverted
         5: Mouse Gesture Button      , default: Gesture Button Navigation   => Gesture Button Navigation 
             mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3
             reporting: diverted, raw XY diverted
         6: Smart Shift               , default: Smart Shift                 => Smart Shift               
             mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3
             reporting: default
         7: Virtual Gesture Button    , default: Virtual Gesture Button      => Virtual Gesture Button    
             divertable, virtual, raw XY, force raw XY, pos:0, group:4, group mask:empty
             reporting: default
     Has 2 gesture(s), 0 param(s) and 4 spec(s):
        Thumbwheel                 Enabled(   0): True   Diverted:(   0) False
        NaturalScrolling           Enabled(   1): True   Diverted:(None) None
        period unit                Spec   (   3): 800
        DVI field width            Spec   (   1): 8
        field widths               Spec   (   2): 8
        resolution                 Spec   (   4): 2048
     Battery: 50%, discharging, next level 20%.
```
</details>

Hi, i'm trying to migrate logiops config to solaar, but i dont know if i could create a rule which will be executed when my mouse side button (forward, back) pressed and mouse moving, without pressing the thumb button. just like logiops does. I found the rule `Mouse Gesture` needs the thumb button pressed.



Discussion:
I don't think you can do this in Solaar.  Solaar is not a user-space driver, which logiops claims to be.   What Solaar does is manipulate device settings and interpret special (HID++) messages that some devices send.

Solaar does have Mouse Gestures, but only sets up a single button to initiate the gesture processing.  Solaar prefers the mouse gesture button for this.  An easy enhancement would allow some other suitable button instead.  Is that what you want?
Thanks for your answer! Maybe what i want is a rule `Mouse move`. Combine keypress with mouse move canbe a simple gesture. i will close this issue if it's difficult for solaar.
I think what you want is the ability to have multiple buttons that initiate mouse gesture processing.  Then gesture rules could do different things depending on which button was used.  This would require quite a bit of work to add to Solaar.

What you say, however, isn't something that Solaar can do.   
Thx! i'll close this issue.

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
@@ -776,7 +776,7 @@ def __init__(self, movements):
             movements = [movements]
         for x in movements:
             if x not in self.MOVEMENTS and x not in _CONTROL:
-                _log.warn('rule Key argument not name of a Logitech key: %s', x)
+                _log.warn('rule Mouse Gesture argument not direction or name of a Logitech key: %s', x)
         self.movements = movements
 
     def __str__(self):
@@ -785,26 +785,28 @@ def __str__(self):
     def evaluate(self, feature, notification, device, status, last_result):
         if feature == _F.MOUSE_GESTURE:
             d = notification.data
-            count = _unpack('!h', d[:2])[0]
-            data = _unpack('!' + ((int(len(d) / 2) - 1) * 'h'), d[2:])
-            if count != len(self.movements):
-                return False
-            x = 0
-            z = 0
-            while x < len(data):
-                if data[x] == 0:
-                    direction = xy_direction(data[x + 1], data[x + 2])
-                    if self.movements[z] != direction:
+            data = _unpack('!' + (int(len(d) / 2) * 'h'), d)
+            data_offset = 0
+            for m in self.movements:
+                if data_offset == 0:
+                    data_offset += 1
+                    if m not in self.MOVEMENTS:  # matching against initiating key
+                        if m != str(_CONTROL[data[0]]):
+                            return False
+                        else:
+                            continue
+                if data_offset >= len(data):
+                    return False
+                if data[data_offset] == 0:
+                    direction = xy_direction(data[data_offset + 1], data[data_offset + 2])
+                    if m != direction:
                         return False
-                    x += 3
-                elif data[x] == 1:
-                    if data[x + 1] not in _CONTROL:
+                    data_offset += 3
+                elif data[data_offset] == 1:
+                    if m != str(_CONTROL[data[data_offset + 1]]):
                         return False
-                    if self.movements[z] != str(_CONTROL[data[x + 1]]):
-                        return False
-                    x += 2
-                z += 1
-            return True
+                    data_offset += 2
+            return data_offset == len(data)
         return False
 
     def data(self):
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
@@ -1410,6 +1410,84 @@ def handler(device, n):  # Called on notification events from the device
         return True
 
 
+class RawXYProcessing:
+    """Special class for processing RawXY action messages initiated by pressing a key with rawXY diversion capability"""
+    def __init__(self, device, name=''):
+        self.device = device
+        self.name = name
+        self.keys = []  # the keys that can initiate processing
+        self.initiating_key = None  # the key that did initiate processing
+        self.active = False
+        self.feature_offset = device.features[_hidpp20.FEATURE.REPROG_CONTROLS_V4]
+        assert self.feature_offset is not False
+
+    def handler(self, device, n):  # Called on notification events from the device
+        if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == _hidpp20.FEATURE.REPROG_CONTROLS_V4:
+            if n.address == 0x00:
+                cids = _unpack('!HHHH', n.data[:8])
+                ## generalize to list of keys
+                if not self.initiating_key:  # no initiating key pressed
+                    for k in self.keys:
+                        if int(k.key) in cids:  # initiating key that was pressed
+                            self.initiating_key = k
+                    if self.initiating_key:
+                        self.press_action(self.initiating_key)
+                else:
+                    if int(self.initiating_key.key) not in cids:  # initiating key released
+                        self.initiating_key = None
+                        self.release_action()
+                    else:
+                        for key in cids:
+                            if key and key != self.initiating_key.key:
+                                self.key_action(key)
+            elif n.address == 0x10:
+                if self.initiating_key:
+                    dx, dy = _unpack('!hh', n.data[:4])
+                    self.move_action(dx, dy)
+
+    def start(self, key):
+        device_key = next((k for k in self.device.keys if k.key == key), None)
+        self.keys.append(device_key)
+        if not self.active:
+            self.active = True
+            self.activate_action()
+            self.device.add_notification_handler(self.name, self.handler)
+        device_key.set_rawXY_reporting(True)
+
+    def stop(self, key):  # only stop if this is the active key
+        if self.active:
+            processing_key = next((k for k in self.keys if k.key == key), None)
+            if processing_key:
+                processing_key.set_rawXY_reporting(False)
+                self.keys.remove(processing_key)
+            if not self.keys:
+                try:
+                    self.device.remove_notification_handler(self.name)
+                except Exception:
+                    if _log.isEnabledFor(_WARNING):
+                        _log.warn('cannot disable %s on %s', self.name, self.device)
+                self.deactivate_action()
+                self.active = False
+
+    def activate_action(self):  # action to take when processing is activated
+        pass
+
+    def deactivate_action(self):  # action to take when processing is deactivated
+        pass
+
+    def press_action(self, key):  # action to take when an initiating key is pressed
+        pass
+
+    def release_action(self):  # action to take when key is released
+        pass
+
+    def move_action(self, dx, dy):  # action to take when mouse is moved while key is down
+        pass
+
+    def key_action(self, key):  # acction to take when some other diverted key is pressed
+        pass
+
+
 def apply_all_settings(device):
     persister = getattr(device, 'persister', None)
     sensitives = persister.get('_sensitive', {}) if persister else {}
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
@@ -45,6 +45,7 @@
 from .settings import PackedRangeValidator as _PackedRangeV
 from .settings import RangeFieldSetting as _RangeFieldSetting
 from .settings import RangeValidator as _RangeV
+from .settings import RawXYProcessing as _RawXYProcessing
 from .settings import Setting as _Setting
 from .settings import Settings as _Settings
 from .special_keys import DISABLE as _DKEY
@@ -448,7 +449,7 @@ class ReprogrammableKeys(_Settings):
     name = 'reprogrammable-keys'
     label = _('Key/Button Actions')
     description = (
-        _('Change the action for the key or button.') + '\n' +
+        _('Change the action for the key or button.') + '  ' + _('Overridden by diversion.') + '\n' +
         _('Changing important actions (such as for the left mouse button) can result in an unusable system.')
     )
     feature = _F.REPROG_CONTROLS_V4
@@ -483,13 +484,140 @@ def build(cls, setting_class, device):
             return cls(choices, key_byte_count=2, byte_count=2, extra_default=0) if choices else None
 
 
+class DpiSlidingXY(_RawXYProcessing):
+    def activate_action(self):
+        self.dpiSetting = next(filter(lambda s: s.name == 'dpi', self.device.settings), None)
+        self.dpiChoices = list(self.dpiSetting.choices)
+        self.otherDpiIdx = self.device.persister.get('_dpi-sliding', -1) if self.device.persister else -1
+        if not isinstance(self.otherDpiIdx, int) or self.otherDpiIdx < 0 or self.otherDpiIdx >= len(self.dpiChoices):
+            self.otherDpiIdx = self.dpiChoices.index(self.dpiSetting.read())
+        self.fsmState = 'idle'
+        self.dx = 0.
+        self.movingDpiIdx = None
+
+    def setNewDpi(self, newDpiIdx):
+        newDpi = self.dpiChoices[newDpiIdx]
+        self.dpiSetting.write(newDpi)
+        from solaar.ui import status_changed as _status_changed
+        _status_changed(self.device, refresh=True)  # update main window
+
+    def displayNewDpi(self, newDpiIdx):
+        from solaar.ui import notify as _notify  # import here to avoid circular import when running `solaar show`,
+        if _notify.available:
+            reason = 'DPI %d [min %d, max %d]' % (self.dpiChoices[newDpiIdx], self.dpiChoices[0], self.dpiChoices[-1])
+            # if there is a progress percentage then the reason isn't shown
+            # asPercentage = int(float(newDpiIdx) / float(len(self.dpiChoices) - 1) * 100.)
+            # _notify.show(self.device, reason=reason, progress=asPercentage)
+            _notify.show(self.device, reason=reason)
+
+    def press_action(self, key):  # start tracking
+        if self.fsmState == 'idle':
+            self.fsmState = 'pressed'
+            self.dx = 0.
+            # While in 'moved' state, the index into 'dpiChoices' of the currently selected DPI setting
+            self.movingDpiIdx = None
+
+    def release_action(self):  # adjust DPI and stop tracking
+        if self.fsmState == 'pressed':  # Swap with other DPI
+            thisIdx = self.dpiChoices.index(self.dpiSetting.read())
+            newDpiIdx, self.otherDpiIdx = self.otherDpiIdx, thisIdx
+            if self.device.persister:
+                self.device.persister['_dpi-sliding'] = self.otherDpiIdx
+            self.setNewDpi(newDpiIdx)
+            self.displayNewDpi(newDpiIdx)
+        elif self.fsmState == 'moved':  # Set DPI according to displacement
+            self.setNewDpi(self.movingDpiIdx)
+        self.fsmState = 'idle'
+
+    def move_action(self, dx, dy):
+        currDpi = self.dpiSetting.read()
+        self.dx += float(dx) / float(currDpi) * 15.  # yields a more-or-less DPI-independent dx of about 5/cm
+        if self.fsmState == 'pressed':
+            if abs(self.dx) >= 1.:
+                self.fsmState = 'moved'
+                self.movingDpiIdx = self.dpiChoices.index(currDpi)
+        elif self.fsmState == 'moved':
+            currIdx = self.dpiChoices.index(self.dpiSetting.read())
+            newMovingDpiIdx = min(max(currIdx + int(self.dx), 0), len(self.dpiChoices) - 1)
+            if newMovingDpiIdx != self.movingDpiIdx:
+                self.movingDpiIdx = newMovingDpiIdx
+                self.displayNewDpi(newMovingDpiIdx)
+
+
+class MouseGesturesXY(_RawXYProcessing):
+    def activate_action(self):
+        self.dpiSetting = next(filter(lambda s: s.name == 'dpi', self.device.settings), None)
+        self.fsmState = 'idle'
+        self.initialize_data()
+
+    def initialize_data(self):
+        self.dx = 0.
+        self.dy = 0.
+        self.lastEv = None
+        self.data = []
+
+    def press_action(self, key):
+        if self.fsmState == 'idle':
+            self.fsmState = 'pressed'
+            self.initialize_data()
+            self.data = [key.key]
+
+    def release_action(self):
+        if self.fsmState == 'pressed':
+            # emit mouse gesture notification
+            from .base import _HIDPP_Notification as _HIDPP_Notification
+            from .diversion import process_notification as _process_notification
+            self.push_mouse_event()
+            if _log.isEnabledFor(_INFO):
+                _log.info('mouse gesture notification %s', self.data)
+            payload = _pack('!' + (len(self.data) * 'h'), *self.data)
+            notification = _HIDPP_Notification(0, 0, 0, 0, payload)
+            _process_notification(self.device, self.device.status, notification, _hidpp20.FEATURE.MOUSE_GESTURE)
+            self.fsmState = 'idle'
+
+    def move_action(self, dx, dy):
+        if self.fsmState == 'pressed':
+            now = _time() * 1000  # _time_ns() / 1e6
+            if self.lastEv is not None and now - self.lastEv > 50.:
+                self.push_mouse_event()
+            dpi = self.dpiSetting.read() if self.dpiSetting else 1000
+            dx = float(dx) / float(dpi) * 15.  # This multiplier yields a more-or-less DPI-independent dx of about 5/cm
+            self.dx += dx
+            dy = float(dy) / float(dpi) * 15.  # This multiplier yields a more-or-less DPI-independent dx of about 5/cm
+            self.dy += dy
+            self.lastEv = now
+
+    def key_action(self, key):
+        self.push_mouse_event()
+        self.data.append(1)
+        self.data.append(key)
+        self.lastEv = _time() * 1000  # _time_ns() / 1e6
+        if _log.isEnabledFor(_DEBUG):
+            _log.debug('mouse gesture key event %d %s', key, self.data)
+
+    def push_mouse_event(self):
+        x = int(self.dx)
+        y = int(self.dy)
+        if x == 0 and y == 0:
+            return
+        self.data.append(0)
+        self.data.append(x)
+        self.data.append(y)
+        self.dx = 0.
+        self.dy = 0.
+        if _log.isEnabledFor(_DEBUG):
+            _log.debug('mouse gesture move event %d %d %s', x, y, self.data)
+
+
 class DivertKeys(_Settings):
     name = 'divert-keys'
     label = _('Key/Button Diversion')
-    description = _('Make the key or button send HID++ notifications (which trigger Solaar rules but are otherwise ignored).')
+    description = _('Make the key or button send HID++ notifications (Diverted) or initiate Mouse Gestures or Sliding DPI')
     feature = _F.REPROG_CONTROLS_V4
     keys_universe = _special_keys.CONTROL
-    choices_universe = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1})
+    choices_universe = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1, _('Mouse Gestures'): 2, _('Sliding DPI'): 3})
+    choices_gesture = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1, _('Mouse Gestures'): 2})
+    choices_divert = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1})
 
     class rw_class:
         def __init__(self, feature):
@@ -504,20 +632,49 @@ def read(self, device, key):
         def write(self, device, key, data_bytes):
             key_index = device.keys.index(key)
             key_struct = device.keys[key_index]
-            key_struct.set_diverted(data_bytes == b'\x01')
+            key_struct.set_diverted(_bytes2int(data_bytes) != 0)  # not regular
             return True
 
     class validator_class(_ChoicesMapV):
+        def __init__(self, choices, key_byte_count=2, byte_count=1, mask=0x01):
+            super().__init__(choices, key_byte_count, byte_count, mask)
+
+        def prepare_write(self, key, new_value):
+            if self.gestures and new_value != 2:  # mouse gestures
+                self.gestures.stop(key)
+            if self.sliding and new_value != 3:  # sliding DPI
+                self.sliding.stop(key)
+            if self.gestures and new_value == 2:  # mouse gestures
+                self.gestures.start(key)
+            if self.sliding and new_value == 3:  # sliding DPI
+                self.sliding.start(key)
+            return super().prepare_write(key, new_value)
+
         @classmethod
         def build(cls, setting_class, device):
+            sliding = gestures = None
             choices = {}
             if device.keys:
                 for k in device.keys:
                     if 'divertable' in k.flags and 'virtual' not in k.flags:
-                        choices[k.key] = setting_class.choices_universe
+                        if 'raw XY' in k.flags:
+                            choices[k.key] = setting_class.choices_gesture
+                            if gestures is None:
+                                gestures = MouseGesturesXY(device, name='MouseGestures')
+                            if _F.ADJUSTABLE_DPI not in device.features:
+                                choices[k.key] = setting_class.choices_gesture
+                            else:
+                                choices[k.key] = setting_class.choices_universe
+                                if sliding is None:
+                                    sliding = DpiSlidingXY(device, name='DpiSlding')
+                        else:
+                            choices[k.key] = setting_class.choices_divert
             if not choices:
                 return None
-            return cls(choices, key_byte_count=2, byte_count=1, mask=0x01)
+            validator = cls(choices, key_byte_count=2, byte_count=1, mask=0x01)
+            validator.sliding = sliding
+            validator.gestures = gestures
+            return validator
 
 
 class AdjustableDpi(_Setting):
@@ -810,11 +967,11 @@ class validator_class(_ChoicesV):
         def build(cls, setting_class, device):
             if device.keys:  # ensure that the device has a keys array
                 keys = []
-                for key in cls.MouseGestureKeys:
-                    key_index = device.keys.index(key)
-                    dkey = device.keys[key_index] if key_index is not None else None
-                    if dkey is not None and 'raw XY' in dkey.flags and 'divertable' in dkey.flags:
-                        keys.append(dkey.key)
+                #                for key in cls.MouseGestureKeys:
+                #                    key_index = device.keys.index(key)
+                #                    dkey = device.keys[key_index] if key_index is not None else None
+                #                    if dkey is not None and 'raw XY' in dkey.flags and 'divertable' in dkey.flags:
+                #                        keys.append(dkey.key)
                 if not keys:  # none of the keys designed for this, so look for any key with correct flags
                     for key in device.keys:
                         if 'raw XY' in key.flags and 'divertable' in key.flags and 'virtual' not in key.flags:
@@ -1218,9 +1375,9 @@ def build(cls, setting_class, device):
     ReportRate,  # working
     PointerSpeed,  # simple
     AdjustableDpi,  # working
-    DpiSliding,  # working
+    #    DpiSliding,  # working
     SpeedChange,
-    MouseGesture,  # working
+    #    MouseGesture,  # working
     #    Backlight,  # not working - disabled temporarily
     Backlight2,  # working
     Backlight3,


Test patch: