2012-03-22 5 views
9

Quindi sto cercando di capire come registrare un hook per tastiera globale usando Python. Da quello che ho letto, sembra che non ci sia il callback in una DLL. Se si utilizza WH_KEYBOARD_LL. Non posso confermarlo con certezza, ma trovo incoraggiante il fatto che non ottengo un errore 1428 come faccio se provo a collegarmi a dire WH_CBT.Applicazione di hook per tastiera di basso livello con Python e SetWindowsHookExA

Ho una maniglia di aggancio ma non compare nulla quando premo i pulsanti sulla tastiera come mi aspetterei.

Qualche idea sul perché il mio callback non viene chiamato? O è anche possibile?

Il codice rilevante:

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() 

risposta

12

non ho potuto ottenere la classe di lavorare, ma ho trovato un modo simile per raggiungere lo stesso obiettivo in this thread.

Ecco il codice adattato:

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() 

Ho fatto una libreria di alto livello per avvolgere questo: keyboard.

+0

Nice, concise, answer –

+0

Dopo molte ore di tentativi di approcci diversi, questo sembra il modo perfetto se hai bisogno di ascoltare la tastiera ad un livello basso (senza avere la console focalizzata). Grazie! –

+0

Funziona alla grande ma ottengo 'key_code's come' 240518168740'. Ho pensato che dovrebbe restituire valori come [questo] (https://msdn.microsoft.com/en-us/library/dd375731%28v=vs.85%29.aspx). Quindi, come posso convertirlo nel carattere/tasto attuale premuto? –

0

non ho provato questo con Python specificamente, ma sì, dovrebbe essere possibile per un basso livello della tastiera o gancio mouse. Per altri tipi di hook, le funzioni di hook devono essere in una DLL.

HOOK_ACTION dovrebbe essere 0, non 13.

4

Il motivo per cui il codice originale di Tim non ha funzionato è dovuto al fatto che il puntatore della funzione ctypes su low_level_keyboard_proc è stato sottoposto a garbage collection, quindi la sua callback non è più valida e non è stata chiamata. È fallito silenziosamente.

Windows non mantiene i puntatori Python, quindi è necessario conservare separatamente un riferimento al parametro del puntatore di funzione callback_decl(callback) ctypes passato a SetWindowsHookEx.

Problemi correlati