rules: partial implementation of rules under Wayland

This commit is contained in:
Peter F. Patel-Schneider 2022-01-25 16:12:22 -05:00
parent cb7845471c
commit 3e2be09cb5
1 changed files with 55 additions and 26 deletions

View File

@ -41,7 +41,12 @@ from .special_keys import CONTROL as _CONTROL
_log = getLogger(__name__) _log = getLogger(__name__)
del getLogger del getLogger
#
# See docs/rules.md for documentation
#
# many of the rule features require X11 so turn rule processing off if X11 is not available # many of the rule features require X11 so turn rule processing off if X11 is not available
XK_KEYS = {}
try: try:
import Xlib import Xlib
from Xlib import X from Xlib import X
@ -57,13 +62,13 @@ try:
NET_WM_PID = disp_prog.intern_atom('_NET_WM_PID') NET_WM_PID = disp_prog.intern_atom('_NET_WM_PID')
WM_CLASS = disp_prog.intern_atom('WM_CLASS') WM_CLASS = disp_prog.intern_atom('WM_CLASS')
except Exception: except Exception:
_log.warn('X11 not available - rules will not be activated', exc_info=_sys.exc_info()) _log.warn(
XK_KEYS = {} 'X11 not available - rules cannot access current process or modifier key state nor can they simulate input',
exc_info=_sys.exc_info()
)
x11 = False x11 = False
# determine current key modifiers # determine current key modifiers - there must be a better way to do this
# there must be a better way to do this
if x11: if x11:
display = Display() display = Display()
try: try:
@ -81,10 +86,10 @@ if x11:
}] }]
) )
except Exception: except Exception:
_log.warn('X11 xtest not available - Modifiers and KeyPress will not work correctly', exc_info=_sys.exc_info()) _log.warn('X11 xtest not available - rules cannot access modifier key state', exc_info=_sys.exc_info())
context = None context = None
modifier_keycodes = display.get_modifier_mapping() modifier_keycodes = display.get_modifier_mapping()
current_key_modifiers = 0 current_key_modifiers = 0
def modifier_code(keycode): def modifier_code(keycode):
@ -112,8 +117,6 @@ if x11 and context is not None:
_thread.start_new_thread(display.record_enable_context, (context, key_press_handler)) _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
key_down = None key_down = None
key_up = None key_up = None
@ -180,6 +183,8 @@ COMPONENTS = {}
if x11: if x11:
displayt = Display() displayt = Display()
else:
displayt = None
class RuleComponent: class RuleComponent:
@ -320,6 +325,8 @@ def x11_pointer_prog():
class Process(Condition): class Process(Condition):
def __init__(self, process): def __init__(self, process):
self.process = process self.process = process
if not x11:
_log.warn('X11 not available - rules cannot access current process - %s', self)
if not isinstance(process, str): if not isinstance(process, str):
_log.warn('rule Process argument not a string: %s', process) _log.warn('rule Process argument not a string: %s', process)
self.process = str(process) self.process = str(process)
@ -330,7 +337,7 @@ class Process(Condition):
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, status, last_result):
if not isinstance(self.process, str): if not isinstance(self.process, str):
return False return False
focus = x11_focus_prog() focus = x11_focus_prog() if x11 else None
result = any(bool(s and s.startswith(self.process)) for s in focus) if focus else None result = any(bool(s and s.startswith(self.process)) for s in focus) if focus else None
return result return result
@ -341,6 +348,8 @@ class Process(Condition):
class MouseProcess(Condition): class MouseProcess(Condition):
def __init__(self, process): def __init__(self, process):
self.process = process self.process = process
if not x11:
_log.warn('X11 not available - rules cannot access current mouse process - %s', self)
if not isinstance(process, str): if not isinstance(process, str):
_log.warn('rule MouseProcess argument not a string: %s', process) _log.warn('rule MouseProcess argument not a string: %s', process)
self.process = str(process) self.process = str(process)
@ -351,7 +360,7 @@ class MouseProcess(Condition):
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, status, last_result):
if not isinstance(self.process, str): if not isinstance(self.process, str):
return False return False
result = any(bool(s and s.startswith(self.process)) for s in x11_pointer_prog()) result = any(bool(s and s.startswith(self.process)) for s in x11_pointer_prog()) if x11 else None
return result return result
def data(self): def data(self):
@ -407,11 +416,15 @@ class Modifiers(Condition):
self.modifiers.append(k) self.modifiers.append(k)
else: else:
_log.warn('unknown rule Modifier value: %s', k) _log.warn('unknown rule Modifier value: %s', k)
if not x11:
_log.warn('X11 not available - rules cannot access keyboard modifier state - %s', self)
def __str__(self): def __str__(self):
return 'Modifiers: ' + str(self.desired) return 'Modifiers: ' + str(self.desired)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, status, last_result):
if not context:
_log.warn('X11 xtest not available - rules cannot access modifier key state - %s', self)
return self.desired == (current_key_modifiers & MODIFIER_MASK) return self.desired == (current_key_modifiers & MODIFIER_MASK)
def data(self): def data(self):
@ -567,27 +580,33 @@ class KeyPress(Action):
if isinstance(keys, str): if isinstance(keys, str):
keys = [keys] keys = [keys]
self.key_symbols = keys self.key_symbols = keys
key_from_string = lambda s: displayt.keysym_to_keycode(Xlib.XK.string_to_keysym(s)) if x11:
self.keys = [isinstance(k, str) and key_from_string(k) for k in keys] key_from_string = lambda s: (displayt.keysym_to_keycode(Xlib.XK.string_to_keysym(s)))
if not all(self.keys): self.keys = [isinstance(k, str) and key_from_string(k) for k in keys]
_log.warn('rule KeyPress argument not sequence of key names %s', keys) if not all(self.keys):
_log.warn('rule KeyPress argument not sequence of key names %s', keys)
self.keys = []
else:
self.keys = [] self.keys = []
_log.warn('X11 not available - rules cannot simulate keyboard input - %s', self)
def __str__(self): def __str__(self):
return 'KeyPress: ' + ' '.join(self.key_symbols) return 'KeyPress: ' + ' '.join(self.key_symbols)
def needed(self, k, current_key_modifiers): def needed(self, k, current_key_modifiers):
if not context:
_log.warn('X11 xtest not available - rules cannot access modifier key state - %s', self)
code = modifier_code(k) code = modifier_code(k)
return not (code and current_key_modifiers & (1 << code)) return not (code and current_key_modifiers & (1 << code))
def keyDown(self, keys, modifiers): def keyDown(self, keys, modifiers):
for k in keys: for k in keys:
if self.needed(k, modifiers): if self.needed(k, modifiers) and x11:
Xlib.ext.xtest.fake_input(displayt, X.KeyPress, k) Xlib.ext.xtest.fake_input(displayt, X.KeyPress, k)
def keyUp(self, keys, modifiers): def keyUp(self, keys, modifiers):
for k in keys: for k in keys:
if self.needed(k, modifiers): if self.needed(k, modifiers) and x11:
Xlib.ext.xtest.fake_input(displayt, X.KeyRelease, k) Xlib.ext.xtest.fake_input(displayt, X.KeyRelease, k)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, status, last_result):
@ -596,7 +615,8 @@ class KeyPress(Action):
_log.info('KeyPress action: %s, modifiers %s %s', self.key_symbols, current, [hex(k) for k in self.keys]) _log.info('KeyPress action: %s, modifiers %s %s', self.key_symbols, current, [hex(k) for k in self.keys])
self.keyDown(self.keys, current) self.keyDown(self.keys, current)
self.keyUp(reversed(self.keys), current) self.keyUp(reversed(self.keys), current)
displayt.sync() if x11:
displayt.sync()
return None return None
def data(self): def data(self):
@ -626,9 +646,12 @@ for i in range(8, 31):
def click(button, count): def click(button, count):
for _ in range(count): if x11:
Xlib.ext.xtest.fake_input(displayt, Xlib.X.ButtonPress, button) for _ in range(count):
Xlib.ext.xtest.fake_input(displayt, Xlib.X.ButtonRelease, button) Xlib.ext.xtest.fake_input(displayt, Xlib.X.ButtonPress, button)
Xlib.ext.xtest.fake_input(displayt, Xlib.X.ButtonRelease, button)
else:
_log.warn('X11 not available - rules cannot simulate mouse clicks')
class MouseScroll(Action): class MouseScroll(Action):
@ -640,6 +663,8 @@ class MouseScroll(Action):
_log.warn('rule MouseScroll argument not two numbers %s', amounts) _log.warn('rule MouseScroll argument not two numbers %s', amounts)
amounts = [0, 0] amounts = [0, 0]
self.amounts = amounts self.amounts = amounts
if not x11:
_log.warn('X11 not available - rules cannot simulate mouse scrolling - %s', self)
def __str__(self): def __str__(self):
return 'MouseScroll: ' + ' '.join([str(a) for a in self.amounts]) return 'MouseScroll: ' + ' '.join([str(a) for a in self.amounts])
@ -657,7 +682,8 @@ class MouseScroll(Action):
click(button=buttons['scroll_right'] if dx > 0 else buttons['scroll_left'], count=abs(dx)) click(button=buttons['scroll_right'] if dx > 0 else buttons['scroll_left'], count=abs(dx))
if dy: if dy:
click(button=buttons['scroll_up'] if dy > 0 else buttons['scroll_down'], count=abs(dy)) click(button=buttons['scroll_up'] if dy > 0 else buttons['scroll_down'], count=abs(dy))
displayt.sync() if x11:
displayt.sync()
return None return None
def data(self): def data(self):
@ -680,6 +706,8 @@ class MouseClick(Action):
except (ValueError, TypeError): except (ValueError, TypeError):
_log.warn('rule MouseClick action: count %s should be an integer', count) _log.warn('rule MouseClick action: count %s should be an integer', count)
self.count = 1 self.count = 1
if not x11:
_log.warn('X11 not available - rules cannot simulate mouse clicks - %s', self)
def __str__(self): def __str__(self):
return 'MouseClick: %s (%d)' % (self.button, self.count) return 'MouseClick: %s (%d)' % (self.button, self.count)
@ -689,7 +717,8 @@ class MouseClick(Action):
_log.info('MouseClick action: %d %s' % (self.count, self.button)) _log.info('MouseClick action: %d %s' % (self.count, self.button))
if self.button and self.count: if self.button and self.count:
click(buttons[self.button], self.count) click(buttons[self.button], self.count)
displayt.sync() if x11:
displayt.sync()
return None return None
def data(self): def data(self):
@ -779,7 +808,7 @@ COMPONENTS = {
} }
built_in_rules = Rule([]) built_in_rules = Rule([])
if x11: if True: # x11
built_in_rules = Rule([ built_in_rules = Rule([
{'Rule': [ # Implement problematic keys for Craft and MX Master {'Rule': [ # Implement problematic keys for Craft and MX Master
{'Rule': [{'Key': 'Brightness Down'}, {'KeyPress': 'XF86_MonBrightnessDown'}]}, {'Rule': [{'Key': 'Brightness Down'}, {'KeyPress': 'XF86_MonBrightnessDown'}]},
@ -817,7 +846,7 @@ mr_key_down = False
# process a notification # process a notification
def process_notification(device, status, notification, feature): def process_notification(device, status, notification, feature):
if not x11: if False: # not x11
return return
global keys_down, g_keys_down, m_keys_down, mr_key_down, key_down, key_up global keys_down, g_keys_down, m_keys_down, mr_key_down, key_down, key_up
key_down, key_up = None, None key_down, key_up = None, None
@ -935,5 +964,5 @@ def _load_config_rule_file():
rules = Rule([Rule(loaded_rules, source=_file_path), built_in_rules]) rules = Rule([Rule(loaded_rules, source=_file_path), built_in_rules])
if x11: if True: # x11
_load_config_rule_file() _load_config_rule_file()