Terminal programs (notably vi and vim) can differentiate between the Esc (escape) key and other special keys like the arrow keys, having different commands for all of them.
In terminal emulators, Esc seems to be encoded using only a single hex byte:
{ 1b }
However, some special keys (arrow keys, page up/down, home/end, insert/delete) are multi-byte sequences starting with that same byte:
{ 1b 5b 41 } up
{ 1b 5b 42 } down
{ 1b 5b 43 } right
{ 1b 5b 44 } left
Given the dual role of this byte, how do I detect when the user just pressed escape, as opposed to pressing one of the other keys?
I cannot wait for the next byte after a
0x1b
, since there may not be one. The user can press escape and then wait indefinitely for my program to respond to that key.I could call
read()
once to read all available data on the terminal and check whether that's only one byte or several bytes long. This works, provided that we trust the terminal is fast enough to send all the relevant bytes in time for theread()
and only sends one keystroke at a time (so theread()
doesn't return extra data or partial keystrokes). Is this a reasonable assumption nowadays that people practically always use terminal emulators? What about network links (e.g. SSH) causing delays?I could call
read()
repeatedly, reading only one byte with eachread()
call until there are no more bytes available. The linenoise library does this, citing compatibility with slow terminals as the reason (but doesn't say which particular terminals those are).
A couple of general considerations:
Should I be doing blocking or non-blocking reads? Does it matter?
Should I be using a read timeout, and if so, is there some empirically determined value that is often used?
I used the following test program to show the bytes for the keystrokes:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
static void dump(void) {
unsigned char bytes[8];
ssize_t i, n;
while ((n = read(0, bytes, sizeof(bytes))) > 0) {
printf("{ ");
for (i = 0; i < n; i++) {
printf("%02x ", bytes[i]);
}
printf("}\r\n");
if (bytes[0] == 'q') {
break;
}
}
}
int main(void) {
struct termios origMode;
struct termios rawMode;
cfmakeraw(&rawMode);
tcgetattr(0, &origMode);
tcsetattr(0, TCSAFLUSH, &rawMode);
dump();
tcsetattr(0, TCSAFLUSH, &origMode);
return 0;
}