Ncurses – Как обрабатывать изменение размера терминала для изображения, которое обновляется в бесконечном цикле

У меня есть программа, использующая ncurses для рисования окна с таймером. Он постоянно обновляется в бесконечном цикле. Он работает так, как задумано, однако, когда я изменяю размер окна терминала, все изображение портится и все портится.

Я читал некоторые статьи на SIGWINCH для ncurses, а также несколько других, но все они полагаются на wgetch(win) или getch(). Эти вызовы блокируют цикл ожидания ввода пользователя. Это кажется довольно стандартным вариантом использования, поэтому пытаемся понять, какова процедура обнаружения изменения размера окна без блокировки цикла.

Обратите внимание, что я использую Windows 11, и для компиляции я запускаю gcc -o test.exe test.c -lncurses -DNCURSES_STATIC

#include <windows.h>
#include <ncurses/ncurses.h>
#include <time.h>

// default window sizes
#define WINDOW_HEIGHT 16
#define WINDOW_WIDTH 45
#define WINDOW_START_Y 5
#define WINDOW_START_X 5

// prototypes
WINDOW *init_window(void);

int main(void)
{
        // global screen init
        initscr();
        WINDOW *win;
        win = init_window();
        refresh();

        // hide cursor
        curs_set(0);

        // draw basic box in the window
        box(win, 0, 0);

        // initialize the timer
        time_t now;
        while (1) {
                
                // update current UTC time
                now = time(0);
                struct tm *tmp = gmtime(&now);

                // modify terminal window
                mvwprintw(win, 1, 1, "           _________           ");
                mvwprintw(win, 2, 1, "          / ======= \\         ");
                mvwprintw(win, 3, 1, "         / __________\\        ");
                mvwprintw(win, 4, 1, "        | ___________ |        ");
                mvwprintw(win, 5, 1, "        | | -       | |        ");
                mvwprintw(win, 6, 1, "        | |         | |              ");
                mvwprintw(win, 7, 1, "        | |_________| |________________  ");
                mvwprintw(win, 8, 1, "        \\=____________/      REM       ) ");
                mvwprintw(win, 9, 1, "        / \"\"\"\"\"\"\"\"\"\"\" \\               /  ");
                mvwprintw(win, 10, 1, "       / ::::::::::::: \\          =D-'   ");
                mvwprintw(win, 11, 1, "      (_________________)                  ");
                mvwprintw(win, 12, 1, "      current time: %02d:%02d:%02d", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
                wrefresh(win);
        }

        // clean up
        endwin();
        return 0;
}

WINDOW* init_window(void)
{
        // create new window
        WINDOW *win = newwin(WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_START_Y, WINDOW_START_X);
        if (!win) {
                printf("ERROR: could not create window\n");
                exit(1);
        }
        return win;
}

Я не уверен в деталях, специфичных для Windows, но вы можете либо дождаться события в файловом дескрипторе стандартного ввода с тайм-аутом, либо установить стандартный ввод в неблокирующий режим и периодически опрашивать его. Или дождитесь двух дескрипторов: периодического таймера и стандартного ввода. Звоните getch(), только если не заблокирует.

dimich 19.04.2024 05:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
142
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Давайте на секунду отложим изменение размеров. Даже без изменения размера ваша программа не очень хороша.

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

Вам следует добавить sleep() или около того. Не спите в течение 1 секунды, потому что тогда часы могут задерживаться на 1 секунду (в среднем на 0,5 секунды), что в конечном итоге может привести к пропуску отображаемой секунды (если эта 1 секунда превратится в 1,0001 секунды, перепрыгивая через два целых числа). ). Вместо этого запросите текущее время, включая доли секунды (с помощью gettimeofday() или clock_gettime()), и спите оставшуюся часть текущей секунды. Если вы хотите постоянно обновлять экран (например, показывать миллисекунды), то переходите в режим сна в соответствии с восприятием человеческого глаза или предполагаемой частотой обновления дисплея, например около 1/60 с.

Теперь вы хотите, чтобы этот сон был прерван на SIGWINCH. Хорошей новостью является то, что по умолчанию он будет прерван, поэтому, вероятно, никаких дальнейших действий здесь не потребуется. Я не знаком с частью ncurses, возможно, вам нужно как-то сообщить, что размер окна изменился. Но как узнать, был ли изменен размер окна? Может быть, вам все равно; вы просто каждый раз обновляете ncurses, указав размер окна (если этот шаг необходим; опять же, я не так уж хорошо знаком с ncurses); если это делается раз в секунду или при каждом изменении размера окна, то это абсолютно нормально.

Действительно правильное решение, если вы хотите, чтобы ваше приложение масштабировалось, хотите добавить больше функций (например, обработку ввода) в будущем или хотите действительно знать, произошло ли изменение размера окна; и если вы хотите сделать все это «вручную»:

