Reading the Device Status Report ANSI escape sequence reply
Asked Answered
H

4

19

I'm trying to retrieve the coordinates of cursor in a VT100 terminal using the following code:

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

I'm using the following ANSI escape sequence:

Device Status Report - ESC[6n

Reports the cursor position to the application as (as though typed at the keyboard) ESC[n;mR, where n is the row and m is the column.

The code compiles and the ANSI sequence is sent, but, upon receiving it, the terminal prints the ^[[x;yR string to the stdout instead of stdin making it imposible for me to retrieve it from the program:

terminal window

Clearly, the string is designated for the program, though, so I must be doing something incorrectly. Does anybody know what it is?

Hartsock answered 16/4, 2013 at 0:33 Comment(0)
E
5

Your program is working but is waiting for an EOL character.

scanf is line oriented so it waits for a new line before processing. Try running your program and then hit the enter key.

The solution is to use something else that doesn't need a new line to read the input and then use sscanf to parse the values out.

You will also need to make stdin non-blocking or you won't get the input until the buffer is full or stdin is closed. See this question Making stdin non-blocking

You should also call fflush(stdout); after your printf to ensure it is actually written (printf is often line buffered so without a newline it may not flush the buffer).

Exteroceptor answered 16/4, 2013 at 16:52 Comment(5)
Isn't there a way of making the ANSI command not write out ^[[x;yR to the terminal at all? I'd like to silently retrieve the coordinates without any visible change to the terminal screen. But this writes to the terminal (undesirable when creating a textual GUI) and thus changes the coordinates of the cursor (which makes it absolutely useless).Hartsock
This is why curses (and ncurses) exists so you don't have to worry about all of these details.Exteroceptor
Sure, but ncurses is utter bloatware. That's why I'm writing my own lightweight ANSI escape sequences lib, which supports merely VT100. But yes, seems like I'll just have to dl ncurses and try to reverse-engineer it to find its solution to this problem.Hartsock
I think ncurses either keeps track of where the cursor is internally or moves it to a known location before output. It has to because some terminals can't report the current location.Exteroceptor
Found that fflush(stdin) also did the trick on MSYS2/mintty.Glaudia
T
9

I ask for the cursor position. If I do not have answer after 100ms (this is arbitrary) I suppose the console is not 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;
}
Triarchy answered 7/6, 2015 at 22:5 Comment(0)
E
5

Your program is working but is waiting for an EOL character.

scanf is line oriented so it waits for a new line before processing. Try running your program and then hit the enter key.

The solution is to use something else that doesn't need a new line to read the input and then use sscanf to parse the values out.

You will also need to make stdin non-blocking or you won't get the input until the buffer is full or stdin is closed. See this question Making stdin non-blocking

You should also call fflush(stdout); after your printf to ensure it is actually written (printf is often line buffered so without a newline it may not flush the buffer).

Exteroceptor answered 16/4, 2013 at 16:52 Comment(5)
Isn't there a way of making the ANSI command not write out ^[[x;yR to the terminal at all? I'd like to silently retrieve the coordinates without any visible change to the terminal screen. But this writes to the terminal (undesirable when creating a textual GUI) and thus changes the coordinates of the cursor (which makes it absolutely useless).Hartsock
This is why curses (and ncurses) exists so you don't have to worry about all of these details.Exteroceptor
Sure, but ncurses is utter bloatware. That's why I'm writing my own lightweight ANSI escape sequences lib, which supports merely VT100. But yes, seems like I'll just have to dl ncurses and try to reverse-engineer it to find its solution to this problem.Hartsock
I think ncurses either keeps track of where the cursor is internally or moves it to a known location before output. It has to because some terminals can't report the current location.Exteroceptor
Found that fflush(stdin) also did the trick on MSYS2/mintty.Glaudia
D
3

I believe that you really get the expected response in stdin. But imagine what happens actually:

  • you send a request as escape sequence to stdout
  • the terminal receives it and formulates a corresponding answer as escape sequence as well
  • the answer is sent to stdin
  • scanf is called and stdin is redirected through the shell where the readline library is used for interactive and editable user input
  • readline captures the escape sequence rather than passing it to the terminal
  • readline re-formulates it with no ESC character to prevent execution of the control sequence but rather makes it readable by only using printable characters
  • the quirked answer reaches scanf but its too late
  • the quirked answer is also echoed to stdout so that the user can instantaneously see what she typed.

To avoid this use a getc() (==fgetc(stdin)) loop instead. If you encounter an ESC (0x1B) than dump the following characters in a string until you find the final delimiter of the ESC sequence (in your case 'n'). After that you may use sscanf(esqString, formatString, ...).

But before you encounter the loop you need to change with termios to raw mode (look at the code example below). Else nothing would be different.

Dissymmetry answered 30/6, 2016 at 20:44 Comment(0)
E
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() is used to detect input (DSR are treated as though typed at the keyboard), and getch() to read and remove the character from standard input

This program relies on conio.h, which is not a standard and hence not recommended for portable C programs.

Elijaheliminate answered 1/7, 2019 at 15:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.