Где проклятия вводят 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
}
Другие вопросы по тегам