为何快速移动光标时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
相关产品推荐
相关产品推荐

