You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
3.6 KiB
120 lines
3.6 KiB
from collections import defaultdict
|
|
|
|
from Xlib import X, XK, display
|
|
from Xlib.ext import record
|
|
from Xlib.protocol import rq
|
|
|
|
from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP, normalize_name
|
|
|
|
|
|
# from ._nixkeyboard import init
|
|
|
|
def cleanup_key(name):
|
|
if name.startswith('XK_'):
|
|
name = name[3:]
|
|
|
|
if name.startswith('KP_'):
|
|
is_keypad = True
|
|
name = name[3:]
|
|
else:
|
|
is_keypad = False
|
|
|
|
if name.endswith('_R'):
|
|
name = 'right ' + name[:-2]
|
|
if name.endswith('_L'):
|
|
name = 'left ' + name[:-2]
|
|
|
|
return normalize_name(name), is_keypad
|
|
|
|
|
|
keysym_to_keys = defaultdict(list)
|
|
name_to_keysyms = defaultdict(list)
|
|
for raw_name in dir(XK):
|
|
if not raw_name.startswith('XK_'): continue
|
|
keysym = getattr(XK, raw_name)
|
|
name, is_keypad = cleanup_key(raw_name)
|
|
keysym_to_keys[keysym].append((name, is_keypad))
|
|
name_to_keysyms[name].append(keysym)
|
|
|
|
local_dpy = None
|
|
record_dpy = None
|
|
ctx = None
|
|
|
|
|
|
def init():
|
|
# Adapted from https://github.com/python-xlib/python-xlib/blob/master/examples/record_demo.py
|
|
global local_dpy
|
|
global record_dpy
|
|
local_dpy = display.Display()
|
|
record_dpy = display.Display()
|
|
|
|
if not record_dpy.has_extension("RECORD"):
|
|
raise ImportError("RECORD extension not found")
|
|
|
|
r = record_dpy.record_get_version(0, 0)
|
|
# print("RECORD extension version %d.%d" % (r.major_version, r.minor_version))
|
|
|
|
# Create a recording context; we only want key and mouse events
|
|
global ctx
|
|
ctx = record_dpy.record_create_context(
|
|
0,
|
|
[record.AllClients],
|
|
[
|
|
{
|
|
'core_requests': (0, 0),
|
|
'core_replies': (0, 0),
|
|
'ext_requests': (0, 0, 0, 0),
|
|
'ext_replies': (0, 0, 0, 0),
|
|
'delivered_events': (0, 0),
|
|
'device_events': (X.KeyPress, X.KeyPress),
|
|
'errors': (0, 0),
|
|
'client_started': False,
|
|
'client_died': False,
|
|
}
|
|
]
|
|
)
|
|
|
|
|
|
def listen(callback):
|
|
def handler(reply):
|
|
if reply.category != record.FromServer:
|
|
return
|
|
if reply.client_swapped:
|
|
print("* received swapped protocol data, cowardly ignored")
|
|
return
|
|
if not len(reply.data) or reply.data[0] < 2:
|
|
# not an event
|
|
return
|
|
|
|
data = reply.data
|
|
while len(data):
|
|
raw_event, data = rq.EventField(None).parse_binary_value(data, record_dpy.display, None, None)
|
|
|
|
event_type = {X.KeyPress: KEY_DOWN, X.KeyRelease: KEY_UP}.get(raw_event.type)
|
|
if event_type:
|
|
keysym = local_dpy.keycode_to_keysym(raw_event.detail, 0)
|
|
# TODO: scan code is not correct.
|
|
if not keysym:
|
|
event = KeyboardEvent(event_type=event_type, scan_code=raw_event.detail)
|
|
else:
|
|
try:
|
|
name, is_keypad = keysym_to_keys[keysym][0]
|
|
except IndexError:
|
|
name, is_keypad = None, None
|
|
event = KeyboardEvent(event_type=event_type, scan_code=keysym, name=name, is_keypad=is_keypad)
|
|
|
|
callback(event)
|
|
|
|
try:
|
|
# Enable the context; this only returns after a call to record_disable_context,
|
|
# while calling the callback function in the meantime
|
|
record_dpy.record_enable_context(ctx, handler)
|
|
finally:
|
|
# Finally free the context
|
|
record_dpy.record_free_context(ctx)
|
|
|
|
|
|
def map_name(name):
|
|
for keysym in name_to_keysyms[name]:
|
|
yield keysym, ()
|