15

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

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

有什么便携式的中断方式getch吗?我目前的方法是ungetch在信号处理程序中使用一个虚拟键,但我不确定这是否允许。实际上,我找不到任何关于是否可以在信号处理程序中使用 curses 函数的文档。知道如何正确处理这个问题吗?

问候

4

4 回答 4

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

就效率而言,半延迟模式是永久等待(不处理大小调整事件)和立即返回(吸收 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;
}

然后简单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;
}

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

如果您使用上面的代码来允许那些这样做的系统,您必须记住为那些这样做的系统处理那个虚假密钥,因此我们也捕获它。

于 2015-05-26T01:55:36.690 回答
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。

于 2015-05-25T07:51:18.963 回答
1

同意@Emilien,这可能是ncurses - resizing glitch的副本。

但是,OP 从未显示过工作代码来演示这种情况。

除了 OpenBSD,该功能被禁用(直到2011 年),getch 应该返回一个KEY_RESIZEon SIGWINCH。禁用它的给定原因sig_atomic_t是在2007年解决的问题(例如,请参阅OpenBSD 的 ncurses 没有 USE_SIGWINCH)。

除此之外,没有得到的通常原因KEY_RESIZE是如果一个人建立了一个SIGWINCH处理程序(这会阻止 ncurses 中的那个人运行)。这是ncurses 中的实际问题 - 调整故障大小(建议的答案都没有解决)。

endwin/refresh 解决方案是众所周知的(请参阅自 1997 年以来的 ncurses 常见问题解答中的处理 SIGWINCH(调整大小事件))。resizeterm手册页在其可移植性部分总结了这一点。如果你读了,你会意识到

  • 如果KEY_RESIZE已定义,则实现支持SIGWINCH与 ncurses 或多/少兼容的处理程序,并且
  • 如果没有,那么 endwin/refresh 解决方案就是所有可用的解决方案。

顺便说一句,在接受的答案中从 ncurses 的curs_getch(3x)手册页中引用的评论不是指阻塞SIGWINCH,而是指库中的一种解决方法,以防止它在信号中断读取时向应用程序返回错误。如果您阅读了该手册页的整个可移植性部分,那应该很明显。

当然,Emacs 只使用 ncurses 的termcap接口,并不是一个 curses 应用程序。所以关于它如何处理的评论SIGWINCH是无关紧要的。

于 2016-03-09T10:07:19.823 回答
0

已经有一段时间了,但我很确定您可以从处理程序中取出 longjmp。不确定它如何与 ncurses 一起使用。

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

编辑:我有一个新答案 ******************************

我认为非阻塞读取是你的票,以下对我来说非常有效。此外,在我的实现中,当调整大小事件发生时,我得到整数值“410”,但这个版本没有使用那个调整大小标志。试试这个?

我将超时设置为 10 毫秒,当什么都没有发生并立即捕获调整大小时,这将返回 ERR 而不是每秒 100 次的字符。每秒 100 次不算什么......在我的低端机器上 8 分钟甚至没有在 cput 'time' 命令上注册(0.000 个用户和系统使用)。

#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;
}
于 2014-12-03T08:43:20.077 回答