ncurses 入门

作者:Jim Hall

如何使用 curses 在终端屏幕上绘图。

虽然图形用户界面非常酷炫,但并非每个程序都需要通过点击界面运行。例如,历史悠久的 vi 编辑器早在第一个 GUI 出现之前就已经在纯文本终端中运行。

vi 编辑器就是一个屏幕导向程序的例子,它在“文本”模式下绘图,使用名为 curses 的库,该库提供了一组编程接口来操作终端屏幕。curses 库起源于 BSD UNIX,但 Linux 系统通过 ncurses 库提供此功能。

[关于 ncurses 的“怀旧之旅”,请参阅 Eric S. Raymond 于 1995 年 9 月 1 日发表的 “ncurses:Linux 的可移植屏幕处理”。]

创建使用 curses 的程序实际上非常简单。在本文中,我将展示一个示例程序,该程序利用 curses 在终端屏幕上绘图。

谢尔宾斯基三角形

演示一些 curses 函数的一个简单方法是生成谢尔宾斯基三角形。如果您不熟悉生成谢尔宾斯基三角形的方法,以下是规则

  1. 设置定义三角形的三个点。
  2. 随机选择任意一点 (x,y)。

然后

  1. 随机选择三角形的其中一个点。
  2. 将新的 x,y 设置为先前 x,y 和三角形点之间的中点。
  3. 重复。

根据这些说明,我编写了这个程序,使用 curses 函数在终端屏幕上绘制谢尔宾斯基三角形


     1  /* triangle.c */
     2
     3  #include <curses.h>
     4  #include <stdlib.h>
     5
     6  #include "getrandom_int.h"
     7
     8  #define ITERMAX 10000
     9
    10  int main(void)
    11  {
    12      long iter;
    13      int yi, xi;
    14      int y[3], x[3];
    15      int index;
    16      int maxlines, maxcols;
    17
    18      /* initialize curses */
    19
    20      initscr();
    21      cbreak();
    22      noecho();
    23
    24      clear();
    25
    26      /* initialize triangle */
    27
    28      maxlines = LINES - 1;
    29      maxcols = COLS - 1;
    30
    31      y[0] = 0;
    32      x[0] = 0;
    33
    34      y[1] = maxlines;
    35      x[1] = maxcols / 2;
    36
    37      y[2] = 0;
    38      x[2] = maxcols;
    39
    40      mvaddch(y[0], x[0], '0');
    41      mvaddch(y[1], x[1], '1');
    42      mvaddch(y[2], x[2], '2');
    43
    44      /* initialize yi,xi with random values */
    45
    46      yi = getrandom_int() % maxlines;
    47      xi = getrandom_int() % maxcols;
    48
    49      mvaddch(yi, xi, '.');
    50
    51      /* iterate the triangle */
    52
    53      for (iter = 0; iter < ITERMAX; iter++) {
    54          index = getrandom_int() % 3;
    55
    56          yi = (yi + y[index]) / 2;
    57          xi = (xi + x[index]) / 2;
    58
    59          mvaddch(yi, xi, '*');
    60          refresh();
    61      }
    62
    63      /* done */
    64
    65      mvaddstr(maxlines, 0, "Press any key to quit");
    66
    67      refresh();
    68
    69      getch();
    70      endwin();
    71
    72      exit(0);
    73  }

让我通过解释来讲解这个程序。首先,getrandom_int() 是我对 Linux getrandom() 系统调用的封装,但它保证返回一个正整数值。否则,您应该能够根据上述规则识别出初始化然后迭代谢尔宾斯基三角形的代码行。除此之外,让我们看看我用来在终端上绘制三角形的 curses 函数。

大多数 curses 程序将以这四个指令开始。1) initscr() 函数确定终端类型,包括其大小和功能,并根据终端可以支持的功能设置 curses 环境。cbreak() 函数禁用行缓冲,并将 curses 设置为一次接收一个字符。noecho() 函数告诉 curses 不要将输入回显到屏幕,而 clear() 函数则清除屏幕


    20      initscr();
    21      cbreak();
    22      noecho();
    23
    24      clear();

