У меня есть программа, использующая 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;
}
Давайте на секунду отложим изменение размеров. Даже без изменения размера ваша программа не очень хороша.
Он работает в бесконечном цикле без каких-либо задержек, запрашивая 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.
Вы правы, я думал о Linux, но совершенно не заметил, что вы на Windows.
Это должен быть дубликат, но важный момент, объединяющий 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 для подтверждения. Есть идеи?
ОБНОВЛЕНИЕ: Cygwin ведет себя так же, как MSYS и MINGW64 в Windows 11.
Я не уверен в деталях, специфичных для Windows, но вы можете либо дождаться события в файловом дескрипторе стандартного ввода с тайм-аутом, либо установить стандартный ввод в неблокирующий режим и периодически опрашивать его. Или дождитесь двух дескрипторов: периодического таймера и стандартного ввода. Звоните
getch()
, только если не заблокирует.