Search code examples
pythonpipebufferstdinbuffering

Python – select on unbuffered stdin


I'm really struggling with buffering in python3. I'm trying to implement a simple radio.

I have a receiver class. It displays available stations to the user. Those stations are dynamic and so they show up and disappear.

Welcome to the radio, select station you want to listen to.
> 1) Rock Station
  2) Hip Hop Station
  3) Country Station

So the receiver must wait for input both: from a Pipe (the information about new station showing up/disappearing) and from Stdin (user can use up and down arrows to change the station).

Moreover as user is changing the stations using arrow keys I have to read one character at a time from stdin.

This is why the standard select.select doesn't work (it waits for user to press ENTER key):


class _GetchUnix:
    def __init__(self):
        import tty, sys

    def __call__(self):
        import sys, tty, termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch


self.char_reader = _GetchUnix()
[...]

def __read_order_from_user(self,):
    k = self.char_reader()
    # Check for up/down arrow keys.
    if k == '\x1b':
        k = self.char_reader()
        if k != '[':
            return
        k = self.char_reader()
        if k == 'A':
            self.__arrow_down()
        if k == 'B':
            self.__arrow_up()

    # And check for enter key.
    if k == '\r':
        self.menu[self.option].handler()


def __update_stations(self,):
    [...]

def run(self):
    self.display()
    while True:
        rfds, _, _ = select.select([pipe, sys.stdin], [], [])

        if pipe in rfds:
                self.__update_stations()

        if sys.stdin in rfds:
            self.__read_order_from_user()

I've found on internet how to read one by one character from stdin: Python read a single character from the user and it does work, but not when used together with select.select.


Solution

  • I'm pasting the solution from VPfB comment here:

    "The raw tty mode is turned on to only to read one character and then turned off. When the select is active, it is turned off, because the routine to get the one character is called after the select. You should turn on raw tty input before the select loop and restore the tty setting after exiting the loop."