The ncurses Library

(with information on using libraries)

by David Eck


C++ HAS A STANDARD LIBRARY that defines many constants, variables, functions, and classes. You gain access to these things by using the appropriate #include directives, such as "#include <iostream>". Our textbook calls iostream a library, but this is not technically correct, at least on Linux. It is a header file. There is only one standard library, but the items that it defines are listed in many standard header files. Each standard header file gives access to a different part of the standard library. Header files generally do not contain actual definitions, just declarations. These declarations allow you to use the declared items in your program. The program can be compiled, but it can't be executed unless the definitions are also available. Fortunately, all the definitions in the standard C++ library are automatically made available when you use g++, so you don't have to do anything special.

Any real C++ system includes other libraries besides the standard libraries. For example, libraries might be available for doing graphics programming. Such libraries come in two parts: declarations in a header file and definitions in a library file. If you want to use items from the library in your program, you have to #include the appropriate header file. Then, since non-standard libraries are not automatically available to g++, you have to make the library itself available when you apply g++ to your program. This is done using the -l option to the g++ command. (That's a hyphen followed by a lower-case "L", standing for "library".) For example, if you are using a non-standard library named "fred", you will probably need the directive "#include <fred.h>" (Most non-standard header files follow the convention that the name ends in .h.) Then, when you apply g++ to your program, you will have to add -lfred to the g++ command. Something like: g++ -lfred myprog.cc

This Web page discusses a particular non-standard library called ncurses. When you write a program that uses this library, you have to include the directive

              #include  <ncurses.h>

When you compile the program with g++, you should add -lncurses to the command. With the other usual options, you might compile a program file named testcurses.cc with the command

              g++  -g  -Wall  -lncurses  -otestcurses  testcurses.cc

We will probably encounter a few other non-standard libraries as we go through the course. All of them are used in a similar way.


The NCurses Library

