如何通过WinAPI区分真实鼠标滚轮事件、模拟事件及滚动方向?
解决方案
1 区分滚轮滚动方向
你之前无法区分滚动方向,是因为没有解析lParam指向的MSLLHOOKSTRUCT结构体内容,滚轮的滚动偏移量存在该结构体的mouseData字段高16位:
- 偏移量>0:滚轮向上滚动
- 偏移量<0:滚轮向下滚动
需要先用ctypes定义对应结构体,再从lParam中读取值,示例修改如下:
import win32api, win32con from ctypes import windll, CFUNCTYPE, c_int, c_void_p, wintypes, byref, Structure, c_long user32 = windll.user32 # 定义POINT结构体,用于存储鼠标坐标 class POINT(Structure): _fields_ = [("x", c_long), ("y", c_long)] # 定义MSLLHOOKSTRUCT结构体,存储低级鼠标钩子的事件数据 class MSLLHOOKSTRUCT(Structure): _fields_ = [ ("pt", POINT), ("mouseData", wintypes.DWORD), ("flags", wintypes.DWORD), ("time", wintypes.DWORD), ("dwExtraInfo", wintypes.ULONG_PTR) ] def LowLevelMouseProc(nCode, wParam, lParam): if nCode >= 0 and wParam == win32con.WM_MOUSEWHEEL: # 把lParam转成MSLLHOOKSTRUCT实例 hook_data = MSLLHOOKSTRUCT.from_address(lParam) mouse_data = hook_data.mouseData # 取高16位得到滚轮偏移量 wheel_delta = (mouse_data >> 16) & 0xFFFF # 处理有符号数转换,大于32767的数值对应负数偏移 if wheel_delta > 32767: wheel_delta -= 65536 if wheel_delta > 0: print("滚轮向上滚动,delta:", wheel_delta) else: print("滚轮向下滚动,delta:", wheel_delta) # 传递事件给下一个钩子,避免影响系统其他鼠标逻辑 return user32.CallNextHookEx(None, nCode, wParam, lParam) CMPFUNC = CFUNCTYPE(c_void_p, c_int, wintypes.WPARAM, wintypes.LPARAM) user32.SetWindowsHookExW.argtypes = [c_int, CMPFUNC, wintypes.HINSTANCE, wintypes.DWORD] pointer = CMPFUNC(LowLevelMouseProc) hook_id = user32.SetWindowsHookExW(win32con.WH_MOUSE_LL, pointer, win32api.GetModuleHandle(None), 0) msg = wintypes.MSG() while user32.GetMessageW(byref(msg), 0, 0, 0) != 0: user32.TranslateMessage(msg) user32.DispatchMessageW(msg)
2 避免模拟滚轮事件触发无限循环
系统会给所有通过mouse_event/SendInput生成的注入事件,在MSLLHOOKSTRUCT的flags字段标记LLMHF_INJECTED(值为0x01)位,只需要在回调中判断该位是否被置1,就可以跳过处理自己生成的模拟事件,修改后的回调逻辑如下:
def LowLevelMouseProc(nCode, wParam, lParam): if nCode >= 0 and wParam == win32con.WM_MOUSEWHEEL: hook_data = MSLLHOOKSTRUCT.from_address(lParam) # 判断是否是注入的模拟事件,如果是直接传递不做业务处理 if hook_data.flags & 0x01: return user32.CallNextHookEx(None, nCode, wParam, lParam) mouse_data = hook_data.mouseData wheel_delta = (mouse_data >> 16) & 0xFFFF if wheel_delta > 32767: wheel_delta -= 65536 # 你的业务逻辑,比如满足条件触发模拟滚轮 if wheel_delta > 0: print("用户向上滚动,触发额外向下滚动") # 这个模拟事件会被系统标记为注入事件,下次回调会直接跳过,不会触发循环 win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 0, 0, -120, 0) return user32.CallNextHookEx(None, nCode, wParam, lParam)
如果你需要区分其他程序的注入事件和自己的注入事件,可以新增一个全局标志位,触发模拟事件前先设置标志位为True,回调中如果标志位为True则直接跳过并重置标志位即可。
内容的提问来源于stack exchange,提问作者Basj
相关产品推荐
相关产品推荐

