简体   繁体   English

在 Python 中设置 WindowsHook(ctypes,Windows API)

[英]Setting up a WindowsHook in Python (ctypes, Windows API)

I am trying to globally track the mouse with a Python (3.4.3) background app (in Windows 7/8).我正在尝试使用 Python (3.4.3) 后台应用程序(在 Windows 7/8 中)全局跟踪鼠标。 This involves setting up a WindowsHook which should return me a valid handle to that specific hook - but my handle is always 0.这涉及设置一个 WindowsHook,它应该向我返回该特定挂钩的有效句柄——但我的句柄始终为 0。

Tracking only the mouse position is very easy with GetCursorPos (as an alternative GetCursorInfo works as well):使用GetCursorPos仅跟踪鼠标 position 非常容易(作为替代方法GetCursorInfo也适用):

from ctypes.wintypes import *
ppoint = ctypes.pointer(POINT())
ctypes.windll.user32.GetCursorPos(ppoint)
print('({}, {})'.format(ppoint[0].x, ppoint[0].y))

Also convenient to track only the position is GetMouseMovePointsEx , which tracks the last 64 mouse positions:仅跟踪 position 也很方便的是GetMouseMovePointsEx ,它跟踪最后 64 个鼠标位置:

from ctypes.wintypes import *

# some additional types and structs
ULONG_PTR = ctypes.c_ulong
class MOUSEMOVEPOINT(ctypes.Structure):
    _fields_ = [
        ("x", ctypes.c_int),
        ("y", ctypes.c_int),
        ("time", DWORD),
        ("dwExtraInfo", ULONG_PTR)
    ]
GMMP_USE_DISPLAY_POINTS = 1

# get initial tracking point
ppoint = ctypes.pointer(POINT())
ctypes.windll.user32.GetCursorPos(ppoint)
point = MOUSEMOVEPOINT(ppoint[0].x,ppoint[0].y)

# track last X points
number_mouse_points = 64
points = (MOUSEMOVEPOINT * number_mouse_points)()
ctypes.windll.user32.GetMouseMovePointsEx(ctypes.sizeof(MOUSEMOVEPOINT), 
    ctypes.pointer(point), ctypes.pointer(points), number_mouse_points, 
    GMMP_USE_DISPLAY_POINTS)

# print results
for point in points:
    print('({}, {})'.format(point.x, point.y))

However I want to be able to also track clicks, drags, etc. A good solution seems to be the LowLevelMouseProc .但是我还希望能够跟踪点击、拖动等。一个好的解决方案似乎是LowLevelMouseProc (There might be another way yet to be explored: Raw Input ) (可能还有另一种方式有待探索: 原始输入

To be able to use the LowLevelMouseProc the documentation tells us to use SetWindowsHookEx(W/A) , which is also covered in various (C++) tutorials (C#), as well as some interesting projects (also C#).为了能够使用 LowLevelMouseProc,文档告诉我们使用SetWindowsHookEx(W/A)各种(C++)教程(C#) 以及一些有趣的项目(也是 C#)也介绍了它。

The documentation defines it in C++ as follows:文档在C++中定义如下:

HHOOK WINAPI SetWindowsHookEx(
  _In_ int       idHook,
  _In_ HOOKPROC  lpfn,
  _In_ HINSTANCE hMod,
  _In_ DWORD     dwThreadId
);

Where the following should be the correct values for me in python:在 python 中,以下应该是我的正确值:

  • idHook : WH_MOUSE_LL = 14 idHook : WH_MOUSE_LL WH_MOUSE_LL = 14
  • hMod : HINSTANCE(0) (basically a null pointer) hMod : HINSTANCE(0) (基本上是一个 null 指针)
  • dwThreadId : ctypes.windll.kernel32.GetCurrentThreadId() dwThreadIdctypes.windll.kernel32.GetCurrentThreadId()

And for the lpfn I need some callback implementing the LowLevelMouseProc , here LLMouseProc :对于lpfn ,我需要一些实现LLMouseProc LowLevelMouseProc

def _LLMouseProc (nCode, wParam, lParam):
    return ctypes.windll.user32.CallNextHookEx(None, nCode, wParam, lParam)
LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, LPARAM)
LLMouseProc = LLMouseProcCB(_LLMouseProc)

Putting it all together I expected this to work:把它们放在一起我希望它能起作用:

from ctypes.wintypes import *

LONG_PTR = ctypes.c_long
LRESULT = LONG_PTR
WH_MOUSE_LL = 14

def _LLMouseProc(nCode, wParam, lParam):
    print("_LLMouseProc({!s}, {!s}, {!s})".format(nCode, wParam, lParam))
    return ctypes.windll.user32.CallNextHookEx(None, nCode, wParam, lParam)
LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, LPARAM)
LLMouseProc = LLMouseProcCB(_LLMouseProc)

threadId = ctypes.windll.kernel32.GetCurrentThreadId()

# register callback as hook
print('hook = SetWindowsHookExW({!s}, {!s}, {!s}, {!s})'.format(WH_MOUSE_LL, LLMouseProc,
    HINSTANCE(0), threadId))
