I am trying to capture ESC key (ASCII 27) on a OSX terminal or xterm
using kbhit to distinguish a real Escape from Arrow keys:
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
static struct termios newt;
static struct termios oldt;
static void kb_fini(void)
{
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
}
void kb_init(void)
{
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= (tcflag_t)~(ICANON | ECHO | ISIG);
newt.c_cc[VMIN] = 1;
newt.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
atexit(kb_fini);
}
static int kb_hit(void)
{
int c = 0;
newt.c_cc[VMIN] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
c = getc(stdin);
newt.c_cc[VMIN] = 1;
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
if (c != -1) {
ungetc(c, stdin);
return 1;
}
return 0;
}
int main(void)
{
int c;
kb_init();
printf("Press ESC several times\n");
while (1) {
c = getchar();
if ((c == 27) && (kb_hit() == 0)) {
printf("You pressed ESC\n");
} else
if (c == '\n') {
break;
}
}
return 0;
}
But it only works the first time, the second time I press the escape key, the terminal doesn't admit more data. It doesn't freeze completely, as the prompt keeps blinking, but pressing more keys doesn't change anything.
Manipulating VTIME
and VMIN
like that would be useful if you were reading the standard input directly using read
. However, you are reading it via the C stream input stdin, which means that you are relying on a particular behavior for that, rather than the low-level termios feature.
The program loops because getchar
has decided it has detected an end-of-file condition, and continues to return -1, never returning to kb_hit
. You can amend that by calling
clearerr(stdin);
after the call to getchar
(since it resets the end-of-file condition), though relying upon any particular behavior or interaction between stream and low-level I/O is nonportable.
For instance, the Linux manual pages for getchar and gets advise
It is not advisable to mix calls to input functions from the stdio library with low-level calls to
read(2)
for the file descriptor associated with the input stream; the results will be undefined and very probably not what you want.
For reference: