curses在endwin上哪里注入KEY_RESIZE并刷新
Where does curses inject KEY_RESIZE on endwin and refresh
运行 此 Python 编码并调整大小 window。它将获得 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代码中却不需要?
这是设计使然...因为 curses 没有任何类似于事件循环的东西,它必须告诉应用程序检测到 SIGWINCH
,并且 curses 库已经更新了它的数据结构使用新的终端尺寸。 (在应用程序中有一个信号处理程序,应用程序可以使用它来 告诉 curses 库不会更好地工作,无论如何让 curses 库做这件事更简单)。
initscr
and getch
手册页提到了此 SIGWINCH
功能。
至于 "where",它是这样做的:在 _nc_update_screensize 中,它检查信号处理程序设置的标志,并从多个地方调用,包括 doupdate
(refresh
和 getch
调用)。如果屏幕尺寸已更改,这将注入一个 KEY_RESIZE
,无论实际上是否存在 SIGWINCH
。
现在...可以通过从新建立的处理程序调用原始处理程序来拥有信号处理程序 链。 (在 C 程序中调用 signal
returns 当前处理程序的地址)。 ncurses 只会在初始化时添加它的处理程序,所以一种(不太可能)的可能性是 python 代码在添加自己的处理程序时可能正在重用底层处理程序。
但是,示例中存在一个更大的问题:它们在信号处理程序中进行 curses 调用。这在 C 中是不安全的(ncurses 的信号处理程序只设置一个标志的原因)。也许在 python 中,这些处理程序被包装了——或者只是由于时间的原因,你会得到意想不到的行为。
Thomas 回答了 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 希望在没有 SA_RESTART
标志的情况下安装 SIGWINCH
处理程序。
ncurses 库调用 ncurses/base/lib_getch.c
中定义的 fifo_push
来读取输入流。当您阻塞 getch
.
时,该函数具有阻塞 read
在 SIGWINCH
上,此调用被中断,并且 returns -1
和 errno
设置为 EINTR
。
ncurses 库将在 _nc_wgetch
中处理此问题,它调用 _nc_handle_sigwinch
来检查 SIGWINCH
是否发生。如果是这样,它会调用 _nc_update_screensize
到 ungetch
a KEY_RESIZE
.
到目前为止一切顺利。但是,如果我们在安装 SIGWINCH
处理程序时使用了 SA_RESTART
怎么办? read
系统调用将在中断时重新启动。这就是为什么 C 程序在 window 调整大小后不会立即退出,而是必须读取另一个按键。
更有趣的是,ncurses 希望在安装信号处理程序时设置 SA_RESTART
(在 ncurses/tty/lib_tstp.c
中):
Note: This code is fragile! Its problem is that different OSs
handle restart of system calls interrupted by signals differently.
The ncurses code needs signal-call restart to happen -- otherwise,
interrupted wgetch() calls will return FAIL, probably making the
application think the input stream has ended and it should
terminate. In particular, you know you have this problem if, when
you suspend an ncurses-using lynx with ^Z and resume, it dies
* immediately.
但是SIGWINCH
是个例外...
#ifdef SA_RESTART
#ifdef SIGWINCH
if (sig != SIGWINCH)
#endif
new_act.sa_flags |= SA_RESTART;
#endif /* SA_RESTART */
原始 C 代码失败,因为 man signal
说:
By default, in glibc 2 and later, the signal() wrapper function does not invoke the kernel system call. Instead, it calls sigaction(2) using flags that supply BSD semantics.
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
}
运行 此 Python 编码并调整大小 window。它将获得 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代码中却不需要?
这是设计使然...因为 curses 没有任何类似于事件循环的东西,它必须告诉应用程序检测到 SIGWINCH
,并且 curses 库已经更新了它的数据结构使用新的终端尺寸。 (在应用程序中有一个信号处理程序,应用程序可以使用它来 告诉 curses 库不会更好地工作,无论如何让 curses 库做这件事更简单)。
initscr
and getch
手册页提到了此 SIGWINCH
功能。
至于 "where",它是这样做的:在 _nc_update_screensize 中,它检查信号处理程序设置的标志,并从多个地方调用,包括 doupdate
(refresh
和 getch
调用)。如果屏幕尺寸已更改,这将注入一个 KEY_RESIZE
,无论实际上是否存在 SIGWINCH
。
现在...可以通过从新建立的处理程序调用原始处理程序来拥有信号处理程序 链。 (在 C 程序中调用 signal
returns 当前处理程序的地址)。 ncurses 只会在初始化时添加它的处理程序,所以一种(不太可能)的可能性是 python 代码在添加自己的处理程序时可能正在重用底层处理程序。
但是,示例中存在一个更大的问题:它们在信号处理程序中进行 curses 调用。这在 C 中是不安全的(ncurses 的信号处理程序只设置一个标志的原因)。也许在 python 中,这些处理程序被包装了——或者只是由于时间的原因,你会得到意想不到的行为。
Thomas 回答了 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 希望在没有 SA_RESTART
标志的情况下安装 SIGWINCH
处理程序。
ncurses 库调用 ncurses/base/lib_getch.c
中定义的 fifo_push
来读取输入流。当您阻塞 getch
.
read
在 SIGWINCH
上,此调用被中断,并且 returns -1
和 errno
设置为 EINTR
。
ncurses 库将在 _nc_wgetch
中处理此问题,它调用 _nc_handle_sigwinch
来检查 SIGWINCH
是否发生。如果是这样,它会调用 _nc_update_screensize
到 ungetch
a KEY_RESIZE
.
到目前为止一切顺利。但是,如果我们在安装 SIGWINCH
处理程序时使用了 SA_RESTART
怎么办? read
系统调用将在中断时重新启动。这就是为什么 C 程序在 window 调整大小后不会立即退出,而是必须读取另一个按键。
更有趣的是,ncurses 希望在安装信号处理程序时设置 SA_RESTART
(在 ncurses/tty/lib_tstp.c
中):
Note: This code is fragile! Its problem is that different OSs handle restart of system calls interrupted by signals differently. The ncurses code needs signal-call restart to happen -- otherwise, interrupted wgetch() calls will return FAIL, probably making the application think the input stream has ended and it should terminate. In particular, you know you have this problem if, when you suspend an ncurses-using lynx with ^Z and resume, it dies * immediately.
但是SIGWINCH
是个例外...
#ifdef SA_RESTART
#ifdef SIGWINCH
if (sig != SIGWINCH)
#endif
new_act.sa_flags |= SA_RESTART;
#endif /* SA_RESTART */
原始 C 代码失败,因为 man signal
说:
By default, in glibc 2 and later, the signal() wrapper function does not invoke the kernel system call. Instead, it calls sigaction(2) using flags that supply BSD semantics.
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
}