Подтвердить что ты не робот

Прерывание (n) проклятия getch на входящем сигнале

Одна из моих программ использует ncurses для рисования маленького tui. Одна из моих целей - сделать его довольно переносимым для других реализаций проклятий. Это означает, что я хочу поймать SIGWINCH, выпущенный эмулятором терминала, на операцию изменения размера сам и обновить мой tui, чтобы придерживаться измененной геометрии (и не зависит от возможностей изменения размеров ncurses). Поскольку POSIX (насколько мне известно) допускает доступ к переменным sig_atomic_t в обработчике сигнала, я устанавливаю его в другое состояние. В основном цикле моя программа проверяет, изменилось ли состояние и обновлено ли tui при необходимости.

Но теперь у меня есть проблема, что моя программа зависает в getch, когда поступает сигнал. В документации по ncurses говорится, что обработанные сигналы никогда не прерывают ее. Это означает, что размер tui не обновляется до нажатия клавиши ввода.

Есть ли какой-либо переносной способ прервать getch? Мой текущий подход заключается в ungetch фиктивном ключе в обработчике сигнала, но я не уверен, разрешено ли это. На самом деле я не мог найти никакой документации относительно того, может ли функция проклятия использоваться в обработчике сигнала или нет. Любая идея, как правильно справиться с этим вопросом?

Привет

4b9b3361

Ответ 1

Из документации FreeBSD getch прерывание getch зависит от используемой вами системы:

Программисты, заинтересованные в переносимости, должны быть подготовлены для из двух случаев: (a) получение сигнала не прерывает getch; (b) сигнал получение прерывает getch и заставляет его возвращать ERR с набором errno к EINTR. При реализации ncurses обрабатываются сигналы никогда не прерывайте getch.

Я думаю, вам стоит подумать об использовании потоков, один поток, на который нужно использовать getch(), и передать данные в основной поток, который их обрабатывает. Вы сможете убить поток, который использует getch(), когда посылается сигнал SIGWINCH, и, если хотите, передать его. Но убить поток, который использует getch(), может быть бесполезным, потому что основной поток не блокируется getch().

Вы можете получить неблокирующие входы без ncurses, как описано на этой странице . Но вы можете использовать read(STDIN_FILENO, &c, sizeof(char)) для чтения ввода вместо fgetc() в примере, потому что чтение возвращает значение <= 0, если оно потерпело неудачу.

Ответ 2

Единственное руководство, которому вы должны следовать, - сделать как можно меньше в процедурах прерывания. Если вы делаете что-то большее, чем установку флага, это значит, что вам следует подумать о том, чтобы пересмотреть свое решение.

Система curses имеет способ справиться с этой проблемой, но требует небольшой работы со стороны разработчика.

Вы установите режим с половинной задержкой с подходящей задержкой, так что getch() вернется с ERR, если в это время не будет нажата клавиша. Это эффективно избавит вас от вызова getch(), чтобы вы могли делать все, что вам нужно.

Итак, вот что я предлагаю. Сначала измените обработчик SIGWINCH, чтобы он просто установил флаг resized, который может обнаружить ваша "основная" программа.

Во-вторых, укажите ваше приложение в специальной форме getch() в строках (псевдокод, очевидно):

def getch_10th():
    set half delay mode for (for example) 1/10th second
    do:
        if resized:
            do whatever it takes to resize window
        set ch to result of real getch() (with timeout, of course)
    while timed out
    return ch

Режим с половинной задержкой - это компромисс, с точки зрения эффективности, между ожиданием навсегда (и без обработки событий изменения размера) и немедленным возвратом (сосание CPU grunt).

Использование этого с умом может заставить ваши окна реагировать достаточно быстро, не беспокоясь о переносимости.


См. следующую программу на примере приложения. Во-первых, функция сигнала и перехвата:

#include <curses.h>
#include <signal.h>

// Flag and signal handler.

static volatile int resized = 1;

static void handle_resize (int sig) {
    resized = 1;
}

// Get a character, handling resize events.

int getch10th (void) {
    int ch;
    do {
        if (resized) {
            resized = 0;
            endwin();
            refresh();
            mvprintw (1, 0, "Size = %dx%d.     \n", COLS, LINES);
            refresh();
        }
        halfdelay (1);
        ch = getch();
    } while (ch == ERR || ch == KEY_RESIZE);
    return ch;
}

