How to get cursor position in C using ANSI code

2019-03-06 15:01发布

问题:

I am trying to get cursor position from a little c program so after googling I found this ANSI code \x1b[6n. It should return x and y location of cursor ( if I am not wrong) So printf("\x1b[6n"); is giving me output : ;1R I can't understand the output in terms of x and y location.

Edit : Platform is Linux (xterm)

回答1:

#include <stdio.h>
#include <termios.h>

int
main() {
 int x = 0, y = 0;
 get_pos(&y, &x);
 printf("x:%d, y:%d\n", x, y);
 return 0;
}

int
get_pos(int *y, int *x) {

 char buf[30]={0};
 int ret, i, pow;
 char ch;

*y = 0; *x = 0;

 struct termios term, restore;

 tcgetattr(0, &term);
 tcgetattr(0, &restore);
 term.c_lflag &= ~(ICANON|ECHO);
 tcsetattr(0, TCSANOW, &term);

 write(1, "\033[6n", 4);

 for( i = 0, ch = 0; ch != 'R'; i++ )
 {
    ret = read(0, &ch, 1);
    if ( !ret ) {
       fprintf(stderr, "getpos: error reading response!\n");
       return 1;
    }
    buf[i] = ch;
    printf("buf[%d]: \t%c \t%d\n", i, ch, ch);
 }

 if (i < 2) {
    printf("i < 2\n");
    return(1);
    }

    for( i -= 2, pow = 1; buf[i] != ';'; i--, pow *= 10)
       *x = *x + ( buf[i] - '0' ) * pow;

    for( i-- , pow = 1; buf[i] != '['; i--, pow *= 10)
       *y = *y + ( buf[i] - '0' ) * pow;

 tcsetattr(0, TCSANOW, &restore);
 return 0;
}


回答2:

On some terminals, such as DEC VT102 and later VTs, and on many terminal emulators, notably XTerm and its many imitations, sending Esc [ 6 n will make the terminal respond with Esc [ row ; column R, where row and column are decimal representations of the position of the text cursor.

So your terminal emulator is not replying with ;1R; it is replying correctly, but the readline routines are eating Esc [ and the decimal digits up to ; (and flash the screen or beep the bell, depending on configuration).

Here is a nice Bash command to illustrate:

out=''; \
echo $'\e[6n'; \
while read -n 1 -s -t 1; do out="$out$REPLY"; done < /dev/tty; \
echo -n "$out" | od -A x -t x1z -v

Running this gives:

$ out=''; \
> echo $'\e[6n'; \
> while read -n 1 -s -t 1; do out="$out$REPLY"; done < /dev/tty; \
> echo -n "$out" | od -A x -t x1z -v

000000 1b 5b 31 36 3b 31 52                             >.[16;1R<
000007

Note that the answer does not necessarily come on standard input: the answer comes from the terminal even if standard input is redirected.

At the inquirer's request, here is a small C program which partially duplicates the functionality of the scriptlet above. Note that the program does not handle setting the terminal in raw mode and back to cooked mode; this must be handled outside the program, as indicated below.

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main (void)

{
  int ttyfd = open ("/dev/tty", O_RDWR);
  if (ttyfd < 0)
    {
      printf ("Cannot open /devv/tty: errno = %d, %s\r\n",
        errno, strerror (errno));
      exit (EXIT_FAILURE);
    }

  write (ttyfd, "\x1B[6n\n", 5);

  unsigned char answer[16];
  size_t answerlen = 0;
  while (answerlen < sizeof (answer) - 1 &&
         read (ttyfd, answer + answerlen, 1) == 1)
    if (answer [answerlen ++] == 'R') break;
  answer [answerlen] = '\0';

  printf ("Answerback = \"");
  for (size_t i = 0; i < answerlen; ++ i)
    if (answer [i] < ' ' || '~' < answer [i])
      printf ("\\x%02X", (unsigned char) answer [i]);
    else
      printf ("%c", answer [i]);
  printf ("\"\r\n");

  return EXIT_SUCCESS;
}

Assuming this little program is answerback.c:

$ gcc -Wall -Wextra answerback.c -o answerback
$ stty raw -echo; ./answerback; stty sane

Answerback = "\x1B[24;1R"
$ _


标签: c ansi