Reputation: 926
I'm trying to detect whether there is data on stdin for me to read.
Specifically, I've turned off canonical mode using tcsetattr
, so I can read one character at a time (blocking). I want to detect escape sequences like those produced by the arrow keys, but distinguish those from a lone escape key. Additionally, I want to know quickly which was entered; I'm assuming my terminal is reasonably quick and the rest of an escape sequence after the ^[
follows reasonably quickly.
Suppose I already know (e.g. using select(2)
) that there's something to read on stdin. I read a character using getchar()
, and it's a ^[
, ASCII 27. Now I want to know whether there is more coming within a certain time interval, or this escape character is everything (which would indicate a hit of the escape key). Using select(2)
for this doesn't seem to work, since (I noticed, and read elsewhere) the rest of the characters are already buffered, so select(2)
has nothing to detect anymore. So I turned to ioctl(2)
using FIONREAD
, but that doesn't seem to work either.
Minimal (non-)working example:
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <assert.h>
struct termios tios_bak;
void initkeyboard(void){
struct termios tios;
tcgetattr(0,&tios_bak);
tios=tios_bak;
tios.c_lflag&=~ICANON;
tios.c_cc[VMIN]=1; // Read one char at a time
tios.c_cc[VTIME]=0; // No timeout on reading, make it a blocking read
tcsetattr(0,TCSAFLUSH,&tios);
}
void endkeyboard(void){
tcsetattr(0,TCSAFLUSH,&tios_bak);
}
int main(void){
initkeyboard();
atexit(endkeyboard);
printf("Press an arrow key or the escape key, or the escape key followed by something else.\n");
char c=getchar();
if(c!=27){
printf("Please input an escape sequence or key\n");
exit(1);
}
// Now we use select(2) to determine whether there's anything more to read.
// If it was a lone escape key, there won't be anything new in a while.
fd_set rdset;
FD_ZERO(&rdset);
FD_SET(0,&rdset);
struct timeval tv;
tv.tv_sec=1; // Here we wait one second; this is just to illustrate. In a real environment
tv.tv_usec=0; // I'd wait something like 100ms, since that's reasonable for a terminal.
int ret=select(1,&rdset,NULL,NULL,&tv);
assert(ret!=-1); // (Error checking basically omitted)
if(ret==0){
printf("select(2) returned 0.\n");
int n;
assert(ioctl(0,FIONREAD,&n)>=0);
assert(n>=0);
if(n==0){
printf("ioctl(2) gave 0; nothing to read: lone escape key\n");
// INSERT printf("%c\n",getchar()); HERE TO DEMONSTRATE THIS IS WRONG IN CASE OF ESCAPE SEQUENCE
} else {
c=getchar();
printf("ioctl(2) says %d bytes in read buffer (first char=%c)\n",n,c);
}
} else {
c=getchar();
printf("select(2) returned %d: there was more to read (first char=%c)\n",ret,c);
}
}
Sorry for the long code. What happens is the following:
select(2)
and ioctl(2)
return there's nothing to read, while clearly there is; this can be easily checked by inserting printf("%c\n",getchar());
at the indicated location. That will print [
(in the case of an arrow key, at least).Question: how to correctly detect input in case (3)?
Upvotes: 5
Views: 1551
Reputation: 41046
From the getchar
man page:
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.
Do not mix buffered and unbuffered input functions.
select
must be used in combination with
read(fileno(stdin), &c, 1);
instead of
c = getchar();
Upvotes: 5