This is a very simple Java library for working with the Linux terminal. It allows client applications to do something like this, for example:
(Flickering is an artifact of the screen capturing process.)
Terminality is designed to allow basic terminal manipulation: moving the cursor around, setting the color of text and background, and non-blocking reading of keyboard input.
I wanted to create terminal applications that could do more than simply print text at the current cursor position and read keyboard input line by line. Unfortunately, this is not possible using the Java Class Library. There is Lanterna which is an absolutely fantastic piece of software, but it comes with a lot of features that I don’t want or need (such as the built-in Swing terminal emulator, an UI library similar to ncurses, Windows support etc.). Hence this project.
The library has been extensively tested on Linux (with GNU C Library) using a variety of terminal emulators (xterm, urxvt, kitty, Konsole and Gnome Terminal). It is supposed to be compatible with macOS, but I have not tested it. It should work in WSL2, but I have not tested it for compatibility with the Windows Terminal. I have not tested Terminality with alternative libc implementations, e.g. musl.
Terminality uses JNA to call native libc functions on Posix-compatible systems. These functions are called to switch the terminal into the so-called “raw mode” (see Low-level Terminal Interface). After the terminal is set up as required, the library uses ANSI Escape sequences to control the output.
To initialise the terminal:
UnixTerminal t = new UnixTerminal();
t.begin(); // enter the raw modeTo exit the raw mode and restore the original attributes of the terminal:
t.end();Even if your program terminates unexpectedly, Terminality will try to leave the terminal in a usable state by using a shutdown hook.
To read the keyboard, use the readKey() method. The method returns an object of the KeyStroke class. It contains the type of the key (see KeyType.java), the character (if ks.type == KeyType.CHARACTER) and the status of the modifier keys (ks.ctrl, ks.alt and ks.shift). The shift field is useful only when the user presses special keys (e.g. [Ctrl]+[Shift]+[F5]), otherwise it would always be false. The library tries to do its best to guess when the [Ctrl] key is used, but due to the nature of the Unix terminal it’s not always possible (for example, there is literally no way to tell whether the user has pressed [Ctrl]+[h] or [Backspace]; this also applies to several other key combinations).
The library supports three modes for reading keyboard input: blocking, non-blocking and asynchronous.
Blocking keyboard I/O is useful when your program simply reacts to user input and does nothing in the background. To use it:
KeyStroke ks = t.readKey(true);Conversely, calling readKey(false) or without any parameters reads the keyboard in the non-blocking manner. In this case, the method returns null if the user has not pressed any keys.
To use asynchronous keyboard input, instantiate the terminal as new UnixTerminal(true). Asynchronous input is always non-blocking.
For the default standard input, Terminality uses the POSIX poll() function to distinguish temporarily unavailable input from end-of-input without blocking. An arbitrary custom InputStream cannot always provide this distinction through the Java API; EOF detection for such a stream depends on its available() implementation or on using a blocking read.
Use the setCursorVisibility() method to control the visibility of the cursor:
t.setCursorVisibility(false); // make the cursor invisibleTo move the cursor around, use the setCursorPosition() method. Row and column indices are zero-based:
t.setCursorPosition(9, 4); // move the cursor to 10-th row and 5-th columnUse the setTextRendition() and resetTextRendition() methods to change text attributes such as color, background color etc. The most common text and background colors are provided as constants in the TextRendition class. See TextRendition.java for more information.
To print text, use the put() method.
t.put('a'); // outputs single character -- 'a't.put("Hello, world!"); // outputs the string "Hello, world!"There are also convenience methods that allow the client to specify text position and text attributes. For example,
t.put("text", TextRendition.FG_WHITE_INTENSE);is the same as calling
t.setTextRendition(TextRendition.FG_WHITE_INTENSE);
t.put("text");
t.resetTextRendition();and
t.put(row, column, "text", TextRendition.FG_WHITE_INTENSE, TextRendition.BG_PURPLE);is the same as calling
t.setCursorPosition(row, column);
t.setTextRendition(TextRendition.FG_WHITE_INTENSE, TextRendition.BG_PURPLE);
t.put("text");
t.resetTextRendition();All output operations in Terminality are buffered, the client has to call flush() to make any changes visible:
t.flush(); // flush the output bufferSince version 0.5, Terminality supports the Builder pattern, which means that you can chain calls to some methods, e.g.:
t.begin().clear().setCursorVisibility(false).setCursorPosition(5, 5).put("Hello!").flush();Use the getTerminalSize() method to get the size of the terminal window:
Terminal.WindowSize ws = t.getTerminalSize();
int cols = ws.columns;
int rows = ws.rows;Each successful call to getTerminalSize() caches the returned dimensions. The sizeChanged() method returns true when the most recent size query detected dimensions different from the cached value, and consumes that notification. Its initial value is true so that an application can perform its initial layout. To detect later changes, call getTerminalSize() before sizeChanged() in each iteration of the application loop. Terminality does not install a SIGWINCH handler.
See BouncyBall.java and KeyStrokeDemo.java in src/main/java/net/prsv/terminality/example.
The project is licensed under the terms of Apache License, version 2.0. See LICENSE for details.