Затем прост main, чтобы проверить это:

// Simplified main capturing keystrokes.

int main (void) {
    WINDOW * w = initscr();
    noecho();
    signal (SIGWINCH, handle_resize);
    for (;;) {
        int ch = getch10th();
        mvprintw(0, 0, "Got character 0x%02x.     \n\n", ch);
    }
    endwin();
    return 0;
}

Астуальные читатели заметили бы наличие KEY_RESIZE в функции getch10th(). Это связано с тем, что в некоторых реализациях будет стоять очередь специального ключа для обработки этого точного случая (принудительное возвращение getch() после повышения SIGWINCH).

Если вы используете приведенный выше код, чтобы позволить тем системам, которые этого не делают, вы должны помнить о том, чтобы обрабатывать этот ложный ключ для тех систем, которые делают, следовательно, почему мы также это фиксируем.

Ответ 3

Прошло некоторое время, но я уверен, что вы можете вывести longjmp из обработчика. Не уверен, как это будет работать с ncurses.

http://www.csl.mtu.edu/cs4411.ck/www/NOTES/non-local-goto/sig-1.html

ИЗМЕНИТЬ: У меня НОВЫЙ ОТВЕТ ******************************

Я думаю, что не блокирующее чтение - это ваш билет, для меня это отлично работает. Кроме того, в моей реализации я получаю целочисленное значение "410", когда происходит событие изменения размера, но эта версия не использует этот флаг изменения размера. Попробуйте это?

У меня установлен тайм-аут до 10 мс, это вернет ERR вместо символа 100 раз в секунду, когда ничего не произойдет, и сразу же перехватит изменения. 100 раз в секунду - ничто... на моей машине с нижним концом 8 минут этого даже не регистрируются в команде cput 'time (0.000 пользователей и sys).

#include <curses.h>
#include <signal.h>

int resized = 0;

void handle_resize(int sig)
{
  resized = 1;
}

int main(int argc, char * argv[])
{
   WINDOW * w;
   w = initscr();
   timeout(10); // getch returns ERR if no data within X ms
   cbreak();
   noecho();

   signal(SIGWINCH, handle_resize);

   int c, i;

   while (1) {
      c = getch();

      if(resized) {
         resized = 0;
         endwin();
         refresh();
         clear();

         mvprintw(0, 0, "COLS = %d, LINES = %d", COLS, LINES);
         for (i = 0; i < COLS; i++)
            mvaddch(1, i, '*');

         refresh();
      } 

      if (c != ERR) {
         mvprintw(2, 0, "Got character %c (%d)\n", c, c);
      }
  }//while 1

   endwin();
   return 0;
}

Ответ 4

Соглашаясь с @Emilien, это, вероятно, дубликат ncurses - изменение размера глюка.

Однако OP никогда не показывал рабочий код, чтобы продемонстрировать ситуацию.

За исключением OpenBSD, где функция отключена (до 2011), getch должен вернуть KEY_RESIZE в SIGWINCH. Данная причина для отключения была связана с проблемой sig_atomic_t, которая была рассмотрена в 2007 (см. OpenBSD ncurses не имеет USE_SIGWINCH, например).

Кроме того, обычная причина не получения KEY_RESIZE заключается в том, что устанавливается SIGWINCH обработчик (который предотвращает запуск одного из ncurses). Это была настоящая проблема в ncurses - изменении размера глюка (о котором не было сказано ни в одном из предложенных ответов).

Решение endwin/refresh хорошо известно (см. Обработка SIGWINCH (события изменения размера) в FAQ по ncurses с 1997 года). На странице справочника resizeterm содержится краткое описание этого раздела. Если вы прочтете это, вы поймете, что

  • Если KEY_RESIZE определено, то реализация поддерживает обработчик SIGWINCH, более/менее совместимый с ncurses, и
  • если это не так, тогда доступно решение endwin/refresh.

Комментарий, процитированный на странице ncurses curs_getch (3x) в принятом ответе, кстати, не относится к блокировке SIGWINCH, но к обходному пути в библиотеке, чтобы предотвратить его возврат к приложению, когда сигнал прерывает чтение. Если вы прочитали весь раздел переносимости этой страницы руководства, это должно быть очевидно.

Emacs, конечно, использует только интерфейс termcap для ncurses и не является средством проклятия. Поэтому комментарии о том, как он обрабатывает SIGWINCH, не имеют значения.