Ncurses - 如何处理在无限循环中更新的图像的终端大小调整

问题描述 投票:0回答:1

我有一个程序利用

ncurses
来绘制带有时钟定时器的窗口。它在无限循环中不断刷新。它按预期工作,但是当我调整终端窗口大小时,整个图像变得混乱并且一切都乱七八糟。

我已经阅读了有关 ncurses 的

SIGWINCH
以及其他一些文章,但它们都依赖于
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;
}
c terminal ncurses
1个回答
0
投票

让我们先把大小调整放在一边。即使没有调整大小,你的程序也不好。

它以无限循环运行,没有任何延迟,要求 ncurses 每秒更新显示数千次,将其中一个 CPU 旋转到最大,从其他应用程序中夺走资源,耗尽电池等。

您应该包括

sleep()
左右。不要休眠 1 秒,因为这样时钟可能会延迟最多 1 秒(平均 0.5 秒),并最终可能导致跳过显示的秒数(如果该 1 秒碰巧变成 1.0001 秒,跳过两个整数) )。相反,查询包括亚秒的当前时间(使用
gettimeofday()
clock_gettime()
)并睡眠当前秒的剩余部分。如果您想连续更新屏幕(例如显示毫秒),则根据人眼的感知或假设的显示更新速率进行睡眠,例如大约1/60秒。

现在,您希望在 SIGWINCH 上中断此睡眠。好消息是:默认情况下它会被中断,因此这里可能不需要采取进一步的操作。不过我不熟悉 ncurses 部分,也许你需要以某种方式告诉它窗口大小发生了变化。但是你怎么知道窗口的大小是否被调整了呢?也许你不在乎;你只需每次更新 ncurses 的窗口大小(如果需要该步骤;同样,我对 ncurses 不太熟悉);如果每秒或每个窗口调整大小完成一次,那么绝对没问题。

如果您希望您的应用程序能够扩展,想要在将来添加更多功能(例如输入处理),或者想要实际知道是否发生了窗口大小调整,那么这是真正合适的解决方案;如果你想“手动”完成所有这些:

阻止 SIGWINCH 传递 (

sigprocmask()
)。安装一个翻转全局布尔值的 SIGWINCH 处理程序 (
sigaction()
)。将
sleep()
调用替换为
pselect()
(或
ppoll()
),仅在此
pselect()
调用期间自动启用 SIGWINCH 传递。设置此
pselect()
作为您想要的超时,如果您愿意,还可以监视文件描述符(即文件描述符 0,如果您想在标准输入上可用时读取数据)。在此
pselect()
调用之后检查 SIGWINCH 的全局布尔值是否已设置;如果是,则采取适当的操作(无论您需要做什么来处理调整大小;如果需要,可以告诉它给 ncurses)并清除该标志。

现有的库可以方便地为您完成此操作,例如如果您使用 GLib,那么您可以设置您想要处理的所有内容,然后输入一个“主循环”来为您完成这项工作。你可能想检查 ncurses 的文档,看看它是否也有这样的“主循环”功能,我不确定它是否有。或者当然您可以坚持上一段中概述的“手动”方法,一旦您掌握了背后的基本概念,它就不那么难了,而且它是一个标准模式,所以我相信您会发现很多例子来展示它。

© www.soinside.com 2019 - 2024. All rights reserved.