Contents

Overview

"But I want this text to be green!" So began my complaint about the lack of console capabilities in Linux. Many people have made this complaint before and were given the same answer I received: "use curses". Now, curses is a pretty ugly beast, written decades ago when dumb terminals existed and terminal control sequences were actually interpreted by sundry horrible hardware designed by complete idiots (or so it seems in hindsight). Hardware that supported only a couple of colors, or a limited number of color combinations (oh, the horror!). Hardware that sometimes gave you nothing but the newline code to work with. As a result of pandering to all these designs, curses became what it is now. There is no need to blame its developers; I am sure that neither you nor I could have done better with the specifications they were given.

In the modern times, however, text consoles are a very different environment. Monochrome monitors, dumb vt100 terminals, and all such weird hardware has been dumped into museums, landfills, or big company junk rooms. Consoles are all emulated now, and support only several capability sets. There are the OS default text console, which on Linux is called "linux", and a couple of X terminal emulators, which try to be compatible with xterm. All of them support color text. None of them have limited "color pairs" number. All have decent positioning, cursor movement, clearing, drawing, and Unicode text writing capabilities. As a result, there is simply no reason to work with them in the same ugly manner that was necessary to support the hundreds of braindead terminal types that existed back in the middle of last century.

And so I wrote this library, which has basically the same functionality as curses, but with a different approach. Whereas curses made no assumptions regarding what sort of an application you want to write, I'm assuming you want it to look like all the text UIs have looked since the glory days of DOS. That is, they draw on the screen only when you tell them to, instead of echoing every character the user types. They don't have to buffer the input to save to cost of transmitting a character over an ancient 128 baud line. They get the escape key as soon as it is pressed, instead of seeing if the user is so 5331 that he can type his own terminal conrol codes. They know how to decode arrow key codes instead of making you write your own code to do it. And so on.

My other concern with curses is that it is a pure C library. Now that C++ specification is stable (unlike at the time when curses was written) and is fully supported on every platform in existence, there is absolutely no reason to use plain C for any application over a few hundred lines. C code makes it really hard to create good designs for your software, and as a result, pretty much all large C projects (like the Linux kernel, for example) either are converting to C++ or are manually implementing C++ features in C (like the Linux kernel does when it builds what are effectively C++ vtables for its drivers). This library is entirely in C++, with interfaces packed into objects, as they are supposed to be, errors handled with exceptions, and cleanup taken care of by destructors.

CTerminfo

"But I want this text to be red!" Is the simplest terminal capability you could ask for. If you have an application that just prints stuff out to the screen, like, say, a compiler, you might not necessarily want to use a fully featured windowing system just to make the error messages print out red. In curses you'd have to create a window, keep track of rotating the lines through the screen, and worry a lot about how to remember where the dirty areas are. CTerminfo answers this problem with a simple stream-based interface that lets you directly print terminal capabilities, taking care of all those little details, like having to set the bold attribute to get all 16 colors. Here's a simple example:

    CTerminfo ti;
    ti.Load();
    cout << ti.Color(red) << "Error: can't open file x" << endl;

The second (optional) argument to Color sets the background color. See EColor enum in ticonst.h for available color values. With color_Preserve value you can keep the fancy backgrounds provided by some graphical terminal emulators (ETerm). You can also draw simple stuff like lines (HLine, VLine) and boxes (Box), positioned where you want them (MoveTo), which is useful for making progress displays nicer than printing "23% complete" every few seconds. You can clear the screen (Clear) or just parts of it (Bar). You can get ASCII art from files and dump it on the screen (Image). In other words, do all those things that DOS programmers had forever; things that let them develop text editors, IDEs (Borland), file managers (Norton Commander), and many wonderful text mode games like zzt. CTerminfo also provides the ability to directly use terminal capabilities, if there is something you really need. You can get the terminfo-given values for booleans (GetBoolean), numbers (GetNumber), or strings (GetString). Take a look at ticonst.h for supported capability names.

There are a couple of things you need to remember when working with this class. First, because many terminfo capabilities are actually programs, some output strings must be built at runtime. CTerminfo uses an internal buffer to store the output before returning it. This allows all operations to work inline instead of requiring you to pass a buffer for each one. What this means to you is that you should not store pointers to the returned values. If you need to do it, copy it into your own string object. However, you can't use it again because of the second caveat, which is that the object keeps the current terminal state. So if you set the color to red and the set the color to red again, nothing will be written. This is a good thing, since it reduces the amount of control code the kernel has to parse, but it means you should not write your own control sequences. If you feel the need to force a write, you can reset the saved state with ResetState. I should also note that while this behaviour is not thread-safe, that it is so is irrelevant since you can't print to the terminal from different threads and expect consistent results. The terminal keeps state too and therefore must be used from one thread only.

The terminal description record is loaded from the terminfo database, usually located under /usr/share/terminfo. The database location may be overridden by setting TERMINFO environment variable to the correct location. If Load is called without an argument (which can give a specific terminal name), as in the example listing above, the terminal name is obtained from the TERM environment variable. If neither is set, the name defaults to "linux", which may or may not be correct. Loading errors are reported by throwing appropriate exceptions.

CGC

