jjh@telesoft.UUCP (Jim Hayes @dance) (09/30/88)
I'm working on a user interface that needs to differentiate between the keyboard arrow keys, which send <esc>[<some character>, and the escape key. Fine, says I, I'll just write a little routine to use when I see <esc> to look for a character in the stdin stream. Well, not so fine. The routine I wrote never detected the '[' after the <esc>, so I interpreted all the arrow keys as escape. Ok, maybe a timing problem; I'll try sleeping a bit before I look for a character. This works better, but, if I type arrow keys fast enough, eventually I get the same result -- even though I've typed a bunch of arrows my function tells me there's nothing in the stdin stream after the <esc>. I've included the current incarnation of my function below; can anyone shed some light on what my problem is? Some environmental information: I'm running under Sun3-3.5 Unix, using curses, and I've got the terminal in cbreak mode when I'm calling this routine. I originally tried using select(2) to look for the character; switching to fcntl(2) and read(2) calls improved things significantly. I've tried playing with the amount of time the routine sleeps -- I tried a single long sleep, a single short sleep, incrementally longer sleeps, and, below, a series of short sleeps. The last two seem to work best. /*===========================================================================*/ short keypressed() { /* Returns 1 if a character is available for reading from stdin; 0 otherwise */ char a_char; int old_flags; int result; int retry; if ( isatty( fileno(stdin) ) == 1 ) { old_flags = fcntl(fileno(stdin), F_GETFL, 0); if (old_flags < 0) { return (0); } if ( fcntl(fileno(stdin), F_SETFL, old_flags | FNDELAY) < 0 ) { return (0); } for (retry = 1; retry <= 10; ++retry) { usleep(2000); result = read(fileno(stdin), &a_char, 1); if (result >= 0) { ungetc(a_char, stdin); (void) fcntl(fileno(stdin), F_SETFL, old_flags); return (1); } } (void) fcntl(fileno(stdin), F_SETFL, old_flags); return (0); } else { return (0); } } ------------------------------------------------------------------------------ "There's no problem so awful that you can't | Jim Hayes add some guilt to it and make it even worse!" | ...!ucsd!telesoft!jjh
leo@philmds.UUCP (Leo de Wit) (10/03/88)
In article <315@telesoft.UUCP> jjh@telesoft.UUCP (Jim Hayes @dance) writes: [introduction omitted]... |/*===========================================================================*/ |short keypressed() { |/* Returns 1 if a character is available for reading from stdin; 0 otherwise */ | | char a_char; | int old_flags; | int result; | int retry; | | if ( isatty( fileno(stdin) ) == 1 ) { | old_flags = fcntl(fileno(stdin), F_GETFL, 0); | if (old_flags < 0) { | return (0); | } | if ( fcntl(fileno(stdin), F_SETFL, old_flags | FNDELAY) < 0 ) { | return (0); | } | for (retry = 1; retry <= 10; ++retry) { | usleep(2000); | result = read(fileno(stdin), &a_char, 1); | if (result >= 0) { | ungetc(a_char, stdin); ^^^^^^^^^^^^^^^^^^^^^^ There's your problem: you can't ungetc a character you never read (inspecting the return value of ungetc would indicate an error). Although you read the character, you didn't do it using stdio, so ungetc will rightly fail. The use of both buffered and 'raw' I/O on the same file descriptor should be discouraged (unless you're very sure about what you're doing) since stdio has generally spoken no means to keep track of your reads/writes etc. Suggestion for keypressed() (this one returns the number of characters actually available, but you're free to use it as you like of course): /* Returns # of characters available for reading from stdin, or a (negative) * error indication */ int keypressed() { int nchars, retval; retval = ioctl(fileno(stdin),FIONREAD,&nchars); return (retval < 0) ? retval : nchars; } This one doesn't have your retry enhancements, but I'm sure you can merge that in. If you want to do asynchronous I/O (I don't know if this applies to your application), you can also consider doing something like: fcntl(fileno(stdin),F_SETFL,FASYNC); /* perhaps also F_SETOWN ? */ and trapping the SIGIO signal, perhaps if (signal(SIGIO,SIG_IGN) != SIG_IGN) { signal(SIGIO,your_handler); } Hope this helps - Leo.