Graham King

Solvitas perambulum

Non blocking console input in Python and Java

software

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:

quickcode:noclick 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.

quickcode:noclick 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.

quickcode:noclick 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.

<quickcode:Java non-blocking console input on Linux - click to view> 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!