Terminal size and ncurses

1

When programs like vi first came along, terminals had a predefined size: usually 80 columns and 24 lines (actually 25 lines, but the last line was reserved to display status information by the terminal). While you ran your program, the terminal couldn’t change its size, because the dimensions were locked in by hardware.

But with the graphical desktop, programs now had to adapt to a terminal emulator—a window that simulated a terminal. And while the terminal window was often given an initial size of 80 columns and 24 lines, the user could change those dimensions, such as by resizing the window on the desktop.

Fortunately, programming with ncurses provides an easy way to notify the program when the terminal size has changed. Let’s write a sample program to see how that works in ncurses.

Center some text

Let’s begin by writing a demonstration that centers a single character on the screen. When writing programs using ncurses, the initscr function will initialize the screen, then set the global variables LINES and COLS with the screen dimensions. We can use those variables to center a character on the screen:

#include <curses.h>

int main()
{
    initscr();

    clear();
    mvaddch(LINES / 2, COLS / 2, 'x');
    refresh();

    getch();

    endwin();
    return 0;
}

In ncurses, you can position the cursor using the move function, then add text to the screen using addstr to add a string, or addch to add a character. But because moving to a new location then adding some text is a very common thing to do in a program, ncurses provides functions that combine those actions. In this case, I’ve used mvaddch to move to a new location then add a single character to the screen.

The refresh function updates the screen after I’ve added my text. And the getch function waits for the user to press a key on the keyboard.

If we compile and run this sample program, we’ll see a single letter x in the middle of the screen:

$ gcc -o center center.c -lncurses
screenshot: centering an x on the screen

My terminal window is 80 columns and 25 lines, so COLS/2 is 40 and LINES/2 is 12.5, which rounds down to 12 when converted to an integer. The x is printed at column 40 and line 12.

Resizing the screen

Let’s update the program to enter a loop while it waits for a keystroke, exiting only when the user presses lowercase q:

#include <curses.h>

int main()
{
    int key;

    initscr();

    clear();
    mvaddch(LINES / 2, COLS / 2, 'x');
    refresh();

    do {
        key = getch();
    } while (key != 'q');

    endwin();
    return 0;
}

This does the same thing as the previous program, except it waits for the user to press q before it quits. The x remains at column 40 and line 12, even when I resize the terminal window to some other size, like 50 columns wide and 15 lines tall:

$ gcc -o center2 center2.c -lncurses
screenshot: the x remains at 40,12

Recognizing the new size

To accommodate this, the getch ncurses function returns a specific value (KEY_RESIZE) when the terminal has a new size. We can use this to adapt our program to recognize when the terminal has been resized, so we can clear the screen and redraw the x:

#include <curses.h>

int main()
{
    int key;

    initscr();

    clear();
    mvaddch(LINES / 2, COLS / 2, 'x');
    refresh();

    do {
        key = getch();

        if (key == KEY_RESIZE) {
            clear();
            mvaddch(LINES / 2, COLS / 2, 'x');
            refresh();
        }
    } while (key != 'q');

    endwin();
    return 0;
}

Now the program will “know” when the terminal size has changed, so it can recenter the x on the screen, such as changing the terminal window to 40 columns and 10 lines:

$ gcc -o center3 center3.c -lncurses
screenshot: the x is always centered

Terminal size and ncurses

Recognizing when the screen has changed size is a powerful feature in the ncurses library. It’s useful for any kind program that draws to the screen, because these programs typically need to position text at specific locations. Add this feature to your next ncurses program so your program can keep track of the terminal size and adjust accordingly.

Leave a Reply