rules: use local file for conversion from key names to keysyms
This commit is contained in:
parent
1869f57f7f
commit
eedf4bfffb
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from pathlib import Path
|
||||||
|
from pprint import pprint
|
||||||
|
from re import findall
|
||||||
|
from subprocess import run
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
repo = 'https://github.com/freedesktop/xorg-proto-x11proto.git'
|
||||||
|
pattern = r'#define XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?'
|
||||||
|
xf86pattern = r'#define XF86XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?'
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
keysymdef = {}
|
||||||
|
|
||||||
|
with TemporaryDirectory() as temp:
|
||||||
|
run(['git', 'clone', repo, '.'], cwd=temp)
|
||||||
|
text = Path(temp, 'keysymdef.h').read_text()
|
||||||
|
for name, sym, uni in findall(pattern, text):
|
||||||
|
sym = int(sym, 16)
|
||||||
|
uni = int(uni, 16) if uni else None
|
||||||
|
if keysymdef.get(name, None):
|
||||||
|
print('KEY DUP', name)
|
||||||
|
keysymdef[name] = sym
|
||||||
|
text = Path(temp, 'XF86keysym.h').read_text()
|
||||||
|
for name, sym, uni in findall(xf86pattern, text):
|
||||||
|
sym = int(sym, 16)
|
||||||
|
uni = int(uni, 16) if uni else None
|
||||||
|
if keysymdef.get('XF86_' + name, None):
|
||||||
|
print('KEY DUP', 'XF86_' + name)
|
||||||
|
keysymdef['XF86_' + name] = sym
|
||||||
|
|
||||||
|
with open('keysymdef.py', 'w') as f:
|
||||||
|
f.write('# flake8: noqa\nkeysymdef = \\\n')
|
||||||
|
pprint(keysymdef, f)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
File diff suppressed because it is too large
Load Diff
|
@ -25,6 +25,7 @@ from logging import INFO as _INFO
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from math import sqrt as _sqrt
|
from math import sqrt as _sqrt
|
||||||
|
|
||||||
|
import keysyms.keysymdef as _keysymdef
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from gi.repository import Gdk, GLib
|
from gi.repository import Gdk, GLib
|
||||||
|
@ -44,18 +45,38 @@ del getLogger
|
||||||
#
|
#
|
||||||
# See docs/rules.md for documentation
|
# See docs/rules.md for documentation
|
||||||
#
|
#
|
||||||
|
# Several capabilities of rules depend on aspects of GDK, X11, or XKB
|
||||||
|
# As the Solaar GUI uses GTK, Glib and GDK are always available and are obtained from gi.repository
|
||||||
|
#
|
||||||
|
# Process condition depends on X11 from python-xlib, and is probably not possible at all in Wayland
|
||||||
|
# MouseProcess condition depends on X11 from python-xlib, and is probably not possible at all in Wayland
|
||||||
|
# Modifiers condition depends only on GDK
|
||||||
|
# KeyPress action currently only works in X11, and is not currently available under Wayland
|
||||||
|
# KeyPress action determines whether a keysym is a currently-down modifier using get_modifier_mapping from python-xlib;
|
||||||
|
# under Wayland no modifier keys are considered down so all modifier keys are pressed, potentially leading to problems
|
||||||
|
# KeyPress action translates key names to keysysms using the local file described for GUI keyname determination
|
||||||
|
# KeyPress action gets the current keyboard group using XkbGetState from libX11.so using ctypes definitions
|
||||||
|
# under Wayland the keyboard group is None resulting in using the first keyboard group
|
||||||
|
# KeyPress action translates keysyms to keycodes using the GDK keymap
|
||||||
|
# KeyPress action simulates keyboard input with X11 XTest from python-xlib
|
||||||
|
# MouseScroll and MouseClick actions currently only work in X11, and are not currently available under Wayland
|
||||||
|
# MouseScroll and MouseClick actions simulate mouse input with X11 XTest from python-xlib
|
||||||
|
#
|
||||||
|
# Rule GUI keyname determination uses a local file generated
|
||||||
|
# from http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
|
||||||
|
# and http://cgit.freedesktop.org/xorg/proto/x11proto/plain/XF86keysym.h
|
||||||
|
# because there does not seem to be a non-X11 file for this set of key names
|
||||||
|
|
||||||
|
XK_KEYS = _keysymdef.keysymdef
|
||||||
|
|
||||||
XK_KEYS = {}
|
|
||||||
try:
|
try:
|
||||||
import Xlib
|
import Xlib
|
||||||
from Xlib import X
|
from Xlib import X
|
||||||
from Xlib.display import Display
|
from Xlib.display import Display
|
||||||
from Xlib import XK as _XK
|
|
||||||
_XK.load_keysym_group('xf86')
|
|
||||||
XK_KEYS = vars(_XK)
|
|
||||||
xdisplay = Display()
|
xdisplay = Display()
|
||||||
modifier_keycodes = xdisplay.get_modifier_mapping() # there should be a way to do this in Gdk
|
modifier_keycodes = xdisplay.get_modifier_mapping() # there should be a way to do this in Gdk
|
||||||
x11 = True
|
x11 = True
|
||||||
|
|
||||||
NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW')
|
NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW')
|
||||||
NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID')
|
NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID')
|
||||||
WM_CLASS = xdisplay.intern_atom('WM_CLASS')
|
WM_CLASS = xdisplay.intern_atom('WM_CLASS')
|
||||||
|
@ -80,8 +101,7 @@ try:
|
||||||
display = X11Lib.XOpenDisplay(None)
|
display = X11Lib.XOpenDisplay(None)
|
||||||
except Exception:
|
except Exception:
|
||||||
_log.warn(
|
_log.warn(
|
||||||
'X11 not available - rules cannot access current process or modifier key state nor can they simulate input',
|
'X11 not available - rules cannot access current process or keyboard group and cannot simulate input. %s',
|
||||||
# 'X11 not available - rules cannot access current process nor can they simulate input',
|
|
||||||
exc_info=_sys.exc_info()
|
exc_info=_sys.exc_info()
|
||||||
)
|
)
|
||||||
modifier_keycodes = []
|
modifier_keycodes = []
|
||||||
|
@ -417,7 +437,6 @@ class Modifiers(Condition):
|
||||||
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):
|
||||||
## (_, _, _, current) = gdisplay.get_pointer() # get the current keyboard modifier
|
|
||||||
current = gkeymap.get_modifier_state() # get the current keyboard modifier
|
current = gkeymap.get_modifier_state() # get the current keyboard modifier
|
||||||
return self.desired == (current & MODIFIER_MASK)
|
return self.desired == (current & MODIFIER_MASK)
|
||||||
|
|
||||||
|
@ -573,20 +592,17 @@ class KeyPress(Action):
|
||||||
def __init__(self, keys):
|
def __init__(self, keys):
|
||||||
if isinstance(keys, str):
|
if isinstance(keys, str):
|
||||||
keys = [keys]
|
keys = [keys]
|
||||||
self.key_symbols = keys
|
self.key_names = keys
|
||||||
if x11:
|
self.key_symbols = [XK_KEYS.get(k, None) for k in keys]
|
||||||
key_from_string = lambda s: (displayt.keysym_to_keycode(Xlib.XK.string_to_keysym(s)))
|
if not all(self.key_symbols):
|
||||||
self.keys = [isinstance(k, str) and key_from_string(k) for k in keys]
|
_log.warn('rule KeyPress not sequence of key names %s', keys)
|
||||||
if not all(self.keys):
|
self.key_symbols = []
|
||||||
_log.warn('rule KeyPress argument not sequence of current key names %s', keys)
|
if not x11:
|
||||||
self.keys = []
|
_log.warn('rule KeyPress action only available in X11 %s', keys)
|
||||||
else:
|
self.key_symbols = []
|
||||||
self.keys = []
|
|
||||||
_log.warn('X11 not available - rules cannot simulate keyboard input - %s', self)
|
|
||||||
|
|
||||||
def string_to_keycode(self, s, modifiers): # should take group and shift into account
|
def keysym_to_keycode(self, keysym, modifiers): # maybe should take shift into account
|
||||||
group = kbdgroup()
|
group = kbdgroup() or 0
|
||||||
keysym = Xlib.XK.string_to_keysym(s)
|
|
||||||
keycodes = gkeymap.get_entries_for_keyval(keysym)
|
keycodes = gkeymap.get_entries_for_keyval(keysym)
|
||||||
if len(keycodes.keys) == 1:
|
if len(keycodes.keys) == 1:
|
||||||
k = keycodes.keys[0]
|
k = keycodes.keys[0]
|
||||||
|
@ -595,24 +611,24 @@ class KeyPress(Action):
|
||||||
for k in keycodes.keys:
|
for k in keycodes.keys:
|
||||||
if group == k.group:
|
if group == k.group:
|
||||||
return k.keycode
|
return k.keycode
|
||||||
_log.warn('rule KeyPress key name not currently available %s', self)
|
_log.warn('rule KeyPress key symbol not currently available %s', self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'KeyPress: ' + ' '.join(self.key_symbols)
|
return 'KeyPress: ' + ' '.join(self.key_names)
|
||||||
|
|
||||||
def needed(self, k, modifiers):
|
def needed(self, k, modifiers):
|
||||||
code = modifier_code(k)
|
code = modifier_code(k)
|
||||||
return not (code is not None and modifiers & (1 << code))
|
return not (code is not None and modifiers & (1 << code))
|
||||||
|
|
||||||
def keyDown(self, keys, modifiers):
|
def keyDown(self, keysyms, modifiers):
|
||||||
for k in keys:
|
for k in keysyms:
|
||||||
keycode = self.string_to_keycode(k, modifiers)
|
keycode = self.keysym_to_keycode(k, modifiers)
|
||||||
if self.needed(keycode, modifiers) and x11 and keycode:
|
if self.needed(keycode, modifiers) and x11 and keycode:
|
||||||
Xlib.ext.xtest.fake_input(displayt, X.KeyPress, keycode)
|
Xlib.ext.xtest.fake_input(displayt, X.KeyPress, keycode)
|
||||||
|
|
||||||
def keyUp(self, keys, modifiers):
|
def keyUp(self, keysyms, modifiers):
|
||||||
for k in keys:
|
for k in keysyms:
|
||||||
keycode = self.string_to_keycode(k, modifiers)
|
keycode = self.keysym_to_keycode(k, modifiers)
|
||||||
if self.needed(keycode, modifiers) and x11 and keycode:
|
if self.needed(keycode, modifiers) and x11 and keycode:
|
||||||
Xlib.ext.xtest.fake_input(displayt, X.KeyRelease, keycode)
|
Xlib.ext.xtest.fake_input(displayt, X.KeyRelease, keycode)
|
||||||
|
|
||||||
|
@ -627,7 +643,7 @@ class KeyPress(Action):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
return {'KeyPress': [str(k) for k in self.key_symbols]}
|
return {'KeyPress': [str(k) for k in self.key_names]}
|
||||||
|
|
||||||
|
|
||||||
# KeyDown is dangerous as the key can auto-repeat and make your system unusable
|
# KeyDown is dangerous as the key can auto-repeat and make your system unusable
|
||||||
|
|
|
@ -1521,7 +1521,7 @@ class KeyPressUI(ActionUI):
|
||||||
def _clicked_add(self, _btn):
|
def _clicked_add(self, _btn):
|
||||||
self.component.__init__(self.collect_value() + [''])
|
self.component.__init__(self.collect_value() + [''])
|
||||||
self.show(self.component)
|
self.show(self.component)
|
||||||
self.fields[len(self.component.key_symbols) - 1].grab_focus()
|
self.fields[len(self.component.key_names) - 1].grab_focus()
|
||||||
|
|
||||||
def _clicked_del(self, _btn, pos):
|
def _clicked_del(self, _btn, pos):
|
||||||
v = self.collect_value()
|
v = self.collect_value()
|
||||||
|
@ -1534,12 +1534,12 @@ class KeyPressUI(ActionUI):
|
||||||
super()._on_update(*args)
|
super()._on_update(*args)
|
||||||
for i, f in enumerate(self.fields):
|
for i, f in enumerate(self.fields):
|
||||||
if f.get_visible():
|
if f.get_visible():
|
||||||
icon = 'dialog-warning' if i < len(self.component.key_symbols
|
icon = 'dialog-warning' if i < len(self.component.key_names
|
||||||
) and self.component.key_symbols[i] not in self.KEY_NAMES else ''
|
) and self.component.key_names[i] not in self.KEY_NAMES else ''
|
||||||
f.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
f.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
||||||
|
|
||||||
def show(self, component, editable=True):
|
def show(self, component, editable=True):
|
||||||
n = len(component.key_symbols)
|
n = len(component.key_names)
|
||||||
while len(self.fields) < n:
|
while len(self.fields) < n:
|
||||||
self._create_field()
|
self._create_field()
|
||||||
self._create_del_btn()
|
self._create_del_btn()
|
||||||
|
@ -1548,7 +1548,7 @@ class KeyPressUI(ActionUI):
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
field = self.fields[i]
|
field = self.fields[i]
|
||||||
with self.ignore_changes():
|
with self.ignore_changes():
|
||||||
field.set_text(component.key_symbols[i])
|
field.set_text(component.key_names[i])
|
||||||
field.set_size_request(int(0.3 * self.panel.get_toplevel().get_size()[0]), 0)
|
field.set_size_request(int(0.3 * self.panel.get_toplevel().get_size()[0]), 0)
|
||||||
field.show_all()
|
field.show_all()
|
||||||
self.del_btns[i].show()
|
self.del_btns[i].show()
|
||||||
|
@ -1566,7 +1566,7 @@ class KeyPressUI(ActionUI):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def right_label(cls, component):
|
def right_label(cls, component):
|
||||||
return ' + '.join(component.key_symbols)
|
return ' + '.join(component.key_names)
|
||||||
|
|
||||||
|
|
||||||
class MouseScrollUI(ActionUI):
|
class MouseScrollUI(ActionUI):
|
||||||
|
|
Loading…
Reference in New Issue