Где проклятия вводят KEY_RESIZE в endwin и обновляют
Запустите этот код 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
выход. Почему мы должны нажать клавишу, чтобы получить KEY_RESIZE
в коде C, который не является необходимым в коде Python?
2 ответа
Это по замыслу... поскольку у curses нет ничего похожего на цикл обработки событий, оно должно сообщить приложению, что SIGWINCH
было обнаружено, и что библиотека curses обновила свои структуры данных для работы с новым размером терминала. (Наличие обработчика сигнала в приложении, которое приложение может использовать, чтобы сообщить библиотеке curses, не будет работать лучше, и наличие библиотеки curses делает это проще, во всяком случае).
initscr
а также getch
справочные страницы упоминают об этом SIGWINCH
особенность.
Что касается "где", он делает это: это находится в _nc_update_screensize, который проверяет флаг, установленный обработчиком сигнала, и вызывается из нескольких мест, включая doupdate
(который refresh
а также getch
вызов). Это введет KEY_RESIZE
был ли на самом деле SIGWINCH
, если размер экрана изменился.
Теперь... возможно иметь цепочку обработчиков сигналов, вызывая оригинальный обработчик из вновь созданных обработчиков. (Calling signal
в программе на C возвращает текущий адрес обработчика). ncurses будет добавлять свой обработчик только во время инициализации, поэтому одна (маловероятная) вероятность состоит в том, что код python, возможно, повторно использует базовый обработчик, когда добавляет свой собственный.
Однако в примерах есть большая проблема: они делают вызовы curses в обработчиках сигналов. Это небезопасно в C (причина, по которой обработчики сигналов ncurses только устанавливают флаг). Возможно, в python эти обработчики обернуты - или просто из-за времени вы получаете неожиданное поведение.
Томас отвечает где KEY_RESIZE
происходит от. Для меня это отличное введение в отладку кода C и ответ на второй вопрос о нажатии клавиш.
Краткий ответ дается в виде рабочего кода (с потенциальной проблемой, возникающей из-за вызова функций ncurses в обработчиках сигналов, см. Обновленный ответ Томаса, но, похоже, здесь нет основной причины проблемы):
#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, возможно, заставляя приложение думать, что входной поток завершился и он должен завершиться. В частности, вы знаете, что у вас есть эта проблема, если, когда вы приостанавливаете lynx, использующий ncurses, с помощью ^Z и возобновляете его работу, он немедленно * умирает.
Но 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() не вызывает системный вызов ядра. Вместо этого он вызывает sigaction(2), используя флаги, которые предоставляют семантику BSD.
И семантика 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
}