V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
工单节点使用指南
• 请用平和的语言准确描述你所遇到的问题
• 厂商的技术支持和你一样也是有喜怒哀乐的普通人类,尊重是相互的
• 如果是关于 V2EX 本身的问题反馈,请使用 反馈 节点
CismonX
V2EX  ›  全球工单系统

XTerm 的 1016 模式存在 bug

  •  
  •   CismonX · 2021-02-07 23:41:44 +08:00 · 746 次点击
    这是一个创建于 1175 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景

    最近,在翻阅 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 复现

    为了更方便排查 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

    其中,PxPy 为鼠标此刻的像素坐标,从 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 修复

    定位到造成 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 的原文可以在这里看到。

    第 1 条附言  ·  2021-02-16 16:19:48 +08:00

    目前该 bug 已在 Patch #366 中修复

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   876 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 21:25 · PVG 05:25 · LAX 14:25 · JFK 17:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.