运行此Python代码并调整大小窗口。它将得到一个KEY_RESIZE
并退出。
import curses
import signal
stdscr = None
def handler(num, _):
curses.endwin()
stdscr.refresh()
stdscr = curses.initscr()
curses.cbreak()
stdscr.keypad(1)
stdscr.refresh()
signal.signal(signal.SIGWINCH, handler)
while True:
ch = stdscr.getch()
if ch == curses.KEY_RESIZE: break
curses.endwin()
这个KEY_RESIZE
注入哪里了?
我还用C代码进行了测试:
#include <ncurses.h>
#include <signal.h>
WINDOW *stdscr = NULL;
void handler(int num) {
endwin();
wrefresh(stdscr);
}
int main()
{
stdscr = initscr();
cbreak();
keypad(stdscr, 1);
wrefresh(stdscr);
signal(SIGWINCH, handler);
while (1) {
int ch = wgetch(stdscr);
if (ch == KEY_RESIZE) break;
}
endwin();
return 0;
}
运行并调整大小,然后按一个键,它将获得一个KEY_RESIZE
出口。为什么我们必须按一个键才能在C代码中获得KEY_RESIZE
,而在Python代码中却不需要?
答案 0 :(得分:1)
这是设计使然...由于curses没有事件循环之类的东西,它必须告诉应用程序检测到SIGWINCH
,并且curses库已更新其数据结构以使其正常工作新的终端尺寸。 (在应用程序中有一个信号处理程序,该应用程序可以用来对curses库进行 tell 的工作不会更好,而让curses库执行此操作更简单。)
initscr
和getch
手册页提到了此SIGWINCH
功能。
对于“ where”,它是这样做的:位于_nc_update_screensize中,它检查信号处理程序设置的标志,并从包括doupdate
(其中refresh
和{ getch
个电话)。如果屏幕尺寸已更改,那么无论实际上是否存在KEY_RESIZE
,这都会注入一个SIGWINCH
。
现在...通过从新建立的处理程序中调用原始处理程序,可以有信号处理程序 chain 。 (在C程序中调用signal
将返回当前处理程序的地址)。 ncurses只会在初始化时添加其处理程序,因此一种(不太可能)的可能性是python代码在添加自己的处理程序时可能正在重用基础处理程序。
但是,示例中存在一个更大的问题:它们正在信号处理程序中进行curses调用。这在C语言中是不安全的(ncurses的信号处理程序仅设置一个标志的原因)。也许在python中,这些处理程序被包装了-或仅仅是由于时间安排,您会得到意想不到的行为。
答案 1 :(得分:0)
托马斯回答KEY_RESIZE
的来源。这对我来说是调试C代码并回答有关按键的第二个问题的很好的介绍。
简短的答案作为工作代码给出(潜在的问题是由于在信号处理程序中调用ncurses函数而引起的,请参见Thomas更新的答案,但这似乎不是问题的根本原因):
#include <ncurses.h>
#include <signal.h>
WINDOW *stdscr = NULL;
void handler(int num) {
endwin();
wrefresh(stdscr);
}
int main()
{
stdscr = initscr();
cbreak();
keypad(stdscr, 1);
wrefresh(stdscr);
struct sigaction old_action;
struct sigaction new_action;
new_action.sa_handler = handler;
new_action.sa_flags = 0; // !
sigemptyset(&new_action.sa_mask);
sigaction(SIGWINCH, &new_action, &old_action);
while (1) {
int ch = wgetch(stdscr);
if (ch == KEY_RESIZE) break;
}
endwin();
return 0;
}
长答案很乏味。
基本上,ncurses期望安装SIGWINCH
处理程序时不带有SA_RESTART
标志。
ncurses库调用fifo_push
中定义的ncurses/base/lib_getch.c
来读取输入流。而且,当您阻止read
时,该函数就会阻止getch
。
在SIGWINCH
上,此调用被中断,并返回-1
且errno
设置为EINTR
的情况。
ncurses库将在_nc_wgetch
中处理此问题,该库将调用_nc_handle_sigwinch
来检查是否发生了SIGWINCH
。如果是这样,它将调用_nc_update_screensize
到ungetch
为KEY_RESIZE
。
到目前为止,一切都很好。但是,如果我们在安装SA_RESTART
处理程序时使用了SIGWINCH
怎么办? read
系统调用将在中断时重新启动。这就是为什么在调整窗口大小后C程序不会立即退出,而是必须读取另一键的原因。
更有趣的是,ncurses期望在安装信号处理程序时(在SA_RESTART
中)设置ncurses/tty/lib_tstp.c
:
注意:此代码易碎!问题是不同的操作系统 处理信号中断的系统调用的重新启动的方式有所不同。 ncurses代码需要重新启动信号调用-否则, 中断的wgetch()调用将返回FAIL,可能是 应用程序认为输入流已经结束,应该 终止。特别是,您知道如果在以下情况下会遇到此问题 你用^ Z挂起一个使用ncurses的天猫并继续,它死了 *立即。
但是SIGWINCH
是一个例外...
#ifdef SA_RESTART
#ifdef SIGWINCH
if (sig != SIGWINCH)
#endif
new_act.sa_flags |= SA_RESTART;
#endif /* SA_RESTART */
原始C代码失败,因为man signal
说:
默认情况下,在glibc 2和更高版本中,signal()包装函数不会调用内核系统调用。相反,它使用提供BSD语义的标志调用sigaction(2)。
BSD的语义是:
sa.sa_flags = SA_RESTART;
Python? Python不会对SA_RESTART
感到困扰:
PyOS_sighandler_t
PyOS_setsig(int sig, PyOS_sighandler_t handler)
{
#ifdef HAVE_SIGACTION
...
struct sigaction context, ocontext;
context.sa_handler = handler;
sigemptyset(&context.sa_mask);
context.sa_flags = 0;
if (sigaction(sig, &context, &ocontext) == -1)
return SIG_ERR;
return ocontext.sa_handler;
#else
...
#endif
}