Aplicación de ganchos de teclado de bajo nivel con Python y SetWindowsHookExA

Así que estoy tratando de averiguar cómo registrar un gancho de teclado global usando Python. Por lo que he leído, parece estar bien no tener la callback en una DLL. Si usas WH_KEYBOARD_LL. No puedo confirmar eso con seguridad, pero me parece alentador que no recibo un error de 1428 como el que hago si bash conectar, por ejemplo, WH_CBT .

Me sale un gancho pero no aparece nada cuando presiono los botones del teclado, como es de esperar.

¿Alguna idea de por qué mi llamada no está siendo llamada? ¿O es esto posible?

El código relevante:

 import time import string import ctypes import functools import atexit import pythoncom from ctypes import windll hookID = 0 class Keyboard(object): KEY_EVENT_DOWN = 0 KEY_EVENT_UP = 2 KEY_ENTER = 2 KEY_SHIFT = 16 KEY_SPACE = 32 HOOK_ACTION = 13 HOOK_KEYBOARD = 13 HOOK_KEYDOWN = 0x100 HOOK_KEYUP = 0x101 class Hook: '''Holds general hook information''' def __init__(self): self.hook = 0 self.struct = None class HookStruct(ctypes.Structure): '''Structure that windows returns for keyboard events''' __fields__ = [ ('keycode', ctypes.c_long), ('scancode', ctypes.c_long), ('flags', ctypes.c_long), ('time', ctypes.c_long), ('info', ctypes.POINTER(ctypes.c_ulong)) ] def ascii_to_keycode(self, char): return windll.user32.VkKeyScanA(ord(char)) def inject_key_down(self, keycode): scancode = windll.user32.MapVirtualKeyA(keycode, 0) windll.user32.keybd_event(keycode, scancode, Keyboard.KEY_EVENT_DOWN, 0) def inject_key_up(self, keycode): scan = windll.user32.MapVirtualKeyA(keycode, 0) windll.user32.keybd_event(keycode, scan, Keyboard.KEY_EVENT_UP, 0) def inject_key_press(self, keycode, pause=0.05): self.inject_key_down(keycode) time.sleep(pause) self.inject_key_up(keycode) def inject_sequence(self, seq, pause=0.05): for key in seq: if key == ' ': self.inject_key_press(Keyboard.KEY_SPACE, pause) elif key == '\n': self.inject_key_press(Keyboard.KEY_ENTER, pause) else: if key in string.ascii_uppercase: self.inject_key_down(Keyboard.KEY_SHIFT) self.inject_key_press(self.ascii_to_keycode(key), pause) self.inject_key_up(Keyboard.KEY_SHIFT) else: self.inject_key_press(self.ascii_to_keycode(key), pause) def _win32_copy_mem(self, dest, src): src = ctypes.c_void_p(src) windll.kernel32.RtlMoveMemory(ctypes.addressof(dest), src, ctypes.sizeof(dest)) def _win32_get_last_error(self): return windll.kernel32.GetLastError() def _win32_get_module(self, mname): return windll.kernel32.GetModuleHandleA(mname) def _win32_call_next_hook(self, id, code, wparam, lparam): return windll.kernel32.CallNextHookEx(id, code, wparam, lparam) def _win32_set_hook(self, id, callback, module, thread): callback_decl = ctypes.WINFUNCTYPE(ctypes.c_long, ctypes.c_long, ctypes.c_long, ctypes.c_long) return windll.user32.SetWindowsHookExA(id, callback_decl(callback), module, thread) def _win32_unhook(self, id): return windll.user32.UnhookWindowsHookEx(id) def keyboard_event(self, data): print data.scancode return False def capture_input(self): self.hook = Keyboard.Hook() self.hook.struct = Keyboard.HookStruct() def low_level_keyboard_proc(code, event_type, kb_data_ptr): # win32 spec says return result of CallNextHookEx if code is less than 0 if code < 0: return self._win32_call_next_hook(self.hook.hook, code, event_type, kb_data_ptr) if code == Keyboard.HOOK_ACTION: # copy data from struct into Python structure self._win32_copy_mem(self.hook.struct, kb_data_ptr) # only call other handlers if we return false from our handler - allows to stop processing of keys if self.keyboard_event(self.hook.struct): return self._win32_call_next_hook(self.hook.hook, code, event_type, kb_data_ptr) # register hook try: hookId = self.hook.hook = self._win32_set_hook(Keyboard.HOOK_KEYBOARD, low_level_keyboard_proc, self._win32_get_module(0), 0) if self.hook.hook == 0: print 'Error - ', self._win32_get_last_error() else: print 'Hook ID - ', self.hook.hook except Exception, error: print error # unregister hook if python exits atexit.register(functools.partial(self._win32_unhook, self.hook.hook)) def end_capture(self): if self.hook.hook: return self._win32_unhook(self.hook.hook) kb = Keyboard()#kb.inject_sequence('This is a test\nand tHis is line 2') kb.capture_input() pythoncom.PumpMessages() kb.end_capture() 

