最近,在翻阅 XTerm 的更新日志的时候,发现在 Patch #359 中引入了一个新功能:
- add a new mouse mode 1016, which uses the same format as mode 1006, but sends the mouse's position in pixels (suggested by Igor van den Hoven).
虽然在终端中获取鼠标的像素粒度的位置坐标这种操作并不稀奇,最早甚至可以追溯到上世纪 80 年代。但那是基于 DEC locator 的。而这个新的 1016 模式基于 X11 mouse 协议,一个在现代的终端模拟器中更广泛支持,也更方便使用的特性。
于是,我打算尝尝鲜,写了个小程序试验了一下,运行效果如下面动图(右侧)所示:
在调试的时候,我偶然发现了 1016 模式中存在一个 bug 。
为了更方便排查 bug,我写了一个很小的用于复现问题的程序。代码如下所示:
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
int main()
{
struct termios old_termios, new_termios;
int ch, fd_in = STDIN_FILENO;
tcgetattr(fd_in, &old_termios);
new_termios = old_termios;
new_termios.c_lflag &= ~(ICANON | ECHO);
tcsetattr(fd_in, TCSANOW, &new_termios);
setvbuf(stdout, NULL, _IONBF, 0);
printf("\033[?1002h");
printf("\033[?1016h");
while (EOF != (ch = getchar())) {
if (ch == 0x04) {
break;
}
}
printf("\033[?1016l");
printf("\033[?1002l");
tcsetattr(fd_in, TCSANOW, &old_termios);
return 0;
}
这个程序做的事情非常简单,将终端置于 non-canonical 和 no-echo 模式,然后通知 XTerm 开启 1002 和 1016 模式。按 Ctrl+D 会重置终端状态,并退出程序。
1002 模式的特点在于,当鼠标按下后,直到按键松开前,每当鼠标指针改变位置,XTerm 都会向宿主输出其坐标。而更常用的 1000 模式只会在鼠标按下和松开的两个时刻做上述输出。
那如果我们将鼠标指针移动到窗口外再松开,会发生什么事情呢?
首先,我们先看一下 1016 模式下的输出格式:
CSI < Pb ; Px ; Py M
CSI < Pb ; Px ; Py m
其中,Px
和 Py
为鼠标此刻的像素坐标,从 1 开始,相对于窗口左上角。显然,这里 Px 和 Py 都不能为负值。因为根据 ECMA-48 规定,CSI 序列的“参数字节”的范围是 0x30~0x3f,而“-”字符的 ASCII 值为 0x2d 。
那 XTerm 是如何实现的呢?我们可以用 script
命令行工具录制上述操作,并使用 GNU Teseq 进行分析:
: Esc [ < 32 ; 9 ; 492 M
: Esc [ < 32 ; 5 ; 492 M
: Esc [ < 32 ; 1 ; 492 M
: Esc [
|<32;-3;492M|
: Esc [
|<32;-5;492M|
: Esc [
|<32;-7;492M|
很不幸,当我们把鼠标移动至超出窗口左侧边界的时候,XTerm 输出的坐标含有负号,导致了对应的 CSI 序列无效。
定位到造成 bug 的代码,在 button.c 中,如下所示:
// ...
if (screen->extend_coords == SET_PIXEL_POSITION_MOUSE) {
row = event->y - OriginY(screen);
col = event->x - OriginX(screen);
} else {
/* Compute character position of mouse pointer */
row = (event->y - screen->border) / FontHeight(screen);
col = (event->x - OriginX(screen)) / FontWidth(screen);
/* Limit to screen dimensions */
if (row < 0)
row = 0;
else if (row > screen->max_row)
row = screen->max_row;
if (col < 0)
col = 0;
else if (col > screen->max_col)
col = screen->max_col;
// ...
可以看到,在 1016 模式下,对于负的行列值,没有置零,所以导致了上述 bug 的发生。打下面这个补丁即可修复:
--- button.c 2021-02-04 09:00:26.000000000 +0800
+++ button.patched.c 2021-02-07 21:31:45.960978282 +0800
@@ -5258,6 +5258,10 @@
if (screen->extend_coords == SET_PIXEL_POSITION_MOUSE) {
row = event->y - OriginY(screen);
col = event->x - OriginX(screen);
+ if (row < 0)
+ row = 0;
+ if (col < 0)
+ col = 0;
} else {
/* Compute character position of mouse pointer */
row = (event->y - screen->border) / FontHeight(screen);
这个 bug 已经提给了 XTerm 的作者,Thomas E. Dickey,截至发帖,作者尚未回应。
Bug report 的原文可以在这里看到。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.