cterminalncurses

Ncurses - How to handle terminal resizes for an image that is being updated in an infinite loop


I have a program utilizing ncurses to draw a window with a clock timer. It is constantly being refreshed in an infinite loop. It works as intended, however when I resize the terminal window, the entire image gets messed up and all out of sorts.

I've read some items on SIGWINCH for ncurses as well as a few others but they are all relying on a wgetch(win) or getch(). These calls block the loop waiting for user input. This seems like a pretty standard use-case, so trying to understand what is the procedure for detecting the window resizing without blocking the loop.

Note I am on Windows 11 and to compile I run gcc -o test.exe test.c -lncurses -DNCURSES_STATIC

#include <windows.h>
#include <ncurses/ncurses.h>
#include <time.h>

// default window sizes
#define WINDOW_HEIGHT 16
#define WINDOW_WIDTH 45
#define WINDOW_START_Y 5
#define WINDOW_START_X 5

// prototypes
WINDOW *init_window(void);

int main(void)
{
        // global screen init
        initscr();
        WINDOW *win;
        win = init_window();
        refresh();

        // hide cursor
        curs_set(0);

        // draw basic box in the window
        box(win, 0, 0);

        // initialize the timer
        time_t now;
        while (1) {
                
                // update current UTC time
                now = time(0);
                struct tm *tmp = gmtime(&now);

                // modify terminal window
                mvwprintw(win, 1, 1, "           _________           ");
                mvwprintw(win, 2, 1, "          / ======= \\         ");
                mvwprintw(win, 3, 1, "         / __________\\        ");
                mvwprintw(win, 4, 1, "        | ___________ |        ");
                mvwprintw(win, 5, 1, "        | | -       | |        ");
                mvwprintw(win, 6, 1, "        | |         | |              ");
                mvwprintw(win, 7, 1, "        | |_________| |________________  ");
                mvwprintw(win, 8, 1, "        \\=____________/      REM       ) ");
                mvwprintw(win, 9, 1, "        / \"\"\"\"\"\"\"\"\"\"\" \\               /  ");
                mvwprintw(win, 10, 1, "       / ::::::::::::: \\          =D-'   ");
                mvwprintw(win, 11, 1, "      (_________________)                  ");
                mvwprintw(win, 12, 1, "      current time: %02d:%02d:%02d", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
                wrefresh(win);
        }

        // clean up
        endwin();
        return 0;
}

WINDOW* init_window(void)
{
        // create new window
        WINDOW *win = newwin(WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_START_Y, WINDOW_START_X);
        if (!win) {
                printf("ERROR: could not create window\n");
                exit(1);
        }
        return win;
}

Solution

  • This should be a duplicate, but the essential point combining SIGWINCH and polled input doesn't appear.

    So... modify the program to

    Here's a modified program which illustrates those points:

    #include <stdlib.h>
    #include <ncurses.h>
    #include <time.h>
    
    // default window sizes
    #define WINDOW_HEIGHT LINES - 2
    #define WINDOW_WIDTH COLS - 2
    #define WINDOW_START_Y 1
    #define WINDOW_START_X 1
    
    // prototypes
    WINDOW *init_window(void);
    
    int main(void)
    {
            initscr();
            cbreak();
            WINDOW *win = init_window();
            nodelay(win, TRUE);
    
            // hide cursor
            curs_set(0);
    
            // draw basic box in the window
            box(win, 0, 0);
    
            // initialize the timer
            time_t start = time(0);
            while (1) {
                    if (wgetch(win) == KEY_RESIZE) {        // process SIGWINCH
                            wresize(win, WINDOW_HEIGHT, WINDOW_WIDTH);
                            clear();
                            wclear(win);
                            box(win, 0, 0);
                            wnoutrefresh(win);
                            start--;        // force a redraw
                    }
                    
                    // update current UTC time
                    time_t now = time(0);
    
                    if (now == start)
                            continue;
                    start = now;
    
                    struct tm *tmp = gmtime(&now);
    
                    // modify terminal window
                    mvwprintw(win, 1, 1, "           _________           ");
                    mvwprintw(win, 2, 1, "          / ======= \\         ");
                    mvwprintw(win, 3, 1, "         / __________\\        ");
                    mvwprintw(win, 4, 1, "        | ___________ |        ");
                    mvwprintw(win, 5, 1, "        | | -       | |        ");
                    mvwprintw(win, 6, 1, "        | |         | |              ");
                    mvwprintw(win, 7, 1, "        | |_________| |________________  ");
                    mvwprintw(win, 8, 1, "        \\=____________/      REM       ) ");
                    mvwprintw(win, 9, 1, "        / \"\"\"\"\"\"\"\"\"\"\" \\               /  ");
                    mvwprintw(win, 10, 1, "       / ::::::::::::: \\          =D-'   ");
                    mvwprintw(win, 11, 1, "      (_________________)                  ");
                    mvwprintw(win, 12, 1, "      current time: %02d:%02d:%02d", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
                    wnoutrefresh(win);
                    doupdate();
            }
    
            // clean up
            endwin();
            return 0;
    }
    
    WINDOW* init_window(void)
    {
            // create new window
            WINDOW *win = newwin(WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_START_Y, WINDOW_START_X);
            if (!win) {
                    printf("ERROR: could not create window\n");
                    exit(1);
            }
            return win;
    }
    

    Now... the answer as posted is ambiguous regarding whether this is (for example) using Cygwin, MinGW or MSYS2. The MinGW configuration doesn't provide a workable KEY_RESIZE; if you are using that, this question should be marked as a duplicate. Otherwise, if the underlying libraries are more-or-less POSIX, the program will work for you.