moses-palmer / pynput

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

Keyboard listener with supress=True breaks gnome shell UI #485

Closed i30817 closed 2 years ago

i30817 commented 2 years ago

Description Alt+tab no longer works, the activities corner button clicking no longer triggers, and if i lose the focus on the console running the script, i can no longer regain it if it's not visible (alt-tab doesn't work).

Platform and pynput version Linux sleipnir 5.13.0-48-generic #54~20.04.1-Ubuntu SMP Thu Jun 2 23:37:17 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux GNOME version : 3.36.8 X11

To Reproduce

#! /usr/bin/env python3

from contextlib import contextmanager
from pynput import keyboard
from pynput.keyboard import Key
from datetime import datetime

presses = 0
def press(key):
    global presses
    presses +=  1

@contextmanager
def keyboard_exclusive_listener():
    listener = keyboard.Listener(on_press=press, suppress=True)
    listener.start()
    listener.wait()
    try:
        yield listener
    finally:
        listener.stop()

def test():
    start_time = datetime.now()
    with keyboard_exclusive_listener():
        while(True):
            time_delta = datetime.now() - start_time
            if time_delta.total_seconds() >= 10:
                break

if __name__ == "__main__":
    test()

Then try to lose focus (click another window in gnome) and alt-tab. It's almost like the keyboard listener is following whatever application screen has focus, not the 'original' one.

btw i also didn't expect 'press' to be constantly called if i kept the key i was pressing held down. In the real application i have a mutex in that function and use the counter as guard (apparently uselessly). This caused a bug that the test for if something was 'pressed > 0' was always true. Replacing it by flags works of course. But it was a little bit surprising, maybe a mention in the main documentation?

i30817 commented 2 years ago

BTW, i need suppress=True just to make sure the user can't corrupt the console output with input at the console cursor. I was wondering if you have a better idea to make it impossible to corrupt that output text, preferably without disabling ctrl+c (after putting in the 'suppress=True' i made escape quit the program instead which is also acceptable).

i30817 commented 2 years ago

Ah... i didn't realize this is 'intended'. Blocks input system wide, paraphrasing from https://pynput.readthedocs.io/en/latest/faq.html#how-do-i-suppress-specific-events-only

Mmm what's a good alternative that only blocks the current console? I guess this is not portable otherwise it would have been done in the last 10 years.

moses-palmer commented 2 years ago

Thank you for your report.

Yes, as you found in the documentation, this is the expected behaviour. If all you want is to suppress output of the keys pressed by the user while you application is running, perhaps you should look into the termios module.

i30817 commented 2 years ago

For anyone using google, I'm converting my application to async with prompt toolkit. This api has a nice example to checkout how to globally listen to keys only on the 'current console' here (you can convert it to a @asynccontextmanager by instead of await done.wait() doing a yield done for the caller to wait for a exit signal (you can set other booleans too):

https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html#reading-keys-from-stdin-one-key-at-a-time-but-without-a-prompt

Then as long as the functions you use to modify and check the guards are synchronous, there is no problem in using them without locks afaict. You'll have other problems converting a app to asyncio, if you don't use it already, but it's relatively easy to just plop await in front of every function that needs it and use asyncio.sleep() or await for if the api you're using permits when iterating over something you want to check a condition set on that loop.