No pude hacer que tu clase trabajara, pero encontré una manera similar de lograr el mismo objective en este hilo .

Aquí está el código adaptado:

 from collections import namedtuple KeyboardEvent = namedtuple('KeyboardEvent', ['event_type', 'key_code', 'scan_code', 'alt_pressed', 'time']) handlers = [] def listen(): """ Calls `handlers` for each keyboard event received. This is a blocking call. """ # Adapted from http://www.hackerthreads.org/Topic-42395 from ctypes import windll, CFUNCTYPE, POINTER, c_int, c_void_p, byref import win32con, win32api, win32gui, atexit event_types = {win32con.WM_KEYDOWN: 'key down', win32con.WM_KEYUP: 'key up', 0x104: 'key down', # WM_SYSKEYDOWN, used for Alt key. 0x105: 'key up', # WM_SYSKEYUP, used for Alt key. } def low_level_handler(nCode, wParam, lParam): """ Processes a low level Windows keyboard event. """ event = KeyboardEvent(event_types[wParam], lParam[0], lParam[1], lParam[2] == 32, lParam[3]) for handler in handlers: handler(event) # Be a good neighbor and call the next hook. return windll.user32.CallNextHookEx(hook_id, nCode, wParam, lParam) # Our low level handler signature. CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p)) # Convert the Python handler into C pointer. pointer = CMPFUNC(low_level_handler) # Hook both key up and key down events for common keys (non-system). hook_id = windll.user32.SetWindowsHookExA(win32con.WH_KEYBOARD_LL, pointer, win32api.GetModuleHandle(None), 0) # Register to remove the hook when the interpreter exits. Unfortunately a # try/finally block doesn't seem to work here. atexit.register(windll.user32.UnhookWindowsHookEx, hook_id) while True: msg = win32gui.GetMessage(None, 0, 0) win32gui.TranslateMessage(byref(msg)) win32gui.DispatchMessage(byref(msg)) if __name__ == '__main__': def print_event(e): print(e) handlers.append(print_event) listen() 

He hecho una biblioteca de alto nivel para envolver esto: keyboard .

La razón por la que el código original de Tim no funcionó es porque el puntero de la función low_level_keyboard_proc a low_level_keyboard_proc fue recolectado como basura, por lo que su callback no fue válida y no fue llamada. Simplemente falló en silencio.

Windows no conserva los punteros de Python, por lo que necesitamos conservar por separado una referencia al parámetro de puntero de función ctypes de callback_decl(callback) exacto que se pasa a SetWindowsHookEx.

No he intentado esto específicamente con Python, pero sí, debería ser posible para un gancho de mouse o teclado de bajo nivel. Para otros tipos de gancho, las funciones de gancho deben estar en una dll.

HOOK_ACTION debe ser 0, no 13.