Writing Programs with NCURSES

by Zeyd M. ben-Halim and Eric S. Raymond
(version 1.8.7, 21 Dec 1994)

Introduction

This document is an introduction to programming with curses. It is not an exhaustive reference for the curses Application Programming Interface (API); that role is filled by the curses manual pages. Rather, it is intended to help C programmers ease into using the package.

The curses package is a subroutine library which presents a high level screen model to the programmer, hiding differences between terminal types and doing automatic optimization of output to change one screenfull of text into another. Curses uses terminfo, which is a database format that can describe the capabilities of thousands of different terminals.

Historically, the first ancestor of curses was the routines written to provide screen-handling for the game rogue; these used the already- existing termcap database facility for describing terminal capabilities. These routines were abstracted into a documented library and first released with the early BSD UNIX versions.

System III UNIX from Bell Labs featured a rewritten and much-improved curses library. It introduced the terminfo format. Terminfo is based on Berkeley's termcap database, but contains a number of improvements and extensions. Parameterized capabilities strings were introduced, making it possible to describe multiple video attributes, and colors and to handle far more unusual terminals than possible with termcap. In the later AT&T System V releases, curses evolved to use more facilities and offer more capabilities, going far beyond BSD curses in power and flexibility.

This document describes ncurses, a freeware implementation of the System V curses API. It includes the following System V curses features:

Also, this package makes use of the insert and delete line and character features of terminals so equipped, and determines how to optimally use these features with no help from the programmer. It allows arbitrary combinations of video attributes to be displayed, even on terminals that leave ``magic cookies'' on the screen to mark changes in attributes.

The ncurses package was originated by Pavel Curtis. The primary maintainer of the package is Zeyd ben-Halim <zmbenhal@netcom.com>. Eric S. Raymond <esr@snark.thyrsus.com> wrote many of the new features in versions after 1.8.1 and coauthored this introduction.

An Overview of the Package

Terminology

In this document, the following terminology is used with reasonable consistency:
window
A data structure describing a sub-rectangle of the screen (possibly the entire screen). You can write to a window as though it were a miniature screen, scrolling independently of other windows on the physical screen.
screens
A subset of windows which are as large as the terminal screen, i.e., they start at the upper left hand corner and encompass the lower right hand corner. One of these, stdscr, is automatically provided for the programmer.
terminal screen
The package's idea of what the terminal display currently looks like, i.e., what the user sees now. This is a special screen.

Compiling Programs using the Package

In order to use the library, it is necessary to have certain types and variables defined. Therefore, the programmer must have a line:

	  #include <curses.h>
at the top of the program source. The screen package uses the Standard I/O library, so <curses.h> includes <stdio.h>. <curses.h> also includes <termios.h>, <termio.h>, or <sgtty.h> depending on your system. It is redundant (but harmless) for the programmer to do these includes, too. In linking with curses you need to have -lncurses in your LDFLAGS or on the command line. There is no need for any other libraries.

Updating the Screen

In order to update the screen optimally, it is necessary for the routines to know what the screen currently looks like and what the programmer wants it to look like next. For this purpose, a data type (structure) named WINDOW is defined which describes a window image to the routines, including its starting position on the screen (the (y, x) coordinates of the upper left hand corner) and its size. One of these (called curscr, for current screen) is a screen image of what the terminal currently looks like. Another screen (called stdscr, for standard screen) is provided by default to make changes on.

A window is a purely internal representation. It is used to build and store a potential image of a portion of the terminal. It doesn't bear any necessary relation to what is really on the terminal screen; it's more like a scratchpad or write buffer.

To make the section of physical screen corresponding to a window reflect the contents of the window structure, the routine refresh() (or wrefresh() if the window is not stdscr) is called.

A given physical screen section may be within the scope of any number of overlapping windows. Also, changes can be made to windows in any order, without regard to motion efficiency. Then, at will, the programmer can effectively say ``make it look like this,'' and let the package implementation determine the most efficient way to repaint the screen.

Standard Windows and Function Naming Conventions

As hinted above, the routines can use several windows, but two are automatically given: curscr, which knows what the terminal looks like, and stdscr, which is what the pro- grammer wants the terminal to look like next. The user should never actually access curscr directly. Changes should be made to through the API, and then the routine refresh() (or wrefresh()) called.

