Где проклятия вводят KEY_RESIZE в endwin и обновляют - PullRequest
0 голосов
/ 17 декабря 2018

Запустите этот код 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 ]

0 голосов
/ 18 декабря 2018

Томас отвечает, откуда 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 a KEY_RESIZE.

Пока все хорошо.Но что, если мы использовали SA_RESTART при установке обработчика SIGWINCH?Системный вызов read будет перезапущен при прерывании.Вот почему программа на C не закрывается сразу после изменения размера окна, а должна прочитать еще одну клавишу.

Более интересно то, что ncurses ожидает установки SA_RESTART при установке обработчиков сигналов (в ncurses/tty/lib_tstp.c):

Примечание. Этот код хрупок!Его проблема в том, что разные ОС по-разному обрабатывают перезапуск системных вызовов, прерываемых сигналами.Код ncurses нуждается в перезапуске сигнального вызова, иначе прерванные вызовы wgetch () вернут FAIL, возможно, заставляя приложение думать, что входной поток завершился и должен завершиться.В частности, вы знаете, что у вас есть эта проблема, если, когда вы приостанавливаете рысь, использующую 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
}
0 голосов
/ 18 декабря 2018

Это специально ... так как curses не имеет ничего похожего на цикл обработки событий, он должен сообщить приложению, что обнаружен SIGWINCH, и что библиотека curses обновила свои структуры данных для работы сновый размер терминала.(Наличие обработчика сигнала в приложении, которое приложение может использовать для указания , что библиотека curses не будет работать лучше, а наличие библиотеки curses делает это проще, во всяком случае).

The initscr и getch страницы справочника упоминают эту функцию SIGWINCH.

Что касается "где", то это происходит: это в _nc_update_screensize , который проверяет флаг, установленный обработчиком сигнала, и вызывается из нескольких мест, включая doupdate (которые вызывают refresh и getch).Это вставит KEY_RESIZE, независимо от того, было ли на самом деле SIGWINCH, если размер экрана изменился.

Теперь ... возможно иметь обработчики сигналов chain , вызываяоригинальный обработчик из недавно созданных обработчиков.(Вызов signal в программе на C возвращает текущий адрес обработчика).ncurses будет добавлять свой обработчик только во время инициализации, поэтому одна (маловероятная) возможность состоит в том, что код python, возможно, повторно использует базовый обработчик, когда добавляет свой собственный.

Однако в примерах есть более серьезная проблема: ониделают ругательства в обработчиках сигналов.Это небезопасно в C (причина, по которой обработчики сигналов ncurses только устанавливают флаг).Возможно, в python эти обработчики обернуты - или просто из-за времени вы получаете неожиданное поведение.

...