aboutsummaryrefslogtreecommitdiff
path: root/Python/venv1/Lib/site-packages/keyboard/_xlibkeyboard.py
blob: 3db9187837184e06347d8b8bf78aec1f9da78150 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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, ()