2015-07-13 100 views
2

我想用Python(3.4.3)后台应用程序(在Windows 7/8中)全局跟踪鼠标。这包括建立一个WindowsHook应该还给我一个有效的句柄具体钩 - 但我的手柄始终为0在Python中设置WindowsHook(ctypes,Windows API)

只跟踪鼠标的位置是很容易与GetCursorPos(作为替代GetCursorInfo作品以及):

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

也方便,只跟踪位置为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)) 

但是我希望能够同时跟踪点击,拖动等。 好的解决方案似乎是LowLevelMouseProc。 (有可能是另一种方式尚待探索:Raw Input

为了能够使用LowLevelMouseProc的文件告诉我们使用SetWindowsHookEx(W/A),其中也包括在various(C++)tutorials(C#),以及一些有趣的projects(也是C#)。

文档定义它在C++中,如下所示:

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

凡以下应正确的值对我在Python:

  • idHookWH_MOUSE_LL = 14
  • hModHINSTANCE(0)(基本上空指针)
  • dwThreadIdctypes.windll.kernel32.GetCurrentThreadId()

而对于lpfn我需要一些回调执行LowLevelMouseProc,这里LLMouseProc

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) 

全部放在一起我预计这个工作:

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 

但输出显示那hook == 0

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

我想,也许回调函数的最后一个参数,名称lParam为LPARAM(这是ctypes.c_long),因为我认为什么是真正预期是一个指针,这个结构是不是真的正确:

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

但更改签名到LLMouseProcCB = ctypes.CFUNCTYPE(LRESULT, ctypes.c_int, WPARAM, ctypes.POINTER(MSLLHOOKSTRUCT))并不能解决问题,我仍然有一个0的钩子。

这是跟踪鼠标的正确方法吗?我需要更改以便能够正确注册与Windows挂钩?

回答

2

如果你检查GetLastError你应该发现错误是ERROR_GLOBAL_ONLY_HOOK(1429),即WH_MOUSE_LL需要设置全局钩子。 dwThreadId参数用于设置本地挂钩。幸运的是,WH_MOUSE_LL是不常见的,因为全局钩子回调可以是挂钩过程中的任何函数,而不必在DLL中定义,即hMod可以是NULL

如果您需要支持32位Windows,请注意调用约定。 32位Windows API通常需要stdcall(被叫栈清理),因此回调需要通过WINFUNCTYPE而不是CFUNCTYPE来定义。

另一个问题是您的代码缺少消息循环。设置钩子的线程需要运行消息循环以便将消息分配给回调。在下面的例子中,我为这个消息循环使用了一个专用线程。该线程设置钩子并进入一个循环,该循环只有在错误发生时才会中断,或者发送消息WM_QUIT。当用户输入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 
+0

我什至不能告诉我从这个答案中学到了多少。很多关于WinAPI的兴趣,我不得不阅读python装饰器。我不完全明白的是,为什么WH_MOUSE_LL的特殊之处在于你不需要将它订阅到DLL--我从https://msdn.microsoft.com/en-us/library/windows /desktop/ms644990(v=vs.85).aspx,它可以与所有人挂钩到当前线程或DLL。在我看来,唯一的区别是一些钩子只能在全球范围内根据他们得到的事件进行工作。 –

+1

['LowLevelMouseProc'](https://msdn.microsoft.com/en-us/library/ms644986)文档指出钩子是在“安装它的线程的上下文中”通过“发送消息到线程“。在其他情况下,如果没有使用或不能使用线程间消息传递,则具有挂钩过程的DLL需要事先加载(除非无法加载,例如32位DLL可以加载不会在64位进程中加载​​;这是如何工作的在SetWindowsHookEx'文档中讨论)。 – eryksun