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 mode
To 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 object with the asyncIO
parameter set to true
. Asynchronous input is always non-blocking.
Use the setCursorVisibility()
method to control the visibility of the cursor:
t.setCursorVisibility(false); // make the cursor invisible
To 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 column
Use 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 buffer
Since 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;
When you create a new UnixTerminal
object, by default the constructor tries to register a handler for the SIGWINCH signal (the OS sends this signal to an app when the size of the terminal window changes). If the terminal is handling SIGWINCH itself, you can query whether the size of the terminal has changed using the sizeChanged()
method. Subsequent calls to this method will return false
until the terminal window is resized again.
You can disable this functionality by calling the constructor with the handleSigwinch
parameter set to false
(for example, if you want to implement your own handler using the unsupported sun.misc.Signal
API). Alternatively, you can simply call getTerminalSize()
on every iteration of your main application loop.
See BouncyBall.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.