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

为何快速移动光标时LowLevelMouseProc会返回负值?

问题原因及解决方法

你遇到的鼠标坐标出现负值的情况,核心原因是低级别鼠标钩子(WH_MOUSE_LL)捕获的是鼠标硬件发送的原始移动数据:当鼠标快速移动时,硬件发送的移动增量累积后,会让坐标短暂超出系统设定的屏幕边界(比如快速向上移动时y坐标变为负数),系统后续会把光标限制在屏幕范围内,但钩子会先捕获到这些未被修正的原始坐标。

另外你的代码里还有一个潜在问题,可能加重异常:CallNextHookEx的第一个参数应该传入SetWindowsHookExW返回的hook_id,而非固定的0,错误参数会导致钩子链执行异常,影响坐标的正确性。

解决办法

1. 修正钩子链调用

把LowLevelMouseProc里的返回语句改成传入正确的hook_id,需要先将hook_id设为全局变量以让钩子函数访问:

import ctypes
import ctypes.wintypes

user32 = ctypes.windll.user32
WM_MOUSEMOVE = 0x0200
hook_id = None  # 全局变量存储钩子ID

class POINT(ctypes.Structure):
    _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]

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

def showxy(x, y):
    print("x=%d y=%d" % (x, y), end="              \r")

def LowLevelMouseProc(nCode, wParam, lParam):
    global hook_id
    if nCode == 0:
        if wParam == WM_MOUSEMOVE:
            msl = ctypes.cast(lParam, ctypes.POINTER(MSLLHOOKSTRUCT)).contents
            x = msl.pt.x
            y = msl.pt.y
            showxy(x, y)
        elif wParam == 0x0201:
            print("Left button pressed")
        elif wParam == 0x0204:
            print("Right button pressed")
    # 传入正确的hook_id
    return user32.CallNextHookEx(hook_id, nCode, ctypes.wintypes.WPARAM(wParam), ctypes.wintypes.LPARAM(lParam))

def main():
    global hook_id
    LRESULT = ctypes.c_long
    MYCFUNC = ctypes.CFUNCTYPE(LRESULT, ctypes.wintypes.INT, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM)
    CFIMCP = MYCFUNC(LowLevelMouseProc)
    hook_id = user32.SetWindowsHookExW(14, CFIMCP, None, 0)
    msg = ctypes.wintypes.MSG()
    while user32.GetMessageW(ctypes.byref(msg), None, 0, 0) != 0:
        user32.TranslateMessage(msg)
        user32.DispatchMessageW(msg)

if __name__ == '__main__':
    main()

2. 过滤超出屏幕范围的坐标

如果需要确保输出坐标始终在屏幕有效范围内,可先获取屏幕分辨率,再对坐标做边界限制:

# 在main函数开头获取屏幕尺寸
screen_width = user32.GetSystemMetrics(0)  # 屏幕宽度
screen_height = user32.GetSystemMetrics(1) # 屏幕高度

# 修改showxy函数
def showxy(x, y):
    # 限制坐标在0到屏幕边界内
    x = max(0, min(x, screen_width - 1))
    y = max(0, min(y, screen_height - 1))
    print("x=%d y=%d" % (x, y), end="              \r")

3. 改用GetCursorPos获取坐标

如果不需要捕获所有鼠标移动事件,只是获取当前光标位置,直接调用GetCursorPos更可靠,它返回的是系统修正后的屏幕坐标,不会出现负值:

import ctypes
import ctypes.wintypes
import time

user32 = ctypes.windll.user32

class POINT(ctypes.Structure):
    _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]

def get_cursor_pos():
    pt = POINT()
    user32.GetCursorPos(ctypes.byref(pt))
    return pt.x, pt.y

def main():
    try:
        while True:
            x, y = get_cursor_pos()
            print(f"x={x} y={y}", end="              \r")
            time.sleep(0.01)
    except KeyboardInterrupt:
        pass

if __name__ == '__main__':
    main()

内容的提问来源于stack exchange,提问作者kouhe3

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 16:03:28