Linux 命令行
熟悉 Linux 的同学可能知道,在 Linux 环境下,命令行是用户界面中的“一等公民”。
我们可以通过各种类型的终端来操作命令行:桌面环境提供方便的“终端模拟器”让我们在图形界面中使用终端;某些发行版也提供 ctrl+shift+数字切换的 tty 终端;安装了 ssh server 的机器可以通过远程连接上一个终端;某些嵌入式 Linux 系统提供串口终端;而screen/tmux等工具提供了可以随时切换和保留状态的终端。
Linux 系统并不关心我们使用的终端类型。在它看来,不同的终端就是一条双向字节流通道。用户通过敲击键盘,通过终端向通道发送信息;而命令执行的结果也通过通道返回终端,并由终端展示给用户。

深究起来,这一块可以展开很深:例如字节流被系统抽象为了字符设备 /dev/tty* 和 /dev/pts;标准输入输出流 stdin stdout 和 stderr 最终也是定向到了此处;对于输入输出系统内部还存在 Line Discipline 等。如此种种暂且按下不表。
终端转义序列
前面说到:终端负责读取用户的输入,并向用户展示命令运行的结果。除了将可显示的字符在对应的位置(行、列)上显示外,终端还支持很多高级功能,例如:输出不同颜色的字体、更改光标位置以在任意位置输出文本等等。正是这些高级功能使得 vim、gdb 这样具有命令行用户界面的程序的编写成为可能。
控制终端行为的字符序列名为转义序列,是由字符 \033(ASCII ESC)开头的序列。转义序列控制终端行为有国际标准 ANSI X3.64 或 ISO/IEC 6429。符合这个标准的终端,可以称之为 ANSI 终端。现代终端支持的转义序列扩展更多,例如 xterm 标准,支持了彩色字体。
可以在自己的终端尝试输出转义序列。例如,清屏序列的十六进制表示是:
0x1b 0x5b 0x32 0x4a在命令行中输入:
printf '\x1b\x5b\x32\x4a'
printf '\033[2J' # alternative就可以发现命令行清屏了。
终端状态机
终端不仅要处理系统发来的转义序列,还要处理终端自身的状态。终端的状态包括:
终端各个行列存在什么字符,各个字符的属性(颜色、粗细、下划线等)是什么
终端的光标在哪个位置
终端之前收到的不完整转义序列是什么
...
在收到常规字符或转义序列后,终端需要根据收到的信息和当前状态,变为新的状态。例如:
| 收到的 | 状态变化 |
|---|---|
| 常规字符 | 光标所在位置字符变为收到的字符,光标位置前进一个字符 |
| 转义序列(清屏) | 清空所有位置的字符 |
| 转义序列(加粗) | 设置内部状态,等收到常规字符后给对应位置设置加粗属性 |
| ... | ... |
终端需要维护的所有状态,统称终端状态机。
Libtmt 有啥用?
libtmt 和它的大哥 libtsm 一样,都是处理终端转义序列,并维护终端状态机的库。需要注意的是,libtmt/libtsm 等库并不负责如何具体向用户显示终端内容,而只是根据收到的字节流(字符、转义序列),维护终端状态机,仅此而已。用户界面可以通过读取 libtmt 的状态来实时更新最新的终端内容,让用户看见。

上面已经提到,终端的类型有很多种。笔者想实现的终端是物理终端。通过两根串口线,笔者的物理终端和 Linux 主机进行通信。而 libtmt 通过读取串口更新终端内部状态,而一个RGB888 屏幕实时地读取 libtmt 的状态,将终端展示给用户。
