簡體   English   中英

一種在 C++ 中區分 `\e` 和 `\e[A` 等轉義鍵的方法

[英]A way to distinguish `\e` from escaped keys like `\e[A` in C++

我正在 C++ 中編寫讀取行替換,並且我想以原始模式處理終端輸入,包括特殊/轉義鍵,如“向上箭頭” \e[A 但是,我還希望能夠區分單次按下轉義鍵\e然后按下[和按下A與按下向上箭頭。

我認為這兩種情況之間的主要區別在於,當按下向上箭頭時,輸入字符會在不到一毫秒的時間內輸入,所以我想我可以這樣做:

#include <termios.h>
#include <absl/strings/escaping.h>
#include <iostream>

termios enter_raw() {
    termios orig;
    termios raw;
    tcgetattr(STDOUT_FILENO, &orig);
    tcgetattr(STDOUT_FILENO, &raw);
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    raw.c_oflag &= ~OPOST;
    raw.c_cflag |= CS8;
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    raw.c_cc[VMIN]  = 1;
    raw.c_cc[VTIME] = 0;
    tcsetattr(STDOUT_FILENO, TCSAFLUSH, &raw);
    return orig;
}

int main() {
    termios orig = enter_raw();
    while(true) {
        char buf[10];
        memset(buf, 0, sizeof(buf));
        std::cin >> buf[0];
        usleep(1000);
        int actual = std::cin.readsome(buf + 1, sizeof(buf) - 2);
        std::cout << "Got string: \"" << absl::CEscape(buf) << "\"\n";
        if(buf[0] == 35) { break; } // received ctrl-c
    }
    tcsetattr(STDOUT_FILENO, TCSAFLUSH, &orig);
    return 0;
}

但是,這個 output 沒有Got string: "\033[A" 相反,它執行了 3 次Got string ,就像它只是一個簡單的字符循環一樣。 改變它休眠的微秒數似乎不會影響任何事情。

有沒有辦法在 C++ 中輕松實現這種事情? 它可以移植到大多數終端嗎? 我不在乎支持 Windows。 答案不需要使用<iostream> 只要能完成工作,它就可以使用 C 風格的終端 IO。

raw.c_cc[VMIN]  = 1;
raw.c_cc[VTIME] = 0;

這是一個阻塞讀取。 termios手冊頁:

   MIN > 0, TIME == 0 (blocking read)
          read(2)  blocks until MIN bytes are available, and returns up to
          the number of bytes requested.

可以保證“自動”生成多字符鍵,例如\033[A 終端也可以自己從底層設備接收\033密鑰。 滿足阻塞讀取的條件,返回逃逸。 下一個角色很快就到了,但為時已晚。

你似乎想要的是:

   MIN > 0, TIME > 0 (read with interbyte timeout)
          TIME specifies the limit for a timer  in  tenths  of  a  second.
          Once  an  initial  byte of input becomes available, the timer is
          restarted after each further byte is received.  read(2)  returns
          when any of the following conditions is met:

          *  MIN bytes have been received.

          *  The interbyte timer expires.

          *  The  number  of bytes requested by read(2) has been received.
             (POSIX does not specify this termination  condition,  and  on
             some  other  implementations  read(2) does not return in this
             case.)

從鍵序列中選擇一個合理的最大字符數。 6個字符是一個合理的建議。 使用 6 表示MIN 現在,您需要某種超時。 也許是 2/10 秒。

所以,現在當\033鍵到達時,終端層將再等待 2/10 秒,看看是否有東西進入。泡沫,沖洗重復。 當計時器超時時,所有進來的東西都會被退回。

請注意,無論如何,您不能保證多字符序列的第二個字符將在 2/10 秒內到達。 或 3/10 秒。 或者一分鍾。

即使在計時器到期之前有另一個字符進入,也不能保證它是一個多字符鍵序列。 即使只有 2/10 秒這樣的短間隔,您也可以在此時間限制內通過用手掌准確地拍打鍵盤來生成幾個字符。

這不是精確的科學。

似乎關鍵是將 termios 置於非阻塞模式,然后使用usleep進行輪詢。 std::cinread混合似乎也打破了這一點; 堅持read

termios enter_raw() { /* ... */ }

int main() {
    termios orig = enter_raw();
    while(true) {
        termios block; tcgetattr(STDOUT_FILENO, &block);
        termios nonblock = block;
        nonblock.c_cc[VMIN] = 0;
        
        char c0;
        read(STDIN_FILENO, &c0, 1);
        if(std::isprint(c0)) {
            std::cout << "Pressed: " << c0 << "\r\n";
        } else if(c0 == '\e') {
            tcsetattr(STDOUT_FILENO, TCSANOW, &nonblock);
            std::string result;
            result.push_back('\e');
            for(int i = 0; i < 20; i++) {
                char c;
                if(read(STDIN_FILENO, &c, 1) == 1) {
                    result.push_back(c);
                }
                usleep(5);
            }
            tcsetattr(STDOUT_FILENO, TCSANOW, &block);
            std::cout << "Pressed: " << absl::CEscape(result) << "\r\n";
        } else if(c0 == 35) {
            break; // received ctrl-c
        }
    }
    tcsetattr(STDOUT_FILENO, TCSAFLUSH, &orig);
    return 0;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM