moses-palmer / pynput

Sends virtual input commands
GNU Lesser General Public License v3.0
1.79k stars 248 forks source link

How to stop listener started from a function/pass variables with pynput? #393

Closed VellichorPrism closed 3 years ago

VellichorPrism commented 3 years ago

Description Hello! I am trying to start a keyboard and mouse listener with pynput. I have to start it from a function because I want to eventually have a button with a GUI call the function to start the listeners. After the listeners detect a mouse click or the press of the esc key, I want to stop the listeners. But because the listener threads are started locally, I don't know how to pass the variables to the listener functions so that the threads can be stopped from the other functions. I have created a small script that can replicate the problem below. What I have right now is when the esc key is pressed, go = False, causing the functions to execute the else blocks to stop the threads. Any of the methods I try do not stop the threads though, and they keep printing "Hello" to prove that the threads have not been stopped. Also, pynput.mouse.Listener.stop and mouse.Listener.stop does not work.

Platform and pynput version Windows 10 pynput version 1.7.3

To Reproduce

from PIL import ImageGrab
from pynput import mouse, keyboard
import time
import sys

go = True

def on_function_esc(key):
    global go
    print('HELLO')
    if go:
        if key == keyboard.Key.esc:
            print('"esc" has been pressed')
            go = False
    else:
        # These do not stop the threads
        mouse_l.stop()
        keyboard_l.stop()
        sys.exit()
        return False

def on_move(x, y):
    global go
    print('HELLO')
    if go:
        print('Mouse is moving')
    else:
        # These do not stop the threads
        mouse_l.stop()
        keyboard_l.stop()
        sys.exit()
        return False

def on_click(x, y, button, pressed):
    global go
    print('HELLO')
    if go:
        if not pressed:
            print('Mouse has been clicked')
            go = False
    else:
        # These do not stop the threads
        mouse_l.stop()
        keyboard_l.stop()
        sys.exit()
        return False

'''
How can I pass the "keyboard_l" and "mouse_l" variables
to be used by the other functions to stop these listener threads?
'''
def activate():
    keyboard_l = keyboard.Listener(on_release=on_function_esc)
    mouse_l = mouse.Listener(on_click=on_click, on_move=on_move)
    keyboard_l.start()
    mouse_l.start()

if __name__ == '__main__':
    activate()

# Placeholder for GUI that will be implemented in the future
while True:
    print('GUI Should Be Running')
    time.sleep(3)
moses-palmer commented 3 years ago

Thank you for your report.

This is not really an issue with pynput, but rather a generic Python question about scoping. In the future, may I ask you to direct those questions to appropriate forums?

In any case, the code below shows how to pass the listener instances to your event callbacks by using a lambda:

from pynput import mouse, keyboard

def on_release(mouse_l, keyboard_l, key):
    if key == keyboard.Key.esc:
        print('"esc" has been pressed')
        mouse_l.stop()
        keyboard_l.stop()

def on_move(mouse_l, keyboard_l, x, y):
    print('move({}, {})'.format(
        x, y))

def on_click(mouse_l, keyboard_l, x, y, button, pressed):
    print('{}[{}]({}, {})'.format(
        'press' if pressed else 'release', button, x, y))

def activate():
    keyboard_l = keyboard.Listener(
        on_release=lambda *a: on_release(mouse_l, keyboard_l, *a))
    mouse_l = mouse.Listener(
        on_click=lambda *a: on_click(mouse_l, keyboard_l, *a),
        on_move=lambda *a: on_move(mouse_l, keyboard_l, *a))
    keyboard_l.start()
    mouse_l.start()

    def poll():
        keyboard_l.join(0.5)
        mouse_l.join(0.5)
        return not (keyboard_l.is_alive() or mouse_l.is_alive())
    return poll

if __name__ == '__main__':
    poll = activate()

    # Placeholder for GUI that will be implemented in the future
    while True:
        if poll():
            break