hook = ctypes.windll.user32.SetWindowsHookExW(WH_MOUSE_LL, LLMouseProc, 
    HINSTANCE(0), threadId)
print('Hook: {}'.format(hook))

import time
try:
    while True:
        time.sleep(0.2)
except KeyboardInterrupt:
    pass

But the output reveals that hook == 0 :但是 output 显示hook == 0

hook = SetWindowsHookExW(14, <CFunctionType object at 0x026183F0>, c_void_p(None), 5700)
Hook: 0

I think that maybe the last parameter of the callback function, name lParam is not really correct as LPARAM (which is ctypes.c_long ), since what I assume is really expected is a pointer to this struct:我认为也许回调的最后一个参数 function,名称lParam作为 LPARAM(即ctypes.c_long )并不真正正确,因为我假设真正期望的是指向此结构的指针:

class MSLLHOOKSTRUCT(ctypes.Structure):
    _fields_ = [
        ("pt", POINT),
        ("mouseData", DWORD),
        ("flags", DWORD),
        ("time", DWORD),
        ("dwExtraInfo", ULONG_PTR)
    ]

But changing the signature to LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, ctypes.POINTER(MSLLHOOKSTRUCT)) does not solve the problem, I still have a hook of 0.但是将签名更改为LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, ctypes.POINTER(MSLLHOOKSTRUCT))并不能解决问题,我仍然有一个 0 的钩子。

Is this the right approach of tracking the mouse?这是跟踪鼠标的正确方法吗? What do I need to change to be able to correctly register hooks with Windows?我需要更改什么才能使用 Windows 正确注册挂钩?

If you check GetLastError you should discover that the error is ERROR_GLOBAL_ONLY_HOOK (1429), ie WH_MOUSE_LL requires setting a global hook. 如果检查GetLastError ,你应该发现该错误是ERROR_GLOBAL_ONLY_HOOK (1429),即WH_MOUSE_LL需要设置全局钩子。 The dwThreadId parameter is for setting a local hook. dwThreadId参数用于设置本地挂钩。 Fortunately WH_MOUSE_LL is unusual in that the global hook callback can be any function in the hooking process instead of having to be defined in a DLL, ie hMod can be NULL . 幸运的是WH_MOUSE_LL是不寻常的,因为全局钩子回调可以是挂钩过程中的任何函数,而不必在DLL中定义,即hMod可以是NULL

Pay attention to the calling convention if you need to support 32-bit Windows. 如果需要支持32位Windows,请注意调用约定。 The 32-bit Windows API generally requires stdcall (callee stack cleanup), so the callback needs to be defined via WINFUNCTYPE instead of CFUNCTYPE . 32位Windows API通常需要stdcall (被调用者堆栈清理),因此需要通过WINFUNCTYPE而不是CFUNCTYPE来定义回调。

Another issue is that your code lacks a message loop. 另一个问题是您的代码缺少消息循环。 The thread that sets the hook needs to run a message loop in order to dispatch the message to the callback. 设置钩子的线程需要运行消息循环,以便将消息分派给回调。 In the example below I use a dedicated thread for this message loop. 在下面的示例中,我为此消息循环使用专用线程。 The thread sets the hook and enters a loop that only breaks on error or when a WM_QUIT message is posted. 该线程设置钩子并进入一个循环,该循环仅在出错时或在发布WM_QUIT消息时中断。 When the user enters Ctrl+C , I call PostThreadMessageW to gracefully exit. 当用户输入Ctrl+C ,我调用PostThreadMessageW以正常退出。

from ctypes import *
from ctypes.wintypes import *

user32 = WinDLL('user32', use_last_error=True)

HC_ACTION = 0
WH_MOUSE_LL = 14

WM_QUIT        = 0x0012
WM_MOUSEMOVE   = 0x0200
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP   = 0x0202
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP   = 0x0205
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP   = 0x0208
WM_MOUSEWHEEL  = 0x020A
WM_MOUSEHWHEEL = 0x020E

MSG_TEXT = {WM_MOUSEMOVE:   'WM_MOUSEMOVE',
            WM_LBUTTONDOWN: 'WM_LBUTTONDOWN',
            WM_LBUTTONUP:   'WM_LBUTTONUP',
            WM_RBUTTONDOWN: 'WM_RBUTTONDOWN',
            WM_RBUTTONUP:   'WM_RBUTTONUP',
            WM_MBUTTONDOWN: 'WM_MBUTTONDOWN',
            WM_MBUTTONUP:   'WM_MBUTTONUP',
            WM_MOUSEWHEEL:  'WM_MOUSEWHEEL',
            WM_MOUSEHWHEEL: 'WM_MOUSEHWHEEL'}

ULONG_PTR = WPARAM
LRESULT = LPARAM
LPMSG = POINTER(MSG)

HOOKPROC = WINFUNCTYPE(LRESULT, c_int, WPARAM, LPARAM)
LowLevelMouseProc = HOOKPROC

class MSLLHOOKSTRUCT(Structure):
    _fields_ = (('pt',          POINT),
                ('mouseData',   DWORD),
                ('flags',       DWORD),
                ('time',        DWORD),
                ('dwExtraInfo', ULONG_PTR))

