You have to be careful when using multiple threads (which is the case here, since mouse.Listener
runs in its own thread). Apparently, as long as you are in the callback function, all events are still processed, even after you have called listener.stop()
. So when replaying, for each mouse position you set, the on_move
callback function is called, so that mouse position is added to your list again, which causes the endless loop.
In general, it's bad practice to implement too much functionality (in this case the "replaying") in a callback function. A better solution would be to use an event to signal another thread that the mouse button has been clicked. See the following example code. A few remarks:
- I've added a few print statements to see what's happening.
- I've added a small delay between the mouse positions to really see the playback. (NB: This also might make breaking out of the application a bit easier in case it hangs!)
- I've changed a few variable names to make more sense. Calling an array "arr" is not a good idea. Try to use names that really describe the variable. In this case it is a list of positions, so I choose to call it
positions
.
- I'm using
return False
to stop the mouse controller. The documentation states "Call pynput.mouse.Listener.stop
from anywhere, raise StopException
or return False
from a callback to stop the listener.", but personally, I think returning False is the cleanest and safest solution.
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)
Note that when you run this in a Windows command prompt, the application might hang because you have pressed the mouse button and are then starting to send mouse positions. This causes a "drag" movement, which pauses the terminal. If this happens, you can just press Escape and the program will continue to run.