中断(n)诅咒输入信号

use*_*062 13 c curses posix tui ncurses

我的一个程序使用ncurses绘制一个小的tui.我的目标之一是使其可以移植到其他curses实现.这意味着我想在自己的调整大小操作上捕获终端模拟器发出的SIGWINCH,并更新我的tui以遵守更改的几何(并且不依赖于ncurses的调整大小设施).由于POSIX(据我所知)只允许访问sig_atomic_t信号处理程序中的变量,因此我将其设置为不同的状态.在主循环中,我的程序检查状态是否已更改并在必要时更新tui.

但是现在,getch当信号到达时,我遇到了我的程序挂起的问题.ncurses文档声明处理信号永远不会中断它.这意味着在按下输入键之前不会更新tui的大小.

是否有任何可移植的中断方式getch?我目前的方法是ungetch在信号处理程序中使用虚拟键,但我不确定是否允许这样做.实际上我找不到任何关于是否可以在信号处理程序中使用curses函数的文档.知道如何正确处理这件事吗?

问候

pax*_*blo 6

您应该遵循的一条准则是在中断例程中尽可能少地执行操作。如果您所做的不仅仅是设置标志,那么您应该考虑重新考虑您的解决方案。

系统curses有办法处理这个问题,但需要开发人员做一些工作。

您可以将半延迟模式设置为适当的延迟,这样如果在此期间没有可用的击键,该模式getch()就会返回。ERR这有效地让你摆脱了getch()这样你就可以进行任何你需要的其他诅咒操作。

所以,这就是我的建议。首先,更改您的 SIGWINCH 处理程序,以便它仅设置一个resized您的“主”程序可以检测到的标志。

其次,为您的应用程序提供一种特殊形式getch()(显然是伪代码):

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
Run Code Online (Sandbox Code Playgroud)

就效率而言,半延迟模式是永远等待(并且不处理调整大小事件)和立即返回(吸收 CPU 运行)之间的折衷方案。

明智地使用它可以使您的 Windows 响应相当快,而不必担心可移植性。


有关将其付诸实践的示例,请参阅以下 C 程序。一、信号与拦截函数:

#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;
}
Run Code Online (Sandbox Code Playgroud)

然后简单main测试一下:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

KEY_RESIZE精明的读者也会注意到该函数中存在getch10th()。这是因为某些实现实际上会排队一个特殊的键来处理这种确切的情况(getch()在 raise 之后强制返回SIGWINCH)。

如果您使用上面的代码来允许那些执行此操作的系统,则必须记住为那些执行此操作的系统处理该伪密钥,这就是为什么我们也捕获它。


Ber*_*125 1

从 FreeBSD 文档getch来看,中断 getch 取决于您所使用的系统:

关注可移植性的程序员应该为以下两种情况做好准备: (a) 信号接收不会中断 getch;(b) 信号接收中断 getch 并使其返回 ERR,并将 errno 设置为 EINTR。在 ncurses 实现下,处理的信号永远不会中断 getch。

我认为您应该考虑使用线程,一个线程负责使用 getch() 并将数据传递到处理它们的主线程。当发送信号 SIGWINCH 时,您将能够终止使用 getch() 的线程,并根据需要重新启动它。但是杀死使用 getch() 的线程可能没有用,因为主线程没有被 getch() 阻塞。

您无需 ncurses 即可获得非阻塞输入,如本页所述。但是您可以使用read(STDIN_FILENO, &c, sizeof(char))读取输入来代替fgetc()示例中的输入,因为如果读取失败,则返回值 <= 0。