ncurses

ncurses 是基于终端的十分强大的图形库。 Vim, screen, sl 等终端程序都用到了这个库(足以见其强大)。

安装

部分系统默认安装了 ncurses ,手动安装的方式是: sudo aptitude install ncurses-dev

使用的程序需要 #include <ncurses.h> 。 编译时需要添加 -lncurses 参数进行链接。

开始和结束

调用 initscr 初始化窗口,endwin 结束窗口。

1
2
3
4
#include<curses.h>

WINDOW *initscr(void);
int endwin(void);

输出

调用 initscr 后调用 endwin 前,printf, std::cout 等标准输出不会显示在屏幕上。 而输出到屏幕上需要 ncurses 提供的相应函数。

函数

1
2
3
4
5
6
7
8
int addch(const chtype char_to_add);   // 当前位置添加字符
int addstr(const char *string_to_add); // 当前位置添加字符串

int printw(char *format, ...); // 类似于 printf
int refresh(void); // 强制刷新物理屏幕

int beep(void); // 终端响铃
int flash(void); // 屏幕闪烁

输入

调用 scanf, getchar, cin 等标准输入函数同样无效。

函数

1
2
3
4
5
6
7
8
9
10
int cbreak();   // 字符一键入,直接传给程序(不用按下回车)
int nocbreak(); // 关闭 cbreak

int echo(void); // 开启输入回显
int noecho(void); // 关闭输入回显

int getch(void); // 读入一个字符
int scanw(char *format, ...); // 类似于 scanf

int clear(void); // 清屏

输入函数通常是阻塞的,但是通过调用 nodelay(stdscr, TRUE); 可以关闭阻塞。 此时若输入函数未读取到内容会返回 ERR 。

光标

控制光标。 调用 initscr 后调用 endwin 前,输出终端控制符改变光标是无效的。

函数及示例

1
2
3
4
5
6
7
8
9
10
11
int move(int x, int y); // 将光标移动到 [x] 行 [y] 列,左上角为 0 行 0 列

int curs_set(int visiblility); // 参数为 0 表示隐藏光标,1 表示显示光标

int getyx(WINDOW* win, int &x, int &y); // 获取指定窗口光标位置,示例如下

void move_up() { // 将光标上移
int x, y;
getyx(stdscr, x, y); // stdscr 表示标准屏幕
move(x - 1, y);
}

指定位置输出

在指定位置输出不必先 move 再 printw , ncurses 提供了 mv 函数前缀在指定位置输出。 例如 mvprintw(1, 2, "%d", 2) 在 1 行 2 列输出 3 。 类似的有 mvaddch, mvaddstr 等。

颜色

初始化

首先需要调用 has_color() 查看当前运行环境是否支持彩色。 调用 start_color() 初始化颜色,成功则返回 OK 。

1
2
bool has_colors(void);
int start_color(void);

成功后会初始化全局变量 COLORS 表示终端支持的颜色数量 还会有 COLOT_WHITE, COLOR_RED 等 8 个表示颜色的变量。

使用

例如希望打印白底黑字的信息:

1
2
3
4
5
6
7
8
9
void print(const char *info) {
init_pair(1, COLOR_BLACK, COLOR_WHITE);
// 的一个参数表示编号,后面两个分别表示字体和背景颜色
attron(COLOR_PAIR(1));
// attron 是一个设置函数,COLOR_PAIR 返回指定编号的颜色信息
addstr(info);
attroff(COLOR_PAIR(1));
// attroff 关闭设置(若接下来需要用其他颜色可以不调用 attroff 而直接使用 attron 覆盖设置
}

错误示例

值得注意的是,必须保证 init_pair 的编号不与其他已初始化的编号重复 一个错误的调用如下:

1
2
3
4
5
6
7
const char *info = "ERROR CODE";
init_pair(1, COLOR_BLACK, COLOR_WHITE);
attron(COLOR_PAIR(1));
addstr(info); // 打印白底黑字
init_pair(1, COLOR_BLACK, COLOR_RED);
attron(COLOR_PAIR(1));
addstr(info); // 打印白底红字

上述代码的期望打印出白字和黑字两种不同的颜色, 但事实上只会打印出红色一种。 解决方案便是将白底红字的 pair 编号设为 2 。

窗口

ncurses 有窗口类 WINDOW 并提供了 stdscr 作为默认窗口。 有时一个窗口无法满足需要,此时需要自己新建窗口。

新建窗口

调用 newwin 来新建窗口。

1
2
// 从 ([x], [y]) 开始新建 [line] 行 [column] 列的窗口。
WINDOW *newwin(int line, int column, int x, int y);

新建的窗口

通用输出

addch, printw 等输出方式只输出到 stdscr 。 ncurses 提供了 w 前缀来输出到指定窗口。 例如 wprintw(win, "%d", 1) 在 [win] 窗口输出 1 。 但是自己新建的窗口与 stdscr 不同, 若想在屏幕上显示需要调用 wrefresh(win) 刷新窗口。

若想在窗口中指定位置输出,可以用 mvw 前缀函数。 例如 mvwprintw(win, 1, 2, "%d", 3) 在 [win] 窗口的 1 行 2 列( 相对位置 )输出 3 。

子窗口

调用 subwin 创建子窗口。

1
2
// 从 ([x], [y]) 开始新建 [line] 行 [column] 列属于 [parent] 的子窗口。
WINDOW *subwin(WINDOW *parent, int line, int column, int x, int y);

子窗口与普通窗口的区别在于它与其父窗口共用屏幕储存空间, 子窗口修改时父窗口会直接受到影响。 比如新建了 stdscr 的子窗口 win , 那么输出到 win 后想显示在屏幕不调用 wrefresh 而是调用 touchwin(stdscr) 。 touchwin 用于标记一个窗口被修改。

销毁窗口

调用 delwin 销毁窗口。

1
int delwin(WINDOW *win); // 销毁 [win] 窗口

窗口销毁后其在屏幕上对应的内容不会改变。

离开

有时候可能需要离开 ncurses 回到行缓冲模式做些事情而且需要在之后回到 ncurses 。 例如 Vim 里面输入 :!ls 就会退出 ncurses 运行 ls 命令,并在用户敲下回车后回到 ncurses。

调用 def_prog_mode 暂存,调用 reset_prog_mode 恢复。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
initscr();
printw("Hello World !!!\n");
getch(); // 等待用户输入
def_prog_mode(); // 存储当前tty 模式
endwin(); // 退出 ncurses 模式
system("sh"); // 返回普通的行缓冲模式
reset_prog_mode(); // 返回到 def_prog_mode() 存储的 tty 模式
refresh(); // 刷新屏幕(必须!)
getch(); // 等待用户输入
endwin(); // 退出 ncurses 模式
return 0;
}

输出中文等非 ASCII 字符

事实上 ncurses 并不支持直接输出中文, 这意味着调用 printw("中文") 会是一堆乱码。 解决方案如下:

安装库

这需要另一个库。 通过 sudo aptitude install libncurses5 libncursesw5 libncursesw5-dbg libncursesw5-dev 安装

头文件

一定是 #include <ncurses.h> 而不是 #include <curses.h> 。 另外在 main.cpp #include <locale.h>

调用

在调用 initscr() 之前 调用 setlocale(LC_ALL, "") 。

编译

-lncurses 参数改为 -lncursesw