A gentle introduction to ncurses

0

In days past, Unix programs ran in “teletype” mode, reading from standard input and writing to standard output such as the ls and awk commands. In more modern times, Linux programs might run in a graphical user interface like GNOME or KDE, allowing the user to interact with the program using a mouse, by clicking on menus and buttons. But another way that programs can interact with the user is via screen control, by manipulating the screen directly. Examples of such text user interfaces include the vi editor, implemented on modern systems as Vim.

You can create your own programs that use a text user interface to control the screen. The classic way to do this is with the curses library from BSD Unix, but Linux systems provide the same interface using ncurses, or new curses. Let’s explore ncurses by writing a simple “Hello world” program.

To follow along, you’ll need to have the ncurses development library installed. On most Linux systems, this is in the ncurses-devel package, which you can install like this on Fedora Linux:

$ sudo dnf install ncurses-devel

Starting up and shutting down

To use screen addressing, you first need to use initscr to initialize the screen. This does several things at once, but all behind the scenes: ncurses determines if the screen is addressable—and if so, prepares the screen for direct addressing. Finally, ncurses also sets the LINES and COLS global variables with the current screen dimensions.

In the process of doing all of this, ncurses may also clear the screen, although your program should still clear the screen on its own to ensure that the terminal is in the right “state.”

When your program is finished, you should use the endwin function to reset the terminal back to its normal state.

Let’s test this by writing a simple program to initialize the screen and then immediately exit the program. I’ve also used the getch function, which is part of ncurses, to get a single keystroke from the keyboard. That way, the program starts up, waits for a key, then exits. This sample program doesn’t do any other work such as clearing the screen or printing text, but it’s a demonstration of the minimum that you need to write a program with ncurses:

#include <curses.h>

int main()
{
    initscr();

    getch();

    endwin();
    return 0;
}

You compile the program like any other C program, but you need to include the ncurses library at link-time like this:

$ gcc -o demo demo.c -lncurses

And when you run the program, you’ll just see the cursor in the upper left corner, waiting for you to press a key. As soon as you press any key on the keyboard, the program immediately exits:

Printing text

Let’s add to this program by printing a short message. When using ncurses, you can position text anywhere on the screen. The standard way to reposition the cursor is with the move function, which takes the line and column of the new screen position. Screen coordinates always start from zero, so move(0,0) would put the cursor in the upper left corner.

For this demonstration, let’s add some text to start at line 5 and column 10. To do that, we’ll move the cursor with move(5,10) and then add a string to the screen using the addstr function:

#include <curses.h>

int main()
{
    initscr();

    move(5, 10);
    addstr("Hello world!");
    refresh();

    getch();

    endwin();
    return 0;
}

When updating the screen, such as to print text with addstr, you also need to redraw the screen with the refresh function. The screen update in ncurses is quite clever, and only updates the on screen characters that have changed; it doesn’t actually “repaint” the entire screen from scratch every time.

Centering a message

Let’s make another update to the “Hello world” program, to center the message on the screen. In this version, we’ll write a new function called center_text that prints the text so it’s on the middle line, and positions the text appropriately so it’s centered on the line.

#include <curses.h>
#include <string.h>

void center_text(const char *s)
{
    int line, col;

    line = LINES / 2;
    col = (COLS / 2) - (strlen(s) / 2);

    mvaddstr(line, col, s);
    refresh();
}

int main()
{
    initscr();

    center_text("Hello world");

    getch();

    endwin();
    return 0;
}

Because moving to a new coordinate and printing text is a common thing to do in a program, ncurses provides a function that combines the two actions. The mvaddstr function first uses move to go to a new screen coordinate, then uses addstr to print the text.

There’s more to explore

This program is a simple example of how to use the curses functions to draw characters to the screen. You can do so much more with curses, depending on what you need your program to do. If you are interested in getting a head start with curses, I encourage you to read the ncurses manual page, in section 3 of the man pages:

$ man 3 ncurses

Leave a Reply