在第5章,我们了解了如何在字符上进行更好的控制以及如何以一种独立终端的方式来提供字符输出。使用通用终端接口(GTI,termios)以及使用tparm及其相关函数操作转义字符的问题在于他需要大量的底层代码。对于许多程序而言,更需要一个高层接口。我们希望可以简单在屏幕上进行绘制,并且使用一个函数库来自动处理终端依赖。
在这一章,我们将要了解的正是这样的一个库,curses库。curses标准的重要性就如同简单的基于线的程序与完全的图形化(而且通常是难于编程的)X Window系统程序例如GTK/GNOME,QT/KDE之间的中点站。Linux也确实有一个svgalib库,但是这并不是一个UNIX标准库,所以通常在其他的类UNIX系统上并不可用。curses库用在许多全屏幕程序中作为一个简单的,独立于终端的方式来编写全屏幕,尽管是基于字符的程序。使用curses库来编写这样的程序要比直接使用转义序列简单得多。curses同时也可以管理键盘,提供一个易于使用,非阻塞的字符输入模式。
我们也许会发现这一章中的一些例子并不如同我们所期望的那样在普通的Linux控制台上显示。当组合使用curses库与控制台终端定义时会出现这样的情况,而当使用curses库时效果也会有些不同。然而,我们会发现,如果我们使用X Window系统,并且使用xterm窗口来显示输出时,事情就会如同我们期望的那样来显示了。
在这一章,我们会讨论下面内容:
使用curses库
curses概念
基本的输入和输出控制
多窗口
键区
颜色
我们会用C语言来重新我们前面所介绍的CD集合程序来结束这一章,并且总结我们所了解的内容。
使用curses编译
curses库的名字来自于优化鼠标移动并且最小化屏幕更新,从而减少需要发送到一个基于文本的终端的字符数的能力。尽管输出的字符数目不再如哑终端和低速调制解调器时代重要,curses作为程序工具集的一个有益补充而存活下来。
因为curses是一个库,要使用他必须由一个合适的系统库包含一个合适的头文件,函数声明,以及宏。现在存在curses的多个实现。在被X/Open标准化之前,最初的版本出现在BSD UNIX中,并且组合进入UNIX的System V版本中。Linux使用ncurses("new curses"),在Linux上开发的System V发布的4.0 curses的模拟。这个实现可以移植到其他的UNIX版本上,尽管包含一些不可移植的额外特性。甚至还有MS-DOS和Windows的curses版本。如果我们发现我们UNIX系统上的curses不支持一些特性,我们建议可以获取一份ncurses的拷贝;Linux用户会发现ncurses库已经在系统上进行安装。
X/Open规范定义了两级curses:基本和扩展。扩展curses包含一些额外的特性,包含一系列的处理多列字符的函数和颜色操作例程。在这一章中除了使用颜色,绝大多数我们都会使用基本函数。
当我们编译curses程序时,我们必须包含头文件curses.h,并且使用-lcurses来链接curses库。在许多Linux系统上,curses包含与库文件仅是链接到ncurses。
我们可以通过执行ls -l /usr/include/curses.h命令来查看头文件以及ls -l/usr/lib/lib*curses*命令来查看库文件,从而来检测我们的curses库的设置。如果我们发现curses文件链接到了ncurses文件,那么(使用#gcc)我们可以用下面的命令来编译这一章中的例子程序:
$ gcc program.c -o program -lcurses
然而,如果我们的curses设置并不是自动使用ncurses,那么我们需要通过包含ncurses.h文件,而不是curses.h文件来显示使用ncurses库,并且使用下面的命令来编译程序:
$ gcc -I/usr/include/ncurses program.c -o program -lncurses
在这里,-I选项要在其中搜索头文件的目录。下载的代码中的Makefile文件假设我们的设置默认使用curses,如果这不符合我们系统的情况下,那么我们需要进行相应的修改或是手动进行编译。
如果我们还不确定我们系统上的curses是如何设置的,可以查看ncurses手册页,或是查看其他的在线文档;通常的位置在/usr/share/doc/目录下,在这里我们也许会找到一个curses或是ncurses目录,通常带有其版本号。
概念
curses例程可以工作在屏幕,窗口,以及子窗口上。一个屏幕是我们要向其上输出的设备(通常为一个终端屏幕,但是也可以是一个xterm屏幕)。他承担这个设备上所有可用的显示。当然,如果为一个X窗口内的终端窗口,屏幕只是简单的终端窗口内部可用的字符位置。至少会有一个curses窗口,stdscr,其尺寸与物理屏幕相同。我们可以创建小于屏幕尺寸的额外窗口。窗口可以彼此覆盖,并且有许多子窗口,但是每一个子窗口必须包含在其父窗口中。
curses库维护两个数据结构,这两个数据结构的作用类似于终端屏幕,stdscr以及cursecr的映射。
stdscr是这两个数据结构中较为重要的,他会在curses函数产生输出时进行更新。stdscr数据结构是"标准屏幕"。其作用与stdio库中的标准输出stdout相类似。他是curses程序中的默认输出窗口。cursecr比较起来较为简单,但是却实际存储当前时刻屏幕显示的内容。当curses库比较stdscr的内容(屏幕应是什么样子)和第二个结构cursecr(当前屏幕是什么样子)时,直到程序调用refresh,写入stdscr的输出才会出现在屏幕上。
一些curses程序需要知道curses维护着一个stdscr结构,而后者需要作为一些curses函数的参数。然而,实际的stdscr是依据实现,并且绝不应被直接访问。curses程序绝不应使用cursecr结构。
所以,在一个curses程序中字符的输出过程为:
1 使用curses函数来更新一个逻辑屏幕
2 请求curses使用refresh来更新物理屏幕
这两种两级方法的优点,除了更易于编程以后,还在于curses屏幕的更新非常高效,而且尽管这在控制器屏幕并不是十分重要,如果我们在一个慢速的串口上或是调制解调器上运行我们的程序,我们就会明显体会到这些差别。
一个curses程序也许会在逻辑屏幕上调用许多输出函数,也许会在整个屏幕上移动光标从而得到正确的位置来输出文本或是绘制线和框。在某些阶段,用户也许需要看到所有这些输出。当出现这种情况时,通常是在调用refresh时,curses会计算与逻辑屏幕相对应的物理屏幕的最佳方法。通过使用合适的终端功能和优化光标动作,与所有的屏幕输出立即发生相比,curses经常可以使用更少的字符输出来更新屏幕。
逻辑屏幕的布局是一个字符数组,由行与列进行排列,屏幕左上角的位置为(0,0)。
所有curses函数所使用的坐标值都是y值(行)在x值(列)前面。每个位置不仅保存屏幕位置的字符,而且保存其属性。可以显示的属性依赖于物理终端的功能,但是通常至少可以使用粗体和下划线。在Linux控制台中,我们甚至可以使用声音与颜色,这些我们会在后面进行讨论。
因为curses库需要创建与销毁一些临时数据结构,所有的curses程序必须在使用之前初始化库,而且在使用之后允许curses保存设置。这是通过一对函数调用来完成的:initscr与endwin。
试验--一个简单的curses程序
下面我们来编写一个简单的curses程序,screen1.c,来实际的演示这些基本的函数。然后我们会描述这些函数原型。
1 我们添加curses.h头文件,并且在main函数中,我们会调用相应的函数来初始化与重新设置curses库。
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>
int main() {
initscr();
...
endwin();
exit(EXIT_SUCCESS);
}
2 在中间部分,我们会添加代码来将光标移动到逻辑屏幕的点(5,15)上,打印"Hello World",然后刷新实际的屏幕。最后,我们调用sleep(2)来将程序挂起2秒,从而我们可以程序退出时看到输出。
move(5, 15);
printw(“%s”, “Hello World”);
refresh();
sleep(2);
当程序运行时,我们会在空白屏幕上看到"Hello World"字符串。