我的一个程序使用ncurses绘制一个小的tui。我的目标之一是使其可以移植到其他curses实现。这意味着我想在自己的调整大小操作上捕获终端模拟器发出的SIGWINCH并更新我的tui以遵守更改的几何(并且不依赖于ncurses的调整大小设施)。由于POSIX(据我所知)只允许访问信号处理程序中的sig_atomic_t
变量,因此我将其设置为不同的状态。在主循环中,我的程序检查状态是否已更改并在必要时更新tui。
但是现在,我遇到的问题是,当信号到达时,我的程序会在getch
中挂起。 ncurses文档声明处理信号永远不会中断它。这意味着在按下输入键之前不会更新tui的大小。
有中断getch
的便携方法吗?我目前的方法是ungetch
信号处理程序中的虚拟键,但我不确定是否允许这样做。实际上我找不到任何关于是否可以在信号处理程序中使用curses函数的文档。知道如何正确处理这件事吗?
此致
答案 0 :(得分:2)
您应遵循的一条准则是在中断例程中尽可能少地执行。如果您正在做的不仅仅是设置标志,那么您应该考虑重新考虑解决方案。
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咕噜声)之间。
明智地使用它可以使您的窗口响应速度相当快,而不必担心可移植性。
有关将此操作付诸实践的示例,请参阅以下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()
在提升SIGWINCH
后返回。)
如果您使用上面的代码来允许那些不执行此操作的系统,您必须记住为这些系统处理那个虚假密钥,因此我们为什么要捕获它同样。
答案 1 :(得分:1)
从FreeBSD文档getch,中断getch取决于您使用的系统:
关注可移植性的程序员应该做好准备 两种情况:(a)信号接收不中断getch; (b)发出信号 接收中断getch并使其返回ERR并设置errno 到EINTR。在ncurses实现下,处理信号 永远不要打断getch。
我认为你应该考虑使用线程,一个线程负责使用getch()并将数据传递给处理它们的主线程。在发送信号SIGWINCH时,您将能够杀死使用getch()的线程,并在需要时重新启动它。但是杀死使用getch()的线程可能没用,因为主线程没有被getch()阻塞。
您可以在没有ncurses的情况下获得非阻塞输入,如page所述。但是您可以使用read(STDIN_FILENO, &c, sizeof(char))
来读取示例中的输入而不是fgetc()
,因为如果读取失败,则读取返回值
答案 2 :(得分:0)
已经有一段时间,但我很确定你可以从一个处理程序中解脱出来。不知道它如何与ncurses一起使用。
http://www.csl.mtu.edu/cs4411.ck/www/NOTES/non-local-goto/sig-1.html
编辑:我有一个新的答案******************************
我认为非阻塞读取是您的票,以下对我来说非常适合。此外,在我的实现中,当调整大小事件时,我得到整数值'410',但此版本没有使用该调整大小标志。试试这个吗?
我将超时设置为10mS,当没有任何事情发生时,这将返回ERR而不是每秒100次,并立即捕获调整大小。每秒100次是没有...在我的低端机器上8分钟甚至没有在cput'time'命令上注册(0.000用户和使用的sys)。
#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;
}
答案 3 :(得分:0)
同意@Emilien,这可能与 ncurses - resizing glitch 重复。
然而,OP从未展示过工作代码来证明这种情况。
除了禁用该功能的OpenBSD(2011之前),getch应在KEY_RESIZE
上返回SIGWINCH
。禁用它的原因是对sig_atomic_t
中KEY_RESIZE
所关注的问题(例如,请参阅 2007 )。
除此之外, 通常 没有获得SIGWINCH
的原因是,如果有人建立KEY_RESIZE
处理程序(这会阻止ncurses中的那个)从跑步)。这是 OpenBSD's ncurses doesn't have USE_SIGWINCH 中的实际问题(没有提出建议的答案)。
endwin / refresh解决方案众所周知(参见 ncurses - resizing glitch ,自1997年以来的ncurses常见问题解答)。 Handling SIGWINCH (resize events)手册页总结了其可移植性部分。如果你读到了,你就会意识到
SIGWINCH
,那么该实现支持SIGWINCH
处理程序与ncurses更兼容/更少兼容,并且顺便说一下,在接受的答案中,ncurses的resizeterm
手册页中引用的注释不是指阻止SIGWINCH
,而是指库中的变通方法,以防止它返回错误信号中断读取时的应用程序。如果您阅读该手册页的整个可移植性部分,那应该是显而易见的。
当然,Emacs只使用ncurses的 termcap 接口,而不是curses应用程序。所以关于它如何处理{{1}}的评论是无关紧要的。