python non-blocking non-messing-my-tty key press detection

python, stdin

Solution

This has been asked before. Someone posted a nice, short, refactored solution

Reposted here

import sys
import select
import tty
import termios

class NonBlockingConsole(object):

    def __enter__(self):
        self.old_settings = termios.tcgetattr(sys.stdin)
        tty.setcbreak(sys.stdin.fileno())
        return self

    def __exit__(self, type, value, traceback):
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)


    def get_data(self):
        if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
            return sys.stdin.read(1)
        return False


if __name__ == '__main__':
    # Use like this
    with NonBlockingConsole() as nbc:
        i = 0
        while 1:
            print i
            i += 1

            if nbc.get_data() == '\x1b':  # x1b is ESC
                break

Problem

I have a loop that does some work and prints a lot of info to stdout. Over and over again (it's a loop...) What I'd like to do is to detect when / if user presses a key (it can be an arrow, enter, or a letter), and do some work when that happens. This should have been a very simple subsubtask, but I've spent last four hours trying different approaches and getting pretty much nowhere. This needs only work in Linux. Best I could get is something like this below. But that works partially, catching the keys only if within the `0.05` sec. ``` import sys,tty,termios class _Getch: def __call__(self, n=1): fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(n) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch def getch(timeout=0.2): inkey = _Getch() k = '' start_sec = time() while(time() - start_sec < timeout): if k == '': k = timeout_call(inkey, timeout_duration=timeout - (time() - start_sec)) if k == u'\x1b': k += inkey(2) if k == u'\x1b[A': return "up" if k == u'\x1b[B': return "down" if k == u'\x1b[C': return "right" if k == u'\x1b[D': return "left" elif k == "q": return 'q' elif k == "\n": return 'enter' else: return None while True: do_some_work_that_lasts_about_0_2_seconds() key = getch(0.05) if key: do_something_with_the(key) ```

Original source

Related problems