2013-04-16 5 views
7

Я пытаюсь получить координаты курсора в терминале VT100, используя следующий код:Чтение состояния устройства Отчет ANSI последовательность побега ответ

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

Я использую следующую последовательность ANSI побега:

Состояние устройства Отчет - ESC [6n

Сообщает позицию курсора в применения в качестве (как будто набирается на клавиатуре) ESC [п, мР, где п строка и m - столбец.

Код компилируется и последовательность ANSI передается, но, получив его, терминал печатает ^[[x;yR строку в stdout вместо stdin делает его невозможным для меня, чтобы извлечь его из программы:

terminal window

Очевидно, что строка предназначена для программы, поэтому я должен что-то делать неправильно. Кто-нибудь знает, что это?

ответ

3

Ваша программа работает, но ждет символ EOL.

scanf это линия, ориентированная так, что ждет новой строки перед обработкой. Попробуйте запустить вашу программу, а затем нажмите клавишу ввода.

Решение состоит в том, чтобы использовать что-то еще, что не нуждается в новой строке для чтения ввода, а затем использовать sscanf для анализа значений.

Вам также нужно будет сделать stdin неблокирующим, или вы не получите входной сигнал до тех пор, пока буфер не будет заполнен или stdin не будет закрыт. См. Этот вопрос Making stdin non-blocking

После печати вы также должны позвонить по номеру fflush(stdout);, чтобы убедиться, что оно написано на самом деле (printf часто буферизируется так, что без новой строки он может не очищать буфер).

+1

Нет ли способа заставить команду ANSI не выписывать '^ [[x; yR' на терминал вообще? Я хотел бы тихо получить координаты без каких-либо видимых изменений на экране терминала. Но это записывается на терминал (нежелательно при создании текстового графического интерфейса) и таким образом изменяет координаты курсора (что делает его абсолютно бесполезным). – Witiko

+0

Вот почему проклятия (и ncurses) существуют, поэтому вам не нужно беспокоиться обо всех этих деталях. – Craig

+2

Несомненно, но ncurses - полная версия. Вот почему я пишу свои легкие escape-последовательности ANSI lib, которые поддерживают только VT100. Но да, похоже, мне просто нужно будет выполнить ncurses и попытаться перестроить его, чтобы найти решение этой проблемы. – Witiko

3

Прошу указать позицию курсора. Если у меня нет ответа после 100 мс (это произвольно), я полагаю, что консоль не 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; 
} 
0

Я считаю, что вы действительно получите ожидаемый ответ в stdin.Но представьте себе, что происходит на самом деле:

  • вы отправляете запрос в управляющей последовательности на стандартный вывод
  • терминал принимает его и формулирует соответствующий ответ, как управляющей последовательности, а
  • ответ отправляется на стандартный ввод
  • scanf вызывается и stdin перенаправляется через оболочку, где библиотека readline используется для интерактивного и редактируемого ввода пользователя
  • readline захватывает escape-последовательность, а не передает ее на терминал
  • Readline повторно формулирует это без ESC характера, чтобы предотвратить выполнение последовательности управления, а делает его доступным для чтения только с помощью печатных символов
  • изогнулись ответ достигает зсапЕ, но ее слишком поздно
  • изогнулись ответ также повторил на stdout, чтобы пользователь мог мгновенно увидеть, что она набрала.

Чтобы избежать этого, вместо этого следует использовать петлю getc()310310 (==fgetc(stdin)). Если вы столкнулись с ESC(), чем сбрасываете следующие символы в строке до тех пор, пока не найдете окончательный разделитель последовательности ESC (в вашем случае 'n'). После этого вы можете использовать sscanf(esqString, formatString, ...).

Но перед тем, как вы столкнетесь с петлей, вам необходимо указать change with termios to raw mode (см. Пример кода ниже). Иначе ничего не изменилось бы.