1.1什么是curses

curses实际上是一个函数开发包,专门用来进行UNIX下终端环境下的屏幕界面处理以及I/O处理。

通过这些函数库,C和C++程序就可以控制终端的视频显示以及输入输出。

使用curses包中的函数,用户可以非常方便的创建和操作窗口,使用菜单以及表单,而且最为重要的一点是使用curses包编写的程序将独立于各种具体的终端,这样的一个直接的好处就是程序具有良好的移植性。

这一点在网络上显得尤其重要,因为你面对的可能是上百种终端,如果为每一个终端都专门重新编写一套新的程序,那么复杂程度出乎想象,而且几乎不可能。

为了能够达到这样的目的,curses包使用了终端描述数据库(TerminalDescriptionDatabases)

terminfo(TERMinalINFOrmationdatabase)或者termcap(TERMinalCAPabilitiedatabase),这两个数据库里存放了不同终端的操作控制码和义序列以及其余相关信息,这样当使用每一个终端的时候,curses将首先在终端描述数据库中查找是否存在该类型的终端描述信息,如果找到则进行适当的处理。

如果数据库中没有这种终端信息,则程序无法在该终端上运行,除非用户自己增加新的终端描述。

1.1.1curses发展历史

curses是怎么来的?

curses的名称起源于“cursoroptimization”,即光标优化的意思。

它最早是由巴克利大学的BillJoy和KenArnold发展而来,主要是处理游戏rogue的屏幕界面。

rogue是一个古老的基于文本的的冒险类游戏。

在当时,仅仅控制游戏屏幕的外观显示就需要编写大量的代码,因为它们使用的是古老的termios甚至是tty接口。

巨大的工作量迫使BillJoy和KenArnold将rogue游戏中的所有的屏幕处理和光标移动的函数汇集到一个函数库中。

这就形成了最早的也是最简单的curses处理库的雏形。

它最终随着BSDUNIX的早期版本发行开来。

在这个版本中使用的是当时业已存在的termcap数据库来描述终端信息。

后来贝尔实验室的MarkHorton在SystemIIIUNIX中重新编写了curses。

它相对以前的版本有了很大的扩展和提高,增加了一些非常新的特性。

它首先将termcap数据库改进为terminfo数据库。

terminfo数据库完全由Horton开发编写,它是从termcap发展而来,而且更为中要重要的是其中引进了参数化性能的概念,这样使得描述多视频属性以及彩色终端成为可能。

在后来的AT&TSystemV版本中,curses就扩展了更多功能和性能,包括了对窗体、菜单、面板、表单等组件以及对鼠标的支持。

这时候的curses内容以及设计与最初的BSD版本的curses在功能和复杂性上已经相去甚远。

1.1.2curses包内容

curses包主要包括下面的四个开发库:

库名

描述

curses

最早的curses包只包含这部分,主要控制屏幕的输入输出,光标操作,窗口创建和操作等。

panel

类似于窗口堆栈,不同的窗口可以存放于其中,并且可以在其中进行移动。

menu

新增的部分,主要包括创建菜单并且与之交互的函数,主要用来接受用户的选择。

form

包括创建表单以及与之进行交互的函数,主要用来接受用户数据输入。

1.1.3curses包移植性

使用curses包与使用低层终端函数编写的程序最主要的差别在于curses程序是独立于具体终端的,也就是说在某个终端上编写的程序可以完整的移植到另外的终端上而不需要进行任何改动。

curses包的可移植性是curses包的最大特性。

curses包的这种终端独立性归根于终端描述数据库terminfo和termcap。

terminfo和termcap数据库中包含了所有终端的描述信息。

termcap数据库是在最早的的BSDUNIX中使用,在后来的SystemIII中则使用terminfo数据库。

terminfo数据库是从termcap数据库发展而来,组织方式相对于termcap来说有了进一步的优化,而且描述的终端信息有了进一步的增加。

需要使用的数据库可以在程序编译的时候通过cc命令指定。

正如前面所说,curses正是通过使用terminfo数据库使得程序可以在不同的终端上可以移植,那么系统是如何做到这一点的呢?

对于使用curses进行处理的程序员来说他实际上处理的是虚拟终端。

curses完成了物理终端到虚拟终端的“映射”。

用curses编写的程序在它们每次被调用的时候都需要引用终端描述数据库。

数据库中的终端描述信息包括了终端的一系列的性能参数,在curses包中我们定义了很多的变量与这些性能参数对应。

