2

While researching this problem, in the comments I found someone mentioning ANSI escape codes to get the terminal size. Since I will be using ANSI escape sequences, I thought this would be a much more elegant way to get the terminal size than ioctl() or getenv().

Here is a very good post about ioctl(), and this is the comment that piqued my interest:

Just stumbled upon this answer, and my jaw dropped when I realized that getenv("COLUMNS") works perfectly when running under watch(1). So now I have a set of fallbacks, all from the TIOCWINSZ ioctl, to getenv if not a tty, down to the classic ANSI escape "move cursor to 9999,9999 and the query cursor pos. The last one works on serial consoles for embedded systems :)

Now, I did find some posts (1, 2) about ANSI, but none answer the specific question I have - how to actually do it?

Using this chart, I deduced that in order to move the cursor to the extreme right and down position, I would need the CUP (CSI n ; m H) "Cursor Position" command, which would probably translate to something like \x1b[9999;9999H. This is the easy part since it is a command.

The second part is where I have a big problem. Even if I could deduce that I need the DSR(CSI 6n) "Device Status Report" command, and that it goes \x1b[6n, how does this querying work, i.e., how can I take the data and store it in a variable, preferrably without disrupting the other data that is being displayed on the terminal?

wovano
  • 4,543
  • 5
  • 22
  • 49
iloveclang
  • 139
  • 9
  • 1
    Does this answer your question? [How can I get position of cursor in terminal?](https://stackoverflow.com/questions/8343250/how-can-i-get-position-of-cursor-in-terminal) – Paul Hankin Nov 14 '22 at 11:48
  • No, because I don't know bash, and I wanted the answer in C, which I got. – iloveclang Nov 14 '22 at 15:22

1 Answers1

1
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <ctype.h>

#define SIZE 100

int main ( void) {
    char in[SIZE] = "";
    int each = 0;
    int ch = 0;
    int rows = 0;
    int cols = 0;
    struct termios original, changed;

    // change terminal settings
    tcgetattr( STDIN_FILENO, &original);
    changed = original;
    changed.c_lflag &= ~( ICANON | ECHO);
    changed.c_cc[VMIN] = 1;
    changed.c_cc[VTIME] = 0;
    tcsetattr( STDIN_FILENO, TCSANOW, &changed);

    printf ( "\033[2J"); //clear screen

    printf ( "\033[9999;9999H"); // cursor should move as far as it can

    printf ( "\033[6n"); // ask for cursor position
    while ( ( ch = getchar ()) != 'R') { // R terminates the response
        if ( EOF == ch) {
            break;
        }
        if ( isprint ( ch)) {
            if ( each + 1 < SIZE) {
                in[each] = ch;
                each++;
                in[each] = '\0';
            }
        }
    }

    printf ( "\033[1;1H"); // move to upper left corner
    if ( 2 == sscanf ( in, "[%d;%d", &rows, &cols)) {
        printf ( "%d rows\n", rows);
        printf ( "%d cols\n", cols);
    }

    // restore terminal settings
    tcsetattr( STDIN_FILENO, TCSANOW, &original);

    return 0;
}
user3121023
  • 8,181
  • 5
  • 18
  • 16
  • In this case, it might be more useful to set c_cc[VMIN] to 0 and c_cc[VTIME] to 1, so reverse. Except using a true serial interface, the terminal will send the answer in one piece, and you get the response for "\e[6n" with one read() of stdin, so you can use scanf(stdin,"\e[%i;%iR",&rows,&cols). Even when sticking on reading character by character (which is more safe), setting c_cc[VTIME] to non-zero value saves unnecessary CPU load at getchar(). – Henrik Haftmann Feb 21 '23 at 08:42