Real-time capture and processing of keypresses (e.

2019-09-04 10:05发布

问题:

Note: I want to do this without using any external packages, like PyGame, etc.

I am attempting to capture individual keypresses as they arrive and perform an action for specific characters, whether I simply want to "re-echo" the character, or not display it at all and do something else.

I have found a cross-platform (though not sure about OS X) getch() implementation because I do not want to read a whole line like input() does:

# http://code.activestate.com/recipes/134892/
def getch():
    try:
        import termios
    except ImportError:
        # Non-POSIX. Return msvcrt's (Windows') getch.
        import msvcrt
        return msvcrt.getch

    # POSIX system. Create and return a getch that manipulates the tty.
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch

[Attempt 1] I first tried a simple while-true loop to poll getch, but if I type too fast, characters go missing. Reducing the sleep time makes it worse. The debug statements only print on press of the enter key and not consistently in time nor position. (It appears there might be some line buffering going on?) Taking out the loop and sleep lets it work once but perfectly.

#!/usr/bin/env python3

import sys
import tty
import time


def main():
    while True:
        time.sleep(1)
        sys.stdout.write(" DEBUG:Before ")
        sys.stdout.write(getch())
        sys.stdout.write(" DEBUG:After ")


if __name__ == "__main__":
    main()

[Attempt 2] I got an example for using a threaded approach (https://stackoverflow.com/a/14043979/2752206) but it "locks up" and won't accept any input (including Ctrl-C, and etc)..

#!/usr/bin/env python3

import sys
import tty
import time
import threading

key = 'Z'


def main():
    threading.Thread(target=getchThread).start()

    while True:
        time.sleep(1)
        sys.stdout.write(" DEBUG:Before ")
        sys.stdout.write(key)
        sys.stdout.write(" DEBUG:After ")


def getchThread():
    global key
    lock = threading.Lock()
    while True:
        with lock:
            key = getch()


if __name__ == "__main__":
    main()

Does anyone have any advice or guidance? Or more importantly, can someone explain why the two attempts do not work? Thanks.

回答1:

First off, I don't really thing you need multithreading. You'd need that if you, for example, wanted to do some tasks like drawing on screen or whatever and capturing keys while you do this.

Let's consider a case where you only want to capture keys and after each keypress execute some action: Exit, if x was pressed, otherwise just print the character. All you need for this case is simple while loop

def process(key):
    if key == 'x':
        exit('exitting')
    else:
        print(key, end="", flush=True)

if __name__ == "__main__":
    while True:
        key = getch()
        process(key)

Notice absence of sleep(). I am assuming you thought getch() won't wait for user input so you set 1s sleep time. However, your getch() waits for one entry and then returns it. In this case, global variable is not really useful, so you might as well just call process(getch()) inside the loop.

print(key, end="", flush=True) => the extra arguments will ensure pressed keys stay on one line, not appending newline character every time you print something.

The other case, where you'd want to execute different stuff simultaneously, should use threading.

Consider this code:

n = 0
quit = False

def process(key):
    if key == 'x':
        global quit
        quit = True
        exit('exitting')
    elif key == 'n':
        global n
        print(n)
    else:
        print(key, end="", flush=True)

def key_capturing():
    while True:
        process(getch())

if __name__ == "__main__":
    threading.Thread(target=key_capturing).start()
    while not quit:
        n += 1
        time.sleep(0.1)

This will create global variable n and increment it 10 times a second in main thread. Simultaneously, key_capturing method listens to keys pressed and does the same thing as in previous example + when you press n on your keyboard, current value of the global variable n will be printed.

Closing note: as @zondo noted, you really missed braces in the getch() definition. return msvcrt.getch should most likely be return msvcrt.getch()