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

问题描述 投票:0回答:5

我正在尝试使用以下代码检索 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
,这使得我无法从程序中检索它:

terminal window

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

c terminal ncurses xterm ansi-escape
5个回答
9
投票

我询问光标位置。如果我在 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;
}

5
投票

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

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

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

您还需要使标准输入非阻塞,否则在缓冲区已满或标准输入关闭之前您将无法获得输入。请参阅这个问题使标准输入非阻塞

您还应该在 printf 之后调用

fflush(stdout);
以确保它确实被写入(printf 通常是行缓冲的,因此如果没有换行符,它可能不会刷新缓冲区)。


3
投票

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

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

为了避免这种情况,请改用

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

但是在遇到循环之前,您需要使用 termios 更改为原始模式(请查看下面的代码示例)。否则一切都会不同。


0
投票
#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 程序。


0
投票
    // Get terminal cursor position.
    // Returns 'false' on error.
    bool
    getxy (int * col, int * row)
    {
        int old_c_lflag;
        struct termios term;
    
        // Disable terminal's canonical mode and echo.
        if (tcgetattr (STDIN_FILENO, &term))
            return false;
        old_c_lflag = term.c_lflag;
        term.c_lflag &= ~(ICANON | ECHO);
        if (tcsetattr (STDIN_FILENO, TCSANOW, &term))
            return false;
        term.c_lflag = old_c_lflag;
    
        // Request cursor position.
        if (0 > write (STDOUT_FILENO, "\033[6n", 4))
            goto error_with_term_restored;
    
        // Read the response.
        char buf[16] = {0};
        if (0 > read (STDIN_FILENO, buf, sizeof(buf) - 1))
            goto error_with_term_restored;
    
        // Scan the response.
        if (2 != sscanf (buf, "\033[%d;%dR", row, col))
            goto error_with_term_restored;
    
        // Restore former terminal status.
        if (tcsetattr (STDIN_FILENO, TCSANOW, &term))
            return false;
    
        return true;
    
    error_with_term_restored:
        tcsetattr (STDIN_FILENO, TCSANOW, &term);
        return false;
}
© www.soinside.com 2019 - 2024. All rights reserved.