2

I want to capture "all" keyboard input on the command line immediately (without waiting for a new line) in a non-blocking way.

This question demonstrates how you can read from stdin in a non-blocking way using select(). This is how it works:

while True:
    if select.select([sys.stdin], [], [], 0)[0] == [sys.stdin]:
        print(sys.stdin.read(1))

    ... do other stuff ...

Unfortunately you only get results after pressing RETURN.

My first guess was that stdin is just line buffered so after reading this question I turned my code into

ubuf_stdin = os.fdopen(sys.stdin.fileno(), 'rb', buffering=0)
while True:
    if select.select([ubuf_stdin], [], [], 0)[0] == [ubuf_stdin]:
        print(ubuf_stdin.read(1))
    ... do other stuff ...

This code works better - the buffer is being read completely without interruptions - but it still waits for RETURN being pressed.

I also tried to detach stdin:

ubuf_stdin = os.fdopen(sys.stdin.detach().fileno(), 'rb', buffering=0)

How do I react on keyboard input immediately after a key press?

Community
  • 1
  • 1
frans
  • 8,868
  • 11
  • 58
  • 132
  • The answer to this [question](http://stackoverflow.com/questions/10247591/setvbuf-not-able-to-make-stdin-unbuffered) suggests it is not possible. – Sergei Lebedev Mar 17 '16 at 17:23

2 Answers2

0

Use the getch module to get a single keypress at a time, as described in this question.

I don't think you'll be able to read stdin in a non-blocking-yet-character-at-a-time way, it's really not intended for that purpose.

user2926055
  • 1,963
  • 11
  • 10
  • Well - that can't be the whole truth - at least `ncurses` (which is available on POSIX as well as on Windows) can read from `stdin` character wise.. – frans Mar 17 '16 at 18:04
  • 1
    I'm pretty sure `ncurses` and its variants are using /dev/tty or the equivalent, not the actual stdin file descriptor. I could be totally speaking out my ass, too. – user2926055 Mar 17 '16 at 19:00
  • Ok, maybe you're right - I have to change my title. What I really want is capture keyboard input in a command line program. `stdin` was just the first thing to come into my mind :) – frans Mar 17 '16 at 19:09
  • Then `getch` is probably your best bet. – user2926055 Mar 17 '16 at 19:21
  • but `getch` blocks, doesn't it? The advantage is that with `getch` you don't see the characters on the screen but you would have to either start a thread or somehow `select()` the terminal device.. – frans Mar 17 '16 at 19:30
  • Yes, but it blocks for a single keypress, so the idea is to just loop forever, and start each loop with a `getch`, and then do something based on that keypress: call a function, buffer it into a sequence of keystrokes, etc. See the code in [this answer](http://stackoverflow.com/a/12179724/2926055). – user2926055 Mar 17 '16 at 20:06
0

Ok this one does it but it has some disadvantages yet:

  • uses the curses module which seems a bit heavy to me
  • input has to be polled - no way to select() yet.
  • the keyboard input still gets printed to the terminal

.. but it works :).

import curses
def read_stdin(self):
    def cb(screen):
        result = []
        screen.nodelay(1)
        while True:
            try:
                result.append(screen.getkey())
            except curses.error:
                return result

    # contains a list of keys pressed since last call
    return curses.wrapper(cb)
frans
  • 8,868
  • 11
  • 58
  • 132