A way to distinguish `\e` from escaped keys like `\e[A` in C++
18:25 26 Mar 2021

I'm writing a readline replacement in C++, and I want to process terminal input in raw mode, including special/escaped keys like "up arrow" \e[A. However, I also want to be able to distinguish between a single press of the escape key \e followed by a press of [ and a press of A vs a press of the up arrow.

I assume that the primary difference between those two situations is that when up arrow is pressed, the input characters come in within less than a millisecond, so I thought I could do something like:

#include 
#include 
#include 

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

However, the output of this is not Got string: "\033[A" as I hoped; instead, it does Got string three times, as it would if it was just a naive loop over characters. Varying the number of microseconds for which it sleeps does not seem to affect anything.

Is there a way to implement this kind of thing easily in C++? Is it portable to most terminals? I don't care about supporting Windows. The answer need not use ; it can use C-style terminal IO as long as it gets the job done.

c++ terminal ansi-escape vt100