折腾完https://maskray.me/blog/2016-03-13-terminal-emulator-fullwidth-color-emoji后发现canonical mode下emoji字符退格只后退了一列,后发现所有宽字符都有问题,因此做了一番调研。
Canonical/noncanonical mode
早期Unix有cooked/cbreak/raw mode三种模式,raw
mode和cbreak模式区别在于signal和输入输出处理,输入都是以字符为单位,即read(STDIN_FILENO, buf, 1)
在键入一个字符后即返回。Cooked
mode与它们差别较大,最重要的区别是终端输入以行为单位进行,并自带一个基础行编辑器,可以使用退格和WERASE(默认为^W
)删除光标前的单词。
termios引入后对输入输出行为(input/output/local
modes)有了更精细的控制,通过一些选项可以定制出原始的cooked/cbreak/raw
mode三种模式。Local
modes中的ICANON
最为重要,区分canonical/noncanonical
mode,canonical mode与早期cooked mode类似,带行编辑器,一些字符如CR EOF
EOL ERASE KILL NL
WERASE等有特殊含义,下面介绍几个比较重要的。更多介绍参见The Linux
Programming Interface 62.4 Terminal Special Characters。
EOF
通常为^D
,可以用ctrl d
输入,作用是使得read()
立即返回该行所有字符,若位于行首则返回0。很多地方把它视为到达文件结束位置的信号,并不再继续读入。但实际上终端输入并没有被关闭,仍可以继续读取字符。
1 |
|
编译运行上面C程序,试试输入若干字符后按^D
的输出。
ERASE
stty -a
中erase =
显示当前ERASE字符设置,通常为^?
或^H
。终端模拟器vte是^?
,退格键发送^?
;xterm是^H
,退格键发送^H
。倘若终端ERASE字符与之不同则可能导致退格不删除字符反而输入了一个^?
或^H
。
INTR
通常为^C
,若local
modes中ISIG
开启,则前台进程组会收到SIGINT。
QUIT
通常为^\
,若local
modes中ISIG
开启,则前台进程组会收到SIGQUIT。
SUSP
通常为^Z
,若local
modes中ISIG
开启,则前台进程组会收到SIGTSTP,默认会停止成为后台任务,shell回到前台。
对于readline等使用noncanonical
mode的应用程序,它们会检测TERM
环境变量获取terminfo信息,从中找出不同功能对应的输出字符序列。
退格
\b
字符的解析方式是光标左移一格。
在canonical mode下当前行仅有一个两个宽字符时,按下退格,光标左移一格并擦除了该字符,但继续按退格也无法回到行首,产生显示问题。
原因是内核tty驱动似乎没有考虑字符宽度信息,只给pseudoterminal
master发送一个\b
,终端模拟器收到\b
后将光标左移了一格。再次按退格时,内核tty驱动判断该行已空,因此不再发送\b
,光标也就无法退回到行首。
介绍一个测试方式:在pseudoterminal的slave端运行canonical
mode的cat
程序,输入退格,可以在master端看到内核发来"\b \b"
三个字符,即后退一格,空格擦除,再后退一格。可以用下面的方法查看:
termite -e cat
创建一个termite终端运行cat
,然后找出termite在pseudoterminal
pair的master端的fd: 1
2% lsof -p $(pgrep -n termite) -Fn | sed -n '/^n\/dev\/ptmx/{g;p};s/^f//;h'
13
之后用strace -e read -p $(pgrep -n termite) |& grep 13
,在termite窗口键入退格,观察master端fd读到的数据。
这很可能是内核的问题,简易的修复方式是头痛医脚,修改使用pseudoterminal的程序(如终端模拟器、tmux)的代码。对于canonical
mode并开启IUTF8
时,从pseudoterminal
master处读到\b
时,判断左侧字符是否为宽字符,是则左移2格(目前尚无更宽的字符)。我做了两个patch:
- https://aur.archlinux.org/packages/vte3-ng-fullwidth-emoji
- https://aur.archlinux.org/packages/tmux-fullwidth-backspace
于是这是我第一次和第二次创建PKGBUILD……
参考
- https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/
info '(libc) Terminal Modes'