The ncurses library defines a large number of constants, variables, and functions that allow you to use a console window as a simple kind of GUI. (It does not define classes. It's actually a C library rather than a C++ library as such.) When you write an ncurses program, you can put the window into "curses mode". In this mode, you can move the cursor -- which determines where the next character will be printed -- to any position in the window. You can output a character or a string of characters at the current cursor position. You can turn character "attributes" on and off. For example, if you turn the "underline" attribute on, then the characters that you output will be underlined. You can read single characters from the user, without having them show up on the screen. These characters can include control characters, arrow keys, and other special characters.

Positions in the console window refer to character positions. That is, the window is divided into rows and columns of cells, where each cell can hold one character, so you don't have pixel-level resolution. Still, you can write interesting programs.

The number of rows in the window is given by LINES, and the rows are numbered from 0 to LINES - 1. The number of columns is given by COLS, and the columns are numbered from 0 to COLS - 1. (LINES and COLS are variables that are defined in the ncurses library. Like all the other ncurses identifiers mentioned below, they can only be used in your program if you #include <ncurses.h>.)

There are a lot of functions in the ncurses library. I will only define a few of them here. If you want to learn more about ncurses, use the man command. Saying "man ncurses" on the command line will get you the basic information and a list of functions. There are also man pages for individual functions, such as "man addch". You might prefer to use KDE's Konqueror program to read man pages. To see the man page for ncurses, for example, you just have to enter man:/ncurses in Konqueror's location box.

The sample program testcurses.cc is a demonstration of some of the features of the curses mode.


Getting into and out of Curses Mode

The commands for setting up curses mode are rather esoteric, and although the command for leaving curses mode is simple enough, it has to be used with some care. I have written a pair of functions, startCurses() and endCurses() to make it easier. You should probably just copy them into your programs. Here they are, as they appear in the test_curses.cc sample program:

               bool curses_started = false;

               void endCurses() {
                  if (curses_started && !isendwin())
                     endwin();
               }


               void startCurses() {
                  if (curses_started) {
                     refresh();
                  }
                  else {
                     initscr();
                     cbreak();
                     noecho();
                     intrflush(stdscr, false);
                     keypad(stdscr, true);
                     atexit(endCurses);
                     curses_started = true;
                  }
               }

When you call startCurses() for the first time, you will start with an empty window. If you leave curses mode, by calling endCurses(), you can resume it by calling startCurses() again. The window will return to the state that it was in when you called endCurses().


Functions in the ncurses Library

Here is information about some of the functions that are declared in ncurses.h:

refresh() ---   Make changes visible in the window. Changes to the appearance of the window that are made by calling the following functions don't actually show up in the window until you call refresh(). The functions actually just make changes to a data structure. When you call refresh(), the changes are copied to the visible window. If you make a series of changes and call refresh() just once at the end, the changes to the window will be made very quickly and efficiently.

move(row,col) ---   Move the current cursor position to position (row,col). The parameters should be ints that specify a valid position in the window. That is, 0 <= row < LINES, and 0 < =col < COLS.

getyx(stdscr,row,col) ---   Get the current cursor position. The first parameter should be the predefined constant stdscr. (Don't worry about it!) The next two parameters must be variables of type int. When the function is executed, the values of these variables will be set to the current row and column of the cursor.

addch(ch) ---   Output a character at the current cursor position. The cursor will move to the next position in the window after the character is output, possibly on the next line. The parameter, ch, can actually be of type int, and it can encode information about attributes such as boldness, in addition to the actual character, but I suggest that you don't worry about that for now. Several constants are defined that specify special "graphical" characters that can be used to draw horizontal and vertical lines and to draw rectangular boxes. These include: ACS_HLINE (a one-character piece of a horizontal line), ACS_VLINE (vertical line), ACS_ULCORNER (upper-left corner of a box), ACS_URCORNER (upper-right corner), ACS_LLCORNER (lower-left corner), ACS_LRCORNER (lower-right corner), ACS_TTEE (top T, where the top of a vertical line touches a horizontal line), ACS_BTEE (bottom T), ACS_LTEE (left T), ACS_RTEE (right T), and ACS_PLUS (intersection of horizontal and vertical line).

addstr(str) ---   Output a string starting at the current cursor position. Same as calling addch(ch) for each character in the string. The parameter can be a constant string, such as "Hello", or a variable that refers to a C-style string (which we will cover later in the term).

attron(attribute) ---   Turn on the specified attribute for subsequently output characters. The parameter can be one of several pre-defined constants, including: A_BOLD (bold characters), A_UNDERLINE (underlined characters), and A_REVERSE (reverse video, that is, foreground and background colors reversed).

attroff(attribute) ---   Turn off the specified attribute for subsequently output characters. The parameter is one of the same constants as in the attron() function.

erase() ---   Erase the screen by filling it with blank characters.

getch() ---   This function waits for the user to type a character, and then returns an int value that represents that character. If the character is a regular printable character or a control character, the returned value is simply the character. However, the return value can be one of several special values that represent special keys, including: KEY_UP (up-arrow key), KEY_DOWN (down-arrow), KEY_LEFT (left-arrow), KEY_RIGHT (right-arrow), KEY_BACKSPACE (backspace), KEY_HOME ("home" key), and KEY_END ("end" key). Note that the return key generates the control character '\n'. Also note that some control characters, such as CTRL-Q and CTRL-S often have special meaning and might not be returned by this function.

timeout(milliseconds) ---   By default, if you call getch(), it will wait forever for the user to press a key. If you call timeout(0) before calling getch(), it won't wait at all -- if the user has already typed a character, it will be returned; if not, the predefined constant ERR will be returned instead. If you specify a positive value for the parameter to timeout(), then getch() will be willing to wait that many milliseconds before returning. For example, if you say timeout(1000) and then call getch(), it will wait up to one second. If the user has typed a character by that time, it will be returned; otherwise, the return value will be ERR. You can call timeout(-1) to reset getch() to its default behavior, waiting forever. You can use timeout() to write an event-driven program that does some kind of animation continually, but still responds when the user presses a key. The main() function would typical look something like:

              int main() {
                 mySetup();
                 startCurses();
                 timeout(25);
                 while (true) {
                    int ch = getch();
                    if (ch != ERR)
                       myProcessChar(ch);  // Process input.
                    myIdle(); // Will be called about 40 times per second.
                 }
                 endCurses();
              }

David Eck, 23 January 2002