使用多个线程时必须小心(这里就是这种情况,因为mouse.Listener
在它自己的线程中运行)。显然,只要你在回调函数中,所有事件都会被处理,即使你调用了listener.stop()
. 所以回放的时候,对于你设置的每一个鼠标位置,都会on_move
调用回调函数,这样鼠标位置就会再次添加到你的列表中,从而导致死循环。
一般来说,在回调函数中实现太多功能(在本例中为“重放”)是不好的做法。更好的解决方案是使用事件来通知另一个线程已单击鼠标按钮。请参阅以下示例代码。几点说明:
- 我添加了一些打印语句来查看发生了什么。
- 我在鼠标位置之间添加了一个小的延迟以真正看到播放。(注意:这也可能会使应用程序挂起时更容易中断!)
- 我更改了一些变量名称以使其更有意义。调用数组“arr”不是一个好主意。尝试使用真正描述变量的名称。在这种情况下,它是一个职位列表,所以我选择调用它
positions
。
- 我
return False
用来停止鼠标控制器。文档指出“从任何地方调用,从回调中pynput.mouse.Listener.stop
引发StopException
或返回False
以停止侦听器。”,但就个人而言,我认为返回 False 是最干净和最安全的解决方案。
import threading
import time
import pynput
positions = []
clicked = threading.Event()
controller = pynput.mouse.Controller()
def on_move(x, y):
print(f'on_move({x}, {y})')
positions.append((x, y))
def on_click(x, y, button, pressed):
print(f'on_move({x}, {y}, {button}, {pressed})')
# Tell the main thread that the mouse is clicked
clicked.set()
return False
listener = pynput.mouse.Listener(on_move=on_move, on_click=on_click)
listener.start()
try:
listener.wait()
# Wait for the signal from the listener thread
clicked.wait()
finally:
listener.stop()
print('*REPLAYING*')
for position in positions:
controller.position = position
time.sleep(0.01)
请注意,当您在 Windows 命令提示符下运行此程序时,应用程序可能会挂起,因为您已按下鼠标按钮,然后开始发送鼠标位置。这会导致“拖动”移动,从而暂停终端。如果发生这种情况,您只需按 Escape,程序将继续运行。