LPMSLLHOOKSTRUCT = POINTER(MSLLHOOKSTRUCT)

def errcheck_bool(result, func, args):
    if not result:
        raise WinError(get_last_error())
    return args

user32.SetWindowsHookExW.errcheck = errcheck_bool
user32.SetWindowsHookExW.restype = HHOOK
user32.SetWindowsHookExW.argtypes = (c_int,     # _In_ idHook
                                     HOOKPROC,  # _In_ lpfn
                                     HINSTANCE, # _In_ hMod
                                     DWORD)     # _In_ dwThreadId

user32.CallNextHookEx.restype = LRESULT
user32.CallNextHookEx.argtypes = (HHOOK,  # _In_opt_ hhk
                                  c_int,  # _In_     nCode
                                  WPARAM, # _In_     wParam
                                  LPARAM) # _In_     lParam

user32.GetMessageW.argtypes = (LPMSG, # _Out_    lpMsg
                               HWND,  # _In_opt_ hWnd
                               UINT,  # _In_     wMsgFilterMin
                               UINT)  # _In_     wMsgFilterMax

user32.TranslateMessage.argtypes = (LPMSG,)
user32.DispatchMessageW.argtypes = (LPMSG,)

@LowLevelMouseProc
def LLMouseProc(nCode, wParam, lParam):
    msg = cast(lParam, LPMSLLHOOKSTRUCT)[0]
    if nCode == HC_ACTION:
        msgid = MSG_TEXT.get(wParam, str(wParam))
        msg = ((msg.pt.x, msg.pt.y),
                msg.mouseData, msg.flags,
                msg.time, msg.dwExtraInfo)
        print('{:15s}: {}'.format(msgid, msg))
    return user32.CallNextHookEx(None, nCode, wParam, lParam)

def mouse_msg_loop():
    hHook = user32.SetWindowsHookExW(WH_MOUSE_LL, LLMouseProc, None, 0)
    msg = MSG()
    while True:
        bRet = user32.GetMessageW(byref(msg), None, 0, 0)
        if not bRet:
            break
        if bRet == -1:
            raise WinError(get_last_error())
        user32.TranslateMessage(byref(msg))
        user32.DispatchMessageW(byref(msg))

if __name__ == '__main__':
    import time
    import threading
    t = threading.Thread(target=mouse_msg_loop)
    t.start()
    while True:
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            user32.PostThreadMessageW(t.ident, WM_QUIT, 0, 0)
            break

A simplified version of the accepted answer已接受答案的简化版本

Note : pip install pywin32 first.注意pip install pywin32

# Created by BaiJiFeiLong@gmail.com at 2022/2/10 22:27

from ctypes import WINFUNCTYPE, c_int, Structure, cast, POINTER, windll
from ctypes.wintypes import LPARAM, WPARAM, DWORD, PULONG, LONG

import win32con
import win32gui


def genStruct(name="Structure", **kwargs):
    return type(name, (Structure,), dict(
        _fields_=list(kwargs.items()),
        __str__=lambda self: "%s(%s)" % (name, ",".join("%s=%s" % (k, getattr(self, k)) for k in kwargs))
    ))


@WINFUNCTYPE(LPARAM, c_int, WPARAM, LPARAM)
def hookProc(nCode, wParam, lParam):
    msg = cast(lParam, POINTER(HookStruct))[0]
    print(msgDict[wParam], msg)
    return windll.user32.CallNextHookEx(None, nCode, WPARAM(wParam), LPARAM(lParam))


HookStruct = genStruct(
    "Hook", pt=genStruct("Point", x=LONG, y=LONG), mouseData=DWORD, flags=DWORD, time=DWORD, dwExtraInfo=PULONG)
msgDict = {v: k for k, v in win32con.__dict__.items() if k.startswith("WM_")}
windll.user32.SetWindowsHookExW(win32con.WH_MOUSE_LL, hookProc, None, 0)
win32gui.PumpMessages()

Sample Output样品 Output

WM_MOUSEMOVE Hook(pt=Point(x=50,y=702),mouseData=0,flags=0,time=343134468,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
WM_MOUSEMOVE Hook(pt=Point(x=49,y=704),mouseData=0,flags=0,time=343134484,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
WM_MOUSEMOVE Hook(pt=Point(x=49,y=705),mouseData=0,flags=0,time=343134484,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
WM_MOUSEMOVE Hook(pt=Point(x=49,y=705),mouseData=0,flags=0,time=343134500,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
WM_MOUSEMOVE Hook(pt=Point(x=49,y=706),mouseData=0,flags=0,time=343134500,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
WM_MOUSEMOVE Hook(pt=Point(x=48,y=707),mouseData=0,flags=0,time=343134515,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
WM_LBUTTONDOWN Hook(pt=Point(x=48,y=707),mouseData=0,flags=0,time=343134593,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)
WM_LBUTTONUP Hook(pt=Point(x=48,y=707),mouseData=0,flags=0,time=343134671,dwExtraInfo=<ctypes.wintypes.LP_c_ulong object at 0x000001A466CDF8C8>)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM