118

Is there an easy way to read a single char from the console as the user is typing it in Java? Is it possible? I've tried with these methods but they all wait for the user to press enter key:

char tmp = (char) System.in.read();
char tmp = (char) new InputStreamReader(System.in).read ();
char tmp = (char) System.console().reader().read();           // Java 6

I'm starting to think that System.in is not aware of the user input until enter is pressed.

Thomas Ahle
  • 28,005
  • 19
  • 77
  • 105
victor hugo
  • 34,018
  • 12
  • 65
  • 76

6 Answers6

62

What you want to do is put the console into "raw" mode (line editing bypassed and no enter key required) as opposed to "cooked" mode (line editing with enter key required.) On UNIX systems, the 'stty' command can change modes.

Now, with respect to Java... see Non blocking console input in Python and Java. Excerpt:

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.

One of the suggestions is to use JNI. Again, that's not very portable. Another suggestion at the end of the thread, and in common with the post above, is to look at using jCurses.

stkent
  • 18,470
  • 14
  • 80
  • 99
Chris W. Rea
  • 5,099
  • 36
  • 53
  • 4
    JCurses is not very portable either.... From the JCurses README: "JCurses consists of two parts: the plattform independent part, and plattform dependent part, that consists of a native shared library making primitive input and output operations available to the first part." – Ryan Fernandes Jul 01 '09 at 03:19
  • 6
    @RyanFernandes sounds quite portable to me - single tool that can be run on multiple systems (using different dependencies) – Antoniossss Oct 19 '17 at 21:56
26

You need to knock your console into raw mode. There is no built-in platform-independent way of getting there. jCurses might be interesting, though.

On a Unix system, this might work:

String[] cmd = {"/bin/sh", "-c", "stty raw </dev/tty"};
Runtime.getRuntime().exec(cmd).waitFor();

For example, if you want to take into account the time between keystrokes, here's sample code to get there.

TylerH
  • 19,065
  • 49
  • 65
  • 86
nes1983
  • 14,012
  • 4
  • 42
  • 63
19

I have written a Java class RawConsoleInput that uses JNA to call operating system functions of Windows and Unix/Linux.

  • On Windows it uses _kbhit() and _getwch() from msvcrt.dll.
  • On Unix it uses tcsetattr() to switch the console to non-canonical mode, System.in.available() to check whether data is available and System.in.read() to read bytes from the console. A CharsetDecoder is used to convert bytes to characters.

It supports non-blocking input and mixing raw mode and normal line mode input.

Christian d'Heureuse
  • 3,823
  • 1
  • 29
  • 26
  • How heavily has this been tested/stress-tested? – Fund Monica's Lawsuit May 15 '16 at 05:01
  • 2
    @QPaysTaxes Stress-testing is difficult for console input. I think, in this case it would be more important to test it in various environments (different Windows/Linux versions, 64/32 bit, Linux via SSH, Telnet, serial port or desktop console, etc.). So far I only use it in my private test tools. But the source code is relatively small, compared to other solutions (like JLine2 which uses Jansi). So there is not much that can go wrong. I wrote it, because JLine2 does not support single character input without blocking. – Christian d'Heureuse May 15 '16 at 23:04
  • That's what I meant by stress-tested -- it's probably the wrong word; my bad. Anyway, nice! I've stolen^H^H^H^H^H^Hused it in a project of mine for school and it helped a bunch. – Fund Monica's Lawsuit May 15 '16 at 23:19
  • Hey - this class looks great. However: I cannot get it to work.. how am I supposed to use it? I have encountered System.in blocking until I press CTRL+D (on Linux) and now I read about console modes and the likes. I think your RawConsoleInput is what I am looking for - but how do I use it? – Igor Aug 25 '16 at 07:48
  • @Igor Just call RawConsoleInput.read(boolean) to read a keyboard character. It's documented in the source code (RawConsoleInput.java). – Christian d'Heureuse Aug 26 '16 at 15:27
  • @Christiand'Heureuse: thanks! I had posted the comment, but after a while I found out how it works. Thank you! It works quite well in a linux terminal (have not tried it on Windows (yet)). – Igor Aug 28 '16 at 13:18
  • Very useful. You might want to add a simple example, e.g. `public static void main(String[] args) { RawConsoleInput GC = new RawConsoleInput(); int CharRead = 0; for (;;) { try { CharRead = GC.read(true); } catch (IOException ex) { Logger.getLogger(RawConsoleInput.class.getName()).log(Level.SEVERE, null, ex); } if (CharRead == -1 || CharRead == 27 || CharRead == 3 || CharRead == 4) // ^c, ^d, or Esc) break; switch (CharRead) { case } } } ` – trindflo Feb 21 '19 at 00:52
  • Nice... I got this working in a Windows command prompt and assume it works fine in Linux. Unfortunately I can't get it to work in a Windows Cygwin (BASH) terminal, even if I switch `isWindows` to `false`. Specifically it gives this error: Exception in thread "main" java.lang.UnsatisfiedLinkError: Unable to load library 'c': Native library (win32-x86-64/c.dll) not found in resource path (.;jna-4.1.0.jar) at com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:271) ... at consolereader.RawConsoleInput.main(RawConsoleInput.java:64) – mike rodent Mar 21 '19 at 16:23
  • Adding the source code to an existing project results in the following error in Netbeans: `package com.sun.jna does not exist`, does this have any external dependencies? Openjdk8 – M.E. Jun 01 '20 at 11:27
  • 1
    @M.E. The [JNA](https://github.com/java-native-access/jna) jar will have to be added in addition to RawConsoleInput.java. Also JNA [deprecated `Pointer.SIZE` in version 5](https://github.com/kaitoy/pcap4j/issues/191) so we also need to replace that with `Native.POINTER_SIZE` on line 170. – Thirdwater Sep 19 '20 at 05:11
  • @Thirdwater Thanks, I have updated the source code for the current JNA version 5.6.0. – Christian d'Heureuse Sep 19 '20 at 21:54
15

There is no portable way to read raw characters from a Java console.

Some platform-dependent workarounds have been presented above. But to be really portable, you'd have to abandon console mode and use a windowing mode, e.g. AWT or Swing.

rustyx
  • 62,971
  • 18
  • 151
  • 210
10

Use jline3:

Example:

Terminal terminal = TerminalBuilder.builder()
    .jna(true)
    .system(true)
    .build();

// raw mode means we get keypresses rather than line buffered input
terminal.enterRawMode();
reader = terminal .reader();
...
int read = reader.read();
....
reader.close();
terminal.close();
Pod
  • 3,510
  • 2
  • 31
  • 40
  • I found that RawConsoleInput based solutions didn't work on MacOS High Sierra; however, this works perfectly. – RawToast Apr 14 '18 at 21:30
  • jline has practically all you need to create an interactive console/terminal system. It works great in Linux. For a more complete example look at: https://github.com/jline/jline3/blob/master/builtins/src/test/java/org/jline/example/Example.java . It has autocomplete, history, password mask, etc. – lepe Jun 26 '18 at 07:28
1

I' ve done it using jcurses...

import jcurses.system.InputChar;
import jcurses.system.Toolkit;

//(works best on the local machine when run through screen)
public class readchar3 {
    public static void main (String[] args)
        {
            String st;
            char ch;
            int i;
            st = "";
            ch = ' ';
            i = 0;
            while (true)
                {
                        InputChar c = Toolkit.readCharacter();
                    ch = c.getCharacter();
                    i = (int) ch;
                    System.out.print ("you typed " + ch + "(" + i + ")\n\r");
                    // break on '#'
                    if (ch == '#') break;
                }
            System.out.println ("Programm wird beendet. Verarbeitung kann beginnen.");
        }
}
ewing
  • 11
  • 1