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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
|
# -*- coding: utf-8 -*-
"""
This is the Windows backend for keyboard events, and is implemented by
invoking the Win32 API through the ctypes module. This is error prone
and can introduce very unpythonic failure modes, such as segfaults and
low level memory leaks. But it is also dependency-free, very performant
well documented on Microsoft's webstie and scattered examples.
# TODO:
- Keypad numbers still print as numbers even when numlock is off.
- No way to specify if user wants a keypad key or not in `map_char`.
"""
from __future__ import unicode_literals
import re
import atexit
import traceback
from threading import Lock
from collections import defaultdict
from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP
from ._canonical_names import normalize_name
try:
# Force Python2 to convert to unicode and not to str.
chr = unichr
except NameError:
pass
# This part is just declaring Win32 API structures using ctypes. In C
# this would be simply #include "windows.h".
import ctypes
from ctypes import c_short, c_char, c_uint8, c_int32, c_int, c_uint, c_uint32, c_long, Structure, CFUNCTYPE, POINTER
from ctypes.wintypes import WORD, DWORD, BOOL, HHOOK, MSG, LPWSTR, WCHAR, WPARAM, LPARAM, LONG, HMODULE, LPCWSTR, HINSTANCE, HWND
LPMSG = POINTER(MSG)
ULONG_PTR = POINTER(DWORD)
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
GetModuleHandleW = kernel32.GetModuleHandleW
GetModuleHandleW.restype = HMODULE
GetModuleHandleW.argtypes = [LPCWSTR]
#https://github.com/boppreh/mouse/issues/1
#user32 = ctypes.windll.user32
user32 = ctypes.WinDLL('user32', use_last_error = True)
VK_PACKET = 0xE7
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2
KEYEVENTF_KEYUP = 0x02
KEYEVENTF_UNICODE = 0x04
class KBDLLHOOKSTRUCT(Structure):
_fields_ = [("vk_code", DWORD),
("scan_code", DWORD),
("flags", DWORD),
("time", c_int),
("dwExtraInfo", ULONG_PTR)]
# Included for completeness.
class MOUSEINPUT(ctypes.Structure):
_fields_ = (('dx', LONG),
('dy', LONG),
('mouseData', DWORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR))
class KEYBDINPUT(ctypes.Structure):
_fields_ = (('wVk', WORD),
('wScan', WORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR))
class HARDWAREINPUT(ctypes.Structure):
_fields_ = (('uMsg', DWORD),
('wParamL', WORD),
('wParamH', WORD))
class _INPUTunion(ctypes.Union):
_fields_ = (('mi', MOUSEINPUT),
('ki', KEYBDINPUT),
('hi', HARDWAREINPUT))
class INPUT(ctypes.Structure):
_fields_ = (('type', DWORD),
('union', _INPUTunion))
LowLevelKeyboardProc = CFUNCTYPE(c_int, WPARAM, LPARAM, POINTER(KBDLLHOOKSTRUCT))
SetWindowsHookEx = user32.SetWindowsHookExW
SetWindowsHookEx.argtypes = [c_int, LowLevelKeyboardProc, HINSTANCE , DWORD]
SetWindowsHookEx.restype = HHOOK
CallNextHookEx = user32.CallNextHookEx
#CallNextHookEx.argtypes = [c_int , c_int, c_int, POINTER(KBDLLHOOKSTRUCT)]
CallNextHookEx.restype = c_int
UnhookWindowsHookEx = user32.UnhookWindowsHookEx
UnhookWindowsHookEx.argtypes = [HHOOK]
UnhookWindowsHookEx.restype = BOOL
GetMessage = user32.GetMessageW
GetMessage.argtypes = [LPMSG, HWND, c_uint, c_uint]
GetMessage.restype = BOOL
TranslateMessage = user32.TranslateMessage
TranslateMessage.argtypes = [LPMSG]
TranslateMessage.restype = BOOL
DispatchMessage = user32.DispatchMessageA
DispatchMessage.argtypes = [LPMSG]
keyboard_state_type = c_uint8 * 256
GetKeyboardState = user32.GetKeyboardState
GetKeyboardState.argtypes = [keyboard_state_type]
GetKeyboardState.restype = BOOL
GetKeyNameText = user32.GetKeyNameTextW
GetKeyNameText.argtypes = [c_long, LPWSTR, c_int]
GetKeyNameText.restype = c_int
MapVirtualKey = user32.MapVirtualKeyW
MapVirtualKey.argtypes = [c_uint, c_uint]
MapVirtualKey.restype = c_uint
ToUnicode = user32.ToUnicode
ToUnicode.argtypes = [c_uint, c_uint, keyboard_state_type, LPWSTR, c_int, c_uint]
ToUnicode.restype = c_int
SendInput = user32.SendInput
SendInput.argtypes = [c_uint, POINTER(INPUT), c_int]
SendInput.restype = c_uint
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646307(v=vs.85).aspx
MAPVK_VK_TO_CHAR = 2
MAPVK_VK_TO_VSC = 0
MAPVK_VSC_TO_VK = 1
MAPVK_VK_TO_VSC_EX = 4
MAPVK_VSC_TO_VK_EX = 3
VkKeyScan = user32.VkKeyScanW
VkKeyScan.argtypes = [WCHAR]
VkKeyScan.restype = c_short
LLKHF_INJECTED = 0x00000010
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_SYSKEYDOWN = 0x104 # Used for ALT key
WM_SYSKEYUP = 0x105
# This marks the end of Win32 API declarations. The rest is ours.
keyboard_event_types = {
WM_KEYDOWN: KEY_DOWN,
WM_KEYUP: KEY_UP,
WM_SYSKEYDOWN: KEY_DOWN,
WM_SYSKEYUP: KEY_UP,
}
# List taken from the official documentation, but stripped of the OEM-specific keys.
# Keys are virtual key codes, values are pairs (name, is_keypad).
official_virtual_keys = {
0x03: ('control-break processing', False),
0x08: ('backspace', False),
0x09: ('tab', False),
0x0c: ('clear', False),
0x0d: ('enter', False),
0x10: ('shift', False),
0x11: ('ctrl', False),
0x12: ('alt', False),
0x13: ('pause', False),
0x14: ('caps lock', False),
0x15: ('ime kana mode', False),
0x15: ('ime hanguel mode', False),
0x15: ('ime hangul mode', False),
0x17: ('ime junja mode', False),
0x18: ('ime final mode', False),
0x19: ('ime hanja mode', False),
0x19: ('ime kanji mode', False),
0x1b: ('esc', False),
0x1c: ('ime convert', False),
0x1d: ('ime nonconvert', False),
0x1e: ('ime accept', False),
0x1f: ('ime mode change request', False),
0x20: ('spacebar', False),
0x21: ('page up', False),
0x22: ('page down', False),
0x23: ('end', False),
0x24: ('home', False),
0x25: ('left', False),
0x26: ('up', False),
0x27: ('right', False),
0x28: ('down', False),
0x29: ('select', False),
0x2a: ('print', False),
0x2b: ('execute', False),
0x2c: ('print screen', False),
0x2d: ('insert', False),
0x2e: ('delete', False),
0x2f: ('help', False),
0x30: ('0', False),
0x31: ('1', False),
0x32: ('2', False),
0x33: ('3', False),
0x34: ('4', False),
0x35: ('5', False),
0x36: ('6', False),
0x37: ('7', False),
0x38: ('8', False),
0x39: ('9', False),
0x41: ('a', False),
0x42: ('b', False),
0x43: ('c', False),
0x44: ('d', False),
0x45: ('e', False),
0x46: ('f', False),
0x47: ('g', False),
0x48: ('h', False),
0x49: ('i', False),
0x4a: ('j', False),
0x4b: ('k', False),
0x4c: ('l', False),
0x4d: ('m', False),
0x4e: ('n', False),
0x4f: ('o', False),
0x50: ('p', False),
0x51: ('q', False),
0x52: ('r', False),
0x53: ('s', False),
0x54: ('t', False),
0x55: ('u', False),
0x56: ('v', False),
0x57: ('w', False),
0x58: ('x', False),
0x59: ('y', False),
0x5a: ('z', False),
0x5b: ('left windows', False),
0x5c: ('right windows', False),
0x5d: ('applications', False),
0x5f: ('sleep', False),
0x60: ('0', True),
0x61: ('1', True),
0x62: ('2', True),
0x63: ('3', True),
0x64: ('4', True),
0x65: ('5', True),
0x66: ('6', True),
0x67: ('7', True),
0x68: ('8', True),
0x69: ('9', True),
0x6a: ('*', True),
0x6b: ('+', True),
0x6c: ('separator', True),
0x6d: ('-', True),
0x6e: ('decimal', True),
0x6f: ('/', True),
0x70: ('f1', False),
0x71: ('f2', False),
0x72: ('f3', False),
0x73: ('f4', False),
0x74: ('f5', False),
0x75: ('f6', False),
0x76: ('f7', False),
0x77: ('f8', False),
0x78: ('f9', False),
0x79: ('f10', False),
0x7a: ('f11', False),
0x7b: ('f12', False),
0x7c: ('f13', False),
0x7d: ('f14', False),
0x7e: ('f15', False),
0x7f: ('f16', False),
0x80: ('f17', False),
0x81: ('f18', False),
0x82: ('f19', False),
0x83: ('f20', False),
0x84: ('f21', False),
0x85: ('f22', False),
0x86: ('f23', False),
0x87: ('f24', False),
0x90: ('num lock', False),
0x91: ('scroll lock', False),
0xa0: ('left shift', False),
0xa1: ('right shift', False),
0xa2: ('left ctrl', False),
0xa3: ('right ctrl', False),
0xa4: ('left menu', False),
0xa5: ('right menu', False),
0xa6: ('browser back', False),
0xa7: ('browser forward', False),
0xa8: ('browser refresh', False),
0xa9: ('browser stop', False),
0xaa: ('browser search key', False),
0xab: ('browser favorites', False),
0xac: ('browser start and home', False),
0xad: ('volume mute', False),
0xae: ('volume down', False),
0xaf: ('volume up', False),
0xb0: ('next track', False),
0xb1: ('previous track', False),
0xb2: ('stop media', False),
0xb3: ('play/pause media', False),
0xb4: ('start mail', False),
0xb5: ('select media', False),
0xb6: ('start application 1', False),
0xb7: ('start application 2', False),
0xbb: ('+', False),
0xbc: (',', False),
0xbd: ('-', False),
0xbe: ('.', False),
#0xbe:('/', False), # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?.
0xe5: ('ime process', False),
0xf6: ('attn', False),
0xf7: ('crsel', False),
0xf8: ('exsel', False),
0xf9: ('erase eof', False),
0xfa: ('play', False),
0xfb: ('zoom', False),
0xfc: ('reserved ', False),
0xfd: ('pa1', False),
0xfe: ('clear', False),
}
tables_lock = Lock()
to_name = defaultdict(list)
from_name = defaultdict(list)
scan_code_to_vk = {}
distinct_modifiers = [
(),
('shift',),
('alt gr',),
('num lock',),
('shift', 'num lock'),
('caps lock',),
('shift', 'caps lock'),
('alt gr', 'num lock'),
]
name_buffer = ctypes.create_unicode_buffer(32)
unicode_buffer = ctypes.create_unicode_buffer(32)
keyboard_state = keyboard_state_type()
def get_event_names(scan_code, vk, is_extended, modifiers):
is_keypad = (scan_code, vk, is_extended) in keypad_keys
is_official = vk in official_virtual_keys
if is_keypad and is_official:
yield official_virtual_keys[vk][0]
keyboard_state[0x10] = 0x80 * ('shift' in modifiers)
keyboard_state[0x11] = 0x80 * ('alt gr' in modifiers)
keyboard_state[0x12] = 0x80 * ('alt gr' in modifiers)
keyboard_state[0x14] = 0x01 * ('caps lock' in modifiers)
keyboard_state[0x90] = 0x01 * ('num lock' in modifiers)
keyboard_state[0x91] = 0x01 * ('scroll lock' in modifiers)
unicode_ret = ToUnicode(vk, scan_code, keyboard_state, unicode_buffer, len(unicode_buffer), 0)
if unicode_ret and unicode_buffer.value:
yield unicode_buffer.value
# unicode_ret == -1 -> is dead key
# ToUnicode has the side effect of setting global flags for dead keys.
# Therefore we need to call it twice to clear those flags.
# If your 6 and 7 keys are named "^6" and "^7", this is the reason.
ToUnicode(vk, scan_code, keyboard_state, unicode_buffer, len(unicode_buffer), 0)
name_ret = GetKeyNameText(scan_code << 16 | is_extended << 24, name_buffer, 1024)
if name_ret and name_buffer.value:
yield name_buffer.value
char = user32.MapVirtualKeyW(vk, MAPVK_VK_TO_CHAR) & 0xFF
if char != 0:
yield chr(char)
if not is_keypad and is_official:
yield official_virtual_keys[vk][0]
def _setup_name_tables():
"""
Ensures the scan code/virtual key code/name translation tables are
filled.
"""
with tables_lock:
if to_name: return
# Go through every possible scan code, and map them to virtual key codes.
# Then vice-versa.
all_scan_codes = [(sc, user32.MapVirtualKeyExW(sc, MAPVK_VSC_TO_VK_EX, 0)) for sc in range(0x100)]
all_vks = [(user32.MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, 0), vk) for vk in range(0x100)]
for scan_code, vk in all_scan_codes + all_vks:
# `to_name` and `from_name` entries will be a tuple (scan_code, vk, extended, shift_state).
if (scan_code, vk, 0, 0, 0) in to_name:
continue
if scan_code not in scan_code_to_vk:
scan_code_to_vk[scan_code] = vk
# Brute force all combinations to find all possible names.
for extended in [0, 1]:
for modifiers in distinct_modifiers:
entry = (scan_code, vk, extended, modifiers)
# Get key names from ToUnicode, GetKeyNameText, MapVirtualKeyW and official virtual keys.
names = list(get_event_names(*entry))
if names:
# Also map lowercased key names, but only after the properly cased ones.
lowercase_names = [name.lower() for name in names]
to_name[entry] = names + lowercase_names
# Remember the "id" of the name, as the first techniques
# have better results and therefore priority.
for i, name in enumerate(map(normalize_name, names + lowercase_names)):
from_name[name].append((i, entry))
# TODO: single quotes on US INTL is returning the dead key (?), and therefore
# not typing properly.
# Alt gr is way outside the usual range of keys (0..127) and on my
# computer is named as 'ctrl'. Therefore we add it manually and hope
# Windows is consistent in its inconsistency.
for extended in [0, 1]:
for modifiers in distinct_modifiers:
to_name[(541, 162, extended, modifiers)] = ['alt gr']
from_name['alt gr'].append((1, (541, 162, extended, modifiers)))
modifiers_preference = defaultdict(lambda: 10)
modifiers_preference.update({(): 0, ('shift',): 1, ('alt gr',): 2, ('ctrl',): 3, ('alt',): 4})
def order_key(line):
i, entry = line
scan_code, vk, extended, modifiers = entry
return modifiers_preference[modifiers], i, extended, vk, scan_code
for name, entries in list(from_name.items()):
from_name[name] = sorted(set(entries), key=order_key)
# Called by keyboard/__init__.py
init = _setup_name_tables
# List created manually.
keypad_keys = [
# (scan_code, virtual_key_code, is_extended)
(126, 194, 0),
(126, 194, 0),
(28, 13, 1),
(28, 13, 1),
(53, 111, 1),
(53, 111, 1),
(55, 106, 0),
(55, 106, 0),
(69, 144, 1),
(69, 144, 1),
(71, 103, 0),
(71, 36, 0),
(72, 104, 0),
(72, 38, 0),
(73, 105, 0),
(73, 33, 0),
(74, 109, 0),
(74, 109, 0),
(75, 100, 0),
(75, 37, 0),
(76, 101, 0),
(76, 12, 0),
(77, 102, 0),
(77, 39, 0),
(78, 107, 0),
(78, 107, 0),
(79, 35, 0),
(79, 97, 0),
(80, 40, 0),
(80, 98, 0),
(81, 34, 0),
(81, 99, 0),
(82, 45, 0),
(82, 96, 0),
(83, 110, 0),
(83, 46, 0),
]
shift_is_pressed = False
altgr_is_pressed = False
ignore_next_right_alt = False
shift_vks = set([0x10, 0xa0, 0xa1])
def prepare_intercept(callback):
"""
Registers a Windows low level keyboard hook. The provided callback will
be invoked for each high-level keyboard event, and is expected to return
True if the key event should be passed to the next program, or False if
the event is to be blocked.
No event is processed until the Windows messages are pumped (see
start_intercept).
"""
_setup_name_tables()
def process_key(event_type, vk, scan_code, is_extended):
global shift_is_pressed, altgr_is_pressed, ignore_next_right_alt
#print(event_type, vk, scan_code, is_extended)
# Pressing alt-gr also generates an extra "right alt" event
if vk == 0xA5 and ignore_next_right_alt:
ignore_next_right_alt = False
return True
modifiers = (
('shift',) * shift_is_pressed +
('alt gr',) * altgr_is_pressed +
('num lock',) * (user32.GetKeyState(0x90) & 1) +
('caps lock',) * (user32.GetKeyState(0x14) & 1) +
('scroll lock',) * (user32.GetKeyState(0x91) & 1)
)
entry = (scan_code, vk, is_extended, modifiers)
if entry not in to_name:
to_name[entry] = list(get_event_names(*entry))
names = to_name[entry]
name = names[0] if names else None
# TODO: inaccurate when holding multiple different shifts.
if vk in shift_vks:
shift_is_pressed = event_type == KEY_DOWN
if scan_code == 541 and vk == 162:
ignore_next_right_alt = True
altgr_is_pressed = event_type == KEY_DOWN
is_keypad = (scan_code, vk, is_extended) in keypad_keys
return callback(KeyboardEvent(event_type=event_type, scan_code=scan_code or -vk, name=name, is_keypad=is_keypad))
def low_level_keyboard_handler(nCode, wParam, lParam):
try:
vk = lParam.contents.vk_code
# Ignore the second `alt` DOWN observed in some cases.
fake_alt = (LLKHF_INJECTED | 0x20)
# Ignore events generated by SendInput with Unicode.
if vk != VK_PACKET and lParam.contents.flags & fake_alt != fake_alt:
event_type = keyboard_event_types[wParam]
is_extended = lParam.contents.flags & 1
scan_code = lParam.contents.scan_code
should_continue = process_key(event_type, vk, scan_code, is_extended)
if not should_continue:
return -1
except Exception as e:
print('Error in keyboard hook:')
traceback.print_exc()
return CallNextHookEx(None, nCode, wParam, lParam)
WH_KEYBOARD_LL = c_int(13)
keyboard_callback = LowLevelKeyboardProc(low_level_keyboard_handler)
handle = GetModuleHandleW(None)
thread_id = DWORD(0)
keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_callback, handle, thread_id)
# Register to remove the hook when the interpreter exits. Unfortunately a
# try/finally block doesn't seem to work here.
atexit.register(UnhookWindowsHookEx, keyboard_callback)
def listen(callback):
prepare_intercept(callback)
msg = LPMSG()
while not GetMessage(msg, 0, 0, 0):
TranslateMessage(msg)
DispatchMessage(msg)
def map_name(name):
_setup_name_tables()
entries = from_name.get(name)
if not entries:
raise ValueError('Key name {} is not mapped to any known key.'.format(repr(name)))
for i, entry in entries:
scan_code, vk, is_extended, modifiers = entry
yield scan_code or -vk, modifiers
def _send_event(code, event_type):
if code == 541:
# Alt-gr is made of ctrl+alt. Just sending even 541 doesn't do anything.
user32.keybd_event(0x11, code, event_type, 0)
user32.keybd_event(0x12, code, event_type, 0)
elif code > 0:
vk = scan_code_to_vk.get(code, 0)
user32.keybd_event(vk, code, event_type, 0)
else:
# Negative scan code is a way to indicate we don't have a scan code,
# and the value actually contains the Virtual key code.
user32.keybd_event(-code, 0, event_type, 0)
def press(code):
_send_event(code, 0)
def release(code):
_send_event(code, 2)
def type_unicode(character):
# This code and related structures are based on
# http://stackoverflow.com/a/11910555/252218
surrogates = bytearray(character.encode('utf-16le'))
presses = []
releases = []
for i in range(0, len(surrogates), 2):
higher, lower = surrogates[i:i+2]
structure = KEYBDINPUT(0, (lower << 8) + higher, KEYEVENTF_UNICODE, 0, None)
presses.append(INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure)))
structure = KEYBDINPUT(0, (lower << 8) + higher, KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, 0, None)
releases.append(INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure)))
inputs = presses + releases
nInputs = len(inputs)
LPINPUT = INPUT * nInputs
pInputs = LPINPUT(*inputs)
cbSize = c_int(ctypes.sizeof(INPUT))
SendInput(nInputs, pInputs, cbSize)
if __name__ == '__main__':
_setup_name_tables()
import pprint
pprint.pprint(to_name)
pprint.pprint(from_name)
#listen(lambda e: print(e.to_json()) or True)
|