返回

ncurses newterm() LINES/COLS 更新问题及解决方案

Linux

newterm()LINESCOLS 更新问题

使用 ncurses 库时,屏幕尺寸管理至关重要。 传统上,通过 initscr() 初始化终端后,全局变量 LINESCOLS 会自动设定,代表屏幕的高度和宽度。 这样的全局变量在后续的窗口创建和调整中提供基础。 但是,当转向使用 newterm() 实现多终端支持时,原有的依赖会失效。 这样会造成 LINESCOLS 无法正确更新,从而引发窗口重叠或者无法随窗口调整等问题。

原因分析

initscr() 会隐式地创建 stdscr,它是 ncurses 的标准窗口, 同时也会负责管理和更新屏幕尺寸,并更新全局变量 LINESCOLS 。 而 newterm() 只创建一个新的终端屏幕环境,并不会自动更新 LINESCOLS,这意味着,在使用 newterm() 创建窗口环境后,如果窗口尺寸发生变化,则LINESCOLS 需要额外的方式进行同步。

解决方案一: 显式更新 LINESCOLS

即使使用 newterm() 创建新的终端环境,也可以在需要的时候显式地读取终端的尺寸并赋值给 LINESCOLS。 这个操作可以利用 getmaxyx() 函数来获取指定窗口的尺寸。 你需要确保 newterm() 返回的窗口实例被作为 getmaxyx 的参数传入。

步骤:

  1. 使用 newterm() 创建终端环境,获得一个窗口指针。
  2. 在任何可能发生窗口尺寸变化的地方,使用 getmaxyx() 获取新尺寸。
  3. getmaxyx() 返回的高度和宽度赋值给 LINESCOLS

代码示例:

#include <ncurses.h>

int main() {
    SCREEN *screen;
    WINDOW *main_win;

    // 初始化终端
    screen = newterm(NULL, stdout, stdin);
    if (screen == NULL) {
       // Error handling...
    }

    main_win = newwin(0,0,0,0);

    int max_y, max_x;

    // 首次获取屏幕尺寸
    getmaxyx(main_win, max_y, max_x);
    LINES = max_y;
    COLS = max_x;

    // ... 进行你的 ncurses 操作 ...

     //当有窗口调整事件时,更新LINES and COLS.  实际应用可能需要注册signal handlers 或者 polling

    getmaxyx(main_win, max_y, max_x);
    LINES = max_y;
    COLS = max_x;

    //清理
    delwin(main_win);
    delscreen(screen);

    return 0;
}

操作说明:

  1. 编译以上代码:gcc your_code.c -o your_program -lncurses
  2. 运行程序:./your_program

需要注意, 由于 getmaxyx() 的调用会从 main_win 参数对应的窗口句柄读取, 务必确保main_win是指向正确创建窗口实例. 另外, LINESCOLS 是全局变量,如果同时有多处使用需要格外小心,务必使用同一数据来源。 在复杂应用中需要合理安排 getmaxyx 调用,并且考虑使用辅助变量暂存新值避免全局修改造成混乱。

解决方案二: 利用窗口事件处理函数

除了显式读取并更新 LINESCOLS 外,另一种做法是在 ncurses 的事件处理机制中更新这些全局变量。 ncurses 可以捕获诸如窗口尺寸调整之类的事件(例如 KEY_RESIZE)。 你可以注册一个函数,当发生窗口调整事件时更新 LINESCOLS。 通常这意味着结合 ncurses 提供的事件轮询函数,在其中捕捉KEY_RESIZE事件并更新。

步骤:

  1. 使用 newterm() 创建终端环境和主窗口。
  2. 启用键值,并捕捉事件
  3. 在事件循环中检查 KEY_RESIZE,并更新全局变量 LINESCOLS

代码示例:

#include <ncurses.h>
#include <signal.h>

int main() {
    SCREEN *screen;
    WINDOW *main_win;
    int max_y, max_x;

    // 初始化终端
    screen = newterm(NULL, stdout, stdin);
    if(screen == NULL){
       // error handling
    }
    main_win = newwin(0, 0, 0, 0);


    // 启用键盘输入,开启捕捉功能
    keypad(main_win, TRUE);

    //初始化LINES 和 COLS
    getmaxyx(main_win, max_y, max_x);
    LINES = max_y;
    COLS = max_x;
    // 启动事件轮询
    while (1) {
        int ch = getch(); //阻塞等待
        if (ch == KEY_RESIZE) {
             getmaxyx(main_win, max_y, max_x);
             LINES = max_y;
             COLS = max_x;
             // 可以输出一下尺寸信息
             mvprintw(0,0,"Lines: %d, Cols: %d", LINES, COLS);
             wrefresh(main_win);

            // 可以执行其它与尺寸相关操作, 比如重建子窗口
        }

        if (ch == 'q'){ //结束程序
             break;
         }
    }
    // 清理
     delwin(main_win);
    delscreen(screen);

    return 0;
}

操作说明:

  1. 编译:gcc your_code.c -o your_program -lncurses
  2. 运行:./your_program
  3. 改变窗口尺寸,你会发现屏幕顶部更新的 LinesCols 会随着窗口尺寸变化而变化。按下 q 可以结束程序。

这个方法的好处在于它可以响应终端窗口的调整,无需手动轮询更新。 但也需要注意, 某些旧的终端可能不支持 resize 事件,因此需要在代码里额外考虑容错和回退。 另外, 窗口大小更新后的重绘逻辑也需要在resize事件处理函数内实现。

这两个方案都允许你在使用newterm()创建终端环境时, 也能保持 LINESCOLS 更新。 选用哪种取决于应用的具体情况和需求。 前一种比较简单直接,可以满足大部分简单情况的需求。后一种通过事件处理更新全局变量更加健壮。选择时需要根据实际情况评估成本,选取最合适的解决方案。