-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathvenv\Lib\site-packages\keyboard\_nixkeyboard.py
More file actions
183 lines (149 loc) · 5.74 KB
/
venv\Lib\site-packages\keyboard\_nixkeyboard.py
File metadata and controls
183 lines (149 loc) · 5.74 KB
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# -*- coding: utf-8 -*-
import struct
import traceback
from time import time as now
from collections import namedtuple
from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP
from ._canonical_names import all_modifiers, normalize_name
from ._nixcommon import EV_KEY, aggregate_devices, ensure_root
# TODO: start by reading current keyboard state, as to not missing any already pressed keys.
# See: http://stackoverflow.com/questions/3649874/how-to-get-keyboard-state-in-linux
def cleanup_key(name):
""" Formats a dumpkeys format to our standard. """
name = name.lstrip('+')
is_keypad = name.startswith('KP_')
for mod in ('Meta_', 'Control_', 'dead_', 'KP_'):
if name.startswith(mod):
name = name[len(mod):]
# Dumpkeys is weird like that.
if name == 'Remove':
name = 'Delete'
elif name == 'Delete':
name = 'Backspace'
if name.endswith('_r'):
name = 'right ' + name[:-2]
if name.endswith('_l'):
name = 'left ' + name[:-2]
return normalize_name(name), is_keypad
def cleanup_modifier(modifier):
modifier = normalize_name(modifier)
if modifier in all_modifiers:
return modifier
if modifier[:-1] in all_modifiers:
return modifier[:-1]
raise ValueError('Unknown modifier {}'.format(modifier))
"""
Use `dumpkeys --keys-only` to list all scan codes and their names. We
then parse the output and built a table. For each scan code and modifiers we
have a list of names and vice-versa.
"""
from subprocess import check_output
from collections import defaultdict
import re
to_name = defaultdict(list)
from_name = defaultdict(list)
keypad_scan_codes = set()
def register_key(key_and_modifiers, name):
if name not in to_name[key_and_modifiers]:
to_name[key_and_modifiers].append(name)
if key_and_modifiers not in from_name[name]:
from_name[name].append(key_and_modifiers)
def build_tables():
if to_name and from_name: return
ensure_root()
modifiers_bits = {
'shift': 1,
'alt gr': 2,
'ctrl': 4,
'alt': 8,
}
keycode_template = r'^keycode\s+(\d+)\s+=(.*?)$'
dump = check_output(['dumpkeys', '--keys-only'], universal_newlines=True)
for str_scan_code, str_names in re.findall(keycode_template, dump, re.MULTILINE):
scan_code = int(str_scan_code)
for i, str_name in enumerate(str_names.strip().split()):
modifiers = tuple(sorted(modifier for modifier, bit in modifiers_bits.items() if i & bit))
name, is_keypad = cleanup_key(str_name)
register_key((scan_code, modifiers), name)
if is_keypad:
keypad_scan_codes.add(scan_code)
register_key((scan_code, modifiers), 'keypad ' + name)
# dumpkeys consistently misreports the Windows key, sometimes
# skipping it completely or reporting as 'alt. 125 = left win,
# 126 = right win.
if (125, ()) not in to_name or to_name[(125, ())] == 'alt':
register_key((125, ()), 'windows')
if (126, ()) not in to_name or to_name[(126, ())] == 'alt':
register_key((126, ()), 'windows')
# The menu key is usually skipped altogether, so we also add it manually.
if (127, ()) not in to_name:
register_key((127, ()), 'menu')
synonyms_template = r'^(\S+)\s+for (.+)$'
dump = check_output(['dumpkeys', '--long-info'], universal_newlines=True)
for synonym_str, original_str in re.findall(synonyms_template, dump, re.MULTILINE):
synonym, _ = cleanup_key(synonym_str)
original, _ = cleanup_key(original_str)
if synonym != original:
from_name[original].extend(from_name[synonym])
from_name[synonym].extend(from_name[original])
device = None
def build_device():
global device
if device: return
ensure_root()
device = aggregate_devices('kbd')
def init():
build_device()
build_tables()
pressed_modifiers = set()
def listen(callback):
build_device()
build_tables()
while True:
time, type, code, value, device_id = device.read_event()
if type != EV_KEY:
continue
scan_code = code
event_type = KEY_DOWN if value else KEY_UP # 0 = UP, 1 = DOWN, 2 = HOLD
pressed_modifiers_tuple = tuple(sorted(pressed_modifiers))
names = to_name[(scan_code, pressed_modifiers_tuple)] or to_name[(scan_code, ())] or ['unknown']
name = names[0]
if name in all_modifiers:
if event_type == KEY_DOWN:
pressed_modifiers.add(name)
else:
pressed_modifiers.discard(name)
is_keypad = scan_code in keypad_scan_codes
callback(KeyboardEvent(event_type=event_type, scan_code=scan_code, name=name, time=time, device=device_id, is_keypad=is_keypad, modifiers=pressed_modifiers_tuple))
def write_event(scan_code, is_down):
build_device()
device.write_event(EV_KEY, scan_code, int(is_down))
def map_name(name):
build_tables()
for entry in from_name[name]:
yield entry
parts = name.split(' ', 1)
if len(parts) > 1 and parts[0] in ('left', 'right'):
for entry in from_name[parts[1]]:
yield entry
def press(scan_code):
write_event(scan_code, True)
def release(scan_code):
write_event(scan_code, False)
def type_unicode(character):
codepoint = ord(character)
hexadecimal = hex(codepoint)[len('0x'):]
for key in ['ctrl', 'shift', 'u']:
scan_code, _ = next(map_name(key))
press(scan_code)
for key in hexadecimal:
scan_code, _ = next(map_name(key))
press(scan_code)
release(scan_code)
for key in ['ctrl', 'shift', 'u']:
scan_code, _ = next(map_name(key))
release(scan_code)
if __name__ == '__main__':
def p(e):
print(e)
listen(p)