15

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

user1678062
  • 573
  • 5
  • 16
  • This is out of my domain, but in this case, you might ask: how does emacs handle resizing events? Seems like there might be a solution there. – fearless_fool Nov 12 '14 at 05:55
  • possible duplicate of [ncurses - resizing glitch](http://stackoverflow.com/questions/13707137/ncurses-resizing-glitch) – Emilien Nov 12 '14 at 12:52
  • @Emilien: The accepted answer of the question recommends to call `endwin` and `refresh` which seems to be a valid way to do, but still I can't find anything about it in the documentation and I don't like using undefined behavior (especially in C). @fearless_fool: As far as I understand the code, emacs does not use (n)curses to retrieve characters or to handle resize events, but does most of it on its own. But maybe I can find another application... But still, I probably won't know whether the solution, such an application uses, is defined behavior or just works by coincidence. – user1678062 Nov 13 '14 at 15:41
  • You are also free to call a lot of POSIX "safe functions" within your signal handler. See `man 7 signal`. – Jite Nov 25 '14 at 21:40
  • @Jite: The only thing I could find about curses functions is that their [behaviour regarding signals is undefined if not defined by the specification](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/intov.html). – user1678062 Nov 27 '14 at 16:40
  • My copy of ncurses (5.9) includes the resizeterm(3X) man page, with the following text: "If ncurses is configured to supply its own SIGWINCH handler, the resizeterm function ungetch's a KEY_RESIZE which will be read on the next call to getch. This is used to alert an application that the screen size has changed, and that it should repaint special features such as pads that cannot be done automatically." Perhaps you could check details of your ncurses install, and/or (it it's easy) try the ungetch of KEY_RESIZE in your handler instead of a dummy key. – sjnarv May 21 '15 at 01:10
  • @sjnarv: Thanks for your response. But this is still library dependent. And the whole intention of this question was to find a portable and specified way to achieve this behavior. Even `curs_getch(3X)` does not say whether `ungetch` may be called in a signal handler or not. – user1678062 May 22 '15 at 16:15

4 Answers4

6

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.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Shouldn’t the `resized` variable be of type `sig_atomic_t`, as suggested by the original poster? And shouldn’t it be declared `volatile`, too? – Roland Illig Mar 06 '16 at 19:05
  • @Roland, `sig_atomic_t`, no, it doesn't have to be so. Since it's only zero/non-zero, atomicity isn't really a problem. As to `volatile`, I'm not *entirely* certain. The variable cannot be modified outside of the compilation unit so the compiler may assume that, since `handle_resize` is never called explicitly, there's only one bit of code that changes the variable. Hence, even if the compiler *were* intelligent enough to realise it may be changed by a signal handler, the safest option is to mark it volatile. Good catch, will change. – paxdiablo Mar 09 '16 at 08:44
  • How about just hanging on `select()`, and only calling `getch()` when input is ready? `select()` will obviously reliably interrupt on receipt of a signal. – Crowman Mar 10 '16 at 02:07
  • @Paul, that should possibly be an answer rather than a comment. – paxdiablo Mar 10 '16 at 02:25
  • I still would suggest you use `volatile` and `sig_atomic_t` if nothing else for making it a habit. @Crowman One reason I can think of is the portability issues with `select(2)` and mixing it up with `curses` seems to be even more problematic. Now it's true that those who are experienced with `select()` will know the things to check but there are a lot of subtleties and things to consider - which I'm guessing you're aware of but still. – Pryftan Jan 05 '20 at 16:26
1

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.

Bertrand125
  • 904
  • 6
  • 13
  • 1
    Mixing in threads with signals seems to me the wrong way to go about it if you can avoid it. Also `read(2)` returns `-1` on error (and sets `errno` appropriately). `0` isn't an error really. You say 'failed' but I wouldn't call reading no bytes a failure. That's probably semantics though. – Pryftan Jan 05 '20 at 16:30
1

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.

Community
  • 1
  • 1
Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • 1
    Oh come now, Thomas. If you believe it's a duplicate of a problem in ncurses it probably is. I mean you are the one who's behind it (and much respect to you for it and the other things you're behind - including your time here)! Funny you should say that about emacs; I hate emacs but I use vim (used to use vi) and I thought until not long ago that it too was a curses program but it seems it just uses termcap (or was it terminfo? It links to libtinfo anyway). Anyway have a +1 for the thorough answer. – Pryftan Jan 05 '20 at 16:24
0

It has been awhile but I'm pretty sure you can longjmp out of a handler. Not sure how it will work with ncurses though.

http://www.csl.mtu.edu/cs4411.ck/www/NOTES/non-local-goto/sig-1.html

EDIT: I HAVE A NEW ANSWER ******************************

I think the non blocking read is your ticket, the following works perfectly for me. Also, in my implementation I get the integer value '410' when a resize event happens, but this version is not using that resize flag. Try this out?

I have the timeout set to 10mS, this will return ERR instead of a character 100 times per second when nothing is happening and catches the resizes right away. 100 times per second is nothing... on my low end machine 8 minutes of this doesn't even register on the cput 'time' command (0.000 user and sys used).

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

int resized = 0;

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

int main(int argc, char * argv[])
{
   WINDOW * w;
   w = initscr();
   timeout(10); // getch returns ERR if no data within X ms
   cbreak();
   noecho();

   signal(SIGWINCH, handle_resize);

   int c, i;

   while (1) {
      c = getch();

      if(resized) {
         resized = 0;
         endwin();
         refresh();
         clear();

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

         refresh();
      } 

      if (c != ERR) {
         mvprintw(2, 0, "Got character %c (%d)\n", c, c);
      }
  }//while 1

   endwin();
   return 0;
}
RobC
  • 502
  • 4
  • 17
  • As you imply yourself, this seems to be highly implementation dependent and my actual goal was to achieve it in a way that is fully conforming to the standards. Besides, I would have to block SIGWINCH in every other signal handler to avoid the undefined behavior that occurs when calling longjmp in nested signal handlers. – user1678062 Dec 04 '14 at 17:25
  • @user1678062 I looked at the man page and it almost considers it a feature :). The only thing I can think of is you make stdin a non-blocking descriptor and poll it... even on embedded systems polling a few dozen times per second should be negligable. Besides that, maybe you can inject a non-printable dummy character onto stdin to trigger the exit from getch, throw that byte out and continue. – RobC Dec 05 '14 at 01:40
  • @user1678062 Thinking of the injection, I wonder if you could turn on local echo and send it a character. I also found this http://unix.stackexchange.com/questions/103885/piping-data-to-a-processs-stdin-without-causing-eof-afterward . Seems like you might be able to setup a pipe to send data on demand to stdin while it is running... not sure if normal stdin works in that scenario. – RobC Dec 05 '14 at 01:55
  • Looks interesting. I think, I'll have to stick with it, if I don't find another solution. Nevertheless, I have to point some things out. First: When resizing the terminal window, getch doesn't return ERR when the timeout occurs but `~Z` (410). Second: If it is possible to inject characters on `stdin` in a portable way, wouldn't it also be possible to `select`/`poll` on stdin and just use a nonblocking `getch` after they return to retrieve the key? But I'm still missing specification about how curses reacts when directly manipulating `stdin`. – user1678062 Dec 06 '14 at 10:18
  • `410` is the `KEY_RESIZE` in Linux, for example. Not all implementations support that though it's _very_ handy. – paxdiablo May 26 '15 at 06:14