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:
Clearly, the string is designated for the program, though, so I must be doing something incorrectly. Does anybody know what it is?
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).
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;
}
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.