自学内容网 自学内容网

curses函数库简介及使用

导语

curses函数库主要用来实现对屏幕和光标的操作,它的功能定位处于简单文本行程序和完全图形化界面之间,在目前图形化界面已经蓬勃发展的现在可能显得有些过时,但是其中很多实现思想和操作仍然值得学习和借鉴,并且curses目前仍然是linux图形化编程的选择之一

curses简介

curses是一个函数库,它提供了许多对光标和终端屏幕的接口函数,在使用的时候必须包括对应的头文件函数声明和宏定义,并且用-lcurses进行定义,有些linux系统因为没有带相关的包,还需要安装类似libncurses5-dev的包

curses工作在屏幕、窗口和子窗口上,对于一个curses窗口,它一般被称为stdscr,与物理屏幕的尺寸一样,当然也可以创造一些别的尺寸小于当前屏幕的窗口,curses用两个ds来映射终端屏幕,stdscr和curscr,前者和stdout非常类似,是curses程序中的默认输出窗口,curscr和stdscr类似,对应的时当前屏幕的样子,需要注意的是,在程序调用refresh之前,输出到stdscr的内容不会显示在屏幕上,stdscr更像一个缓存,暂时存储一些改变

在调用refresh时,curses会比较stdscr和curscr(屏幕预状态和当前状态)之间的异同,之后根据差异刷新屏幕

一般来说,curses刷新逻辑屏幕的频率比刷新物理频率高,这很好理解,可以用计组中cache和内存之间的关系来类比,只有在程序执行的某些阶段,用户需要看到全部结果时,curses才会通过refresh计算出逻辑和物理之间对应的最佳途径,换句话说,curses只把最后的逻辑结果传递给物理屏幕

逻辑屏幕的布局是一个二维数组,每个位置不仅包含字符,还包含它的属性(例如粗体和下划线等),由于curses函数在使用的时候需要创建和删除一些临时ds,所以所有curses程序在开始使用前必须初始化,然后在结束使用之后恢复,通过initscr和endwin函数实现

屏幕

所有调用curses函数的程序必须以initscr和endwin结束,前者只能调用一次,成功则返回stdscr指针,否则返回错误信息,后者成功返回OK,失败返回ERR

输出

curses函数库有很多刷新屏幕的基本函数,书上很多,这里摘录并解释一些常用的函数,需要注意的是chtype是curses自己的字符类型,比标准char有更多位

int addch(const chtype char_to_add);
//在当前位置添加指定的字符
int printw(char *format);
//在光标位置输出,用法和printf一样
int refresh(void);
//刷新屏幕
int box(WINDOW *win_ptr, chtype vertical_char, chtype horizontal_char);
//围绕一个窗口画框,横竖用给定的字符
int insch(chtype char_to_insert);
//插入字符,将已有字符右移,准确来说是头插

读取

从屏幕上读取字符并不常用,但curses还是提供了对应的函数

chtype inch(void);
//返回光标当前位置字符和属性信息
int instr(char *string);
//返回字符串,写入string
int innstr(char *string,int number_of_characters);
//同上,但指定长度

清除

curses提供了四种清楚区域的方法,具体如下

int erase(void);
//在屏幕每个位置写上空白字符
int clear(void);
//彻底清除屏幕
int clrtobot(void);
//清除光标到屏幕尾
int clrtoeol(void);
//清除光标到当前行尾

移动

curses对光标的操作给的很少,只有两个函数,具体如下

int move(int new_y,int new_x);
//将逻辑光标移到指定地点,有外部整数LINES和COLUMNS
//并不会移动物理光标,需要移动则要用refresh
int leaveok(WINDOW *window_ptr,bool leave_flag);
//设置标志,控制屏幕刷新后物理光标的位置,0则和逻辑光标一样否则随机

字符

先前已经提到过,cureses中的每个字符是有属性的,这些属性是用宏来定义的,当需要修改时,直接使用这些宏作为对应参数给到函数即可,一些相关函数具体如下

int attron/attroff/attrset(chtype attribute);
//set是设置,on和off是开关
int standout/standend(void);
//反色显示

书上给出了一个例子,下面是代码和运行结果

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <curses.h>
int main()
{
    const char one[]=" First Witch ";
    const char two[]=" Second Witch ";
    initscr();
    move(5,15);
    attron(A_BOLD);//设置模式
    printw("%s","Bold test");//加粗输出
    attroff(A_BOLD);
    refresh();//刷新
    sleep(1);

    move(8,15);
    attron(A_STANDOUT);//设置模式
    printw("%s","test light");
    attroff(A_STANDOUT);//关闭反色
    refresh();
    sleep(1);

    move (10,10);//设置位置输出
    printw("%s","test sentence 1");
    move (11,13);
    printw("%s","test sentence 2");
    move(13,10);
    printw("%s","test sentence 3");
    move (14,23);
    printw("%s","test sentence 4");
    refresh();
    sleep(1);

    attron(A_DIM);
    int len=strlen(one);
    for(int i=len-1;i>=0;i--)//倒序插入
    {
        move(10,10);
        insch(one[i]);
    }
    len=strlen(two);
    for(int i=len-1;i>0;i--)
    {
        move(13,10);
        insch(two[i]);
    }

    attroff(A_DIM);
    refresh();
    sleep(1);

    move(LINES-1,COLS-1);
    refresh();
    sleep(5);
    endwin();
    return 0;
}

