2012-03-22 17 views
9

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 devolución de llamada en una DLL. Si usa WH_KEYBOARD_LL. No puedo confirmarlo con certeza, pero me parece alentador que no obtenga un error 1428 como lo hago si trato de conectarme, por ejemplo, WH_CBT.Aplicando ganchos de teclado de bajo nivel con Python y SetWindowsHookExA

Obtengo un gancho pero no aparece nada cuando presiono los botones del teclado como era de esperar.

¿Alguna idea de por qué no se llama a mi devolución de llamada? ¿O esto es 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() 

Respuesta

12

no podía conseguir su clase para trabajar, pero he encontrado una manera similar para lograr el mismo objetivo in this thread.

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 terminar con esto: keyboard.

+0

Bueno, conciso, respuesta –

+0

Después de muchas horas de probar varios enfoques diferentes, esta parece ser la manera perfecta si necesita escuchar el teclado a un nivel bajo (sin tener la consola enfocada). ¡Gracias! –

+0

Funciona de maravilla pero obtengo 'key_code's como' 240518168740'. Pensé que debería devolver valores como [esto] (https://msdn.microsoft.com/en-us/library/dd375731%28v=vs.85%29.aspx). Entonces, ¿cómo convierto eso en el carácter/clave real que se está presionando? –

0

no he probado esto con Python específicamente, pero sí, debería ser posible para un teclado de bajo nivel o gancho de ratón. Para otros tipos de gancho, las funciones de gancho deben estar en un dll.

HOOK_ACTION debe ser 0, no 13.

4

La razón por la que el código original de Tim no funcionó es porque el puntero de la función ctypes a low_level_keyboard_proc fue basura recolectada, por lo que su devolución de llamada se volvió inválida y no se invocó. Simplemente falló silenciosamente.

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

Cuestiones relacionadas