Вы должны быть осторожны при использовании нескольких потоков (в данном случае, поскольку mouse.Listener
работает в своем собственном потоке).Очевидно, что пока вы находитесь в функции обратного вызова, все события все еще обрабатываются, даже после того, как вы вызвали listener.stop()
.Таким образом, при воспроизведении для каждой установленной позиции мыши вызывается функция обратного вызова on_move
, поэтому позиция мыши снова добавляется в ваш список, что вызывает бесконечный цикл.
В общем, это плохая практикареализовать слишком много функций (в данном случае «воспроизведение») в функции обратного вызова.Лучшим решением было бы использовать событие, чтобы сообщить другому потоку, что кнопка мыши нажата.Смотрите следующий пример кода.Несколько замечаний:
- Я добавил несколько операторов печати, чтобы увидеть, что происходит.
- Я добавил небольшую задержку между позициями мыши, чтобы действительно увидеть воспроизведение.(NB: Это также может облегчить выход из приложения в случае зависания!)
- Я изменил несколько имен переменных, чтобы сделать их более понятными.Называть массив "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, и программа продолжит работу.