December 18, 2007

Non blocking console input in Python and Java

Posted in Software at 21:42 by graham

Update Sep 2011: If you’re using Google’s Go Lang see Non blocking console read in Go. Thanks Ostsol


Heavily updated 9th January 2008: This post was originally entitled Non blocking console IO is not possible. Two helpful comments helped me see the error of my ways. Many thanks to ‘schlenk’ and ‘Bob’ for the help. Non blocking console IO is possible, it just isn’t (easily) portable. Read on to find out how.


I have been doing some programming exercises in Python, Java and ActionScript (Flex), using this list from Prashant N Mhatre. The first exercise sounds simple on the surface:

Display series of numbers (1,2,3,4, 5….etc) in an infinite loop. The program should quit if someone hits a specific key (Say ESCAPE key)

Displaying a list of numbers in an infinite loop is trivial, and stopping on Ctrl-C is trivial, but stopping on a key of your choice (let’s use ESC), makes the problem much more interesting.

By default the console on Linux and Windows is buffered. It does not send you character data until the Enter key is pressed. In Python the raw_input method will block until it gets input. In Java you can test the characters available, non blocking, using System.in.available(), but this still doesn’t fill up until Enter is pressed. There are two ways to solve this:

The portable way: A windowing toolkit

If you don’t do console IO at all, but use a very simple graphical program, you can easily attach listeners to key presses. To ease deployment you may want to use a toolkit included with your language, such as Tkinter in Python and Swing in Java. As I am doing an exercise, that’s not what I chose.

Once you install the dependencies, both of these programs run as is on Linux and Windows.

Python with pygame

In Python I chose pygame, which gives us:

import pygame from pygame.locals import *

def display(str):

        text = font.render(str, True, (255, 255, 255), (159, 182, 205))
        textRect = text.get_rect()
        textRect.centerx = screen.get_rect().centerx
        textRect.centery = screen.get_rect().centery

        screen.blit(text, textRect)
        pygame.display.update()

pygame.init()
screen = pygame.display.set_mode( (640,480) )
pygame.display.set_caption('Python numbers')
screen.fill((159, 182, 205))

font = pygame.font.Font(None, 17)

num = 0
done = False
while not done:

        display( str(num) )
        num += 1

        pygame.event.pump()
        keys = pygame.key.get_pressed()
        if keys[K_ESCAPE]:
                done = True

Java with jCurses

In Java I chose to use [curses](http://en.wikipedia.org/wiki/Curses_(programming_library)) library for that authentic 80s look using the JCurses library.

import java.io.IOException;

import jcurses.widgets.*;
import jcurses.system.Toolkit;
import jcurses.system.CharColor;

public class Numbers {
        public static void main(String[] args) {

            Window w = new Window(40, 20, true, "Numbers");
            DefaultLayoutManager mgr = new DefaultLayoutManager();
            mgr.bindToContainer(w.getRootPanel());

            CharColor color = new CharColor(CharColor.WHITE, CharColor.GREEN);

            w.show();

            int num = 0;

            while ( ! w.isClosed() ) {
                Toolkit.printString( ""+ num, 45, 17, color);
                num++;
            }
        }
}

Real non-blocking console input

If your program must be console based, you have to switch your terminal out of line mode into character mode, and remember to restore it before your program quits. There is no portable way to do this across operating systems.

Python non-blocking console input on Linux

The tty module has an interface to set the terminal to character mode, the termios module allows you to save and restore the console setup, and the select module lets you know if there is any input available.

import sys import select import tty import termios

def isData():
        return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

old_settings = termios.tcgetattr(sys.stdin)
try:
        tty.setcbreak(sys.stdin.fileno())

        i = 0
        while 1:
                print i
                i += 1

                if isData():
                        c = sys.stdin.read(1)
                        if c == '\x1b':         # x1b is ESC
                                break

finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

Java non-blocking console input on Linux

Java does not make the pragmatic exceptions to portability that Python does, so there is no convenient package to switch the console to character mode. Instead we have to run stty from a shell. Otherwise the principle is the same. Much of this code is borrowed from the JLine project.

import java.io.IOException; import java.io.ByteArrayOutputStream; import java.io.InputStream;

public class NumbersConsole {

    private static String ttyConfig;

    public static void main(String[] args) {

            try {
                    setTerminalToCBreak();

                    int i=0;
                    while (true) {

                            System.out.println( ""+ i++ );

                            if ( System.in.available() != 0 ) {
                                    int c = System.in.read();
                                    if ( c == 0x1B ) {
                                            break;
                                    }
                            }

                    } // end while
            }
            catch (IOException e) {
                    System.err.println("IOException");
            }
            catch (InterruptedException e) {
                    System.err.println("InterruptedException");
            }
            finally {
                try {
                    stty( ttyConfig.trim() );
                 }
                 catch (Exception e) {
                     System.err.println("Exception restoring tty config");
                 }
            }

    }

    private static void setTerminalToCBreak() throws IOException, InterruptedException {

        ttyConfig = stty("-g");

        // set the console to be character-buffered instead of line-buffered
        stty("-icanon min 1");

        // disable character echoing
        stty("-echo");
    }

    /**
     *  Execute the stty command with the specified arguments
     *  against the current active terminal.
     */
    private static String stty(final String args)
                    throws IOException, InterruptedException {
        String cmd = "stty " + args + " < /dev/tty";

        return exec(new String[] {
                    "sh",
                    "-c",
                    cmd
                });
    }

    /**
     *  Execute the specified command and return the output
     *  (both stdout and stderr).
     */
    private static String exec(final String[] cmd)
                    throws IOException, InterruptedException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        Process p = Runtime.getRuntime().exec(cmd);
        int c;
        InputStream in = p.getInputStream();

        while ((c = in.read()) != -1) {
            bout.write(c);
        }

        in = p.getErrorStream();

        while ((c = in.read()) != -1) {
            bout.write(c);
        }

        p.waitFor();

        String result = new String(bout.toByteArray());
        return result;
    }

}

What about Windows?

In Python there is an msvcrt module which provides a kbhit() and a getch() method.

In Java, the JLine project provides a Windows DLL to set the console to character mode. JLine also manages to provide a portable (between Windows and Linux) way of setting the console to character or raw mode, by using either its DLL or stty, depending on the OS detected. This would be a good place to start to build your own portable non-blocking console package in Java.

For Microsoft languages, there is a SetConsoleMode method that allows you to disable the ENABLE_LINE_INPUT flag, thus switching to character mode.

Happy non-blocked inputting!

16 Comments »

  1. read single char from console in Java (as user types it) | Coding and Programing said,

    June 5, 2014 at 14:11

    […] with respect to Java… see Non blocking console input in Python and Java. […]

  2. smitty said,

    January 13, 2014 at 20:54

    !/usr/bin/python -tt

    Your’es to use

    import sys import select import tty import termios import time

    def isData(): return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

    def inkey(): old_settings = termios.tcgetattr(sys.stdin) try: tty.setcbreak(sys.stdin.fileno()) if isData(): c = sys.stdin.read(1) else: c=None finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) return c

    tryer

    i = 0 while 1: print i i += 1 x=inkey() if x != None: print “bdang”,x time.sleep(2) if x == ‘q': break

  3. Nonblocking console input in Python | Andrew Berezovskiy notes said,

    February 8, 2012 at 00:35

    […] Nonblocking console input in Python Posted on 08.02.2012 by admin By Nemesis Fixx: […]

  4. douniwan5788 said,

    November 25, 2011 at 15:50

    I got an idea , use PushbackInputStream encapsulate system.in,always left a char in buffer ,then its become non block,i don’t know wether it works,i am running a test

  5. Robbie said,

    November 18, 2011 at 13:38

    Bill, your suggestion is great but only if you don’t want to follow every key press right away. You don’t see the content of “raw_input()” until “\n” is pressed…

  6. Simple key listener – java – Programmers Goodies said,

    November 14, 2011 at 19:36

    […] This page presents a method of setting the console into non-blocking mode in order to read a character, which you could use to break your loop. It also presents a few other methods for both Python and Java, but it has to be considered somewhat hacky and non-portable (wouldn’t work under Windows for example). I don’t think there is a ‘nice’ easy way to do it I’m afraid. […]

  7. Graham King said,

    September 30, 2011 at 17:58

    @ro0t You still need to set your console to raw mode, or readline (in your console) won’t give you any data until Enter is pressed.

    In my example I’m printing numbers, so I need a busy loop. In the general case yes poll is much nicer, thanks. If we’re only using poll for this case, I don’t think you even need to check the return value, so you have:

    import sys, select, tty, termios
    old_settings = termios.tcgetattr(sys.stdin)
    try:
        tty.setcbreak(sys.stdin.fileno())
        poll = select.poll()
        poll.register(sys.stdin, select.POLLIN)
        while 1:
            poll.poll()
            c = sys.stdin.read(1)
            print(c)
    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
    
  8. ro0t said,

    June 13, 2011 at 22:30

    learn about poll system call… on Python, lets use poll since is portable:

    poll = select.poll()
    poll.register(sys.stdin, select.POLLIN)
    events = poll.poll()
    for fid, event in events:
        if fid == sys.stdin.fileno():
            in = raw_input()
            print 'user,says something:', in
        else:
           make something else...
    
  9. Nemesis Fixx said,

    June 9, 2011 at 12:32

    Thanks Graham; your python non-blocking solution for the console is elegant (python is prose with power).

    Though I particularly failed to get Bill Hamilton’s said ‘elegant’ solution to pull off the trick, this is how I combined both your approaches to do the thing with threading thrown in:

    import sys
    import select
    import tty
    import termios
    from threading import Thread
    
    program_run = True
    input_thread_timeout = 0.005 #seconds
    quit_key = '\x1b' # x1b is ESC
    
    #check stdin for input...
    def isData():
            return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
    
    #check n terminate program on terminal condition,
    #from a separate thread
    class waitOnInput(Thread):
        def run(self):
            old_settings = termios.tcgetattr(sys.stdin)
            try:
                tty.setcbreak(sys.stdin.fileno())
                global program_run
                thread_run = True
                while thread_run:
                    if isData():
                        c = sys.stdin.read(1)
                        if c == quit_key:
                            break
                            thread_run = False
                            program_run = False
            finally:
                termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
                thread_run = False
    
    
    t = waitOnInput()
    
    #start work here...
    i = 1
    
    while program_run :
        if not t.is_alive():
            t.start()
    
        #test for terminal condition or timeout...
        t.join(input_thread_timeout)
    
        if t.is_alive():
            #continue work here...
            print i
            i += 1
        else:
            break
    
  10. Strawbot said,

    May 13, 2011 at 19:31

    the spam protection math below should accept a text answer since it’s query is in text.

  11. Strawbot said,

    May 13, 2011 at 19:30

    Thanks to all those above. I thought I’d post my interpretation of the above information for a simple test program that works on macs and windows. This reads and prints 10 characters with no echoes of character and it blocks on the single character:

    import sys
    try:
        if sys.platform == 'win32':
            import msvcrt
            def getKey():  // blocking read of one character for windows
                return msvcrt.getch()
        else:
            import termios, tty
            old_settings = termios.tcgetattr(sys.stdin)
            tty.setcbreak(sys.stdin.fileno())
    
            def getKey(): // blocking read of one character for mac
                return sys.stdin.read(1)
    
        for i in range(10):
            k = getKey()
            print k
    finally:
        if sys.platform != 'win32':
            import termios
            termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
    
  12. Aubrey Bourke said,

    August 25, 2010 at 19:55

    Hi Graham,

    Thanks for taking the time to provide the non blocking example in Java. Your code works well.

    However, when I add to your code, with other character input, like this:

     if ( System.in.available() != 0 ) {
        int c = System.in.read();
        if ( c == 0x1B ) {
            break;
        }
        if (c ==0x1F ){
            System.out.println("down arrow pressed")'
        }
    
    }
    

    it unexpectedly breaks every time, even when i hit the down arrow key!!

    Why does that happen? & What should I do to get it to work?

    Regards Aubrey.

  13. Bill Hamilton said,

    December 21, 2009 at 21:14

    Hey There,

    I find your solution was not very elegant in python. Unfortunately Im using mac and although I LOVE pygame, I cant seem to get it to work. Also Im using console and don’t want to be unix specific. So what I did is this:

    from threading import Thread
    program_run = True
    
    class waitOnInput(Thread):
        def run(self):
        global program_run
        thread_run = True
        while thread_run :
                data = raw_input('input command (q for quit)->')
            print 'got input : ', data
            if data == 'q' :
                thread_run = False
            program_run = False
    
    while program_run :
        t = waitOnInput()
        t.start()
        #do stuff
    
    t.join()
    
  14. Codifex Maximus said,

    March 9, 2009 at 15:23

    Graham,

    Your notes on Python non-blocking console input on Linux were extremely helpful.

    The listed code seems to work fine in Solaris as well. Because the code is based on POSIX, it should be cross-platform capable. I haven’t tested it on Windows though.

    Thank you, Codifex

  15. schlenk said,

    December 31, 2007 at 13:36

    Graham: I updated the page heavily thanks to this comment and Bob’s one below. Thanks!

    Thats just plain wrong and just a limitation of your console code.

    On Unix you can set your console from cooked to raw mode with the right stty invocation before running your stuff and then have the behaviour you need. On windows you just need a lib to configure your console correctly and you get single char input just fine. Most libs use line input on windows though, but thats just a matter of adding some flags when configuring the console.

    See http://msdn2.microsoft.com/en-us/library/ms686033(VS.85).aspx

    Michael

  16. Bob said,

    December 31, 2007 at 02:42

    I think you mean “Non-blocking console IO is not possible in the languages I’m using.”

    For everyone else non-blocking IO is a piece of cake. Try checking out the man pages for fcntl, paying special attention to the O_NONBLOCK flag.

Leave a Comment

Note: Your comment will only appear on the site once I approve it manually. This can take a day or two. Thanks for taking the time to comment.