当程序执行的时候,程序首先获取终端类型,然后根据终端类型获取终端描述数据库中具体的性能,最后将这些性能参数读进curses中预定义的相应的变量中。

当程序与终端进行交互从而需要调用相应的函数的时候,它将从头文件的性能变量中为终端获取必要的控制码,一旦需要某个性能参数,只要找到相应的变量即可,从而达到以不变应万变的效果。

例如在curses包中我们定义了LINES和COLS变量对应终端能够显示的最大行数和最大列数这两个性能,不同的终端的LINES和COLS的值可能不同,比如通常的终端的行数为39行,如果使用了软标签,行数将减一变为38。

但这种变化都由curses幕后自动完成,用户完全不需要理会,用户需要记住的仅是LINES和COLS以及它们代表的含义。

这样,程序就可以运行在各种不同的终端上,唯一的缺陷就是这种终端首先必须在终端信息描述库中存在,否则就无法直接使用curses包,弥补的办法就是需要自己在终端信息描述库中增加终端描述信息。

安装

sudo yum install libncurses5-dev

curses的基本用法

1. 包含头文件:curses.h

2. 编译时应加上链接语句-lcurses,如:gcc temp.c -o temp -lcurses

3. 重要的函数:

initscr()​:初始化curses库和ttty。

(在开始curses编程之前,必须使用initscr()这个函数来开启curses模式)

endwin()​:关闭curses并重置tty。

(结束curses编程时,最后调用的一个函数)

cbreak()​:开启cbreak模式,除了 DELETE 或 CTRL 等仍被视为特殊控制字元外一切输入的字元将立刻被一一读取。

crmode()​:使得终端进入到cbreak模式。

==raw()和cbreak()==都可以禁止行缓冲。

区别是:

在raw()函数模式下,处理挂起(CTRLZ)、终端或退出(CTRLC)等控制字符时,将直接传送给程序去处理而不产生终端信号;

而在cbreak()函数模式下,控制字符被终端驱动程序解释成其它字符。

move(y,x)​: 将游标移动至 x,y 的位置。

getyx(win,y,x)​: 得到目前游标的位置。(请注意! 是 y,x 而不是&y,&x )

clear() and erase()​: 将整个萤幕清除。(请注意配合refresh() 使用)

echochar(ch)​: 显示某个字元.

== int noecho(void)==: 用户输入字符不回显。

nl()/nonl()​:输出时,换行是否作为回车字符。

nl函数将换行作为回车符,而nonl作用相反。

addch(ch)​: 在当前位置画字符ch。

mvaddch(y,x,ch)​: 在(x,y) 上显示某个字元。

相当于呼叫move(y,x);addch(ch);

addstr(str)​: 在当前位置画字符串str。

mvaddstr(y,x,str)​: 在(x,y) 上显示一串字串。相当于呼叫move(y,x);addstr(str);

printw(format,str)​: 类似 printf() , 以一定的格式输出至萤幕。

mvprintw(y,x,format,str)​: 在(x,y) 位置上做 printw 的工作.。

相当於呼叫move(y,x);printw(format,str);

getch()​: 从键盘读取一个字元.。(注意! 传回的是整数值)

getstr()​: 从键盘读取一串字元。

scanw(format,&arg1,&arg2…)​: 如同 scanf, 从键盘读取一串字元.

int mvhline(int x, int y, chtype ch, int n)​:在光标(x,y)位置画n个ch组成的线。

mvhlin画水平线,mvvline画竖线。

光标位置不变。

调用成功返回OK,否则返回ERR。

beep()​: 发出一声哔声.

box(win,ch1,ch2)​: 自动画方框

intrflush(WINDOW *win,bool bf)​: win为标准输出。

当bf为true时输入Break,可以加快中断的响应。

但是,有可能会造成屏幕输出信息的混乱。

refresh()​: 使屏幕按照你的意图显示。

比较工作屏幕和真实屏幕的差异,然后refresh通过终端驱动送出那些能使真实屏幕于工作屏幕一致的字符和控制码,把虚拟屏幕上的图像输出到终端屏幕上。

(工作屏幕就像磁盘缓存,curses中的大部分的函数都只对它进行修改)

调用成功返回OK,否则返回ERR。

standout()​: 启动standout模式(一般使屏幕发色)

测试

include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
initscr();
move( 5, 15 );
printw( "%s", "Hello world" );
refresh();
sleep(2);
endwin();
exit(EXIT_SUCCESS);
}

执行:

g++ HelloCurses.c -lncurses