Repository: pwr-Solaar/Solaar

Issue:
Solaar error on Wayland
Solar: 1.0.4-22-gf42ff35
Kernel 5.9.8

Dump:
```
Nov 19 16:49:15 xxx solaar.desktop[4106058]:  * Please make sure that you have an X server running, and that the DISPLAY environment variable is set correctly
Nov 19 16:49:15 xxx solaar.desktop[4106058]: Try one of the following resolutions:
Nov 19 16:49:15 xxx solaar.desktop[4106058]: ImportError: this platform is not supported: ('failed to acquire X connection: Can\'t connect to display ":0": b\'No protocol specifie>
Nov 19 16:49:15 xxx solaar.desktop[4106058]:     raise ImportError('this platform is not supported: {}'.format(
Nov 19 16:49:15 xxx solaar.desktop[4106058]:   File "/usr/local/lib/python3.8/dist-packages/pynput/_util/__init__.py", line 76, in backend
Nov 19 16:49:15 xxx solaar.desktop[4106058]:     backend = backend(__name__)
Nov 19 16:49:15 xxx solaar.desktop[4106058]:   File "/usr/local/lib/python3.8/dist-packages/pynput/keyboard/__init__.py", line 31, in <module>
Nov 19 16:49:15 xxx solaar.desktop[4106058]:     from . import keyboard
Nov 19 16:49:15 xxx solaar.desktop[4106058]:   File "/usr/local/lib/python3.8/dist-packages/pynput/__init__.py", line 40, in <module>
Nov 19 16:49:15 xxx solaar.desktop[4106058]:     from pynput import keyboard as _keyboard
Nov 19 16:49:15 xxx solaar.desktop[4106058]:   File "/usr/local/lib/python3.8/dist-packages/logitech_receiver/diversion.py", line 30, in <module>
Nov 19 16:49:15 xxx solaar.desktop[4106058]:     from . import diversion as _diversion
Nov 19 16:49:15 xxx solaar.desktop[4106058]:   File "/usr/local/lib/python3.8/dist-packages/logitech_receiver/notifications.py", line 30, in <module>
Nov 19 16:49:15 xxx solaar.desktop[4106058]:     from logitech_receiver import notifications as _notifications
Nov 19 16:49:15 xxx solaar.desktop[4106058]:   File "/usr/local/lib/python3.8/dist-packages/solaar/listener.py", line 32, in <module>
Nov 19 16:49:15 xxx solaar.desktop[4106058]:     import solaar.listener as listener
Nov 19 16:49:15 xxx solaar.desktop[4106058]:   File "/usr/local/lib/python3.8/dist-packages/solaar/gtk.py", line 153, in main
Nov 19 16:49:15 xxx solaar.desktop[4106058]: solaar: error: Traceback (most recent call last):
```


Discussion:
Yeah, the notification rule code requires X11.   I'll make it conditional.  In the meantime downgrade to a released version of Solaar.
PR #1007 makes rule processing conditional on X11.  Please try out this PR.  If you run `bin/solaar -d` you should see a warning stating that rule processing is not available but everything else should work.





To clone and work with Solar
```
git clone https://github.com/pwr-Solaar/Solaar.git
cd Solaar
```
Run Solaar as bin/solaar from this directory.


To run PR #1007, first clone Solaar if you have not already done so and cd to the clone directory.  The first time you download the pull request, fetch it into a new branch and checkout that branch, as in:
```
git fetch origin pull/1007/head:pull_1007
git checkout pull_1007
```
To download a new version of the pull request, fetch it and then set your pull branch
to the new fetch, as in:
```
git checkout pull_1007
git fetch origin pull/1007/head
git reset --hard FETCH_HEAD
```

If anyone knows how to find out the modifier key status and simulate input in Wayland without needing root access please comment here.
As far as I know both X11 and Wayland are using libinput to handle events. So seems you could use it too. There is python-libinput module as well.
That requires special privileges, unfortunately.  Fortunately full root access is not needed, so this is a possibility.

What is needed for rule processing is a way of obtaining the state of the keyboard modifier flags on a seat basis (which is used as a condition in rules) and a way of simulating keyboard and mouse input.  It looks as if libinput doesn't provide either.

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
@@ -330,7 +330,7 @@ def get_btid(btid):
     settings=[_FS.new_fn_swap()],
 )
 _D('Craft Advanced Keyboard', codename='Craft', protocol=4.5, wpid='4066', btid=0xB350)