然后,该程序设置一些变量来定义定义三角形的三个点。请注意此处使用了 LINESCOLS,它们由 initscr() 设置。这些值告诉程序终端上有多少行和列。屏幕坐标从零开始,因此屏幕的左上角是第 0 行,第 0 列。屏幕的右下角是第 LINES - 1 行,第 COLS - 1 列。为了方便记忆,我的程序分别在变量 maxlinesmaxcols 中设置了这些值。

在屏幕上绘制文本的两种简单方法是 addch()addstr() 函数。要将文本放在屏幕上的特定位置,请使用相关的 mvaddch()mvaddstr() 函数。我的程序在几个地方使用了这些函数。首先,程序绘制了定义三角形的三个点,标记为“0”、“1”和“2”


    40      mvaddch(y[0], x[0], '0');
    41      mvaddch(y[1], x[1], '1');
    42      mvaddch(y[2], x[2], '2');

为了绘制随机起点,程序进行了类似的调用


    49      mvaddch(yi, xi, '.');

为了绘制谢尔宾斯基三角形迭代中的每个后续点


    59          mvaddch(yi, xi, '*');

程序完成后,它会在屏幕的左下角(第 maxlines 行,第 0 列)显示一条有用的消息


    65      mvaddstr(maxlines, 0, "Press any key to quit");

重要的是要注意,curses 在内存中维护屏幕的一个版本,并且仅在您要求时才更新屏幕。这提供了更高的性能,尤其是在您想要在屏幕上显示大量文本时。这是因为 curses 只能更新自上次更新以来屏幕上发生更改的部分。要使 curses 更新终端屏幕,请使用 refresh() 函数。

在我的示例程序中,我选择在“绘制”谢尔宾斯基三角形中的每个后续点后更新屏幕。通过这样做,用户应该能够观察到三角形中的每次迭代。

在退出之前,我使用 getch() 函数等待用户按下按键。然后我调用 endwin() 来退出 curses 环境并将终端屏幕恢复正常控制


    69      getch();
    70      endwin();

编译和示例输出

现在您已经有了您的第一个示例 curses 程序,是时候编译并运行它了。请记住,Linux 系统通过 ncurses 库实现 curses 功能,因此您需要在编译时链接 -lncurses,例如


$ ls
getrandom_int.c  getrandom_int.h  triangle.c

$ gcc -Wall -lncurses -o triangle triangle.c getrandom_int.c

在标准的 80x24 终端上运行 triangle 程序并不是很有趣。在这种分辨率下,您看不到谢尔宾斯基三角形的太多细节。如果您运行终端窗口并设置非常小的字体大小,您可以更容易地看到谢尔宾斯基三角形的分形特性。在我的系统上,输出看起来像图 1。

图 1. triangle 程序的输出

尽管迭代具有随机性,但每次运行谢尔宾斯基三角形看起来都几乎相同。唯一的区别是最初的几个点绘制在屏幕上的位置。在本例中,您可以看到开始三角形的单个点,靠近点 1。看起来程序接下来选择了点 2,您可以看到点和“2”之间一半位置的星号。而且看起来程序为下一个随机数随机选择了点 2,因为您可以看到第一个星号和“2”之间一半位置的星号。从那里开始,就无法判断三角形是如何绘制的了,因为所有后续的点都落在三角形区域内。

开始学习 ncurses

这个程序是如何使用 curses 函数在屏幕上绘制字符的简单示例。根据您的程序需要做什么,您可以使用 curses 做更多的事情。在后续文章中,我将展示如何使用 curses 允许用户与屏幕交互。如果您有兴趣开始学习 curses,我鼓励您阅读 Linux 文档项目中的 Pradeep Padala 的 “NCURSES 编程 HOWTO”

Jim Hall 是一位开源软件倡导者和开发者,可能最出名的是作为 FreeDOS 的创始人。Jim 还非常积极地参与 GNOME 等开源软件项目的可用性测试。在工作中,Jim 是 Hallmentum 的 CEO,这是一家 IT 执行咨询公司,帮助 CIO 和 IT 领导者进行战略规划和组织发展。

加载 Disqus 评论