rules: make rule processing conditional on X11 being available

This commit is contained in:
Peter F. Patel-Schneider 2020-11-20 08:07:27 -05:00
parent 60afd53257
commit 419a7722ad
4 changed files with 59 additions and 50 deletions

View File

@ -109,7 +109,8 @@ program.
Solaar can process HID++ Feature Notifications from devices to, for example, 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 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. Users can edit rules using a GUI by clicking on the `Edit Rule` button in the Solaar main window.

View File

@ -3,6 +3,8 @@ title: Rule Processing of HID++ Notifications
layout: page 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 Logitech devices that use HID++ version 2.0 or greater produce feature-based
notifications that Solaar can process using a simple rule language. For notifications that Solaar can process using a simple rule language. For
example, using rules Solaar can emulate an `XF86_MonBrightnessDown` key tap example, using rules Solaar can emulate an `XF86_MonBrightnessDown` key tap

View File

@ -25,12 +25,7 @@ from logging import getLogger
import _thread import _thread
import psutil 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 add_representer as _yaml_add_representer
from yaml import dump_all as _yaml_dump_all from yaml import dump_all as _yaml_dump_all
from yaml import safe_load_all as _yaml_safe_load_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__) _log = getLogger(__name__)
del getLogger 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 # 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 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?? # display.record_free_context(context) when should this be done??
# See docs/rules.md for documentation # See docs/rules.md for documentation
@ -578,49 +588,46 @@ COMPONENTS = {
'Execute': Execute, 'Execute': Execute,
} }
built_in_rules = Rule([])
built_in_rules = Rule([ if x11:
## Some malformed Rules for testing built_in_rules = Rule([
## Rule([Process(0), Feature(0), Modifiers(['XX', 0]), Modifiers('XXX'), Modifiers([0]), {'Rule': [ # Implement problematic keys for Craft and MX Master
## KeyPress(['XXXXX', 0]), KeyPress(['XXXXXX']), KeyPress(0), {'Feature': 'REPROG_CONTROLS_V4'},
## MouseScroll(0), MouseScroll([0, 0, 0]), MouseScroll(['a', 0]), {'Report': 0x0},
## Rule(["XXXXXXX"])]), {'Rule': [{'Key': 'Brightness Down'}, {'KeyPress': 'XF86_MonBrightnessDown'}]},
## Rule([Feature(0)]), {'Rule': [{'Key': 'Brightness Up'}, {'KeyPress': 'XF86_MonBrightnessUp'}]},
## Rule([Modifiers(['XXXXXXXXX', 0])]), ]},
## Rule([KeyPress(['XXXXXSSSSS', 0])]), {'Rule': [ # In firefox, crown emits keys that move up and down if not pressed, rotate through tabs otherwise
{'Rule': [ # Implement problematic keys for Craft and MX Master {'Process': 'firefox'},
{'Feature': 'REPROG_CONTROLS_V4'}, {'Feature': 'CROWN'},
{'Report': 0x0}, {'Report': 0x0},
{'Rule': [{'Key': 'Brightness Down'}, {'KeyPress': 'XF86_MonBrightnessDown'}]}, {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': ['Control_R', 'Tab']}]},
{'Rule': [{'Key': 'Brightness Up'}, {'KeyPress': 'XF86_MonBrightnessUp'}]}, {'Rule': [{'Test': 'crown_pressed'},
]}, {'Test': 'crown_left_ratchet'},
{'Rule': [ # In firefox, crown movements emits keys that move up and down if not pressed, rotate through tabs otherwise {'KeyPress': ['Control_R', 'Shift_R', 'Tab']}]},
{'Process': 'firefox'}, {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'Down'}]},
{'Feature': 'CROWN'}, Rule([Test('crown_left_ratchet'), KeyPress(['Up'])]),
{'Report': 0x0}, ]},
{'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': ['Control_R', 'Tab']}]}, {'Rule': [ # Otherwise, crown movements emit keys that modify volume if not pressed, move between tracks otherwise
{'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': ['Control_R', 'Shift_R', 'Tab']}]}, {'Feature': 'CROWN'}, {'Report': 0x0},
{'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'Down'}]}, {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioNext'}]},
Rule([Test('crown_left_ratchet'), KeyPress(['Up'])]), {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioPrev'}]},
]}, {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioRaiseVolume'}]},
{'Rule': [ # Otherwise, crown movements emit keys that modify volume if not pressed, move between tracks otherwise {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioLowerVolume'}]}
{'Feature': 'CROWN'}, {'Report': 0x0}, ]},
{'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioNext'}]}, {'Rule': [ # Thumb wheel does horizontal movement, doubled if control key not pressed
{'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioPrev'}]}, {'Feature': 'THUMB WHEEL'}, # with control modifier on mouse scrolling sometimes does something different!
{'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioRaiseVolume'}]}, {'Rule': [{'Modifiers': 'Control'}, {'Test': 'thumb_wheel_up'}, {'MouseScroll': [-1, 0]}]},
{'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioLowerVolume'}]} {'Rule': [{'Modifiers': 'Control'}, {'Test': 'thumb_wheel_down'}, {'MouseScroll': [-1, 0]}]},
]}, {'Rule': [{'Or': [{'Test': 'thumb_wheel_up'}, {'Test': 'thumb_wheel_down'}]}, {'MouseScroll': [-2, 0]}]}
{'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 # process a notification
def process_notification(device, status, notification, feature): def process_notification(device, status, notification, feature):
if not x11:
return
global keys_down, key_down global keys_down, key_down
key_down = None key_down = None
# need to keep track of keys that are down to find a new key down # need to keep track of keys that are down to find a new key down

View File

@ -24,10 +24,9 @@ from contextlib import contextmanager as contextlib_contextmanager
from logging import getLogger from logging import getLogger
from shlex import quote as shlex_quote from shlex import quote as shlex_quote
import Xlib.XK
from gi.repository import Gdk, GObject, Gtk from gi.repository import Gdk, GObject, Gtk
from logitech_receiver import diversion as _DIV 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.diversion import buttons as _buttons
from logitech_receiver.hidpp20 import FEATURE as _ALL_FEATURES from logitech_receiver.hidpp20 import FEATURE as _ALL_FEATURES
from logitech_receiver.special_keys import CONTROL as _CONTROL from logitech_receiver.special_keys import CONTROL as _CONTROL
@ -1086,7 +1085,7 @@ class ActionUI(RuleComponentUI):
class KeyPressUI(ActionUI): class KeyPressUI(ActionUI):
CLASS = _DIV.KeyPress 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): def create_widgets(self):
self.widgets = {} self.widgets = {}