[comp.unix.wizards] How to write keypressed

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.