The UNIX terminal is an archaic text interface to the computer. It evolved from teletype machines, which were essentially keyboards with a line printer attached. Any text I/O would be directly printed on paper. Later, the paper was replaced by a monitor. If you want to create a terminal program that has a text interface anything fancier than just lines of text scrolling up the screen, you are likely to go with ncurses. ncurses is a library for creating text mode user interfaces.
A word of warning, like with all things that are new, learning to use ncurses is very much a step by step process. ncurses is not a magical tool for easily crafting great UIs. It has the same 1970s style interface as the rest of the
UNIX that we all love.
In this post I will throw a lot of library function names at you with only a minimal description of what they do. Trust me, this is all you need to get you kickstarted with ncurses, but do check out the man pages afterwards to get more deeply involved.
Start off by including
<ncurses.h>. First thing in
main(), initialize the library with a call to
initscr(). To deinitialize, call
endwin(). You should always call
endwin() upon program termination to prevent leaving the terminal in a state that the
UNIX shell (or rather, the user) doesn't like. By the way, why
endwin() isn't named
deinitscr(), we may never know.
initscr() initializes a global variable in the library named
stdscr. It's a pointer to an ncurses window that represents the terminal screen. It is not really the terminal screen, but think of it as a back buffer; you do your operations on the back buffer, then call
refresh() to get output to the screen. Because of this, do not use
printf() to display text. Instead, use the
wprintw() function to print to a window and call
refresh() at an appropriate time afterwards. To print text at a fixed position on the screen or window, use
mvwprintw(). You can print text with attributes and in other colors using
attron() and
attroff(). For example,
attron(A_BOLD) enables the bold attribute. Clear the screen with
clear(). To get the screen dimensions, call the macro
getmaxyx(stdscr, height, width).
The cursor can be freely controlled. You can hide or show it using
curs_set(visible), and you can move it around with
move(y, x).
You can get input key presses with
getch(). But before that, you should put the terminal in the right mode, usually during program initialization. Normally, the terminal is in cooked mode, meaning that a lot of processing has already been done before the pressed key was passed on to the running process. For example, the user might suspend the process by hitting Ctrl-Z.
By default, input is line buffered. This means that the user has to hit return before the key is passed to the process. To keep this from happening, set cbreak mode (break after each character). Setting cbreak mode is easy: call
cbreak().
getch() will echo the characters to the screen. If you don't want this, call
noecho(). What's really nice,
getch() responds to nearly all keys on your keyboard, including the arrow keys, page up, page down, and so on. The symbolic constants for keys are named
KEY_xxx. They should be in the man page for
curs_getch or do a grep for "
KEY_" in
/usr/include/ncurses.h.
The function keys are not enabled by default. Set
keypad(stdscr, true) to enable them.
getch() will implicitly call
refresh() as necessary.
Call
raw() to set the terminal in raw mode. In raw mode, interrupt and flow control characters like Ctrl-C, Ctrl-Z, and the like behave like any other key press without producing a signal. My personal preference is to set cbreak, noecho, and keypad, but there are cases in which raw mode is well-suited too.
You may be familiar with the
tcsetattr()
function that manipulates the terminal mode. Word of advice: don't bother using
it—the ncurses functions are easier and more
comprehensible.
ncurses allows you to work with windows. A window is a rectangular screen area. Allocate a new window with
newwin(), and deallocate it with
delwin(). To neatly close a window and erase its content, use
wborder() and call
wrefresh(), before finally deallocating it with
delwin().
Subwindows are created using
derwin() [as in ‘derived window’]. I prefer
derwin() over
subwin() because
derwin() uses coordinates relative to its parent window.
Subwindows are windows just like any other ncurses window, they are all of type
WINDOW pointer. Again, deallocate with
delwin().
Windows are confined to the screen area. To create windows larger than the screen, or even off-screen windows, use pads. A pad window is allocated using
newpad(), and deallocated using ...
delwin(). Because a pad can only have a small visible portion onscreen, you should use
prefresh() rather than
wrefresh() to redisplay pad windows.
This is ncurses in a nutshell. It should be just enough to get you started. Don't forget to call
refresh()! Frankly, I just couldn't get used to ncurses odd parameter order of "h,w,y,x" so I wrapped everything in my own interface. People say it's better to learn the standard API, but hey. ncurses is not exactly UIKit for terminal based apps. But fair enough, it's fun being able to handle the arrow keys in a
UNIX program, and to print text at any screen position without having to write ANSI escape sequences.
For a more elaborate instruction with code examples and everything, please see the
NCURSES Programming HOWTO at The Linux Documentation Project.