【问题标题】:Applying low-level keyboard hooks with Python and SetWindowsHookExA使用 Python 和 SetWindowsHookExA 应用低级键盘挂钩
【发布时间】:2012-04-06 17:36:56
【问题描述】:

所以我想弄清楚如何使用 Python 注册一个全局键盘钩子。根据我的阅读,在 DLL 中没有回调似乎是可以的。如果您使用 WH_KEYBOARD_LL。我无法肯定地确认这一点,但我发现如果我尝试挂断WH_CBT 时不会出现 1428 错误,这令人鼓舞。

我得到了一个钩子手柄,但是当我按下键盘上的按钮时什么都没有显示。

知道为什么我的回调没有被调用吗?或者这甚至可能吗?

相关代码:

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

【问题讨论】:

    标签: python winapi


    【解决方案1】:

    我无法让您的课程开始工作,但我找到了类似的方法来实现相同的目标in this thread

    这是修改后的代码:

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

    我已经制作了一个高级库来包装它:keyboard

    【讨论】:

    • 很好,简洁,回答
    • 在尝试了许多不同的方法数小时后,如果您需要在低级别听键盘(无需集中控制台),这似乎是完美的方法。谢谢!
    • 效果很好,但我得到了key_codes,比如240518168740。我认为它应该返回像this 这样的值。那么,如何将其转换为实际按下的字符/键?
    • 尝试删除高位:240518168740 -&gt; 0x38000000A4 &amp; 0xFFFFFFFF = 0xA4A4 看起来像一个 alt 键。那是对的吗?使用示例:github.com/boppreh/keyboard
    • 这里是在黑暗中拍摄,但我想你不知道当焦点在某些应用程序(例如 Steam)中时如何让它工作?
    【解决方案2】:

    Tim 原来的代码不起作用的原因是因为指向low_level_keyboard_proc 的ctypes 函数指针被垃圾回收了,所以他的回调变得无效并且没有被调用。它只是默默地失败了。

    Windows 不保留 Python 指针,因此我们需要单独保留对传递给 SetWindowsHookEx 的确切 callback_decl(callback) ctypes 函数指针参数的引用。

    【讨论】:

      【解决方案3】:

      我没有专门用 Python 尝试过这个,但是是的,它应该可以用于低级键盘或鼠标挂钩。对于其他钩子类型,钩子函数必须在 dll 中。

      HOOK_ACTION 应该是 0,而不是 13。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-05
        相关资源
        最近更新 更多