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,
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.

View File

@ -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

View File

@ -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

View File

@ -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 = {}