2015-07-13 99 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