You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 02:00:00