8

终端模拟器下宽字符退格

 4 years ago
source link: http://maskray.me/blog/2016-03-22-terminal-emulator-fullwidth-backspace
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

折腾完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。

通常为^D,可以用ctrl d输入,作用是使得read()立即返回该行所有字符,若位于行首则返回0。很多地方把它视为到达文件结束位置的信号,并不再继续读入。但实际上终端输入并没有被关闭,仍可以继续读取字符。

#include <stdio.h>
#include <unistd.h>
int main()
char buf[9];
for(;;) {
printf("%d\n", read(0, buf, 9));

编译运行上面C程序,试试输入若干字符后按^D的输出。

ERASE

stty -aerase =显示当前ERASE字符设置,通常为^?^H。终端模拟器vte是^?,退格键发送^?;xterm是^H,退格键发送^H。倘若终端ERASE字符与之不同则可能导致退格不删除字符反而输入了一个^?^H

通常为^C,若local modes中ISIG开启,则前台进程组会收到SIGINT。

通常为^\,若local modes中ISIG开启,则前台进程组会收到SIGQUIT。

通常为^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:

% lsof -p $(pgrep -n termite) -Fn | sed -n '/^n\/dev\/ptmx/{g;p};s/^f//;h'

之后用strace -e read -p $(pgrep -n termite) |& grep 13,在termite窗口键入退格,观察master端fd读到的数据。

这很可能是内核的问题,简易的修复方式是头痛医脚,修改使用pseudoterminal的程序(如终端模拟器、tmux)的代码。对于canonical mode并开启IUTF8时,从pseudoterminal master处读到\b时,判断左侧字符是否为宽字符,是则左移2格(目前尚无更宽的字符)。我做了两个patch:

于是这是我第一次和第二次创建PKGBUILD……


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK