_winkeyboard.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. # -*- coding: utf-8 -*-
  2. """
  3. This is the Windows backend for keyboard events, and is implemented by
  4. invoking the Win32 API through the ctypes module. This is error prone
  5. and can introduce very unpythonic failure modes, such as segfaults and
  6. low level memory leaks. But it is also dependency-free, very performant
  7. well documented on Microsoft's webstie and scattered examples.
  8. """
  9. import atexit
  10. from threading import Lock
  11. import re
  12. from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP, normalize_name
  13. from ._suppress import KeyTable
  14. # This part is just declaring Win32 API structures using ctypes. In C
  15. # this would be simply #include "windows.h".
  16. import ctypes
  17. from ctypes import c_short, c_char, c_uint8, c_int32, c_int, c_uint, c_uint32, c_long, Structure, CFUNCTYPE, POINTER
  18. from ctypes.wintypes import WORD, DWORD, BOOL, HHOOK, MSG, LPWSTR, WCHAR, WPARAM, LPARAM, LONG
  19. LPMSG = POINTER(MSG)
  20. ULONG_PTR = POINTER(DWORD)
  21. #https://github.com/boppreh/mouse/issues/1
  22. #user32 = ctypes.windll.user32
  23. user32 = ctypes.WinDLL('user32', use_last_error = True)
  24. VK_PACKET = 0xE7
  25. INPUT_MOUSE = 0
  26. INPUT_KEYBOARD = 1
  27. INPUT_HARDWARE = 2
  28. KEYEVENTF_KEYUP = 0x02
  29. KEYEVENTF_UNICODE = 0x04
  30. class KBDLLHOOKSTRUCT(Structure):
  31. _fields_ = [("vk_code", DWORD),
  32. ("scan_code", DWORD),
  33. ("flags", DWORD),
  34. ("time", c_int),
  35. ("dwExtraInfo", ULONG_PTR)]
  36. # Included for completeness.
  37. class MOUSEINPUT(ctypes.Structure):
  38. _fields_ = (('dx', LONG),
  39. ('dy', LONG),
  40. ('mouseData', DWORD),
  41. ('dwFlags', DWORD),
  42. ('time', DWORD),
  43. ('dwExtraInfo', ULONG_PTR))
  44. class KEYBDINPUT(ctypes.Structure):
  45. _fields_ = (('wVk', WORD),
  46. ('wScan', WORD),
  47. ('dwFlags', DWORD),
  48. ('time', DWORD),
  49. ('dwExtraInfo', ULONG_PTR))
  50. class HARDWAREINPUT(ctypes.Structure):
  51. _fields_ = (('uMsg', DWORD),
  52. ('wParamL', WORD),
  53. ('wParamH', WORD))
  54. class _INPUTunion(ctypes.Union):
  55. _fields_ = (('mi', MOUSEINPUT),
  56. ('ki', KEYBDINPUT),
  57. ('hi', HARDWAREINPUT))
  58. class INPUT(ctypes.Structure):
  59. _fields_ = (('type', DWORD),
  60. ('union', _INPUTunion))
  61. LowLevelKeyboardProc = CFUNCTYPE(c_int, WPARAM, LPARAM, POINTER(KBDLLHOOKSTRUCT))
  62. SetWindowsHookEx = user32.SetWindowsHookExA
  63. #SetWindowsHookEx.argtypes = [c_int, LowLevelKeyboardProc, c_int, c_int]
  64. SetWindowsHookEx.restype = HHOOK
  65. CallNextHookEx = user32.CallNextHookEx
  66. #CallNextHookEx.argtypes = [c_int , c_int, c_int, POINTER(KBDLLHOOKSTRUCT)]
  67. CallNextHookEx.restype = c_int
  68. UnhookWindowsHookEx = user32.UnhookWindowsHookEx
  69. UnhookWindowsHookEx.argtypes = [HHOOK]
  70. UnhookWindowsHookEx.restype = BOOL
  71. GetMessage = user32.GetMessageW
  72. GetMessage.argtypes = [LPMSG, c_int, c_int, c_int]
  73. GetMessage.restype = BOOL
  74. TranslateMessage = user32.TranslateMessage
  75. TranslateMessage.argtypes = [LPMSG]
  76. TranslateMessage.restype = BOOL
  77. DispatchMessage = user32.DispatchMessageA
  78. DispatchMessage.argtypes = [LPMSG]
  79. keyboard_state_type = c_uint8 * 256
  80. GetKeyboardState = user32.GetKeyboardState
  81. GetKeyboardState.argtypes = [keyboard_state_type]
  82. GetKeyboardState.restype = BOOL
  83. GetKeyNameText = user32.GetKeyNameTextW
  84. GetKeyNameText.argtypes = [c_long, LPWSTR, c_int]
  85. GetKeyNameText.restype = c_int
  86. MapVirtualKey = user32.MapVirtualKeyW
  87. MapVirtualKey.argtypes = [c_uint, c_uint]
  88. MapVirtualKey.restype = c_uint
  89. ToUnicode = user32.ToUnicode
  90. ToUnicode.argtypes = [c_uint, c_uint, keyboard_state_type, LPWSTR, c_int, c_uint]
  91. ToUnicode.restype = c_int
  92. SendInput = user32.SendInput
  93. SendInput.argtypes = [c_uint, POINTER(INPUT), c_int]
  94. SendInput.restype = c_uint
  95. MAPVK_VK_TO_VSC = 0
  96. MAPVK_VSC_TO_VK = 1
  97. VkKeyScan = user32.VkKeyScanW
  98. VkKeyScan.argtypes = [WCHAR]
  99. VkKeyScan.restype = c_short
  100. NULL = c_int(0)
  101. WM_KEYDOWN = 0x0100
  102. WM_KEYUP = 0x0101
  103. WM_SYSKEYDOWN = 0x104 # Used for ALT key
  104. WM_SYSKEYUP = 0x105
  105. # This marks the end of Win32 API declarations. The rest is ours.
  106. keyboard_event_types = {
  107. WM_KEYDOWN: KEY_DOWN,
  108. WM_KEYUP: KEY_UP,
  109. WM_SYSKEYDOWN: KEY_DOWN,
  110. WM_SYSKEYUP: KEY_UP,
  111. }
  112. # List taken from the official documentation, but stripped of the OEM-specific keys.
  113. # Keys are virtual key codes, values are pairs (name, is_keypad).
  114. from_virtual_key = {
  115. 0x03: ('control-break processing', False),
  116. 0x08: ('backspace', False),
  117. 0x09: ('tab', False),
  118. 0x0c: ('clear', False),
  119. 0x0d: ('enter', False),
  120. 0x10: ('shift', False),
  121. 0x11: ('ctrl', False),
  122. 0x12: ('alt', False),
  123. 0x13: ('pause', False),
  124. 0x14: ('caps lock', False),
  125. 0x15: ('ime kana mode', False),
  126. 0x15: ('ime hanguel mode', False),
  127. 0x15: ('ime hangul mode', False),
  128. 0x17: ('ime junja mode', False),
  129. 0x18: ('ime final mode', False),
  130. 0x19: ('ime hanja mode', False),
  131. 0x19: ('ime kanji mode', False),
  132. 0x1b: ('esc', False),
  133. 0x1c: ('ime convert', False),
  134. 0x1d: ('ime nonconvert', False),
  135. 0x1e: ('ime accept', False),
  136. 0x1f: ('ime mode change request', False),
  137. 0x20: ('spacebar', False),
  138. 0x21: ('page up', False),
  139. 0x22: ('page down', False),
  140. 0x23: ('end', False),
  141. 0x24: ('home', False),
  142. 0x25: ('left', False),
  143. 0x26: ('up', False),
  144. 0x27: ('right', False),
  145. 0x28: ('down', False),
  146. 0x29: ('select', False),
  147. 0x2a: ('print', False),
  148. 0x2b: ('execute', False),
  149. 0x2c: ('print screen', False),
  150. 0x2d: ('insert', False),
  151. 0x2e: ('delete', False),
  152. 0x2f: ('help', False),
  153. 0x30: ('0', False),
  154. 0x31: ('1', False),
  155. 0x32: ('2', False),
  156. 0x33: ('3', False),
  157. 0x34: ('4', False),
  158. 0x35: ('5', False),
  159. 0x36: ('6', False),
  160. 0x37: ('7', False),
  161. 0x38: ('8', False),
  162. 0x39: ('9', False),
  163. 0x41: ('a', False),
  164. 0x42: ('b', False),
  165. 0x43: ('c', False),
  166. 0x44: ('d', False),
  167. 0x45: ('e', False),
  168. 0x46: ('f', False),
  169. 0x47: ('g', False),
  170. 0x48: ('h', False),
  171. 0x49: ('i', False),
  172. 0x4a: ('j', False),
  173. 0x4b: ('k', False),
  174. 0x4c: ('l', False),
  175. 0x4d: ('m', False),
  176. 0x4e: ('n', False),
  177. 0x4f: ('o', False),
  178. 0x50: ('p', False),
  179. 0x51: ('q', False),
  180. 0x52: ('r', False),
  181. 0x53: ('s', False),
  182. 0x54: ('t', False),
  183. 0x55: ('u', False),
  184. 0x56: ('v', False),
  185. 0x57: ('w', False),
  186. 0x58: ('x', False),
  187. 0x59: ('y', False),
  188. 0x5a: ('z', False),
  189. 0x5b: ('left windows', False),
  190. 0x5c: ('right windows', False),
  191. 0x5d: ('applications', False),
  192. 0x5f: ('sleep', False),
  193. 0x60: ('0', True),
  194. 0x61: ('1', True),
  195. 0x62: ('2', True),
  196. 0x63: ('3', True),
  197. 0x64: ('4', True),
  198. 0x65: ('5', True),
  199. 0x66: ('6', True),
  200. 0x67: ('7', True),
  201. 0x68: ('8', True),
  202. 0x69: ('9', True),
  203. 0x6a: ('*', True),
  204. 0x6b: ('+', True),
  205. 0x6c: ('separator', True),
  206. 0x6d: ('-', True),
  207. 0x6e: ('decimal', True),
  208. 0x6f: ('/', True),
  209. 0x70: ('f1', False),
  210. 0x71: ('f2', False),
  211. 0x72: ('f3', False),
  212. 0x73: ('f4', False),
  213. 0x74: ('f5', False),
  214. 0x75: ('f6', False),
  215. 0x76: ('f7', False),
  216. 0x77: ('f8', False),
  217. 0x78: ('f9', False),
  218. 0x79: ('f10', False),
  219. 0x7a: ('f11', False),
  220. 0x7b: ('f12', False),
  221. 0x7c: ('f13', False),
  222. 0x7d: ('f14', False),
  223. 0x7e: ('f15', False),
  224. 0x7f: ('f16', False),
  225. 0x80: ('f17', False),
  226. 0x81: ('f18', False),
  227. 0x82: ('f19', False),
  228. 0x83: ('f20', False),
  229. 0x84: ('f21', False),
  230. 0x85: ('f22', False),
  231. 0x86: ('f23', False),
  232. 0x87: ('f24', False),
  233. 0x90: ('num lock', False),
  234. 0x91: ('scroll lock', False),
  235. 0xa0: ('left shift', False),
  236. 0xa1: ('right shift', False),
  237. 0xa2: ('left ctrl', False),
  238. 0xa3: ('right ctrl', False),
  239. 0xa4: ('left menu', False),
  240. 0xa5: ('right menu', False),
  241. 0xa6: ('browser back', False),
  242. 0xa7: ('browser forward', False),
  243. 0xa8: ('browser refresh', False),
  244. 0xa9: ('browser stop', False),
  245. 0xaa: ('browser search key ', False),
  246. 0xab: ('browser favorites', False),
  247. 0xac: ('browser start and home', False),
  248. 0xad: ('volume mute', False),
  249. 0xae: ('volume down', False),
  250. 0xaf: ('volume up', False),
  251. 0xb0: ('next track', False),
  252. 0xb1: ('previous track', False),
  253. 0xb2: ('stop media', False),
  254. 0xb3: ('play/pause media', False),
  255. 0xb4: ('start mail', False),
  256. 0xb5: ('select media', False),
  257. 0xb6: ('start application 1', False),
  258. 0xb7: ('start application 2', False),
  259. 0xbb: ('+', False),
  260. 0xbc: (',', False),
  261. 0xbd: ('-', False),
  262. 0xbe: ('.', False),
  263. # 0xbe: ('/', False), # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key.
  264. 0xe5: ('ime process', False),
  265. 0xf6: ('attn', False),
  266. 0xf7: ('crsel', False),
  267. 0xf8: ('exsel', False),
  268. 0xf9: ('erase eof', False),
  269. 0xfa: ('play', False),
  270. 0xfb: ('zoom', False),
  271. 0xfc: ('reserved ', False),
  272. 0xfd: ('pa1', False),
  273. 0xfe: ('clear', False),
  274. }
  275. # Exceptions to our logic. Still trying to figure out what is happening.
  276. possible_extended_keys = [0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0xc, 0x6b, 0x2e, 0x2d, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f]
  277. reversed_extended_keys = [0x6f, 0xd]
  278. from_scan_code = {}
  279. to_scan_code = {}
  280. vk_to_scan_code = {}
  281. scan_code_to_vk = {}
  282. tables_lock = Lock()
  283. # Alt gr is way outside the usual range of keys (0..127) and on my
  284. # computer is named as 'ctrl'. Therefore we add it manually and hope
  285. # Windows is consistent in its inconsistency.
  286. alt_gr_scan_code = 541
  287. # These tables are used as backup when a key name can not be found by virtual
  288. # key code.
  289. def _setup_tables():
  290. """
  291. Ensures the scan code/virtual key code/name translation tables are
  292. filled.
  293. """
  294. tables_lock.acquire()
  295. try:
  296. if from_scan_code and to_scan_code: return
  297. for vk in range(0x01, 0x100):
  298. scan_code = MapVirtualKey(vk, MAPVK_VK_TO_VSC)
  299. if not scan_code: continue
  300. # Scan codes may map to multiple virtual key codes.
  301. # In this case prefer the officially defined ones.
  302. if scan_code_to_vk.get(scan_code, 0) not in from_virtual_key:
  303. scan_code_to_vk[scan_code] = vk
  304. vk_to_scan_code[vk] = scan_code
  305. name_buffer = ctypes.create_unicode_buffer(32)
  306. keyboard_state = keyboard_state_type()
  307. for scan_code in range(2**(23-16)):
  308. from_scan_code[scan_code] = ['unknown', 'unknown']
  309. # Get pure key name, such as "shift". This depends on locale and
  310. # may return a translated name.
  311. for enhanced in [1, 0]:
  312. ret = GetKeyNameText(scan_code << 16 | enhanced << 24, name_buffer, 1024)
  313. if not ret:
  314. continue
  315. name = normalize_name(name_buffer.value)
  316. from_scan_code[scan_code] = [name, name]
  317. to_scan_code[name] = (scan_code, False)
  318. if scan_code not in scan_code_to_vk: continue
  319. # Get associated character, such as "^", possibly overwriting the pure key name.
  320. for shift_state in [0, 1]:
  321. keyboard_state[0x10] = shift_state * 0xFF
  322. # Try both manual and automatic scan_code->vk translations.
  323. for vk in [scan_code_to_vk.get(scan_code, 0), user32.MapVirtualKeyW(scan_code, 3)]:
  324. ret = ToUnicode(vk, scan_code, keyboard_state, name_buffer, len(name_buffer), 0)
  325. if ret:
  326. # Sometimes two characters are written before the char we want,
  327. # usually an accented one such as Â. Couldn't figure out why.
  328. char = name_buffer.value[-1]
  329. if char not in to_scan_code:
  330. to_scan_code[char] = (scan_code, bool(shift_state))
  331. if scan_code not in from_scan_code:
  332. from_scan_code[scan_code][shift_state] = char
  333. from_scan_code[alt_gr_scan_code] = ['alt gr', 'alt gr']
  334. to_scan_code['alt gr'] = (alt_gr_scan_code, False)
  335. finally:
  336. tables_lock.release()
  337. shift_is_pressed = False
  338. alt_gr_is_pressed = False
  339. init = _setup_tables
  340. def prepare_intercept(callback):
  341. """
  342. Registers a Windows low level keyboard hook. The provided callback will
  343. be invoked for each high-level keyboard event, and is expected to return
  344. True if the key event should be passed to the next program, or False if
  345. the event is to be blocked.
  346. No event is processed until the Windows messages are pumped (see
  347. start_intercept).
  348. """
  349. _setup_tables()
  350. def process_key(event_type, vk, scan_code, is_extended):
  351. global alt_gr_is_pressed
  352. global shift_is_pressed
  353. name = 'unknown'
  354. is_keypad = False
  355. if scan_code == alt_gr_scan_code:
  356. alt_gr_is_pressed = event_type == KEY_DOWN
  357. name = 'alt gr'
  358. else:
  359. if vk in from_virtual_key:
  360. # Pressing AltGr also triggers "right menu" quickly after. We
  361. # try to filter out this event. The `alt_gr_is_pressed` flag
  362. # is to avoid messing with keyboards that don't even have an
  363. # alt gr key.
  364. if vk == 165:
  365. return True
  366. name, is_keypad = from_virtual_key[vk]
  367. if vk in possible_extended_keys and not is_extended:
  368. is_keypad = True
  369. # What the hell Windows?
  370. if vk in reversed_extended_keys and is_extended:
  371. is_keypad = True
  372. elif scan_code in from_scan_code:
  373. name = from_scan_code[scan_code][shift_is_pressed]
  374. if event_type == KEY_DOWN and name == 'shift':
  375. shift_is_pressed = True
  376. elif event_type == KEY_UP and name == 'shift':
  377. shift_is_pressed = False
  378. return callback(KeyboardEvent(event_type=event_type, scan_code=scan_code, name=name, is_keypad=is_keypad))
  379. def low_level_keyboard_handler(nCode, wParam, lParam):
  380. try:
  381. vk = lParam.contents.vk_code
  382. # Ignore events generated by SendInput with Unicode.
  383. if vk != VK_PACKET:
  384. event_type = keyboard_event_types[wParam]
  385. is_extended = lParam.contents.flags & 1
  386. scan_code = lParam.contents.scan_code
  387. should_continue = process_key(event_type, vk, scan_code, is_extended)
  388. if not should_continue:
  389. return -1
  390. except Exception as e:
  391. print('Error in keyboard hook: ', e)
  392. return CallNextHookEx(NULL, nCode, wParam, lParam)
  393. WH_KEYBOARD_LL = c_int(13)
  394. keyboard_callback = LowLevelKeyboardProc(low_level_keyboard_handler)
  395. keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_callback, NULL, NULL)
  396. # Register to remove the hook when the interpreter exits. Unfortunately a
  397. # try/finally block doesn't seem to work here.
  398. atexit.register(UnhookWindowsHookEx, keyboard_callback)
  399. def _start_intercept():
  400. """
  401. Starts pumping Windows messages, which invokes the registered low
  402. level keyboard hook.
  403. """
  404. # TODO: why does this work, without the whole Translate/Dispatch dance?
  405. GetMessage(LPMSG(), NULL, NULL, NULL)
  406. #msg = LPMSG()
  407. #while not GetMessage(msg, NULL, NULL, NULL):
  408. # TranslateMessage(msg)
  409. # DispatchMessage(msg)
  410. def listen(queue, is_allowed=lambda *args: True):
  411. prepare_intercept(lambda e: queue.put(e) or is_allowed(e.name, e.event_type == KEY_UP))
  412. _start_intercept()
  413. def map_char(name):
  414. _setup_tables()
  415. wants_keypad = name.startswith('keypad ')
  416. if wants_keypad:
  417. name = name[len('keypad '):]
  418. for vk in from_virtual_key:
  419. candidate_name, is_keypad = from_virtual_key[vk]
  420. if candidate_name in (name, 'left ' + name, 'right ' + name) and is_keypad == wants_keypad:
  421. # HACK: use negative scan codes to identify virtual key codes.
  422. # This is required to correctly associate media keys, since they report a scan
  423. # code of 0 but still have valid virtual key codes. It also helps standardizing
  424. # the key names, since the virtual key code table is constant.
  425. return -vk, []
  426. if name in to_scan_code:
  427. scan_code, shift = to_scan_code[name]
  428. return scan_code, ['shift'] if shift else []
  429. else:
  430. raise ValueError('Key name {} is not mapped to any known key.'.format(repr(name)))
  431. # For pressing and releasing, we need both the scan code and virtual key code.
  432. # Only one is necessary most of the time, but some intrusive software require both.
  433. def _send_event(code, event_type):
  434. if code < 0:
  435. vk = -code
  436. code = vk_to_scan_code[vk]
  437. else:
  438. vk = scan_code_to_vk.get(code, 0)
  439. user32.keybd_event(vk, code, event_type, 0)
  440. def press(code):
  441. _send_event(code, 0)
  442. def release(code):
  443. _send_event(code, 2)
  444. def type_unicode(character):
  445. # This code and related structures are based on
  446. # http://stackoverflow.com/a/11910555/252218
  447. inputs = []
  448. surrogates = bytearray(character.encode('utf-16le'))
  449. for i in range(0, len(surrogates), 2):
  450. higher, lower = surrogates[i:i+2]
  451. structure = KEYBDINPUT(0, (lower << 8) + higher, KEYEVENTF_UNICODE, 0, None)
  452. inputs.append(INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure)))
  453. nInputs = len(inputs)
  454. LPINPUT = INPUT * nInputs
  455. pInputs = LPINPUT(*inputs)
  456. cbSize = c_int(ctypes.sizeof(INPUT))
  457. SendInput(nInputs, pInputs, cbSize)
  458. if __name__ == '__main__':
  459. import keyboard
  460. def p(event):
  461. print(event)
  462. keyboard.hook(p)
  463. input()