12

I'm writing an ncurses program and am trying to make it respond correctly to terminal resizing. While I can read the terminal dimensions correctly in my program, ncurses doesn't seem to deal with new dimensions correctly. Here's a (somewhat lengthy) sample program:

#include <ncurses.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>

void handle_winch(int sig){

    struct winsize w;
    ioctl(0, TIOCGWINSZ, &w);
    COLS = w.ws_col;
    LINES = w.ws_row;

    wresize(stdscr, LINES, COLS);
    clear();

    mvprintw(0, 0, "COLS = %d, LINES = %d", COLS, LINES);
    for (int i = 0; i < COLS; i++)
        mvaddch(1, i, '*');

    refresh();
}

int main(int argc, char *argv[]){

    initscr();

    struct sigaction sa;
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = handle_winch;
    sigaction(SIGWINCH, &sa, NULL);

    while(getch() != 27) {}

    endwin();
    return 0;
}

If you run it, you can see that the terminal dimensions are correctly retrieved. But the second line, which is supposed to draw *-characters across the screen, doesn't work. Try resizing the window horizontally to make it larger, the line of *s will not get larger.

What's the problem here? I'm aware that one can temporarily leave curses mode, but I'd prefer a cleaner solution. Thanks!

jww
  • 97,681
  • 90
  • 411
  • 885
ryyst
  • 9,563
  • 18
  • 70
  • 97

1 Answers1

26

Do not set COLS and LINES. These are managed by ncurses. Also, let ncurses reinitialize properly after a resize. That means, don't call wresize(). Just call endwin() instead. Make sure to call refresh() directly after an endwin() call before using other ncurses functions.

You also don't need the ioctl() at all. ncurses takes care of detecting the new size automatically.

So what you need is pretty much just an endwin() call:

void handle_winch(int sig)
{
    endwin();
    // Needs to be called after an endwin() so ncurses will initialize
    // itself with the new terminal dimensions.
    refresh();
    clear();

    mvprintw(0, 0, "COLS = %d, LINES = %d", COLS, LINES);
    for (int i = 0; i < COLS; i++)
        mvaddch(1, i, '*');
    refresh();
}

Furthermore, some ncurses versions are configured to supply their own SIGWINCH handler. Those versions return KEY_RESIZE as a key input when a resize occurs. If you were to make use of that, you wouldn't need a signal handler at all. Instead, all you need is:

#include <ncurses.h>
#include <string.h>

int main()
{

    initscr();

    int key;
    while ((key = getch()) != 27) {
        if (key == KEY_RESIZE) {
            clear();
            mvprintw(0, 0, "COLS = %d, LINES = %d", COLS, LINES);
            for (int i = 0; i < COLS; i++)
                mvaddch(1, i, '*');
            refresh();
        }
    }

    endwin();
    return 0;
}

Unfortunately, you can't rely on all ncurses installation being configured with KEY_RESIZE, so the signal handler is the most portable solution.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • 1
    @ryyst I added information on KEY_RESIZE too, in case you want to go that route. – Nikos C. Dec 04 '12 at 17:01
  • 1
    ncurses has `resizeterm(lines,cols)` if you don't want to use KEY_RESIZE, but it is an ncurses extension as well. – Craig Dec 05 '12 at 18:58
  • @ryyst ... the problem can be solved by calling `doupdate()` upon receiving a `KEY_ERR`. – ryyst Dec 08 '12 at 11:41
  • @NikosC. I'm wondering how this signal-reliant code would perform in an `ncurses` application utilizing `use_window` to perform synchronized window updates? I.e., is there a way to make this thread-safe? –  Mar 26 '13 at 03:33
  • 4
    Let me reiterate what @NikosC. said, when handling `SIGWINCH` you must call `endwin()` followed by `refresh()`. Only calling `endwin()` is going to leave the `getmaxyx` variables in an undefined state. – Joe Jan 15 '16 at 23:46
  • This doesn't make too much sense to me. You're calling `endwin()`, but you're not reinitializing the screen with `initscr()`. Care to elaborate? – kevr Sep 17 '22 at 00:53
  • 1
    @kevr `endwin()` does not de-initialize the library. It only disables curses mode and restores the terminal to its normal mode. The next `refresh()` will enable curses mode again. See the documentation (`man endwin`). – Nikos C. Sep 18 '22 at 01:14