自动点击器模拟点击干扰硬件鼠标状态检测问题排查
问题根源:pynput的
click方法干扰了物理鼠标状态检测 你的推断完全正确——问题出在pynput.mouse.Controller的click()方法上。当你调用rat.click(mouse.Button.left, 1)时,这个方法会执行以下隐含操作:
- 如果当前鼠标左键处于按下状态(也就是你物理按住左键的时候),它会先发送一个左键释放事件
- 然后发送左键按下事件
- 最后发送左键释放事件
这就导致在模拟点击的瞬间,系统会认为左键被短暂释放了,而GetAsyncKeyState(0x01)会读取到这个临时的释放状态,让你的clicker循环中的条件不满足,进而暂停点击。当物理左键还保持按住时,下一次循环又会检测到按下状态,重新开始点击,最终造成断断续续的卡顿甚至看起来像是停止运行的问题。
解决方案:跳过pynput的鼠标模拟,改用ctypes直接发送输入事件
既然我们已经用ctypes读取硬件鼠标状态,不如直接用它来模拟鼠标点击,这样可以完全控制输入事件,避免pynput的自动状态处理干扰。我们可以使用Windows API的SendInput函数来发送鼠标点击事件,这样不会影响物理按键的状态检测。
下面是修改后的完整代码:
import time import threading import ctypes from pynput import keyboard # 定义Windows API相关结构和函数 PUL = ctypes.POINTER(ctypes.c_ulong) class KeyBdInput(ctypes.Structure): _fields_ = [("wVk", ctypes.c_ushort), ("wScan", ctypes.c_ushort), ("dwFlags", ctypes.c_ulong), ("time", ctypes.c_ulong), ("dwExtraInfo", PUL)] class HardwareInput(ctypes.Structure): _fields_ = [("uMsg", ctypes.c_ulong), ("wParamL", ctypes.c_short), ("wParamH", ctypes.c_ushort)] class MouseInput(ctypes.Structure): _fields_ = [("dx", ctypes.c_long), ("dy", ctypes.c_long), ("mouseData", ctypes.c_ulong), ("dwFlags", ctypes.c_ulong), ("time",ctypes.c_ulong), ("dwExtraInfo", PUL)] class Input_I(ctypes.Union): _fields_ = [("ki", KeyBdInput), ("mi", MouseInput), ("hi", HardwareInput)] class Input(ctypes.Structure): _fields_ = [("type", ctypes.c_ulong), ("ii", Input_I)] def simulate_left_click(): # 发送左键按下事件 extra = ctypes.c_ulong(0) ii_ = Input_I() ii_.mi = MouseInput(0, 0, 0, 0x0002, 0, ctypes.pointer(extra)) x = Input(ctypes.c_ulong(0), ii_) ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) # 发送左键释放事件 ii_.mi = MouseInput(0, 0, 0, 0x0004, 0, ctypes.pointer(extra)) x = Input(ctypes.c_ulong(0), ii_) ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) toggle = keyboard.Key.f6 clicking = False def clicker(): while True: if clicking and (ctypes.windll.user32.GetAsyncKeyState(0x01) & 0x8000 != 0): simulate_left_click() time.sleep(0.01) else: time.sleep(0.005) def toggled(key): global clicking if key == toggle: clicking = not clicking print("Autoclicker:", clicking) clonk = threading.Thread(target=clicker, daemon=True) clonk.start() with keyboard.Listener(on_press=toggled) as key_listener: key_listener.join()
为什么这个方案有效?
SendInput直接向Windows输入队列发送原始输入事件,不会像pynput的click方法那样先检查当前鼠标状态再调整行为。我们手动发送按下和释放事件,而物理上按住的左键状态会被GetAsyncKeyState持续检测到——因为这个函数读取的是硬件层面的按键状态,不受模拟输入事件的影响。
这样一来,只要你物理按住左键,点击器就会持续发送模拟点击,不会因为模拟操作导致状态检测中断。
内容的提问来源于stack exchange,提问作者ToeSpreader