Заблокируйте доставку SIGWINCH (sigprocmask()). Установите обработчик SIGWINCH, который меняет глобальное логическое значение (sigaction()). Замените вызов sleep() на pselect() (или ppoll()), который атомарно включает доставку SIGWINCH только на время этого вызова pselect(). Установите это pselect() на желаемый тайм-аут, а также следите за файловыми дескрипторами, если хотите (т. е. файловый дескриптор 0, если вы хотите читать данные всякий раз, когда они доступны на стандартном вводе). После этого pselect() вызовите проверку, установлено ли глобальное логическое значение SIGWINCH; если это так, то предпримите правильные действия (все, что вам нужно сделать, чтобы обработать изменение размера; возможно, при необходимости сообщите об этом ncurses) и очистите этот флаг.

Существуют библиотеки, которые могут удобно сделать это за вас, например. если вы используете GLib, вы можете настроить все, что хотите обработать, а затем войти в «основной цикл», который сделает это за вас. Возможно, вы захотите проверить документацию ncurses, чтобы узнать, есть ли у нее такая функция «основного цикла», я не уверен, что она есть. Или, конечно, вы можете придерживаться «ручного» подхода, описанного в предыдущем абзаце. Это не так уж и сложно, если вы усвоили основные концепции, и это стандартный шаблон, поэтому я уверен, что вы найдете множество примеров, подтверждающих его.

Спасибо за критику. Эта программа во время работы потребляет <0,5% мощности моего процессора, но я приму это к сведению. Также, как уже упоминалось в моем вопросе, это касается Windows, и я считаю, что многие ваши советы предназначены для Linux.

Coldchain9 19.04.2024 14:01

Вы правы, я думал о Linux, но совершенно не заметил, что вы на Windows.

egmont 19.04.2024 15:20
Ответ принят как подходящий

Это должен быть дубликат, но важный момент, объединяющий SIGWINCH и опросный ввод, не отображается.

Итак... измените программу так, чтобы

  • используйте wgetch без задержки, чтобы он мог прочитать KEY_RESIZE
  • размер окна следует изменить, чтобы учесть поля и т. д.
  • рисовать окно при каждом вызове time расточительно (проверьте и пропустите)

Вот модифицированная программа, которая иллюстрирует эти моменты:

#include <stdlib.h>
#include <ncurses.h>
#include <time.h>

// default window sizes
#define WINDOW_HEIGHT LINES - 2
#define WINDOW_WIDTH COLS - 2
#define WINDOW_START_Y 1
#define WINDOW_START_X 1

// prototypes
WINDOW *init_window(void);

int main(void)
{
        initscr();
        cbreak();
        WINDOW *win = init_window();
        nodelay(win, TRUE);

        // hide cursor
        curs_set(0);

        // draw basic box in the window
        box(win, 0, 0);

        // initialize the timer
        time_t start = time(0);
        while (1) {
                if (wgetch(win) == KEY_RESIZE) {        // process SIGWINCH
                        wresize(win, WINDOW_HEIGHT, WINDOW_WIDTH);
                        clear();
                        wclear(win);
                        box(win, 0, 0);
                        wnoutrefresh(win);
                        start--;        // force a redraw
                }
                
                // update current UTC time
                time_t now = time(0);

                if (now == start)
                        continue;
                start = now;

                struct tm *tmp = gmtime(&now);

                // modify terminal window
                mvwprintw(win, 1, 1, "           _________           ");
                mvwprintw(win, 2, 1, "          / ======= \\         ");
                mvwprintw(win, 3, 1, "         / __________\\        ");
                mvwprintw(win, 4, 1, "        | ___________ |        ");
                mvwprintw(win, 5, 1, "        | | -       | |        ");
                mvwprintw(win, 6, 1, "        | |         | |              ");
                mvwprintw(win, 7, 1, "        | |_________| |________________  ");
                mvwprintw(win, 8, 1, "        \\=____________/      REM       ) ");
                mvwprintw(win, 9, 1, "        / \"\"\"\"\"\"\"\"\"\"\" \\               /  ");
                mvwprintw(win, 10, 1, "       / ::::::::::::: \\          =D-'   ");
                mvwprintw(win, 11, 1, "      (_________________)                  ");
                mvwprintw(win, 12, 1, "      current time: %02d:%02d:%02d", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
                wnoutrefresh(win);
                doupdate();
        }

        // clean up
        endwin();
        return 0;
}

WINDOW* init_window(void)
{
        // create new window
        WINDOW *win = newwin(WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_START_Y, WINDOW_START_X);
        if (!win) {
                printf("ERROR: could not create window\n");
                exit(1);
        }
        return win;
}

Теперь... опубликованный ответ неоднозначен относительно того, используется ли это (например) Cygwin, MinGW или MSYS2. Конфигурация MinGW не обеспечивает работоспособности KEY_RESIZE; если вы это используете, этот вопрос должен быть помечен как дубликат . В противном случае, если базовые библиотеки более или менее соответствуют POSIX, программа вам подойдет.

Спасибо за Ваш ответ. Я заметил, что при использовании терминала MSYS у меня все еще наблюдается странное поведение при изменении размера. Я пробовал использовать терминал mingw64, и поведение похожее. Я еще не пробовал на Cygwin для подтверждения. Есть идеи?

Coldchain9 22.04.2024 14:44

ОБНОВЛЕНИЕ: Cygwin ведет себя так же, как MSYS и MINGW64 в Windows 11.

Coldchain9 22.04.2024 15:01

Другие вопросы по теме