20 | linux的图形函数库 curses(基本的输入输出,以及字符显示格式)
许多全屏幕的应用程序都使用curses函数库,它易于使用,并且提供了终端无关的方式来编写全屏幕的基于字符的程序。在编写这类程序时,使用curses
函数库总是比直接使用escape转义序列要容易得多。curses还可以管理键盘,它还提供了一种简单易用的非阻塞字符输入模式。
定位:
你就将学习函数库curses。curses标准 作为一个重要的过渡,位于简单的文本行程序和完全图形化界面(一般也更难于编程)的X视窗系统程序(如GTK+/GNOME和QtKDE)之间。
[toc]
用 curses 函数库进行编译
我们主要介绍的都是基本curses函数。
当对使用curses函数库的程序进行编译时,你必须在程序中包含头文件curses.h,并在编译命令行中用-lcurses选项来链接curses函数库。
curses 术语和概念
curses例程工作在屏幕、窗口和子窗口之上。无论何时,至少存在一一个curses窗口,我们称之为stdscr,它与物理屏幕的尺寸完全一样。
curses函数库用两个数据结构来映射终端屏幕
stdscr: 更重要一些,它会在curses函数产生输出时被刷新。stdscr 数据结构对应的是“标准屏幕”,它的工作方式与stdio函数库中的标准输出stdout非常相似。它是curses程序中的默认输出窗口。
curscr: 和stdscr相似,但它对应的是当前屏幕的样子。在程序调用refresh函数之前,输出到stdscr上的内容不会显示在屏幕上。curses函数库会在refresh函数被调用时比较stdscr(屏幕将会是什么样子)与第二个数据结构curscr (屏幕当前的样子)之间的不同之处,然后用这两个数据结构之间的差异来刷新屏幕。
综上所述,在curses程序中输出字符的过程如下所示。.
(1)使用curses函数刷新逻辑屏幕。
(2)要求curses用refresh函数来刷新物理屏幕。
除了易于编程以外,分成两个步骤来完成字符输出的好处还在于,curses屏幕的刷新效率很高。虽然这点对控制台屏幕来说并不重要,但如果你是通过慢速网络连接到主机上来运行程序,则屏幕刷
新效率的提高意义就很大了。
所有的curses函数使用的坐标都是y值(行号)在前、x值(列号)在后。每个位置不仅包含该屏幕位置处的字符,还包含它的属性。可显示的属性依赖物理终端的功能标志,但一般至少会支持粗体和下划线这两个属性。Linux控制台通常还支持反白显示和色彩属性。
由于curses函数库在使用时需要创建和删除一些临时的数据结构,所以所有的curses程序必须在开始使用curses函数库之前对其进行初始化,并在结東使用后允许curses恢复原先设置。这两项
工作是由initscr和endwin函数分别完成的。
🔧hello world
#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);
}
屏幕
正如你所看到的,所有的curses程序必须以initscr函数开始,以endwin 函数结束。下面是它们的头文件定义:
#include <curses.h>
WINDOW *initscr (void) ;
int endwin (void) ;
initscr函数在一个程序中只能调用一次。 如果成功,它返回一个指向stdscr结构的指针;如果失败,它就输出一条诊断错误信息并使程序退出。
endwin函数在成功时返回OK,失败时返回ERR。
你可以先调用endwin函数退出curses,然后通过调用clearok (stdscr, 1)和refresh函数继续curses操作。这实际上是首先让curses忘记物理屏幕的样子,然后强迫它执行一次完整的屏幕原文重现。
输出到屏幕
curses函数库提供了一些用于刷新屏幕的基本函数,它们是: ;
#include <curses.h>
int addch (const chtype char_to_add) ;
int addchstr (chtype *const string_to_add) ;
int printw(char *format,...);
int refresh (void) ;
int box (WINDOW *win_ptr, chtype vertical_char, chtype horizontal_char) ;
int insch (chtype char_to_insert) ;
int insertln(void) ;
int delch (void) ;
int deleteln(void) ;
int beep(void) ;
int flash (void) ;
函数 | 作用 |
---|---|
add系列函数 | 在光标的当前位置添加指定的字符或字符串。 |
printw函数 | 采用与printf函数相同的方法对字符串进行格式化,然后将其添加到光标的当前位置。 |
refresh函数 | 刷新物理屏幕,成功时返回OK,发生错误时返回ERR。 |
box函数 | 用来围绕一个窗口绘制方框。 |
insch函数 | 插入一个字符将已有字符向右移,但此操作对行尾的影响并未定义,具体情况取决于你所使用的终端。 |
insertln函数 | 插入一个空白行将现有行依次向下移-行 |
beep函数 | 让程序发出声音。但因为有极少部分终端不能发出声音,所以有些curses设置会在调用beep函数时让屏幕闪烁。 |
flash函数 | 使屏幕闪烁,但如果无法产生闪烁效果,它将尝试在终端上发出声音 |
curses有其自己的字符类型chtype,在ncurses的标准Linux版本中,chtype实际上是unsigned long类型的一一个typedef类型定义。
从屏幕读取
你可以从屏幕上读取字符,虽然这个功能并不常用,因为一般来说,要想了解屏幕上所写内容很容易。但如果需要该功能,可用下面这些函数实现它:
#include <curses.h>
chtype inch (void) ;
int instr (char *string) ;
int innstr (char *string, int number_of_characters) ;
inch函数总是可用的,但instr和innstr函数并不总被支持。inch函数返回光标当前位置的字符及其属性信息。注意,inch函数返回的并不是一个字符,而是一个chtype类型的变量,而instr和
innstr函数则将返回内容写到字符数组中。
清除屏幕
清除屏幕上的某个区域主要有4种方法,它们是:
#include <curses . h>
int erase (void) ;
int clear (void) ;
int clrtobot (void) ;
int clrtoeol (void) ;
清除屏幕上的某个区域主要有4种方法,它们是:
#include <curses. h>
int erase (void) ;
int clear (void) ;
int clrtobot (void) ;
int clrtoeol (void) ;
erase函数在每个屏幕位置写上空白字符。
clear函数的功能类似erase函数,它也是用于清屏,但它还通过在内部调用一个底层函数clearok来强制重现屏幕原文。
clearok函数会强制执行清屏操作,并在下次调用refresh函数时重现屏幕原文。
clear函数通常是使用一个终端命令来清除整个屏幕,而不是尝试删除当前屏幕上每个非空白的位置。因此,clear函数是一“种可以彻底清除屏幕的可靠方法。当屏幕显示变得混乱时,clear函数
和refresh函数的结合提供了一种有效的重新绘制屏幕的手段。
clrtobot函数清除当前光标位置直到屏幕结尾的所有内容。
clrtoeol函数清除当前光标位置直到光标所处行行尾的所有内容。
移动光标
用于移动光标的函数只有1个,另有1个函数用来控制在刷新屏幕后curses将光标放置的位置:
#include <curses.h>
int move (int new_y, int new_x) ;
int leaveok (WINDOW *window_ptr, bool leave_flag);
move函数用来将逻辑光标的位置移到指定地点。记住,屏幕坐标以左上角(0,0) 为起点。
在大多数curses版本中,有两个包含物理屏幕尺寸大小的外部整数LINES和COLUMNS,它们可用于决定参数new_y和Inew_x的最大可取值。
调用move函数本身并不会使物理光标移动,它仅改变逻辑屏幕上的光标位置,下次的输出内容就将出现在该位置上。
如果希望物理屏幕上的光标位置在调用move函数之后立刻有变化,就需在它之后立刻调用refresh函数。
leaveok函数设置了一个标志,该标志用于控制在屏幕刷新后curses将物理光标放置的位置。默
认情况下,该标志为false,这意味着屏幕刷新后,硬件光标将停留在屏幕上逻辑光标所处的位置。
如果该标志被设置为true,则硬件光标会被随机地放置在屏幕上的任意位置。一般来说,默认选项更符合用户的需求,这能确保光标停留在一一个有意义的位置。
字符属性
每个curses字符都可以有一些属性用于控制该字符在屏幕上的显示方式,前提是用于显示的硬件
设备能够支持要求的属性。预定义的属性有A_BLINK、 A_BOLD、A_DIM、A_REVERSE、A_STANDOUT和A_UNDERLINE.
你可以用下面这些函数来设置单个属性或同时设置多个属性:
#include <curses .h>
int attron (chtype attribute) ;
int attroff (chtype attribute) ;
int attrset (chtype attribute) ;
int standout (void) ;
int standend (void) ;
attrset函数设置curses属性;
attron和attroff函数在不影响其他属性的前提下启用或关闭指定的属性;
standout和standend函数提供了一种更加通用的强调或“突出”模式,在大多数终端上,它通常被映射为反白显示。
🔧 小实验
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <curses.h>
int main(){
const char witch_one[]="first witch";
const char witch_two[]="second witch";
const char *scan_ptr;
initscr();
move(5,15);
// 显示3组文本,以一秒为间隔依次显示再屏幕上,请注意对文本标志的开关
attron(A_BOLD); //粗体
printw("%s","Macbeth");
attroff(A_BOLD);
refresh();
sleep(1);
move(8,15);
attron(A_STANDOUT); // 突出显示
printw("%s","Thunder and Lightning");
attroff(A_STANDOUT);
refresh();
sleep(1);
move(10,10);
printw("%s","when shall we three meet again");
move(11,23);
printw("%s","in thunder");
refresh();
sleep(1);
//字符串的显示用到 “哨兵思想”
attron(A_DIM); //灰体
scan_ptr=witch_one+strlen(witch_one)-1;
while(scan_ptr!=witch_one){
move(10,10);
insch(*scan_ptr--);
}
scan_ptr=witch_two+strlen(witch_two)-1;
while(scan_ptr!=witch_two){
move(13,10);
insch(*scan_ptr--);
}
attroff(A_DIM);
refresh();
sleep(1);
//最后将光标移动到屏幕的右下角,结束程序
move(LINES-1,COLS-1);
refresh();
sleep(1);
endwin();
exit(EXIT_SUCCESS);
}
在初始化一些变量和curses屏幕之后,使用move函数在屏幕上移动光标。通过attron和attroff函数来控制显示在屏幕上指定位置的文本的属性。然后,程序使用insch函数来演示如何插入字符。最后,程序关闭curses函数库并结束。
键盘
curses 函数库不仅提供了控制屏幕显示的易用接口,还提供了控制键盘的简单方法。
键盘模式
键盘读取例程由键盘模式控制。用于设置键盘模式的函数有:
#include <curses . h>
int echo (void) ;
int noecho(void) ;
int cbreak(void) ;
int nocbreak(void) ;
int raw(void) ;
int noraw (void) ;
两个echo函数用于开启或关闭输入字符的回显功能。其余4个函数调用用于控制在终端上输入的字符传送给curses程序的方式。
cooked模式
为解释清楚cbreak函数的作用,你需要首先理解何为默认输入模式。当curses程序通过调用initscr函数开始运行时,输入模式被设置为预处理模式(或称为cooked模式)。这意味着所有处理都是基于行的,也就是说,只有在用户按下回车键之后,输入的数据才会被传送给程序。
在这种模式下,键盘特殊字符被启用,所以按下合适的组合键即可在程序中产生一个信号,如果是通过串行口或调制解调器等连接终端,则流控也处于启用状态。
cbreak模式
程序可通过调用cbreak函数将输入模式设置为cbreak模式,在这种模式下,字符一经键入就被立刻传递给程序,而不像在cooked模式中那样首先缓存字符,直到用户按下回车键后才将用户输入的字符传递给程序。cbreak模式 与cooked模式一样,键盘特殊字符也被启用,但一些简单的特殊字符,如退格键Backspace会被直接传递给程序处理,所以如果想让退格键保留原来的功能,你就必须自己在程序中实现它。
raw函数调用的作用是关闭特殊字符的处理,所以执行该函数调用后,再想通过输入特殊字符序列来产生信号或进行流控就不可能了。nocbreak函数调用将输入模式重新设置为cooked模式,但特殊字符的处理方式保持不变。
noraw函数调用同时恢复cooked模式和特殊字符处理功能。(完好如初)
键盘输入
#include <curses.h>
int getch (void) ; .
int getstr(char * string) ;
int getnstr(char *string, int number_ of_ characters);
int scanw(char *format, ...);
这些函数的行为与它们的非curses版本getchar、gets和scanf非常相似。要注意的是,getstr 函数对其返回的字符串的长度没有限制,所以使用这个函数时要非常小心。如果所使用的curses版本支持getnstr函数(它可以对读取的字符数目加以限制),你就应该尽可能地用它来替代getstr函数。
🔧小实验
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <curses.h>
#define PW_LEN 256
#define NAME_LEN 256
int main(){
char name[NAME_LEN];
char password[PW_LEN];
const char*real_password="xyzzy";
int i =0 ;
initscr();
move(5,10);
printw("%s","please login:");
move(7,10);
printw("%s","user name:");
getstr(name);
move(8,10);
printw("%s","Password:");
refresh();
cbreak();
noecho();
memset(password,'\0',sizeof(password));
while(i<PW_LEN){
password[i]=getch();
if(password[i]=='\n') break;
move(8,20+i);
addch('*');
// 与printw("*") 效果一样
refresh();
i++;
}
//重新启动键盘回显,并给出密码验证成功或者失败的消息
echo();
nocbreak();
move(11,10);
if(strncmp(real_password,password,strlen(real_password))==0)
printw("%s","correct");
else printw("%s","wrong");
printw("%s","password");
refresh();
sleep(2);
endwin();
exit(EXIT_SUCCESS);
}