While CTerminfo is a decent interface for a simple, output-only application, a more sophisticated approach is usually desired for a truly interactive one. A well-designed, user friendly application would want to have more control over the screen contents, implementing some type of an event-driven windowing system. It would usually have a lot of stuff on the screen and so would want to optimize the output to redraw only the areas changed since the last update. This results in faster, more responsive application, since parsing the terminal control strings is a major bottleneck on the console. Furthermore, a windowing system always needs some type of a backbuffer into which each window can draw, to allow proper clipping and overlapping window support without making each window write its own code for them.

For all these purposes, this library provides the CGC class, roughly equivalent to the WINDOW structure in curses, and to the drawing context in most graphical UI environments. It contains a memory buffer into which all the drawing operations go. When you have finished writing into it, it can be blitted to the screen with the Image command in CTerminfo. The intended use, however is a triple-buffer rotating system that writes only differences between updates. Here is an example of how it might work:

    CGC gc;		// This is where the code draws.
    CGC scr;		// This contains the current contents of the screen.

    // Make both the same size as the screen.
    gc.Resize (m_TI.Width(), m_TI.Height());
    scr.Resize (m_TI.Width(), m_TI.Height());

    while (inEventLoop) {
	Draw (gc);		// Draws everything that should be on the screen.
	gc.MakeDiffFrom (scr);	// Only the differences need to be written, so find them.
	// gc now has only new stuff, the rest is zeroed out, and isn't drawn.
	cout << m_TI.Image (0, 0, gc.Width(), gc.Height(), gc.Canvas().begin());
	cout.flush();
	screen.Image (gc);	// Now apply the same diff to the screen cache.
	gc.Image (screen);	// ... and copy it back for a fresh start.
	WaitForEvent();
    }

CKeyboard

An interactive application is worthless if you can't interact with it, so some code is invariably required to accept keystrokes from the user and to convert them to some usable values. If you have ever tried to read the keyboard on a Linux console, you know just how much of a pain it is. Instead of giving you a keycode for a key, it gives you escape sequences. Sequences that are different for every terminal. Modifier keys like Ctrl, Alt, or Shift, are intercepted by the kernel and translated into what it thinks are the right things to print on the screen. Yes, you can actually disable this translation and put the terminal in RAW mode, but woe be to you if anything happens to your application before it is able to unset it. The "cbreak" mode, used by curses and this library, is not too bad. If your application crashes, you can still type and restore normal operation. If you were in RAW mode though, only a hardware reboot will fix the computer, since you will not be able to type anything at all. Another downside of RAW mode is that the keyboard layout has to be manually loaded and interpreted, which would greatly increase the code size of this library. Thankfully, it is possible to run in semi-normal "cooked" mode and still interpret keycodes correctly. That is what CKeyboard is for.

    m_Kb.Open (m_TI);	// Also places the terminal in UI-friendly mode.
    wchar_t key = 0;
    CKeyboard::metastate_t meta;
    while (key != 'q') {
	key = m_Kb.GetKey (&meta);	// Synchronous call.
	// ... do whatever with key ...
    }

Look in ticonst.h for EKeyDataValue enum of possible return codes. They are what will be returned instead of the escape sequences. Metakey state is in meta (which is an optional argument, in case you don't care about it). The above example demonstrates synchronous call usage. If you give GetKey false as a second argument, the call will return immediately with 0 if there is nothing to read. Then you can continue with your idle loop or do a select call on whatever file descriptors you are currently watching.

WARNING: It is very important to allow CKeyboard to call its destructor (or to manually call LeaveUIMode) before exiting. If it fails to do so, the keyboard remains in the UI state and the shell will not be very usable. There will be no echo, no cursor, and no scrollback. (You can manually fix it with "stty sane" and "echo ^v ESC c") This cleanup problem is common to all UI environments, be it curses, console, the framebuffer, or X. Crashing the UI environment without cleanup is a very bad idea. It's also pretty bad for your objects in general, possibly resulting in leaking resources, memory, IPC resources, etc. Most object oriented UI frameworks take care of this for you in some way. Some require an application object, which is guaranteed destructed on exit; you can see an example implementation of this approach in the demo directory. Some just register atexit() functions to clean up anything that really needs to be cleaned up. And then there are those who just assume nothing bad will ever happen and that the user knows how to reboot. Try not to fall into the last category.

How to build it

At this point I'm going to mention that this project is no longer being developed. I have discovered that terminfo entries are just plain wrong in many cases and nobody is particularly interested in fixing them. Specific terminfo entries must be written for currently existing terminal emulators, including xterm, which no longer conforms to the original terminfo. I am not interested in doing it due to the amount of work involved, but you are, of course, welcome to try. If you do, you may want to take over this project. Anyway...

Building this library is a fairly standard procedure. Download the package from https://github.com/msharov/utio/releases/latest. Make sure you have the necessary dependencies: C++11 compiler, such as gcc 4.6+, the uSTL library version 1.1 or higher, and the terminfo database, available in the ncurses package, if your distribution doesn't provide it separately.

% ./configure && make && make install

./configure --help lists configuration options, like setting an installation path other than /usr/local. To use the library, include utio.h and link with -lutio. Look at the example programs in the demo directory to start with something that is known to work.

What if it doesn't work?

Problems should be reported using Github project trackers reachable from the library project page.