'reading from stdin without blocking

I'm writing a python application that needs to explicitly parse all the keyboard input. Therefore I wrote a little loop that keeps reading from stdin. This works fine, however, the stdin.read(1) blocks until we type a character. Now I would like it to timeout after (for example) 1 second so other things can happen. I read about the select module in python and now I have the following:

def getch(timeout):                                                                                                                                          
    fd = sys.stdin.fileno()                                                                                                                                   
    old_settings = termios.tcgetattr(fd)                                                                                                                      
    ch = None                                                                                                                                                 
    try:                                                                                                                                                      
        tty.setraw(fd)                                                                                                                                        
        rlist, _, _ = select([sys.stdin], [], [], timeout)                                                                                                    
        if len(rlist) > 0:                                                                                                                                    
            ch = sys.stdin.read(1)                                                                                                                            
    finally:                                                                                                                                                  
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)                                                                                                
    return ch                                                                                                                                                 

The problem with this code, is when I hit an arrow key, I only receive the '\x1b'. The select function never triggers for the remaining '[' and 'D'.

How can I properly read these arrow key characters? Or how can I make the select function trigger again (because there is still data available on stdin).



Solution 1:[1]

Arrow keys are ANSI scape sequences. They start with Escape (Hex: \x1b, Octal: \033, Decimal: 27) followed by a bracket [. Following are the key codes:

import sys, termios, tty

esc = 27
up = "\033[A"
down = "\033[B"
right = "\033[C"
left = "\033[D"

if __name__ == "__main__":
    fd_input = sys.stdin.fileno()
    
    # save previous terminal attributes
    term_attrs_old = termios.tcgetattr(fd_input)
    
    # Set terminal to raw mode. It gives you back what ever was typed,
    # as raw bytes without any processing
    tty.setraw(fd_input)

    # read 4 bytes and encode it
    ch = sys.stdin.buffer.raw.read(4).decode(sys.stdin.encoding)

    if len(ch) == 1:
        # if it is 1 char, and non-printable, return its Unicode code
        if ord(ch) < 32 or ord(ch) > 126:
            ch = ord(ch)
    
    elif ord(ch[0]) == 27:
        # if the input is more that 1 char and it starts with 27, 
        # user has entered a scape sequence
        # read its rest
        ch = "\033" + ch[1:]
        if ch == down:
            ch = "down"
        if ch == up:
            ch = "up"
        if ch == right:
            ch = "right"
        if ch == left:
            ch = "left"
    
    # reset terminal back to its setting (disable raw mode again)
    termios.tcsetattr(fd_input, termios.TCSADRAIN, term_attrs_old)
    print(ch)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 zardosht