可以看到加粗、反色和其他效果的字符串是什么样的

在这里插入图片描述

键盘

除了屏幕之外,curses还有针对键盘的一系列接口

键盘模式

在curses中有许多模式,例如预处理,默认输入等,在使用initscr时,输入模式处于预处理模式,只有在用户按下回车之后,输入的数据才会被传给程序(键盘的特殊字符被启用,组合键可产生信号),如果程序调用cbreak就可以将输入模式设置为cbreak模式,字符一经键入就被立刻传递给程序(特殊字符启用,一些简单字符直传)

一些相关的函数如下

int echo/noecho(void);
//启用/不启用回显
int cbreak/nocbreak(void);
//启用/不启用cbreak
int raw/noraw(void);
//启用/不启用特殊字符的处理

输入

一些读取键盘输入的函数具体如下

int getch(void);
//类似getchar
int getstr(char* string);
//类似gets
int getnstr(char* string,int number_of_characters);
//类似getnstr
int scanw(char* format,...);
//类似scanf

书上给出的一个具体的例子如下

#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
#include <string.h>
#include <unistd.h>
int main()
{
    char name[256],pw[256];
    const char* rpw="1111";//真正的密码,用来检测
    initscr();//开模式
    move(5,10);
    printw("%s","login:");

    move(7,10);
    printw("%s","user name:");
    getstr(name);//拿用户名
 
    move(8,10);
    printw("%s","password:");

    cbreak();
    noecho();//关闭回显

    memset(pw,0,sizeof(pw));
    for(int i=0; i<256; i++)
    {
        pw[i]=getch();
        if(pw[i]=='\n')break;
        move(8,20+i);
        addch('*');//把输入的对应位置插入*
        refresh();
    }

    echo();
    nocbreak();

    move(11,10);
    if(strncmp(rpw,pw,strlen(rpw)))
        printw("%s","wrong");
    else
        printw("%s","correct");
    refresh();
    sleep(2);

    endwin();
    return 0;
}

可以看到,这个例子实现了一个简单的密码隐藏和检测的功能,通过调用curses和设置光标的位置来实现

在这里插入图片描述

窗口

curses可以在物理屏幕上同时显示多个不同尺寸的窗口,而不仅仅是对单一窗口进行操控

WINDOW

stdscr是WINDOW的一个特例,是默认存在的,除此之外,在WINDOW中可以使用newwin和delwin来创建和销毁窗口,具体函数如下

WINDOW *newwin(int num_of_lines,int num_of_cols,int start_y,int start_x);
//创建一个新窗口,行数列数和开始位置
//新窗口完全独立于已存在窗口,并且覆盖它们的内容
int delwin(WINDOW *window_to_delete);
//删除,不能删除stdscr和curscr

常用函数

先前已经使用过一些屏幕上的函数,例如addch、printw等,但是这些函数还可以加上一些前缀变成通用函数,前缀w用于窗口,mv用于光标移动,mvw用于在窗口中移动光标,需要注意的是,当加上了对应的前缀时,函数的参数也会发生对应的变化,下面给出一些加上前缀后的具体函数

int mvwaddch(WINDOW* window_pointer, int y, int x, const chtype char);
int mvwprint(WINDOW *window_pointer, int y, int x, const chtype char);
int mvwin(WINDOW *window_to_move, int new_y, int new_x, int new_x);
int touchwin(WINDOW *window_ptr);
//通知curses窗口内容已改变,下次刷新必须重新绘制
int scrollok(WINDOW *window_ptr, bool scroll_flag);
//控制卷屏,传递给函数是布尔值则允许卷
int scroll(WINDOW *window_ptr);
//窗口内容上卷一行

书上给出的一个运行的例子,代码和部分运行结果如下

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <curses.h>
int main()
{
    WINDOW *nw_ptr,*pw_ptr;//新窗口和弹出创刊
    char ch='a';
    initscr();//开模式
    move(5,5);//移动光标
    printw("%s","Multiple windows");
    refresh();
    for(int i=0;i<255;i++)
        for(int j=0;j<255;j++)
            {
                mvwaddch(stdscr,i,j,ch);//把数据插入整个屏幕
                ch=(++ch-'a')%26+'a';//循环小写字母
            }
    refresh();
    sleep(2);

    nw_ptr=newwin(10,20,5,5);//新窗口
    mvwprintw(nw_ptr,2,2,"%s","Hello World");
    mvwprintw(nw_ptr,5,2,"%s","1111111111111111111111111111111111111");
    wrefresh(nw_ptr);//刷新以显示
    sleep(2);

    ch='0';
    for(int i=0;i<255;i++)
        for(int j=0;j<255;j++)
        {
            mvwaddch(stdscr,i,j,ch);
            ch=(++ch-'0')%10+'0';//循环数字
        }
    refresh();
    sleep(2);
    
    touchwin(nw_ptr);//通知curses窗口内容已变,调用刷新要重写窗口
    wrefresh(nw_ptr);
    sleep(2);

    pw_ptr=newwin(10,20,8,8);//创建新窗口
    box(pw_ptr,'|','-');//加上框
    mvwprintw(pw_ptr,5,2,"%s","Pop up Window!");
    wrefresh(pw_ptr);
    sleep(2);

    touchwin(nw_ptr);//显示
    wrefresh(nw_ptr);
    sleep(2);
    wclear(nw_ptr);//清屏
    wrefresh(nw_ptr);
    sleep(2);
    delwin(nw_ptr);
    touchwin(pw_ptr);
    wrefresh(pw_ptr);
    sleep(2);
    delwin(pw_ptr);
    touchwin(stdscr);
    refresh();
    sleep(2);
    endwin();

    return 0;
}

运行的时候可以看到,创建的新窗口覆盖了原有的背景窗口不分,然后新的两个窗口彼此相互覆盖,需要注意的是,如果要用curses刷新多个窗口,只能人为的管理这些窗口之间的先后关系

在这里插入图片描述

屏幕刷新优化

对屏幕的优化刷新需要一定技巧,当要更新的终端是通过慢速链路连接到主机时,就可能因为设备之间的不同步出问题,在慢速链路上,屏幕绘制的速度会非常慢,curses提供了相对应的手段,具体如下

int wnoutrefresh(WINDOW *window_ptr);
//决定把哪些字符发到屏幕上,但是只是缓存
int doupdate(void);
//把最后的更新结果输出到屏幕上
//如果想重新绘制多个窗口,可以为每个窗口调用wnoutrefresh,然后最后调用doupdate即可
//这是利用了缓存的思路

子窗口

子窗口和窗口的关系有点类似父进程和子进程,它的创建和删除的相关函数如下

WINDOW *subwin(WINDOW *parent, int num_of_lines,int num_of_cols, int start_y, int start_x);  
//类似newwin,子窗口和父窗口共享同一字符存储空间
int delwin(WINDOW *window_to_delete);

子窗口最主要的用途是,提供一种简洁方式卷动另一窗口的部分内容,在使用子窗口的时候,刷新屏幕必须先对父窗口调用touchwin,下面是书上给的一个例子代码和运行结果

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

int main()
{
    WINDOW *sub;//窗口指针
    char ch='1';
    initscr();
    for(int i=0;i<255;i++)
        for(int j=0;j<255;j++)
        {
            mvwaddch(stdscr,i,j,ch);//在对应位置插入字符
            ch=(++ch-'0')%9+'1';
        }

    sub=subwin(stdscr,10,20,10,10);//创建子窗口
    scrollok(sub,1);
    touchwin(stdscr);//刷新屏幕前调用父窗口
    refresh();
    sleep(1);

    werase(sub);//删除父窗口的对应区域
    mvwprintw(sub,2,0,"%s","scrolling");//输出
    wrefresh(sub);//刷新窗口
    sleep(1);

    for(int i=1;i<10;i++)//重新输出并滚动
    {
        wprintw(sub,"%s","wapping and scrolling");
        wrefresh(sub);
        sleep(1);
    }

    delwin(sub);
    touchwin(stdscr);
    refresh();
    sleep(1);

    endwin();

    
    return 0;
}

可以看到父窗口的一个小区域被清空,然后生成了一个子窗口,对子窗口进行输出并滚动

在这里插入图片描述

keypad

对于键盘上的按键,并不是所有的都可以通过ascall码来表示,比如insert、delete等,这些键在实际输入的时候往往是以escape字符开头的字符串序列,但这样就出现一个问题,系统需要识别单独按下escape键和以该键为首的字符串序列,curses提供了区分它们的实现,curses在启动时会关闭转义序列与逻辑键间的转换功能,通过keypad实现

int keypad(WINDOW *window_ptr, bool keypad_on);
//为了区分不同,curses会在检测到escape之后等待一小段时间,特别是启用keypad后

书上给出了一个keypad的实例,下面是代码和运行结果

#include <unistd.h>
#include <stdlib.h>
#include <curses.h>
#include <string.h>
#include <ctype.h>
int main()
{
    initscr();
    crmode();
    keypad(stdscr,1);//启用keypad模式
    noecho();//不回显
    clear();
    mvprintw(5,5,"q to quit");//打印字符
    move(7,5);//移动光标
    refresh();
    int key=getch();
    while(key!=ERR&&key!='q')
    {
        move(7,5);
        clrtoeol();//清除当前位置到行尾
        if(isalpha(key))//如果是字符
            printw("Key was %c", key);
        else
        {
            switch(key)//用特殊的宏来判断
            {
                case 27: printw("%s","Escape key");break;
                case KEY_END: printw("%s","END key");break;
                case KEY_BEG: printw("%s","BEGINNING key");break;
                case KEY_RIGHT: printw("%s","RIGHT key");break;
                case KEY_LEFT: printw("%s", "LEFT key");break;
                case KEY_UP: printw("%s","UP key");break;
                case KEY_DOWN: printw("%s","DOWN key");break;
                default: printw("Unmatched - %d",key);break;
            }
        }
        refresh();
        key=getch();

    }
    endwin();
    return 0;
}

可以看到程序可以识别所有的字符,以及一些设定好的特殊按键

在这里插入图片描述

彩色显示

curses对颜色的支持是通过组合来实现的,对于每个想使用的颜色组合,用户必须同时定义一个字符的前景和背景,相关函数具体如下

bool has_colors(void);
//判断终端是否支持彩色显示
int start_color(void);
//初始化颜色显示
int init_pair(short pair_number, short foreground, short background);
//初始化一个颜色组合,用pair_number来替代
int COLOR_PAIR(int pair_number);
//把颜色组合作为属性返回
int pair_content(short pair_number, short *foreground, short *background);
//获得已定义的颜色组合信息
int init_color(short color_number, short red, short green, short blue);
//把一个已有的颜色重新定义

书上给出的例子和运行结果如下

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <curses.h>
int main()
{
    initscr();
    if(!has_colors())
    {
        endwin();
        fprintf(stderr,"ERROR1\n");
        exit(1);
    }
    if(start_color()!=OK)
    {
        endwin();
        fprintf(stderr,"ERROR2\n");
        exit(2);
    }        
    clear();
    mvprintw(5,5,"Colors types: %d, Color pairs: %d",COLORS,COLOR_PAIRS);
    refresh();
    
    init_pair(1,COLOR_RED,COLOR_BLACK);//初始化几个组合
    init_pair(2,COLOR_RED,COLOR_GREEN);
    init_pair(3,COLOR_GREEN,COLOR_RED);

    for(int i=1;i<=3;i++)
    {
        attroff(A_BOLD);//关闭加粗
        attrset(COLOR_PAIR(i));//设置颜色组合
        mvprintw(5+i,5,"COLOR pair %d", i);
        attrset(COLOR_PAIR(i)|A_BOLD);//设置颜色组合+加粗
        mvprintw(5+i,25,"Bold color pair %d", i);
        refresh();
        sleep(3);
    }
    endwin();
    return 0;
}

可以看到以不同的颜色组合和加粗显示了字符串

在这里插入图片描述

pad

有时在使用curses时需要先建立一个逻辑屏幕,然后再把其部分内容投射到物理屏幕上,这个逻辑屏幕的尺寸可能会大于实际屏幕,curses提供了一个数据结构pad,它可以控制尺寸大于正常窗口的逻辑屏幕,具体函数如下

WINDOW *newpad(int number_of_lines, int number_of_columns);
int prefresh(WINDOW *pad_ptr, int pad_row, int pad_column,
int screen_row_min, int screen_col_min, int screen_row_max, int screen_col_max);
//将pad从指定坐标开始的区域写到屏幕上指定的显示区域,后两个坐标是显示范围

书上给出的例子和运行结果如下

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

int main()
{
    WINDOW*pad;
    initscr();
    int lines=LINES+50,cols=COLS+50;
    pad=newpad(lines,cols);//创建新pad
    char ch='a';

    for(int i=0;i<lines;i++)
        for(int j=0;j<cols;j++)
        {
            mvwaddch(pad,i,j,ch);
            ch=(++ch-'a')%26+'a';
        }

    prefresh(pad,5,7,2,2,9,9);//刷新显示
    sleep(1);
    prefresh(pad,LINES+5,COLS+7,5,5,21,19);//移动,再刷新显示一遍
    sleep(1);
    delwin(pad);
    endwin();
    return 0;
}

在这里插入图片描述

总结

可以看到,linux通过curses实现了对屏幕、键盘的读取,以及对子窗口,pad等的调用,为C语言实现可交互的应用程序提供了很好的实现方法和思路

参考文献

  1. 《Linux程序设计(第四版)》

原文地址:https://blog.csdn.net/C_eeking/article/details/142557709

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!