Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interrupt (n)curses getch on incoming signal

One of my programs uses ncurses for drawing a small tui. One of my goals is to make it rather portable to other curses implementations. This means that I want to catch a SIGWINCH issued by the terminal emulator on a resize operation myself and update my tui to adhere the changed geometry (and not depend on the resizing facilities of ncurses). Since POSIX (as far as I know) only allows access to sig_atomic_t variables within the signal handler, I set one to a different state. In the main loop, my program checks whether the state has changed and updates the tui if necessary.

But now, I have the problem that my program hangs in getch, when an signal arrives. The ncurses documentation states that handled signals never interrupt it. This means the size of the tui is not updated until an input key is pressed.

Is there any portable way to interrupt getch? My current approach is to ungetch a dummy key in the signal handler but I'm not sure if this is allowed. Actually I could not find any documentation regarding the fact whether an curses function may be used in a signal handler or not. Any idea how to correctly handle this matter?

Regards

like image 509
user1678062 Avatar asked Nov 07 '14 18:11

user1678062


3 Answers

The one guideline you should follow is to do as little as possible in interrupt routines. If you're doing anything more than setting a flag, that's when you should consider re-thinking your solution.

The curses system has a way to handle this problem but it requires a little work on the part of the developer.

You set half-delay mode with a suitable delay, so that getch() will return with ERR if there's no keystroke available during that time. That effectively gets you out of the getch() call so you can then do whatever other curses manipulations you need.

So, here's what I'd suggest. First, change your SIGWINCH handler so it simply sets a flag resized that your "main" program can detect.

Second, provide your application with a special form of getch() along the lines of (pseudo-code, obviously):

def getch_10th():
    set half delay mode for (for example) 1/10th second
    do:
        if resized:
            do whatever it takes to resize window
        set ch to result of real getch() (with timeout, of course)
    while timed out
    return ch

The half delay mode is a compromise, in terms of efficiency, between waiting forever (and not handling resizing events) and returning immediately (sucking up CPU grunt).

Using it wisely can make your windows respond reasonably fast without having to worry about portability.


See the following C program for an example of putting this into action. First, the signal and intercept function:

#include <curses.h>
#include <signal.h>

// Flag and signal handler.

static volatile int resized = 1;

static void handle_resize (int sig) {
    resized = 1;
}

// Get a character, handling resize events.

int getch10th (void) {
    int ch;
    do {
        if (resized) {
            resized = 0;
            endwin();
            refresh();
            mvprintw (1, 0, "Size = %dx%d.     \n", COLS, LINES);
            refresh();
        }
        halfdelay (1);
        ch = getch();
    } while (ch == ERR || ch == KEY_RESIZE);
    return ch;
}

Then a simple main to test it out:

// Simplified main capturing keystrokes.

int main (void) {
    WINDOW * w = initscr();
    noecho();
    signal (SIGWINCH, handle_resize);
    for (;;) {
        int ch = getch10th();
        mvprintw(0, 0, "Got character 0x%02x.     \n\n", ch);
    }
    endwin();
    return 0;
}

Astute readers will have noticed the presence of KEY_RESIZE in the getch10th() function as well. This is because some implementations will actually queue a special key to handle this exact case (forcing getch() to return after raising SIGWINCH).

If you're using the above code to allow for those systems that don't do this, you have to remember to handle that spurious key for those systems that do, hence why we capture that as well.

like image 72
paxdiablo Avatar answered Oct 20 '22 17:10

paxdiablo


From the FreeBSD documentation getch, interrupt getch depends of the system you are using:

Programmers concerned about portability should be prepared for either of two cases: (a) signal receipt does not interrupt getch; (b) signal receipt interrupts getch and causes it to return ERR with errno set to EINTR. Under the ncurses implementation, handled signals never interrupt getch.

I think you should consider using threads, one thread charged to use getch() and to pass datas to the main thread that treat them. You will be able to kill the thread that uses getch() when the signal SIGWINCH is sent, and to relauch it if you want. But killing the thread that uses getch() maybe useless, because the main thread isn't blocked by getch().

You can get non-blocking inputs without ncurses, as described on this page. But you can use read(STDIN_FILENO, &c, sizeof(char)) to read the input instead of fgetc() in the example, because read return a value <= 0 if it has failed.

like image 43
Bertrand125 Avatar answered Oct 20 '22 19:10

Bertrand125


Agreeing with @Emilien, this likely is a duplicate of ncurses - resizing glitch.

However, OP never showed working code to demonstrate the situation.

Except for OpenBSD, where the feature was disabled (until 2011), getch should return a KEY_RESIZE on SIGWINCH. The given reason for disabling it was a concern about sig_atomic_t that was addressed in 2007 (see OpenBSD's ncurses doesn't have USE_SIGWINCH for instance).

Other than that, the usual reason for not getting KEY_RESIZE is if one establishes a SIGWINCH handler (which prevents the one in ncurses from running). That was the actual problem in ncurses - resizing glitch (which none of the suggested answers addressed).

The endwin/refresh solution is well-known (see Handling SIGWINCH (resize events), in the ncurses FAQ since 1997). The resizeterm manual page summarizes that in its portability section. If you read that, you will realize that

  • if KEY_RESIZE is defined, then the implementation supports the SIGWINCH handler more/less compatible with ncurses, and
  • if it does not, then the endwin/refresh solution is all that is available.

The comment quoted from ncurses's curs_getch(3x) manual page in the accepted answer, by the way, does not refer to blocking SIGWINCH, but to a workaround in the library to prevent it from returning an error to the application when a signal interrupts a read. If you read the entire portability section of that manual page, that should be apparent.

Emacs, of course, uses only the termcap interface of ncurses, and is not a curses application. So the comments about how it handles SIGWINCH are irrelevant.

like image 34
Thomas Dickey Avatar answered Oct 20 '22 19:10

Thomas Dickey