折腾完https://maskray.me/blog/2016-03-13-terminal-emulator-fullwidth-color-emoji后发现canonical mode下emoji字符退格只后退了一列,后发现所有宽字符都有问题,因此做了一番调研。
早期Unix有cooked/cbreak/raw mode三种模式,raw mode和cbreak模式区别在于signal和输入输出处理,cooked mode与它们差别较大,自带一个非常弱的行编辑器,可以使用退格和WERASE(默认为^W
)输出光标前的单词。
termios引入后对输入输出行为有了更精细的控制,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。
通常为^D
,可以用ctrl d
输入,作用是使得read()
立即返回该行所有字符。如果位于行首则read()
返回0,很多地方把它视为到达文件结束位置的信号。
1 | #include <stdio.h> |
编译运行上面C程序,试试输入若干字符后按^D
的输出。
stty -a
可以查看当前ERASE字符设置,通常为^?
或^H
。vte是^?
,退格键发送^?
;xterm是^H
,退格键发送^H
。
通常为^C
,若local modes中ISIG
开启,则前台进程组会收到SIGINT。
通常为^\
,若local modes中ISIG
开启,则前台进程组会收到SIGQUIT。
通常为^Z
,若local modes中ISIG
开启,则前台进程组会收到SIGTSTP,默认会停止成为后台任务,shell回到前台。
如果input modes开启IUTF8
(stty
命令可以查看是否开启,vte默认),内核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的vte和tmux都没有正确处理宽字符的"\b"
,它们都只把光标位置左移一格。正常的做法是:对于canonical mode并开启IUTF8
时,退格时判断左侧字符是否为宽字符,是则左移2格(目前尚无更宽的字符)。我做了两个patch:
于是这是我第一次和第二次创建PKGBUILD……
info '(libc) Terminal Modes'