I'm trying my hand at Pynput, and I'm starting off with creating a simple program to record the movements of a mouse, and then replay those movements once a button is clicked.
However, every time I click the mouse, it just starts to freak out and endlessly loop. I think it's going through the movements at a super high speed, but I eventually have to Alt-F4 the shell to stop it.
Any help would be appreciated.
import pynput
arr = []
from pynput import mouse
mou = pynput.mouse.Controller()
def on_move(x,y):
Pos = mou.position
arr.append(Pos)
def on_click(x, y, button, pressed):
listener.stop()
for i in arr:
mou.position = i
print("Done")
listener = mouse.Listener(on_move = on_move, on_click=on_click)
listener.start()
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:
positions
.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.