While translating an old Basic game to C, I found myself needing a function to get one character from the keyboard. You can't do this with common standard C library functions like getchar()
because the standard input stream is line-buffered (i.e. it will store whole line of input, including the terminating \n
, in its internal buffer although getchar()
only uses its first character. Subsequent calls to getchar()
will consume buffer's remaining characters until exhausted and only then will it resume accepting user's new inputs). This causes problems because if you need inputting twice, the second time you will get some unexpected value instead of letting user entering second character.
Now, I know the way around this is to use operating system calls such as ioctl
and read
to set input to an unbuffered state and read a character (or better yet use a library like Curses
which abstracts away all this in a cross-platform way.) but I started wondering if it was possible to do this entirely via the standard C library.
My first attempt was to empty out the remaining characters in stdin
's buffer by adding a call to:
fflush(stdin);
...after calling getchar()
but this didn't do anything.
My next attempt was to try and take stdin
out of line buffer mode like this:
setvbuf(stdin, NULL, _IONBF, 0);
I had high hopes this would work but actually it seems calling setvbuf
on stdin
is undefined behavior. It certainly does not work on Linux.
So finally I came up with this function. It works but I have a nagging feeling it could be improved. What do you think?
int getkey(const char* prompt = "") {
/* Print the prompt message if there is one */
if (strcmp(prompt, "") != 0) {
puts(prompt);
}
/* Get a character and examine it. If it is a newline from a previous
call to this function eat it otherwise put it back in the buffer. */
int c = getchar();
if (c != '\n') {
ungetc(c, stdin);
}
/* This is the character we really want. */
c = getchar();
/* Drain the input buffer so any extra characters which were pressed are
discarded except for newline which is needed to actually send the input
to stdin. */
int next;
while(!feof(stdin) && next != '\n') {
next = getchar();
}
return c;
}