moses-palmer / pynput

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

Is it possible to create 2 separate Threads for on_press and on_release events alongside the Main thread? #327

Open fabriciopirini opened 3 years ago

fabriciopirini commented 3 years ago

Hi! I'm trying to translate keyboard keys to joystick movements on an emulator screen (BlueStacks) on my Mac Catalina.

What I'm trying to achieve is to keep the movement until the key is released so I decided to create a loop to repeat the movement until the key is released. However, the loop is blocking the Thread so I can't accomplish that easily. I tried then to create 2 Threads, 1 for on_press events and another for on_release events but that don't work either since I always get AttributeErrors from the objc for any key I press.

Part of my code:

CURRENT_STATE = 'released'
DELAY = 0.25

def on_press(key):
    try:
        global CURRENT_STATE
        CURRENT_STATE = 'pressing'

        X, Y = INITIAL['X'], INITIAL['Y']

        if key == Key.shift:
            print('UP')
            X, Y = MOVES['UP']
        elif key == Key.ctrl:
            print('LEFT')
            X, Y = MOVES['LEFT']
        elif key == Key.alt:
            print('DOWN')
            X, Y = MOVES['DOWN']
        elif key == Key.cmd:
            print('RIGHT')
            X, Y = MOVES['RIGHT']

        if key in VALID_KEYS:

            while CURRENT_STATE == 'pressing':
                device.input_swipe(INITIAL['X'], INITIAL['Y'], X, Y, DELAY * 1000)
                time.sleep(DELAY)

    except Exception as err:
        print(err)

def on_release(key):
    if key in VALID_KEYS:
        global CURRENT_STATE
        CURRENT_STATE = 'released'
        print('released')
    elif key == Key.cmd_r:
        # Stop listener
        return False

def main():
    press_listener = Listener(on_press=on_press)
    press_listener.start()
    release_listener = Listener(on_release=on_release)
    release_listener.start()

    while True:
        continue

main()

Thanks for any help and directions =)

Disclaimer: I tried to use pyinput for mouse events but I couldn't make it work on my Mac, even allowing my terminal and IDE on Accessibility (couldn't auth Python) and running with sudo so I'm using ADB.

moses-palmer commented 3 years ago

Have you considered creating a thread responsible for sending device events (device.input_swipe) that reads a global state, like:

class State(enum.Enum):
    IDLE = 0
    MOVE_DOWN = 1
   # ...
    QUIT = N

and then just update the global state from the callback? The thread body would be a simple while STATE != State.Quit-loop.

And your problems with mouse events seem strange, since keyboard events require even more privileges. Do you have a stack trace or other information? Or do you simply not receive any events?

fabriciopirini commented 3 years ago

Thanks for the reply! I will soon try again to use pyinput and I will try out your approach and post the feedback here.

I don't have any stack trace, no events were coming indeed so I believe Mac was swallowing them.

moses-palmer commented 3 years ago

To investigate the missing events, you might want to add some debug logging here.