Many functions are defined to use stdscr as a default screen. For example, to add a character to stdscr, one calls addch() with the desired character as argument. To write to a different window. use the routine waddch() (for `w'indow-specific addch()) is provided. This convention of prepending function names with a `w' when they are to be applied to specific windows is consistent. The only routines which do not follow it are those for which a window must always be specified.

In order to move the current (y, x) coordinates from one point to another, the routines move() and wmove() are provided. However, it is often desirable to first move and then perform some I/O operation. In order to avoid clumsiness, most I/O routines can be preceded by the prefix 'mv' and the desired (y, x) coordinates prepended to the arguments to the function. For example, the calls


	  move(y, x);
	  addch(ch);
can be replaced by

	  mvaddch(y, x, ch);
and

	  wmove(win, y, x);
	  waddch(win, ch);
can be replaced by

	  mvwaddch(win, y, x, ch);
Note that the window description pointer (win) comes before the added (y, x) coordinates. If a function requires a window pointer, it is always the first parameter passed.

Variables

The curses library sets some variables describing the terminal capabilities.

      type   name      description
      ------------------------------------------------------------------
      int    LINES     number of lines on the terminal
      int    COLS      number of columns on the terminal
The curses.h also introduces some #define constants and types of general usefulness:
bool
boolean type, actually a `char' (e.g., bool doneit;)
TRUE
boolean `true' flag (1).
FALSE
boolean `false' flag (0).
ERR
error flag returned by routines on a fail (-1).
OK
error flag returned by routines when things go right.

Using the Library

Now we describe how to actually use the screen package. In it, we assume all updating, reading, etc. is applied to stdscr. These instructions will work on any window, providing you change the function names and parameters as mentioned above.

Here is a sample program to motivate the discussion:


#include <curses.h>
#include <signal.h>

static void finish(int sig);

main(int argc, char *argv[])
{
    /* initialize your non-curses data structures here */

    (void) signal(SIGINT, finish);      /* arrange interrupts to terminate */

    (void) initscr();      /* initialize the curses library */
    keypad(stdscr, TRUE);  /* enable keyboard mapping */
    (void) nonl();         /* tell curses not to do NL->CR/NL on output */
    (void) cbreak();       /* take input chars one at a time, no wait for \n */
    (void) noecho();       /* don't echo input */

    if (has_colors())
    {
        start_color();

        /*
         * Simple color assignment, often all we need.
         */
        init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK);
        init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK);
        init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK);
        init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK);
        init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK);
        init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
        init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK);
        init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK);
    }

    for (;;)
    {
        int c = getch();     /* accept a single keystroke of input */

        /* process the command keystroke */

        refresh();           /* repaint the screen */
    }

    finish(0);               /* we're done */
}

static void finish(int sig)
{
    endwin();

    /* do your non-curses wrapup here */

    exit(0);
}

Starting up

In order to use the screen package, the routines must know about terminal characteristics, and the space for curscr and stdscr must be allocated. These function initscr() does both these things. Since it must allocate space for the windows, it can overflow memory when attempting to do so. On the rare occasions this happens, initscr() will terminate the program with an error message. initscr() must always be called before any of the routines which affect windows are used. If it is not, the program will core dump as soon as either curscr or stdscr are referenced. However, it is usually best to wait to call it until after you are sure you will need it, like after checking for startup errors. Terminal status changing routines like nl() and cbreak() should be called after initscr().

Once the screen windows have been allocated, you can set them up for your program. If you want to, say, allow a screen to scroll, use scrollok(). If you want the cursor to be left in place after the last change, use leaveok(). If this isn't done, refresh() will move the cursor to the window's current (y, x) coordinates after updating it.

You can create new windows of your own using the functions newwin(), derwin(), and subwin(). The routine delwin() will allow you to get rid of old windows. All the options described above can be applied to any window.

Output

Now that we have set things up, we will want to actually update the terminal. The basic functions used to change what will go on a window are addch() and move(). addch() adds a character at the current (y, x) coordinates. move() changes the current (y, x) coordinates to whatever you want them to be. It returns ERR if you try to move off the window. As mentioned above, you can combine the two into mvaddch() to do both things at once.

The other output functions, such as addstr() and printw(), all call addch() to add characters to the window.

After you have put on the window what you want there, when you want the portion of the terminal covered by the window to be made to look like it, you must call refresh(). In order to optimize finding changes, refresh() assumes that any part of the window not changed since the last refresh() of that window has not been changed on the terminal, i.e., that you have not refreshed a portion of the terminal with an overlapping window. If this is not the case, the routine touchwin() is provided to make it look like the entire window has been changed, thus making refresh() check the whole subsection of the terminal for changes.

If you call wrefresh() with curscr as its argument, it will make the screen look like curscr thinks it looks like. This is useful for implementing a command which would redraw the screen in case it get messed up.

Input

The complementary function to addch() is getch() which, if echo is set, will call addch() to echo the character. Since the screen package needs to know what is on the terminal at all times, if characters are to be echoed, the tty must be in raw or cbreak mode. Since initially the terminal has echoing enabled and is in ordinary ``cooked'' mode, one or the other has to changed before calling getch(); otherwise, the program's output will be unpredictable.

When you need to accept line-oriented input in a window, the functions wgetstr and friends are available. There is even a wscanw function that can do scanf(3)-style multi-field parsing on window input. These pseudo-line-oriented functions turn on echoing while they execute.

The example code above uses the call keypad(stdscr, TRUE) to enable support for function-key mapping. With this feature, the getch() code watches the input stream for character sequences that correspond to arrow and function keys. These sequences are returned as pseudo-character values. The #define values returned are listed in the ncurses.h The mapping from sequences to #define values is determined by key_ capabilities in the terminal's terminfo entry.

Using Forms Characters

The addch function (and some others, including box and border) can accept some pseudo-character arguments which are specially defined by ncurses. These are #define values set up in the ncurses.h header; see there for a complete list (look for the prefix ACS_).

The most useful of the ACS defines are the forms-drawing characters. You can use these to draw boxes and simple graphs on the screen. If the terminal does not have such characters, ncurses.h will map them to a recognizable (though ugly) set of ASCII defaults.

Character Attributes and Color

The ncurses.h package supports screen highlights including standout, reverse-video, underline, and blink. It also supports color, which is treated as another kind of highlight.

Highlights are encoded, internally, as high bits of the pseudo-character type (chtype) that ncurses.h uses to represent the contents of a screen cell. See the ncurses.h header file for a complete list of highlight mask values (look for the prefix A_).

There are two ways to make highlights. One is to logical-or the value of the highlights you want into the character argument of an addch call, or any other output call that takes a chtype argument.

The other is to set the current-highlight value. This is logical-or'ed with any highlight you specify the first way. You do this with the functions attron, attroff, and attrset; see the manual pages for details. Color is a special kind of highlight. The package actually thinks in terms of color pairs, combinations of foreground and background colors. The sample code above sets up eight color pairs, all of the guaranteed-available colors on black. Note that each color pair is, in effect, given the name of its foreground color. Any other range of eight non-conflicting values could have been used as the first arguments of the init_pair values.

Once you've done an init_pair that creates color-pair N, you can use COLOR_PAIR(N) as a highlight that invokes that particular color combination. Note that COLOR_PAIR(N), for constant N, is itself a compile-time constant and can be used in initializers.

Finishing Up

In order to clean up after the ncurses routines, the routine endwin() is provided. It restores tty modes to what they were when initscr() was first called, and moves the cursor down to the lower-left corner. Thus, anytime after the call to initscr, endwin() should be called before exiting.

Hints, Tips, and Tricks

The ncurses manual pages are a complete reference for this library. In the remainder of this document, we discuss various useful methods that may not be obvious from the manual page descriptions.

Some Notes of Caution

You are much less likely to run into problems if you design your screen layouts to use tiled rather than overlapping windows. Historically, curses support for overlapping windows has been weak, fragile, and poorly documented. The \fBncurses\fR library is not yet an exception to this rule.

There is a freeware panels library available available for use withn curses that does a pretty good job of strengthening the overlapping-windows facilities.

Try to avoid using the global variables LINES and COLS. Use getmaxyx() on the stdscr context instead. Reason: your code may be ported to run in an environment with window resizes, in which case several screens could be open with different sizes.

Temporarily Leaving ncurses Mode

Sometimes you will want to write a program that spends most of its time in screen mode, but occasionally returns to ordinary `cooked' mode. A common reason for this is to support shell-out. This behavior is simple to arrange in ncurses.

To leave ncurses mode, call endwin() as you would if you were intending to terminate the program. This will take the screen back to cooked mode; you can do your shell-out. When you want to return to ncurses mode, simply call refresh(). This will repaint the screen.

There is a boolean function, isendwin(), which code can use to test whether ncurses screen mode is active. It returns TRUE in the interval between an endwin() call and the following refresh(), FALSE otherwise.

Using ncurses With X

A resize operation in X sends SIGWINCH to the running application. The ncurses library does not catch this signal, because it cannot in general know how you want the screen re-painted. You will have to write the SIGWINCH handler yourself.

At the moment, ncurses has no hooks that are specialized to help you do this, though future versions will probably have them.

At minimum, your SIGWINCH handler should do a clearok(), followed by a wrefresh() on each of your windows (or, equivalently but more efficiently, wnoutrefresh() on each one followed by doupdate()).

Handling Multiple Terminal Screens

The initscr() function actually calls a function named newterm() to do most of its work. If you are writing a program that opens multiple terminals, use newterm() directly.

For each call, you will have to specify a terminal type and a pair of file pointers; each call will return a screen reference, and stdscr will be set to the last one allocated. You will switch between screens with the set_term call. Note that you will also have to call def_shell_mode and def_prog_mode on each tty yourself.

Testing for Terminal Capabilities

Sometimes you may want to write programs that test for the presence of various capabilities before deciding whether to go into ncurses mode. An easy way to do this is to call setupterm(), then use the functions tigetflag(), tigetnum(), and tigetstr() to do your testing.

Tuning for Speed

Use the addchstr() family of functions for fast screen-painting of text when you know the text doesn't contain any control characters. Try to make attribute changes infrequent on your screens. Don't use the immedok() option!

Special Features of NCURSES

When running on PC-clones, ncurses has enhanced support for the IBM high-half and ROM characters. The ACS forms characters default not to the ugly ASCII makeshifts but to a set picked out of the IBM high-half and ROM graphics. A new highlight, \fBA_PCCHARSET\fR, enables display of the PC ROM graphics 0-31 that are normally interpreted as control characters.
Eric S. Raymond <esr@snark.thyrsus.com>