Как получить позицию курсора в C, используя код ANSI

Я пытаюсь получить позицию курсора из небольшой программы на языке C, поэтому после поиска в Google я нашел этот код ANSI \x1b[6n. Он должен возвращать местоположение курсора x и y (если я не ошибаюсь). Итак, printf("\x1b[6n"); дает мне вывод: ;1R Я не могу понять вывод с точки зрения местоположения x и y.

Редактировать: Платформа Linux (xterm)


person Aux    schedule 16.06.2018    source источник
comment
Для получения позиции курсора в C нужно гораздо больше, чем вы ожидали. Поскольку в стандарте C нет понятия курсора мыши, ответ на этот вопрос будет зависеть от того, какую среду и операционную систему вы используете. Если Linux/Unix/BSD с системой X Window, см. это. В противном случае, пожалуйста, укажите.   -  person Marc.2377    schedule 16.06.2018
comment
Различные страницы рассказывают вам о кодах ANSI. Вот один из них: en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences   -  person Arndt Jonasson    schedule 16.06.2018
comment
Я знаю о кодах ANSI, но я спрашиваю о выводе, который вызывает у меня головную боль, потому что я не могу его понять.   -  person Aux    schedule 16.06.2018
comment
У меня есть этот эск. сл. в моем старом коде работает нормально, но я не могу сделать небольшой пример: D Извините.   -  person purec    schedule 16.06.2018
comment
Какой код? Можете выложить на pastebin?   -  person Aux    schedule 16.06.2018
comment
Я постараюсь опубликовать ответ. Короче говоря, вам нужно отключить эхо и проанализировать этот вывод.   -  person purec    schedule 16.06.2018


Ответы (2)


На некоторых терминалах, таких как DEC VT102 и более поздних версиях VT, а также на многие эмуляторы терминала, особенно XTerm и его многочисленные имитации, отправляющие Esc < kbd>[ 6 n заставит терминал ответить Esc [ row< /em> ; column R, где row и column — десятичные представления положение текстового курсора.

Итак, ваш эмулятор терминала не отвечает ;1R; он отвечает правильно, но процедуры чтения поглощают Esc [ и десятичные цифры до ; (и мигают на экране или издают звуковой сигнал , в зависимости от комплектации).

Вот хорошая команда Bash для иллюстрации:

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

Запуск этого дает:

$ 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

Обратите внимание, что ответ не обязательно приходит на стандартный ввод: ответ приходит с терминала, даже если стандартный ввод перенаправляется.

По просьбе спрашивающего, вот небольшая программа на C, которая частично дублирует функциональность скриптлета выше. Обратите внимание, что программа не обрабатывает перевод терминала в необработанный режим и обратно в приготовленный режим; это должно быть обработано вне программы, как указано ниже.

#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;
}

Предполагая, что эта маленькая программа answerback.c:

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

Answerback = "\x1B[24;1R"
$ _
person AlexP    schedule 16.06.2018
comment
Я не знаком с bash, поэтому ваша иллюстрация мне не поможет, можете ли вы дать то же самое на C. - person Aux; 16.06.2018
comment
@Aux, добавлена ​​​​программа C. - person AlexP; 16.06.2018
comment
Я сомневаюсь, что вам нужно сделать этот цикл для чтения с терминала. лучше просто читать с терминала. читать (терминал, ответ, 4096); - person ; 26.07.2019
comment
@AlexP Я добавил комментарий к ответу выше о проблеме, буду очень признателен за любую помощь: D - person Koen du Buf; 27.03.2021

person    schedule
comment
один вопрос, и в этом случае if(i<2) будет истинным в вашем коде - person Aux; 17.06.2018
comment
Я не помню. - person purec; 17.06.2018
comment
Я имею в виду, о какой причине или логике вы подумали, прежде чем написать эту строку if(i<2) - person Aux; 18.06.2018
comment
Это не работает, если есть другой поток, читающий стандартный ввод, так как я могу получить позицию, если другой поток выполняет fgets()? - person zhao; 12.01.2019
comment
@zhao, ответ AlexP дает хорошее решение для этого случая. Используйте другое подключение к вашему tty. Однако пример AlexP не включает удаление ICANON и ECHO. Они вам еще понадобятся. - person Alexis Wilke; 25.06.2019
comment
@Aux, у первого for() есть i -= 2, поэтому он, вероятно, хотел убедиться, что все не взорвется. Правда в том, что он должен проверить i >= 0, прежде чем делать buf[i]. Его код имеет очень сильные предположения, которые, если они неверны, могут привести к сбою вашего программного обеспечения... - person Alexis Wilke; 25.06.2019
comment
У меня есть небольшой вопрос/проблема с этим, это решение работает отлично... пока я не добавил другую программу, которая передает свой вывод в мою программу. Я также попробовал это с решением AlexP ниже, но это все еще не исправило, хотя я больше не использую стандартный ввод, чтобы правильно определить местоположение? (поскольку я предполагаю, что это проблема после добавления трубы). Поведение после передачи ввода в программе заключается в том, что на экране отображается ^[[7;1R (или аналогичный)), а мой вызов fgetc после этого метода больше не считывает ввод. Любые идеи @purec может быть? - person Koen du Buf; 27.03.2021
comment
После тестирования кажется, что проблема может заключаться в настройке необработанного режима в коде? работает с stty raw -echo, похоже, работает, но использование кода в этом ответе для установки необработанного режима не работает с каналом - person Koen du Buf; 27.03.2021
comment
@KoenduBuf: Трубка? Трубы не терминалы. Что ты пытаешься сделать с трубой? - person AlexP; 28.03.2021
comment
@AlexP Я имею в виду, что запуск ./compiled печатает Answerback = ..., как в вашем решении, но запуск echo "hey" | ./compiled печатает правильный ответ, но не Answerback = ..., а также не завершается (поэтому я предполагаю, что вызов чтения больше не получает ввод). С другой стороны, опять же, запуск stty raw -echo; echo "test" | ./compiled; stty sane печатает Answerback = ..., поэтому я сейчас предполагаю, что term.c_lflag &= ~(ICANON|ECHO); tcsetattr(0, TCSANOW, &term); не работает как stty raw -echo, но я не знаю.. (wtools.io/paste-code/b4uc) - person Koen du Buf; 28.03.2021
comment
@KoenduBuf: Почему тебя это удивляет? Это ожидаемое поведение. Трубы не являются терминалами; трубы не являются терминалами, у них нет понятия сырых и приготовленных режимов. Когда вы запускаете ··· | ./compiled, файловый дескриптор 0 ./compiled является каналом; вызовы tcgetattr() и tcsetattr() потерпят неудачу. - person AlexP; 28.03.2021
comment
@AlexP аааа, я не подумал о разнице в файловых дескрипторах, это объясняет, спасибо! Есть ли способ заставить эту работу работать? Моя цель - передать ввод в мою программу, которая затем анализируется и отображается как своего рода анимация, а затем я хотел бы, чтобы пользователь мог нажать клавишу, чтобы приостановить анимацию (например), я только сейчас понимаю, что пользовательский ввод и канал будут использовать стандартный ввод, как-нибудь обойти это? Единственное, что я могу придумать, это получить ввод данных на другом порту, но это было бы менее удобно для пользователя (было бы неплохо просто сделать data | myprogram) - person Koen du Buf; 28.03.2021
comment
@KoenduBuf: Если stdout — это терминал, вы можете использовать файловый дескриптор 1. Если stderr — это терминал, вы можете использовать файловый дескриптор 2. (Используйте isatty(), чтобы узнать.) Если у программы есть управляющий терминал, это /dev/tty. - person AlexP; 28.03.2021
comment
Спасибо! Это решило мою проблему и дало мне совершенно новое понимание дескрипторов файлов: D - person Koen du Buf; 28.03.2021