读取设备状态报告 ANSI 转义序列回复

Posted

技术标签:

【中文标题】读取设备状态报告 ANSI 转义序列回复【英文标题】:Reading the Device Status Report ANSI escape sequence reply 【发布时间】:2013-04-08 06:09:44 【问题描述】:

我正在尝试使用以下代码检索 VT100 终端中光标的坐标:

void getCursor(int* x, int* y) 
  printf("\033[6n");
   scanf("\033[%d;%dR", x, y);

我正在使用以下 ANSI 转义序列:

设备状态报告 - ESC[6n

将光标位置报告给 应用程序(就像在键盘上键入一样)ESC[n;mR,其中 n 是 行,m 是列。

代码编译并发送 ANSI 序列,但是,在接收到它后,终端将 ^[[x;yR 字符串打印到 stdout 而不是 stdin,这使我无法从程序中检索它:

很明显,字符串是为程序指定的,所以我一定是做错了什么。有人知道是什么吗?

【问题讨论】:

【参考方案1】:

我询问光标位置。如果我在 100 毫秒后没有答案(这是任意的),我想控制台不是 ansi。

/* This function tries to get the position of the cursor on the terminal. 
It can also be used to detect if the terminal is ANSI.
Return 1 in case of success, 0 otherwise.*/

int console_try_to_get_cursor_position(int* x, int *y)

    fd_set readset;
    int success = 0;
    struct timeval time;
    struct termios term, initial_term;

    /*We store the actual properties of the input console and set it as:
    no buffered (~ICANON): avoid blocking 
    no echoing (~ECHO): do not display the result on the console*/
    tcgetattr(STDIN_FILENO, &initial_term);
    term = initial_term;
    term.c_lflag &=~ICANON;
    term.c_lflag &=~ECHO;
    tcsetattr(STDIN_FILENO, TCSANOW, &term);

    //We request position
    print_escape_command("6n");
    fflush(stdout);

    //We wait 100ms for a terminal answer
    FD_ZERO(&readset);
    FD_SET(STDIN_FILENO, &readset);
    time.tv_sec = 0;
    time.tv_usec = 100000;

    //If it success we try to read the cursor value
    if (select(STDIN_FILENO + 1, &readset, NULL, NULL, &time) == 1) 
      if (scanf("\033[%d;%dR", x, y) == 2) success = 1;

    //We set back the properties of the terminal
    tcsetattr(STDIN_FILENO, TCSADRAIN, &initial_term);

    return success;

【讨论】:

【参考方案2】:

您的程序正在运行,但正在等待 EOL 字符。

scanf 是面向行的,因此它在处理之前等待新行。尝试运行您的程序,然后按回车键。

解决方案是使用其他不需要换行的东西来读取输入,然后使用 sscanf 将值解析出来。

您还需要将标准输入设为非阻塞,否则在缓冲区已满或标准输入关闭之前您将无法获得输入。看到这个问题Making stdin non-blocking

您还应该在 printf 之后调用 fflush(stdout); 以确保它被实际写入(printf 通常是行缓冲的,因此如果没有换行符,它可能不会刷新缓冲区)。

【讨论】:

难道没有办法让 ANSI 命令根本不写出^[[x;yR 到终端吗?我想默默地检索坐标,而不会对终端屏幕进行任何可见的更改。但这会写入终端(在创建文本 GUI 时不受欢迎)并因此更改光标的坐标(这使得它完全没用)。 这就是 curses(和 ncurses)存在的原因,因此您不必担心所有这些细节。 当然可以,但是 ncurses 是完全过时的软件。这就是为什么我要编写自己的轻量级 ANSI 转义序列库,它仅支持 VT100。但是,是的,似乎我只需要 dl ncurses 并尝试对其进行逆向工程以找到解决此问题的方法。 我认为 ncurses 要么跟踪光标在内部的位置,要么在输出之前将其移动到已知位置。必须这样做,因为有些终端无法报告当前位置。【参考方案3】:

我相信您确实在标准输入中得到了预期的响应。但是想象一下实际发生的事情:

您将请求作为转义序列发送到标准输出 终端接收它并将相应的答案制定为转义序列 答案发送到标准输入 scanf 被调用并通过 shell 重定向标准输入,其中 readline 库用于交互式和可编辑的用户输入 readline 捕获转义序列,而不是将其传递给终端 readline 重新制定它而不使用 ESC 字符以防止执行控制序列,而是仅使用可打印字符使其可读 奇怪的答案到达 scanf 但为时已晚 奇怪的答案也会回显到标准输出,以便用户可以立即看到她输入的内容。

为避免这种情况,请改用getc() (==fgetc(stdin)) 循环。如果遇到 ESC (0x1B),则将以下字符转储到字符串中,直到找到 ESC 序列的最终分隔符(在您的情况下为 'n')。之后你可以使用sscanf(esqString, formatString, ...)

但是在你遇到循环之前你需要change with termios to raw mode(看下面的代码示例)。否则没有什么不同。

【讨论】:

【参考方案4】:
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

void readCoord(void* row, void* col)
    int i = 0;
    char* input = malloc(10);
    printf("\e[6n");
    while(!_kbhit()) _sleep(1);
    while(_kbhit()) *(input + (i++)) = getch();
    *(input + i) = '\0';
    sscanf(input, "\e[%d;%dR", row, col);


void main(void)
    int i = 0, r, c;
    char* coord = malloc(10);
    printf("Hello");
    readCoord(&r , &c);
    printf("\nYour coordinate is (%d, %d)", c, r);

_kbhit() 用于检测输入(DSR 被视为在键盘上键入),并且 getch() 从标准输入中读取和删除字符

这个程序依赖于conio.h,这不是一个标准,因此不推荐用于可移植的 C 程序。

【讨论】:

以上是关于读取设备状态报告 ANSI 转义序列回复的主要内容,如果未能解决你的问题,请参考以下文章

ANSI 颜色转义序列列表

ANSI 颜色转义序列字符出现在 String.length();

使用 PHP 将 ANSI 转义序列转换为 HTML

text BASH:为ANSI文本颜色转义序列设置变量

如何让 win32 控制台识别 ANSI/VT100 转义序列?

如何在 Windows 10 控制台中使用对 ANSI 转义序列的新支持?