跳转至

交互式编程与 ncurses — 非规范模式、MIN/TIME 与窗口库

这一章讲什么: 第 18 章给了你终端的仪表盘,本章教你开车: 关掉规范模式,程序就能按到键就收到、不敲回车、不回显、 还能让 CTRL-C 只是普通字符。VMIN/VTIME 四个组合决定 read 的返回时机——这是 vi、top、less 的共同地基。然后用这些 手艺写一个终端游戏(sprite),最后交棒给 ncurses—— 把「清屏、光标、按键、窗口」全部封装好的库,top 和 vim 的底层就是它。

1. 顶层全景:从规范到非规范

  规范模式(ICANON):攒行 + 行编辑(erase/kill/werase)+ 特殊字符变信号
      │  stty -icanon 立刻可见:逐字符生效、退格不再编辑、CTRL-D 变普通字符
      ▼
  非规范模式:read 何时返回?→ c_cc[VMIN] 与 c_cc[VTIME] 四组合
      ├─ 0/0  轮询读:立刻返回,无数据返 0
      ├─ n/0  阻塞读:攒够 n 字节才返(vi 用 1/0:按键即返)
      ├─ 0/n  定时读:等 n/10 秒,来一个字符或超时即返
      └─ n/n  字节间定时:首字节起计时,字节间隔超时即返
              → vi 区分 ESC[B(方向键)与 E-S-C-[-B 的原理!
      ▼
  raw vs cbreak:raw=cfmakeraw(全脱);cbreak=非规范但保留信号
      ▼
  手工派:ANSI 转义 + 备用屏幕 → sprite 游戏(综合实战)
      ▼
  库派:ncurses — initscr/窗口/refresh 双缓冲/getch/endwin

图说:左半边是「自己拿 termios 造轮子」,右半边是「交 给 ncurses」;路径不同,但底下都是第 18 章的那五个 termios 成员。

2. MIN/TIME 四组合:read 的四种性格

规范模式下被忽略的 c_cc[VMIN]/c_cc[VTIME](0–255), 在非规范模式接管 read 语义1:

MIN TIME 行为 用途
0 0 轮询读:立即返回;无数据返 0(不是 -1,区别于 O_NONBLOCK) 动画主循环里顺手查键盘(sprite)
n>0 0 阻塞读:Linux 下攒够 min(n, 请求数) 即返 n=1 = 按一键返一次(vi 命令模式)
0 n>0 定时读:调用即起 n/10 秒定时,来一字节或超时返 「等输入但别傻等」
n>0 n>0 字节间定时:首字节到起表,每来一字节重新起表;凑够 MIN 或表停即返 分辨方向键三连击 ESC[B 与三个慢按的字符

第四组合的存在理由(原书点明):方向键发三字节序列 ESC [ B。 按得快 = 一个转义序列(光标移动);按得慢 = 三个独立字符—— vi 就靠字节间定时做这个区分2。mintime_test 程序用 -m/-t 两个选项实测全部六种搭配(含 MIN 大于 read 请求数的 corner case,Linux 与 POSIX 文档不符处也如实标出)3。

O_NONBLOCK vs MIN=0 的选择(NOTE):O_NONBLOCK 只改 本进程的连接,改 termios 的 VMIN 则影响所有共享这个 终端的进程——动谁的决策4。

3. 手工派:sprite 游戏

init_terminal() 五行定终身:c_lflag &= ~(ICANON|ECHO|ISIG) + VMIN=0, VTIME=0(轮询读)。配套 save_tty/restore_tty—— 退出前必须还原 termios,否则 shell 回来时终端还光着5。 主循环:挪 sprite → 画 → read 轮询键盘 → 处理命令 (q/p/c/u/r/d/l)——选轮询而不用信号驱动做动画,因为 handler 里要做 async-signal-safe 与临界区太麻烦6。

屏幕管理全靠 ANSI 转义(第 9 章的手法升级):moveto、清屏、 隐藏光标、以及本章新招——备用屏幕(\e[?1049h): 进则切到一块全新屏幕,退则还原原来的滚动区——vim、less 退出 后你的 shell 输出还在,就是它7。窗口尺寸变化:SIGWINCH → 重置画面重开一局(TIOCGWINSZ)。

4. 库派:ncurses

手写转义不可移植(终端类型各异)——同一个「清屏」,不同终端要发不同的字节序列, 连「终端能干什么」都各说各话。ncurses 把这份差异收进库里:程序面对统一接口, 由库按终端类型(terminfo 数据库)翻译成真字节10。

4.1 名字乱局与编译链接

先认名字,不然查文档会迷路。这段历史原书专门花了一节11:

名字 是什么
curses 1980 年 Berkeley 为地下城游戏 rogue 攒的库(Ken Arnold 主力),基于 termcap;名字来自 cursor optimization
SVR4 curses AT&T 专有版,基于 terminfo;与 BSD 版 API 不同,程序两头没法移植
ncurses 1990 年代中 Berkeley 开发者做的自由实现,兼容 SVR4 与 X/Open 标准——Linux 现在用的就是它
XSI Curses / X/Open Curses, Issue 7 现行标准本身;标准管库叫「Curses 库」,管程序叫「curses 程序」

落到操作层面只有三条:

#include <curses.h>                       # ncurses.h 是它的符号链接,用 curses.h
$ gcc -o myprog myprog.c -lncurses       # 库文件名是 libncurses

外加一处分叉:普通库只处理 8 位字符;要支持中文这类多字节字符,链 ncursesw(宽字符库),函数名带 _w 子串(如 add_wch())12。 原书只用普通库。

如果错,会错在: 程序里 #include <ncurses.h> 能编译,但照标准的写法是 <curses.h>——两者目前是同一件东西,可一旦换了平台,跟着标准走的那一个 不容易被落下。

4.2 坐标系:矩阵坐标,(y,x) 先行后列

ncurses 的坐标系来自矩阵而不是直角坐标:原点在屏幕左上角 (0,0), 坐标对写作 (y, x) = 第 y 行第 x 列,行号向下增长13。 两个全局变量在 initscr() 之后即可用:LINES(总行数)、COLS(总列数), 居中显示、边界判断都靠它俩。

一个环境变量的坑:如果环境里设了 LINES 或 COLUMNS,ncurses 会拿它们的值 顶替 terminfo 探测到的真实尺寸,此后窗口缩放处理变差。要正确处理缩放, 在 initscr() 之前 unsetenv("LINES"); unsetenv("COLUMNS")——改的只是本进程 的环境拷贝,不影响 shell8。

如果错,会错在: ① 把 (x, y) 当 (y, x) 传——move(10, 5) 落在第 10 行 第 5 列,画面整个歪掉;② 以为坐标从 (1,1) 起——sprite 移植时所有边界判断 差一格,就是忘了 curses 从 (0,0) 数起14。

4.3 三种对象与双缓冲

库里先认三种对象,分清它们,后面几百个函数名归位15:

对象 是什么 关键点
TERMINAL 逻辑 I/O 设备:一切用户交互发生在它里面 不透明类型;存终端能力、终端模式、I/O 状态
SCREEN 物理输出设备:一个 terminal 恰好一个 不透明类型;封装 fd、缓冲、尺寸、属性
WINDOW 二维字符数组:整块或部分屏幕 真结构体:原点位置、大小、光标、背景属性、滚动状态、输入模式

WINDOW 里还有一个标志位:内容一旦改过就被置位——库管这叫 touch (像 touch 命令改文件修改时间)。刷新时只重画「被 touch」的部分。 initscr() 造出默认窗口 stdscr(标准屏幕,大小=整个终端);子窗 (subwindow)是建在别的窗口里面的窗口。

另有一种特殊窗口叫 Pad:不受屏幕尺寸限制、内容不一定显示—— 把它想成比终端大的一整张画布,终端是画布上可以挪动的取景框16。 原书不展开 pad(函数名带 p 前缀的都属此类)。

三个名字本身就是陷阱(原书点名):newterm() 创建的是 screen 不是 terminal;curscr 和 stdscr 是 window 不是 screen16。

双缓冲是 curses 的核心机制:每个 screen 有两块画布——物理屏幕 (可见,内部名 curscr)与虚拟屏幕(内存里,内部名 newscr)。 所有 move/addstr/printw 只改窗口数据结构;refresh()/wrefresh(win) 才分两步:先把窗口内容映射进 newscr,再算出 newscr 与 curscr 的差异, 用最优转义序列一次刷出9。重叠窗口各自刷新时,库只重画有变化的交集部分; 想强制整块重画,用 touchwin(win) 把它整窗标脏。

4.4 命名约定:一个函数最多八个变体

curses 几百个函数靠命名约定猜义17:

前缀 含义
w 第一个参数是 WINDOW(如 waddstr(win, s))
无前缀 操作 stdscr(base 函数)
mv 先 move 到 (y, x) 再干正事(坐标摆在其余参数之前)
mvw 窗口 + 坐标都要(顺序:窗口 → 坐标 → 原参数)
n 最后一个参数 N,最多处理 N 个字符

以 addstr 为例,man 页列出全部八个:addstr / addnstr / waddstr / waddnstr / mvaddstr / mvaddnstr / mvwaddstr / mvwaddnstr。 原书只讲 base 函数,变体照推。

配套的配置五连(典型交互程序开头,背下来):initscr(); cbreak(); noecho(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);——要轮询键盘 再加 nodelay(stdscr, TRUE)18。其中:

  • cbreak() = 关 icanon + MIN=1/TIME=0,但保留 isig/ixon(对照第 18 章);
  • keypad(win, TRUE) 开启功能键译码,getch() 收到方向键时返回 KEY_LEFT/KEY_UP/... 符号常量,而不是三字节转义序列18;
  • nodelay(win, TRUE) 让 getch() 无输入时返回 ERR 而不是睡觉。

4.5 输出函数族:覆盖、插入、删除三动作

向窗口写内容只有三类动作,所有输出函数都归进这三类19:

动作 函数 边界
覆盖(替换光标处内容) addch(ch) 单字符,到右边界自动卷绕;addstr(s) 字符串;addchstr(chstr) chtype 数组;printw(fmt, ...) = printf addchstr 不动光标、不卷绕、越右边距直接截断,也不把控制字符展开成 ^X——与 addstr 恰好相反
插入(把光标处的字符往右挤) insch(ch) 单字符;insstr(s) 字符串 行尾最右字符可能被挤出窗口丢失;光标本身不动
删除 delch() 删光标处字符;deleteln() 删光标所在整行 也属于「输出函数」——它们改的是屏幕内容

为什么 chtype 而不是 char:一个屏幕格子里存的不只是字符,还有 颜色等属性,所以单格是 chtype(整数类型,字符+属性打包); attr_t 则是专门存属性的整数类型。curses.h 另定义全局常量 ERR(失败)与 OK(成功),返回值判错靠它俩20。

输入侧另有两个函数:getstr(buf) = 循环 getch 直到回车(cbreak 下 也要敲回车,否则程序不知道哪算完);scanw(fmt, ...) = getstr + sscanf21。

4.6 窗口函数族与平铺纪律

把屏幕切成几块、各画各的,靠这组函数22:

函数 干什么 边界
newwin(nlines, ncols, y, x) 新建窗口,左上角在 (y, x) 参数为 0 有特别含义,见 man 页
delwin(win) 删除窗口、释放内存 不擦它在屏幕上的像——先 werase/wclear 再删
mvwin(win, y, x) 把窗口挪到新左上角 挪出屏幕边界 = 报错且不挪;挪完要 refresh;旧位置不擦,程序自己处理
putwin(win, fp) / getwin(fp) 窗口内容存文件 / 从文件恢复 tiled_windows 里按 s 就是把内容窗存盘
getyx(win, y, x) / getmaxyx(win, y, x) 取光标位置 / 取窗口尺寸 这是宏,参数不是指针,传入普通 int 变量即可
inch() 取光标处字符(含属性) 返回 chtype

然后是两条承重纪律,都来自 tiled_windows 小项目(上 3 行信息窗 + 下方内容窗,中间 mvwhline 画一条横线)23:

  1. 平铺,不重叠。 ncurses 不处理重叠窗口——那是 panel 库的活。 所以一个程序要么只用 stdscr,要么把屏幕完全平铺、之后完全不碰 stdscr。平铺窗坐在 stdscr 上面把它盖住,但 stdscr 仍然活着: 每个 curses 调用必须显式指明操作哪扇窗。
  2. 一个 screen 只有一个光标。 多窗程序要自己管光标停在哪扇窗—— 先给 A 窗定好光标再 refresh B 窗,光标会被 B 吸走。刷窗顺序 因此有讲究。

如果错,会错在: 把「重叠窗口」当平铺窗用(比如想让 stdscr 当 内容区、上面浮一个信息窗)——man 页明写这会产生不可预知的效果; 以及删窗忘了先擦屏,屏幕上留一块删不掉的残影。

sprite 的 curses 版顺带说明了「手工派换库派」的账单:moveto 系列、 终端状态存取、私有行列变量全删;LINES/COLS 顶上;resize 处理走 土法——endwin(); refresh(); initscr(); 再重画一遍(ncurses 作者 在官网教程里给的建议);信号清理函数瘦成 endwin(); raise(SIGTERM); 两行1424。

5. 项目走查:spl_top——把前面十九章焊在一起

原书把它放在全书最后,理由写在收尾:这是全书最大最复杂的程序, 而它之所以值得写,是因为它把 ncurses、/proc、第 17 章的 pselect、 第 7 章的 scandir、第 10 章的 spl_ps 全焊在了一处25。

先给现象:top 把屏幕切成上下两区——上面摘要(五行系统概况),下面 进程列表,每 3 秒一起刷新(可配),运行中随时接受键盘命令改 排序、过滤、滚动。ps -ef 是快照,top 是连续剧。仿写版定名 spl_top.c,目标不是复刻(真 top 的交互命令有几十条),而是用 最小功能集走通「动态多窗交互程序」的全部关键环节26。

5.1 四个承重设计决策

决策一:输入模式与光标。 cbreak + noecho + curs_set(0) 隐藏 光标(top 运行时你看不到光标);keypad(cnt_win, TRUE) 开方向键。 例外一处:U 过滤命令要用户敲用户名,那一刻临时 echo() 开回显, 敲完关掉27。

决策二:三窗平铺,滚动归程序。 summary 窗(6 行,第 6 行专留 给提示)、heading 窗(1 行,standout 反显列头)、content 窗(剩下 全部)——SUMMARY_HEIGHT 6 一个宏钉死。为什么不一窗到底:进程行 一多就会把整个屏幕往上顶,摘要行被卷走;为什么必须平铺:§4.6 的 纪律。scrollok(win, FALSE) 把 ncurses 的自动滚动也关掉—— 滚不滚、滚到哪,程序自己说了算(上下箭头改的是 startline):

case KEY_DOWN: if ( startline < numprocs - contentlines ) startline++; break;
case KEY_UP:   if ( startline > 0 ) startline--; break;

决策三:主循环用 pselect,不用定时器信号也不用轮询。 pselect(1, &fds, NULL, NULL, &delay, &empty_mask) 一次调用,三个 出口:输入就绪(>0,去读键盘)、定时到期(=0,回循环顶部重刷)、 信号送达(EINTR,handler 早把 sig_atomic_t 标志置好,清理退出)。 全程屏蔽终止信号,只在 pselect 里交付——这正是第 17 章给 select 族 的处方:更新屏幕和内部数据结构时不许被信号打断28。

决策四:排序一个函数通吃,靠 qsort_r 的第三参数。 qsort_r(base, n, size, compar, arg) 比 qsort 多一个可传给比较函数的 arg——传进去一个「升序/降序」标志,每个字段写一个比较函数, sortprocs(arr, n, fieldtab[sortfield].sortfunc, sortdir) 一行调完, 切字段切方向都不用换函数29。

5.2 fieldtab:一张表驱动十二列

12 个显示字段(PID、USER、PR、NI、VIRT、RES、SHR、S、%CPU、%MEM、 TIME+、COMMAND)如果按「打印一行 = 12 段 sprintf」写,加删一列要 改三处。原书的做法是把每个字段的信息收进一个结构,再排成数组30:

typedef struct {
    char     *name;        /* 字段名,留作将来删列时用 */
    fieldmask mask;        /* 列开关位(1<<下标) */
    char     *fmt;         /* 数据值的 printf 格式 */
    char     *colheading;  /* 列头文字 */
    char     *headingfmt;  /* 列头的 printf 格式 */
    int       width;       /* 列宽 */
    compar_t sortfunc;     /* 该字段的比较函数,可为 NULL */
} field;
field fieldtab[] = { {"pid", F_PID, "%7d", "PID", "%7s", 7, pid_cmp}, /* …共 12 行… */ };

enum 常量(PID..COMMAND)当数组下标,F_* 位掩码(F_ALL = 07777) 当「这一列印不印」的总开关。效果:列头一行是「遍历数组、查掩码、 拼 headingfmt」的循环;打印一行是同样的循环套一个 switch—— C 没有「遍历结构体成员」的反射,switch 是绕不开的,但格式、宽度、 排序函数全部收在表里,将来做「用户删列」只动掩码和界面30。

5.3 数据从哪来:五行摘要的五个出处

摘要五行,每行一个数据源,全是「打开文件、按标签抠值」的路数31:

行 内容 数据源 坑
1 时间 + up 时长 + 用户数 + load average /proc/uptime 第一个数;utmpx 文件;/proc/loadavg 用户数不能数 procstat 里的 uid(root、systemd 会混入)——要读 utmpx 的 USER_PROCESS 记录,哈希表去重
2 Tasks: 总数/运行/睡眠/停止/僵死 /proc 的数字目录数 + 每个进程的 state 分类计数 睡眠口径与真 top 略有出入,原书如实承认
3 %Cpu(s): us/sy/ni/id/wa/hi/si/st 八态 /proc/stat 第一行(多 CPU 的总和行) 百分比是与上次的差分——static 数组 prev_cpustate[8] 保存前值,两值相减再归一;us/sy 打印顺序与原值颠倒,是照抄 top 的怪癖
4–5 物理内存 / 交换空间 /proc/meminfo getline 逐行扫,sscanf 按标签(MemTotal/MemFree/…/SwapFree)取值;used = total − available/free

每进程数据是第 10 章 spl_ps 的加长版:stat 文件照旧 parse_buf, uid 照旧 stat() /proc/[pid] 目录;新增内存三格来自 status 文件—— VmSize = VIRT、VmRSS = RES、SHR = RssFile + RssShmem,同样逐行 扫标签32。

5.4 loadprocs:三个麻烦与差分法

loadprocs() 每轮刷新干四件事:scandir 扫 /proc 拿全部数字目录 (一次过,免两遍)→ 每个目录 stat 取 uid、读 stat、读 status → 先存上轮的 (pid, utime+stime) 再释放旧数组 → 算 CPU% 与 MEM%33。

三个麻烦各有一句解:

  1. 不知道进程有多少个 → scandir 的过滤器只认纯数字目录名, 返回即数组,直接 calloc 同长度的 procstat 数组;
  2. 进程读到一半死了 → stat/open 失败就跳过这个目录, continue,不做任何假设;
  3. CPU% 必须差分 → 上轮的 cpu 时间要按 pid 对出来求差; 首轮没有前值,就把累计时间直接当差分。公式: cpu_pct = 100.0 × diff / (delaysecs × ticks)——ticks 是 sysconf(_SC_CLK_TCK)。MEM% 的分母不是 /proc/meminfo 的 MemTotal,而是程序自己累加的 Σrss33。

显示端 print_procs(content_win, ..., startline, filter_uid, fmask) 一次收齐滚动、过滤、列开关:filter_uid = -1 不滤,否则只印 uid 匹配 的行;每行 mvwaddnstr(win, count-start, 0, psline, COLS) 限长 COLS 字符,防溢出34。主循环的完整拼装:

  show_summary(summary_win, …)   → 五行摘要逐窗画
  wclear(content_win); loadprocs(&procarray, &numprocs)
  sortprocs(…fieldtab[sortfield].sortfunc, sortdir)
  print_procs(content_win, …, startline, filter_uid, printfields)
  wrefresh(content_win)
  iowait(&delay) > 0 → switch ( wgetch(content_win) )  ← §5.1 决策三

图说:一轮刷新 = 取数(loadprocs)→ 排序(sortprocs)→ 填窗 (print_procs + show_summary)→ 等事(iowait),键盘命令只改 sortfield/sortdir/startline/filter_uid 四个旋钮,下一轮自动生效。

如果错,会错在: ① prev_cpustate 不声明 static——每轮前值清零, CPU 行全是无意义的 100%;② 把滚动交给 ncurses(scrollok(TRUE))—— 内容窗自己会卷,但滚动位置没法用上下箭头精确控制,这正是关掉它的 理由;③ 差分对 pid 时忽略「上轮有这轮没了」的进程,数组越界或配对 错乱。

5.5 收尾

原书收尾两句值得带走:真 top 比这复杂得多;再吓人的项目,拆成 一步步、配着文档查,也能拿下——这章教的不是 ncurses,是这个25。

6. 判断/边界/可带走

作者的判断: 动画放主循环轮询、不放信号 handler——为 规避 async-signal-safe 与竞态,理由直白6;选轮询而非 O_NONBLOCK 的理由(影响面)也摆明了4。ncurses 侧的取舍 同样明白:平铺不重叠、滚动归程序、定时用 pselect 三出口、 排序一个函数靠 qsort_r 的方向参数2829。如果错,会错在: 轮询节奏被主循环劫持——getch() 默认阻塞等键,忘了配 nodelay/timeout 的话,一次按键不及时,定时刷新和动画 帧就整体漂移;窗口变化(SIGWINCH)也只能等下一轮询才被 看见,resize 瞬间画面错乱一拍;纯忙等轮询还白烧 CPU—— 这些代价都换「信号里零逻辑」的安全性,重交互程序若接受 事件驱动框架,平衡点会挪。边界: ncurses 的 panel/menu/form 扩展库、颜色机制、鼠标支持、pad、宽字符库 ncursesw 未展开;窗口缩放的正规处理(resizeterm/resize_term) 是扩展且「用对很 tricky」,原书 sprite 版只用 xterm 场景下 的 endwin+refresh 土法24;spl_top 干脆不支持缩放,收到信号 清理退出26。备用屏幕的 1049 序列是 xterm 扩展不是 POSIX。 可带走的: ① MIN/TIME 四组合表;② 字节间定时 = 转义序列的判读器; ③ 退出必 restore_tty / endwin;④ 备用屏幕进出现象;⑤ 双缓冲 refresh 模型;⑥ (y,x) 先行后列 + LINES/COLS;⑦ 命名约定 w/mv/mvw/n 与输出三动作(覆盖/插入/删除); ⑧ 平铺纪律与单光标;⑨ pselect 三出口主循环;⑩ 表驱动 字段 + qsort_r 方向标志 + CPU% 差分法。

7. 原文地图

主题 原书章 原文位置
非规范实验 Chapter 19: Interactive Programming and the ncurses Library text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:63(搜「stty -icanon」)
raw/cbreak/cooked 同上 :91(搜「cbreak, and cooked」)
MIN/TIME 四组合 同上 :130(搜「Polling Read」) · :188(搜「Interbyte Timer」) · :201(搜「escape sequence」)
O_NONBLOCK vs VMIN 同上 :376(搜「alters only」)
init_terminal/restore 同上 :437(搜「c_lflag &= ~ICANON」) · :452(搜「restore」)
sprite 主循环取舍 同上 :355(搜「signal handlers」)
备用屏幕 同上 :490(搜「1049h」)
curses 历史/标准/命名 同上 :791(搜「History, Standards, and Names」) · :816(搜「Issue 7」)
编译链接/-lncurses 同上 :886(搜「Compiling, Building」) · :891(搜「lncurses」) · :894(搜「LINES or COLUMNS」)
ncursesw 宽字符库 同上 :1071(搜「configured in one of two ways」) · :1074(搜「wide library」)
坐标系 (y,x)/LINES·COLS 同上 :912(搜「matrix coordinates」) · :1022(搜「number of lines in the terminal screen」)
术语 TERMINAL/Screen/Pad 同上 :844(搜「opaque data type」) · :874(搜「Pad」) · :882(搜「creates a new screen」)
WINDOW/refresh 双缓冲 同上 :860(搜「WINDOW data type」) · :976(搜「newscr」) · :990(搜「wrefresh」)
命名约定八变体 同上 :1186(搜「Naming Convention」) · :1204(搜「eight variants」)
配置五连/keypad/nodelay 同上 :1239(搜「cbreak(void)」) · :1255(搜「keypad(WINDOW」) · :1263(搜「nodelay(WINDOW」)
输出函数族 同上 :1325(搜「addch(const chtype」) · :1333(搜「doesn’t advance the cursor」) · :1350(搜「deleteln」)
窗口函数族 同上 :1358(搜「newwin」) · :1366(搜「doesn’t erase the window」) · :1376(搜「putwin」) · :1394(搜「getyx」)
平铺纪律/单光标 同上 :1472(搜「does not handle overlapping windows」) · :1501(搜「single cursor」)
sprite_curses/resize 土法 同上 :1587(搜「easier approach」) · :1624(搜「coordinate system at (0,0)」)
top 需求与分屏 同上 :1721(搜「spl_top」) · :1881(搜「SUMMARY_HEIGHT」) · :1930(搜「scrollok(WINDOW」)
pselect 主循环 同上 :1965(搜「block all signals」) · :1978(搜「pselect(1」)
fieldtab/qsort_r 同上 :2061(搜「Good data structures」) · :2155(搜「fieldtab」) · :2285(搜「sortprocs」)
摘要五行数据源 同上 :2341(搜「/proc/uptime」) · :2352(搜「utmpx」) · :2372(搜「/proc/loadavg」) · :2448(搜「/proc/stat」) · :2495(搜「/proc/meminfo」)
每进程数据/loadprocs 同上 :2555(搜「VmSize」) · :2588(搜「scandir」) · :2677(搜「cpu_pct = 100.0」)
main 拼装/键盘命令 同上 :2747(搜「show_summary(summary_win」) · :2768(搜「KEY_DOWN」)

  1. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 107–128 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:113,搜「c_cc[VMIN]」)。 ↩

  2. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 188–236 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:201,搜「escape sequence」)。 ↩

  3. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 237–316 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:309,搜「Polling」)。 ↩

  4. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 376–380 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:376,搜「alters only」)。 ↩↩

  5. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 426–467 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:437,搜「c_lflag &= ~ICANON」) · :452(搜「restore」)。 ↩

  6. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 334–367 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:355,搜「signal handlers」)。 ↩↩

  7. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 484–491 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:490,搜「1049h」)。 ↩

  8. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 886–1064 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:894,搜「LINES or COLUMNS are」) · :1045(搜「WINDOW *initscr」) · :1059(搜「endwin」) · :1064(搜「state for the shell」)。 ↩

  9. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 860–998 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:860,搜「WINDOW data type」) · :990(搜「wrefresh」)。 ↩

  10. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 779–790 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:786,搜「ncurses(3ncurses)」)。 ↩

  11. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 791–837 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:791,搜「History, Standards, and Names」) · :800(搜「cursor optimization」) · :816(搜「Issue 7」) · :824(搜「symbolic link」)。 ↩

  12. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1067–1079 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1071,搜「configured in one of two ways」) · :1074(搜「wide library」) · :1078(搜「_w substring」)。 ↩

  13. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 911–933 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:912,搜「matrix coordinates」)。 ↩

  14. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1523–1679 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1553,搜「unsetenv("LINES")」) · :1622(搜「raise(SIGTERM)」) · :1624(搜「coordinate system at (0,0)」)。 ↩↩

  15. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 839–873 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:844,搜「opaque data type」) · :868(搜「touching the window」)。 ↩

  16. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 874–884 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:877,搜「canvas larger than」) · :882(搜「creates a new screen」)。 ↩↩

  17. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1186–1226 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1191,搜「prefixed with w」) · :1204(搜「eight variants」)。 ↩

  18. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1228–1278 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1239,搜「cbreak(void)」) · :1250(搜「intrflush」) · :1255(搜「keypad(WINDOW」) · :1263(搜「nodelay(WINDOW」) · :1277(搜「poll the keyboard」)。 ↩↩

  19. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1321–1351 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1325,搜「addch(const chtype」) · :1329(搜「addchstr」) · :1337(搜「insch(chtype」) · :1340(搜「rightmost character」) · :1341(搜「does not change the cursor position」) · :1350(搜「deleteln」)。 ↩

  20. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 999–1027 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1002,搜「attr_t」) · :1005(搜「An integer type that stores a character」) · :1011(搜「ERR」) · :1024(搜「TABSIZE」)。 ↩

  21. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1280–1319 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1283,搜「getstr(char *str)」) · :1287(搜「scanw」) · :1290(搜「requires the user to enter a newline」)。 ↩

  22. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1352–1402 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1358,搜「newwin(int nlines」) · :1364(搜「delwin(WINDOW *win)」) · :1366(搜「doesn’t erase the window」) · :1370(搜「mvwin(winptr」) · :1372(搜「off the screen」) · :1376(搜「putwin(WINDOW」) · :1380(搜「getwin(FILE」) · :1394(搜「getyx(WINDOW」) · :1397(搜「this is a macro」) · :1401(搜「chtype inch」)。 ↩

  23. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1408–1521 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1450,搜「ACS_HLINE」) · :1472(搜「does not handle overlapping windows」) · :1488(搜「mvwhline(info_win」) · :1496(搜「order of events here is significant」) · :1501(搜「single cursor」)。 ↩

  24. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1582–1623 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1587,搜「easier approach」) · :1592(搜「ncurses-intro.html」) · :1622(搜「raise(SIGTERM)」)。 ↩↩

  25. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 2801–2808 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:2802,搜「most complex」) · :2806(搜「No matter how hard」)。 ↩↩

  26. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1681–1761 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1721,搜「spl_top.c」) · :1745(搜「Sort by the %CPU」) · :1758(搜「handle all terminating signals」)。 ↩↩

  27. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1763–1831 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1806,搜「keypad() function」) · :1811(搜「turn off echoing」) · :1815(搜「don’t want the cursor」) · :1819(搜「curs_set」)。 ↩

  28. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 1763–1800 段、第 1950–2049 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:1965,搜「block all signals」) · :1978(搜「pselect(1」)。 ↩↩

  29. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 2102–2134 段、第 2248–2297 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:2109,搜「qsort_r(void」) · :2132(搜「qsort_r() function」) · :2133(搜「pass an extra variable」) · :2285(搜「sortprocs」)。 ↩↩

  30. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 2060–2246 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:2061,搜「Good data structures」) · :2064(搜「stores all of the data」) · :2138(搜「comparison function to pass」) · :2155(搜「field fieldtab」) · :2175(搜「enum field_t」) · :2182(搜「F_PID」) · :2219(搜「print_one_proc」)。 ↩↩

  31. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 2320–2540 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:2326,搜「Summary information consists」) · :2341(搜「/proc/uptime」) · :2352(搜「utmpx」) · :2357(搜「get_numusers」) · :2372(搜「/proc/loadavg」) · :2434(搜「eight possible states」) · :2448(搜「/proc/stat」) · :2468(搜「prev_cpustate」) · :2495(搜「/proc/meminfo」) · :2509(搜「get_mem_summary」)。 ↩

  32. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 2542–2566 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:2549,搜「top man page describes」) · :2555(搜「VmSize」) · :2558(搜「RssShmem」) · :2564(搜「get_procmem_usage」)。 ↩

  33. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 2568–2685 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:2568,搜「loadprocs」) · :2574(搜「store all process data」) · :2578(搜「between the time」) · :2588(搜「scandir」) · :2642(搜「stat(pathname」) · :2666(搜「prevnumprocs」) · :2677(搜「cpu_pct = 100.0」) · :2681(搜「mem_pct = 100.0」)。 ↩↩

  34. 出处:「Chapter 19: Interactive Programming and the ncurses Library」第 2687–2799 段(text/25-ch19-chapter-19-interactive-programming-and-the-ncurs.txt:2693,搜「print_procs」) · :2698(搜「MIN(win_lines」) · :2705(搜「mvwaddnstr」) · :2747(搜「show_summary(summary_win」) · :2768(搜「KEY_DOWN」)。 ↩