-_D('MX Keys Keyboard', codename='MK Keys', protocol=4.5, wpid='408A', btid=0xB35B)
+_D('MX Keys Keyboard', codename='MX Keys', protocol=4.5, wpid='408A', btid=0xB35B)
 _D(
     'Wireless Keyboard S510',
     codename='S510',
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
@@ -25,12 +25,7 @@
 
 import _thread
 import psutil
-import Xlib
 
-from Xlib import X
-from Xlib.display import Display
-from Xlib.ext import record
-from Xlib.protocol import rq
 from yaml import add_representer as _yaml_add_representer
 from yaml import dump_all as _yaml_dump_all
 from yaml import safe_load_all as _yaml_safe_load_all
@@ -42,7 +37,21 @@
 _log = getLogger(__name__)
 del getLogger
 
-Xlib.XK.load_keysym_group('xf86')
+# many of the rule features require X11 so turn rule processing off if X11 is not available
+try:
+    import Xlib
+    from Xlib import X
+    from Xlib.display import Display
+    from Xlib.ext import record
+    from Xlib.protocol import rq
+    from Xlib import XK as _XK
+    _XK.load_keysym_group('xf86')
+    XK_KEYS = vars(_XK)
+    x11 = True
+except Exception:
+    _log.warn('Xlib not available - rules will not be activated')
+    XK_KEYS = {}
+    x11 = False
 
 # determine name of active process
 
@@ -118,7 +127,8 @@ def key_press_handler(reply):
             current_key_modifiers = event.state & ~(1 << mod) if mod is not None else event.state
 
 
-_thread.start_new_thread(display.record_enable_context, (context, key_press_handler))
+if x11:
+    _thread.start_new_thread(display.record_enable_context, (context, key_press_handler))
 # display.record_free_context(context)  when should this be done??
 
 # See docs/rules.md for documentation
@@ -578,49 +588,46 @@ def data(self):
     'Execute': Execute,
 }
 
-
-built_in_rules = Rule([
-    ## Some malformed Rules for testing
-    ##    Rule([Process(0), Feature(0), Modifiers(['XX', 0]), Modifiers('XXX'), Modifiers([0]),
-    ##         KeyPress(['XXXXX', 0]), KeyPress(['XXXXXX']), KeyPress(0),
-    ##         MouseScroll(0), MouseScroll([0, 0, 0]), MouseScroll(['a', 0]),
-    ##         Rule(["XXXXXXX"])]),
-    ##    Rule([Feature(0)]),
-    ##    Rule([Modifiers(['XXXXXXXXX', 0])]),
-    ##    Rule([KeyPress(['XXXXXSSSSS', 0])]),
-    {'Rule': [  # Implement problematic keys for Craft and MX Master
-        {'Feature': 'REPROG_CONTROLS_V4'},
-        {'Report': 0x0},
-        {'Rule': [{'Key': 'Brightness Down'}, {'KeyPress': 'XF86_MonBrightnessDown'}]},
-        {'Rule': [{'Key': 'Brightness Up'}, {'KeyPress': 'XF86_MonBrightnessUp'}]},
-    ]},
-    {'Rule': [  # In firefox, crown movements emits keys that move up and down if not pressed, rotate through tabs otherwise
-        {'Process': 'firefox'},
-        {'Feature': 'CROWN'},
-        {'Report': 0x0},
-        {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': ['Control_R', 'Tab']}]},
-        {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': ['Control_R', 'Shift_R', 'Tab']}]},
-        {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'Down'}]},
-        Rule([Test('crown_left_ratchet'), KeyPress(['Up'])]),
-    ]},
-    {'Rule': [  # Otherwise, crown movements emit keys that modify volume if not pressed, move between tracks otherwise
-        {'Feature': 'CROWN'}, {'Report': 0x0},
-        {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioNext'}]},
-        {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioPrev'}]},
-        {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioRaiseVolume'}]},
-        {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioLowerVolume'}]}
-    ]},
-    {'Rule': [  # Thumb wheel does horizontal movement, doubled if control key not pressed
-        {'Feature': 'THUMB WHEEL'},  # with control modifier on mouse scrolling sometimes does something different!
-        {'Rule': [{'Modifiers': 'Control'}, {'Test': 'thumb_wheel_up'}, {'MouseScroll': [-1, 0]}]},
-        {'Rule': [{'Modifiers': 'Control'}, {'Test': 'thumb_wheel_down'}, {'MouseScroll': [-1, 0]}]},
-        {'Rule': [{'Or': [{'Test': 'thumb_wheel_up'}, {'Test': 'thumb_wheel_down'}]}, {'MouseScroll': [-2, 0]}]}
-    ]}
-])
+built_in_rules = Rule([])
+if x11:
+    built_in_rules = Rule([
+        {'Rule': [  # Implement problematic keys for Craft and MX Master
+            {'Feature': 'REPROG_CONTROLS_V4'},
+            {'Report': 0x0},
+            {'Rule': [{'Key': 'Brightness Down'}, {'KeyPress': 'XF86_MonBrightnessDown'}]},
+            {'Rule': [{'Key': 'Brightness Up'}, {'KeyPress': 'XF86_MonBrightnessUp'}]},
+        ]},
+        {'Rule': [  # In firefox, crown emits keys that move up and down if not pressed, rotate through tabs otherwise
+            {'Process': 'firefox'},
+            {'Feature': 'CROWN'},
+            {'Report': 0x0},
+            {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': ['Control_R', 'Tab']}]},
+            {'Rule': [{'Test': 'crown_pressed'},
+                      {'Test': 'crown_left_ratchet'},
+                      {'KeyPress': ['Control_R', 'Shift_R', 'Tab']}]},
+            {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'Down'}]},
+            Rule([Test('crown_left_ratchet'), KeyPress(['Up'])]),
+        ]},
+        {'Rule': [  # Otherwise, crown movements emit keys that modify volume if not pressed, move between tracks otherwise
+            {'Feature': 'CROWN'}, {'Report': 0x0},
+            {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioNext'}]},
+            {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioPrev'}]},
+            {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioRaiseVolume'}]},
+            {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioLowerVolume'}]}
+        ]},
+        {'Rule': [  # Thumb wheel does horizontal movement, doubled if control key not pressed
+            {'Feature': 'THUMB WHEEL'},  # with control modifier on mouse scrolling sometimes does something different!
+            {'Rule': [{'Modifiers': 'Control'}, {'Test': 'thumb_wheel_up'}, {'MouseScroll': [-1, 0]}]},
+            {'Rule': [{'Modifiers': 'Control'}, {'Test': 'thumb_wheel_down'}, {'MouseScroll': [-1, 0]}]},
+            {'Rule': [{'Or': [{'Test': 'thumb_wheel_up'}, {'Test': 'thumb_wheel_down'}]}, {'MouseScroll': [-2, 0]}]}
+        ]}
+    ])
 
 
 # process a notification
 def process_notification(device, status, notification, feature):
+    if not x11:
+        return
     global keys_down, key_down
     key_down = None
     # need to keep track of keys that are down to find a new key down
diff --git a/lib/solaar/ui/diversion_rules.py b/lib/solaar/ui/diversion_rules.py
--- a/lib/solaar/ui/diversion_rules.py
+++ b/lib/solaar/ui/diversion_rules.py
@@ -24,10 +24,9 @@
 from logging import getLogger
 from shlex import quote as shlex_quote
 
-import Xlib.XK
-
 from gi.repository import Gdk, GObject, Gtk
 from logitech_receiver import diversion as _DIV
+from logitech_receiver.diversion import XK_KEYS as _XK_KEYS
 from logitech_receiver.diversion import buttons as _buttons
 from logitech_receiver.hidpp20 import FEATURE as _ALL_FEATURES
 from logitech_receiver.special_keys import CONTROL as _CONTROL
@@ -1086,7 +1085,7 @@ def icon_name(cls):
 class KeyPressUI(ActionUI):
 
     CLASS = _DIV.KeyPress
-    KEY_NAMES = [k[3:] if k.startswith('XK_') else k for k, v in vars(Xlib.XK).items() if isinstance(v, int)]
+    KEY_NAMES = [k[3:] if k.startswith('XK_') else k for k, v in _XK_KEYS.items() if isinstance(v, int)]
 
     def create_widgets(self):
         self.widgets = {}


Test patch: