使用 curses 来了解终端

程序的能力受到它在其上运行的终端能力的部分限制。

此部分提供了有关初始化终端和标识它们能力的信息。

操作多个终端

有了 curses,可对输入和输出使用一个或多个终端。 终端子例程使您能够建立新的终端、切换输入和输出处理,并检索终端能力。

可使用 initscr 子例程在一个缺省屏幕上启动 curses。 如果应用程序将输出发送到多个终端,请使用 newterm 子例程。 对每个终端调用 newterm 子例程。 如果应用程序想要指示错误情况以使终端在无法支持面向屏幕的程序时应用程序可以面向行的方式继续运行,还可使用 newterm 子例程。

当它完成时,程序必须为它使用的每个终端调用 endwin 子例程。 如果对同一终端多次调用 newterm 子例程,那么所指的第一个终端必须是对其调用 endwin 子例程的最后一个终端。

set_term 子例程在不同终端之间切换输入和输出处理。

确定终端能力

curses 提供以下子例程帮助您确定终端的能力:
子例程 描述
has_ic 确定终端是否具有插入字符能力
has_il 确定终端是否具有插入行能力
longname 返回终端的详细名称

longname 子例程返回一个指针,该指针指向包含当前终端详细描述的静态区域。 仅在调用 initscrnewterm 子例程后,才定义此静态区域。 如果想要对多个终端使用 longname 子例程,那么每次调用 newterm 子例程均会覆盖此区域。 调用 set_term 子例程将不会恢复此值。 而是将调用之间的此区域保存到 newterm 子例程。

如果终端具有插入和删除字符的能力,那么 has_ic 子例程返回 TRUE。

如果终端具有插入和删除行的能力,或可使用滚动区域模拟这些能力,那么 has_il 子例程返回 TRUE。 使用 has_il 子例程检查它是否适合于使用 scrollokidlok 子例程开启物理滚动。

设置终端输入和输出方式

控制输入和输出的子例程确定应用程序如何检索数据并将数据显示给用户。

输入方式

特殊输入字符包括流量控制字符、中断字符、擦除字符以及行删除符。 以下互相排斥 curses 方式允许应用程序控制输入字符的影响:

成熟方式
此方式可进行正常的一次一行处理并将特殊字符在应用程序外进行处理,与规范方式输入处理作用相同。 通过调用 nocbreak( ) 在输入此方式时不更改 ISIG 和 IXON 标志的状态,并通过调用 noraw( ) 在输入此方式时设置此状态。

实现支持来自任何受支持语言环境的擦除字符和行删除符,而无需考虑字符的宽度。

cbreak 方式
用户输入的字符马上便可用于应用程序,且 curses 不在擦除字符或行删除符上执行特殊处理。 应用程序可选择 cbreak 方式进行其自身的行编辑,但可使用异常终止字符来异常终止任务。 此方式与非规范方式“例子 B”输入处理(将 MIN 设为 1,且清除了 ICRNL)作用相同。 输入此方式时,ISIG 和 IXON 标记的状态不更改。
半延迟方式
作用同于 cbreak,除输入功能等待直至字符可用或应用程序定义的时间间隔过去(这两者先发生哪个均可)之外。 此方式与非规范方式“例子 C”输入处理(将 TIME 设为应用程序指定的值)作用相同。 输入此方式时,ISIG 和 IXON 标记的状态不更改。
原始方式
原始方式将对终端输入的最大控制权给予应用程序。 应用程序将每个字符都看成输入的原样。 这与非规范方式“例子 D”输入处理作用相同。 ISIG 和 IXON 标记在输入此方式时被清除。

当进程调用 initscrnewterm 子例程在 endwin 子例程被调用时初始化 curses 并恢复这些设置时,记录终端接口设置。 不指定 curses 操作的初始输入方式,除非实现支持改进的 curses 一致性,而在其中初始输入方式为 cbreak 方式。

BREAK 键的行为依赖于 curses 未设置的显示驱动程序中的其他位。

延迟方式

以下互斥延迟方式指定在调用函数时没有任何终端输入等待的情况下,某些 curses 函数返回到应用程序的速度。
延迟 描述
无延迟 函数失败。
延迟 应用程序等待,直至实现将文本传递给应用程序。 如果设置了 cbreak 方式或原始方式,是在一个字符之后。 否则,将在第一个换行符、行结束符或文件结束符之后。

功能键处理上的无延迟方式的作用未指定。

回传方式处理

回传方式确定 curses 是否将已输入的字符回传到屏幕。 回传方式的作用类似于 termios 结构(于与连接到窗口的结构设备关联)的本地方式字段中回传标记的作用。 然而,curses 总是在被调用时清除回传标记,以禁止操作系统执行回传。 此回传字符的方法与操作系统的回传字符方法不同,因为 curses 执行终端输入的附加处理。

如果处于回传方式,那么 curses 执行其自身的回传。 尽管调用了 addch 子例程,并具有如光标移动和回绕等所有后续影响,任何可视输入字符均被存储在当前窗口中或指定窗口中(由应用程序调用的输入函数指定)的窗口光标所在位置。

如果不处于回传方式,那么应用程序必须执行输入的任何回传。 应用程序经常在屏幕的控制区中执行它们自身的回传,或根本不回传,因此它们禁用回传方式。

可能无法对同步和网络异步终端关闭回传处理,因为回传处理是直接由终端执行的。 运行在此类终端上的应用程序应当注意,输入的任何字符将会显示在屏幕上光标所在的位置。

以下是子例程回传处理系列的一部分:
子例程 描述
cbreaknocbreak 将终端置于 CBREAK 方式,或使其脱离这一方式
延迟输出 以毫秒为单位设置输出延迟
echonoecho 控制输入字符到屏幕的回传
半延迟 如果阻塞给定的一段时间后,未输入任何输入,那么返回 ERR
nlnonl 确定 curses 是否在输出时将新行转换成回车符和换行,并在输入时将回车符转换成新行。
rawnoraw 将终端置于此方式,或使之脱离此方式

cbreak 子例程执行由 raw 子例程执行的部分函数。 在 cbreak 方式中,用户输入的字符马上便可用于程序,且不执行擦除或行删除符处理。 不同于原始方式,对中断和流字符进行操作。 否则,tty 驱动程序将缓存输入的字符,直至输入新行或回车符。

注: CBREAK 方式禁用 tty 驱动程序的转换。

delay_output 子例程将输出延迟设置为指定的毫秒数。 不要过多地使用此子例程,因为它使用的是填充字符而不是处理器暂停。

echo 子例程将终端置于 echo 方式。 在回传方式中,curses 将用户输入的字符写到终端上物理光标所在的位置。 noecho 子例程使终端脱离 echo 方式。

nlnonl 子例程分别控制 curses 是否将新行转换为回车符和输出的换行符。 以及 curses 是否将回车符转换为输入上的新行。 初始时,发生这些转换。 通过禁用这些转换,curses 子例程库对换行能力具有更多的控制权,因而能够使光标更快的移动。

nocbreak 子例程使终端脱离 cbreak 方式。

raw 子例程将终端置于原始方式。 在原始方式中,用户输入的字符马上便可用于程序。 此外,没有对中断、退出、暂挂和流量控制字符进行解释便将它们传递,而不是像它们在 cbreak 方式中那样生成信号。 noraw 子例程使终端脱离原始方式。

使用 terminfo 和 termcap 文件

初始化 curses 时,它检查 TERM 环境变量以标识终端类型。 然后,curses 查找说明终端能力的定义。 此信息通常保存在 TERMINFO 环境变量指定的本地目录中或 /usr/share/lib/terminfo 目录中。 所有的 curses 程序都首先检查是否已经定义了 TERMINFO 环境变量。 如果此变量未定义,那么检查 /usr/share/lib/terminfo 目录。

例如,如果 TERM 变量设置为vt100TERMINFO 变量设置为 /usr/mark/myterms 文件, curses 检查 /usr/mark/myterms/v/vt100 文件。 如果此文件不存在,那么 curses 检查 /usr/share/lib/terminfo/v/vt100 文件。

此外,可将 LINESCOLUMNS 环境变量设为覆盖终端描述。

编写使用 terminfo 子例程的程序

当程序必须直接处理 terminfo 数据库时,请使用 terminfo 子例程。 例如,使用这些子例程对功能键进行编程。 在所有其他情况下,curses 子例程更为适合,因此推荐使用它。

初始化终端

程序应该从调用 setupterm 子例程开始。 通常,通过调用 initscrnewterm 子例程来间接调用此子例程。 setupterm 子例程读取在 terminfo 数据库中定义的终端相关变量。 terminfo 数据库包括布尔、数字和字符串变量。 所有这些 terminfo 变量均使用为指定终端定义的值。 读取完数据库,setupterm 子例程使用终端定义初始化 cur_term 变量。 处理多个终端时,可使用 set_curterm 子例程将 cur_term 变量设为特定终端。

另一个子例程 restartterm类似于 setupterm 子例程。 然而,将内存恢复到先前状态后调用它。 例如,在调用 scr_restore 子例程后调用 restartterm 子例程。 restartterm 子例程假定输入和输出选项与保存内存时相同,但终端类型和波特率可能不同。

del_curterm 子例程释放包含指定终端能力信息的空间。

头文件

curses.hterm.h 文件按以下顺序包括到程序中:

#include <curses.h>
#include <term.h>

这些文件包含 terminfo 数据库中字符串、数字和标记的定义。

处理终端能力

通过 tparm 子例程传递所有参数化字符串以将它们实例化。 使用 tputsputp 子例程显示所有 terminfo 字符串以及 tparm 子例程的输出。

子例程 描述
输出 提供 tput 子例程的快捷方式
tparm 使用参数实例化字符串
tput 将填充信息应用到给定字符串并将它输出

使用以下子例程获取并传递终端能力:

子例程 描述
tigetflag 返回指定布尔能力的值。 如果该能力不是布尔值,那么返回 -1。
tigetnum 返回指定数字能力的值。 如果该能力不是数字的,那么返回 -2。
tigetstr 返回指定字符串能力的值。 如果指定能力不是字符串,那么 tigetstr 子例程返回值 (char *) -1。

退出程序

程序存在时,将 tty 方式恢复为其原始状态。 为此,请调用 reset_shell_mode 子例程。 如果程序使用光标寻址,那么在启动时它应该输出 enter_ca_mode 字符串,而在退出时应该输出 exit_ca_mode 字符串。

在调用 shell 之前,使用 shell 转义的程序应该调用 reset_shell_mode 子例程,并输出 exit_ca_mode 字符串。 从 shell 返回后,程序应该输出 enter_ca_mode 字符串,并调用 reset_prog_mode 子例程。 此进程不同于在退出时调用 endwin 子例程的标准 curses 操作。

低级屏幕子例程

使用以下子例程进行低级屏幕操作:

子例程 描述
ripoffline 从 stdscr 剥离单个行
Scr_dump 将虚屏的内容转储到指定文件
Scr_init 从指定文件初始化 curses 数据结构
Scr_restore 将虚屏恢复到先前转储的文件的内容

termcap 子例程

如果程序使用 termcap 文件来获取终端信息,那么将 termcap 子例程作为转换辅助程序也被包括在其中。 对于 termcap 子例程,参数是相同的。 curses 使用 terminfo 数据库模仿子例程。 提供以下 termcap 子例程:

子例程 描述
tgetent 仿真 setupterm 子例程。
tgetflag 为 termcap 标识返回布尔条目。
tgetnum 为 termcap 标识返回数字条目。
tgetstr 为 termcap 标识返回字符串条目。
tgoto 重复 tparm 子例程。 应将 tgoto 子例程的输出传递给 tputs 子例程。

将 termcap 描述转换为 terminfo 描述

captoinfo 命令将 termcap 描述转换为 terminfo 描述。 以下示例演示了 captoinfo 命令是如何工作的:
captoinfo /usr/lib/libtermcap/termcap.src

此命令将 /usr/lib/libtermcap/termcap.src 文件转换为 terminfo 源。 captoinfo 命令将输出写到标准输出,并将注释和其他信息保留在文件中。

操作 TTY

以下函数保存并恢复终端方式的状态:

函数 描述
savetty 保存 tty 方式的状态。
重置 将 tty 方式的状态恢复为上次调用 savetty 子例程时它们所处的状态。

同步和联网异步终端

同步、联网同步 (NWA) 或非标准直接连接的异步终端经常用于大型机环境中,并以阻塞方式通知主机。 也就是,用户在终端输入字符,然后按下特殊键开始向主机传输字符。

注: 虽然可能可以向主机发送任意大小的块,但不可能也不希望仅使用单个击键来传输字符。 如此操作可能会使使用单个字符输入的应用程序出现一些问题。

output

可将 curses 接口用于与输出到终端相关的所有操作,可能的例外情况是,在某些终端上,refresh 例程为执行任何更新,可能需要重新绘制整个屏幕内容。

如果另外需要在执行每个此类操作之前清除屏幕,那么结果可能不理想。

input

由于同步(阻塞方式)和 NWA 终端操作的性质,可能不支持所有或任何 curses 输入功能。 特别需要注意以下几点:
  • 可能不能输入单个字符。 可能需要按下特殊键将在终端输入的所有字符传输到主机。
  • 有时可能不能禁用回传。 字符回传可由终端直接执行。 在以此方法运作的终端上,执行输入的任何 curses 应用程序应当注意已输入的任何字符将会出现在屏幕上光标所在的位置。 这不必与窗口中光标的位置对应。