2013-04-16 24 views
7

我嘗試使用下面的代碼來獲取光標在VT100終端座標:讀取設備狀態報告ANSI轉義序列回覆

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

我使用下面的ANSI轉義序列:

設備狀態報告 - ESC [6N

報告光標位置到 應用程序(如,雖然在鍵盤上鍵入)ESC [N; MR,其中n是 行和m是列。

代碼編譯和ANSI序列被髮送,但在接收到它時,終端打印^[[x;yR字符串到stdout代替stdin使它IMPOSIBLE爲我從節目檢索:

terminal window

顯然,該字符串指定的程序,雖然如此,我一定要正確地做事。有人知道它是什麼嗎?

回答

3

您的程序工作,但正在等待EOL字符。

scanf是面向行的,以便它等待處理之前一個新行。嘗試運行你的程序,然後按回車鍵。

的解決方案是使用別的東西,並不需要一個新的生產線來讀取輸入,然後使用sscanf的解析值了。

您還需要進行標準輸入非阻塞或直到緩衝區已滿或標準輸入被關閉,你不會得到輸入。看到這個問題Making stdin non-blocking

你也應該在printf之後調用fflush(stdout);以確保它實際被寫入(printf通常是行緩衝的,所以沒有換行符,它可能不會刷新緩衝區)。

+1

難道沒有辦法讓ANSI命令不把'^ [[x; yR'寫到終端上嗎?我想默默地檢索座標,而不會在終端屏幕上發生任何可見的變化。但是這寫入到終端(創建文本GUI時不合需要),從而改變了光標的座標(這使得它絕對無用)。 – Witiko

+0

這就是爲什麼curses(和ncurses)存在,所以你不必擔心所有這些細節。 – Craig

+2

當然,但ncurses完全是英國媒體報道。這就是爲什麼我編寫自己的輕量級ANSI轉義序列庫,它僅支持VT100。但是,是的,好像我只需要使用ncurses並嘗試對其進行反向工程即可找到解決此問題的方法。 – Witiko

3

我要求光標位置。如果我在100ms後沒有回答(這是任意的),我想控制檯不是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中得到了預期的響應。但是想象一下,到底發生了什麼:

  • 你發送一個請求作爲轉義序列到stdout
  • 終端接收,並制定相應的答案轉義序列以及
  • 的答案被髮送到標準輸入
  • scanf被調用,stdin被重定向到shell,其中readline庫被用於交互式和可編輯用戶輸入
  • readline 捕獲轉義序列而不是將它傳遞給終端
  • readline的重新制定,沒有ESC字符,以防止控制序列的執行它,而是使它可讀僅使用可打印字符
  • 的挑起答案達到scanf函數,但爲時已晚,
  • 的挑起回答也是呼應標準輸出,以便用戶可以即時查看輸入內容。

爲避免這種情況,請使用getc()==fgetc(stdin))代替。如果遇到ESC(0x1B),則直接在字符串中轉儲下列字符,直到找到ESC序列的最後一個分隔符(在您的案例中爲'n')。之後,您可以使用sscanf(esqString, formatString, ...)

但是在你遇到這個循環之前,你需要change with termios to raw mode(請看下面的代碼示例)。否則沒有什麼不同。