1

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.

David Ranieri
  • 39,972
  • 7
  • 52
  • 94

1 Answers1

2

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:

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • Thank you Thomas, you mean it is preferable to use `read` instead of `getchar`? (regardless of fault) – David Ranieri Dec 12 '15 at 09:52
  • 1
    yes: without delving into the source-code for OSX's `getchar`, I could not tell exactly *why* it set the `EOF` condition. These internal details are not well-standardized (and can differ). But using `read`, you can make the prorgram much more predictable and portable. – Thomas Dickey Dec 12 '15 at 12:20
  • Does `read` puts `stdout` in line buffered mode?, I have to `fflush(stdout);` when I use `read` instead of `getchar()` – David Ranieri Dec 12 '15 at 14:15
  • `read` does not affect `stdout`. Your program may mix other calls such as `write` and `printf` which could be relevant. – Thomas Dickey Dec 12 '15 at 14:35
  • Thank you again Thomas, sorry but I don't get you, before switching from `getchar` to `read`, I was able to `printf("something");` without a trailing newline or a call to `fflush(stdout);`. Something has changed. – David Ranieri Dec 12 '15 at 15:34
  • Perhaps you could add your revised program to the current question (or start a new question). – Thomas Dickey Dec 12 '15 at 15:36
  • Yes, I ask a new question. – David Ranieri Dec 12 '15 at 15:36