From 419a7722ad68d37c26d2554294de57b9ca942fac Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Fri, 20 Nov 2020 08:07:27 -0500 Subject: [PATCH] rules: make rule processing conditional on X11 being available --- docs/capabilities.md | 3 +- docs/rules.md | 2 + lib/logitech_receiver/diversion.py | 99 ++++++++++++++++-------------- lib/solaar/ui/diversion_rules.py | 5 +- 4 files changed, 59 insertions(+), 50 deletions(-) diff --git a/docs/capabilities.md b/docs/capabilities.md index e5a4c7ef..8ad3ee01 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -109,7 +109,8 @@ program. Solaar can process HID++ Feature Notifications from devices to, for example, change the speed of some thumb wheels. For more information on this capability of Solaar see -[the rules page](https://pwr-solaar.github.io/Solaar/rules). +[the rules page](https://pwr-solaar.github.io/Solaar/rules). As much of rule processing +depends on X11, this capability is only when running under X11. Users can edit rules using a GUI by clicking on the `Edit Rule` button in the Solaar main window. diff --git a/docs/rules.md b/docs/rules.md index a0359e3c..1ab859cf 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -3,6 +3,8 @@ title: Rule Processing of HID++ Notifications layout: page --- +Note that rule processing is only available when running under X11. + Logitech devices that use HID++ version 2.0 or greater produce feature-based notifications that Solaar can process using a simple rule language. For example, using rules Solaar can emulate an `XF86_MonBrightnessDown` key tap diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py index ca375b9a..e9fc7cc0 100644 --- a/lib/logitech_receiver/diversion.py +++ b/lib/logitech_receiver/diversion.py @@ -25,12 +25,7 @@ from logging import getLogger 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 @@ from .special_keys import CONTROL as _CONTROL _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 @@ COMPONENTS = { '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 index a5c38307..4091e673 100644 --- a/lib/solaar/ui/diversion_rules.py +++ b/lib/solaar/ui/diversion_rules.py @@ -24,10 +24,9 @@ from contextlib import contextmanager as contextlib_contextmanager 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 @@ class ActionUI(RuleComponentUI): 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 = {}