Re: Checking for a keypress on Linux ?????

From: Floyd Davidson (floyd_at_barrow.com)
Date: 09/12/03


Date: 12 Sep 2003 02:17:21 -0800


"Koen" <no@ssppaamm.com> wrote:
>Hi,
>
>could anyone please tell me how one can check (using C or C++ code)
>whether a key was pressed on the keyboard? This is for use with
>console based applications that don't have fancy UI, but do need
>polling for (or callback on) a keypress on the keyboard. I'm not
>talking about getting a character from the input queue, since that
>won't work until you press enter...
>In the Windows world, this is *very* simple using kbhit() from
>conio.h, but I didn't find anything in Linux and of course I must be
>able to do it on Linux too!
>
>Koen

There are at least three basic, and relatively simple, ways to
write a kbhit() function for unix. However, there are many more
minor variations one might choose to implement, and there are
also some tricks that don't meet the eye immediately.

The below demo program is one that I've been posting for more
than a decade. It has undergone a number of changes, as it
originally used K&R C and termio instead of POSIX termios.

There have also been modification as a result of feedback from
people who've tried it. For example, the short delay in kbhit()
was not originally there, until some fool who didn't know you
shouldn't put it in a tight loop did. It dropped characters!
So that fool convinced the fool who knew better to make is work
in that tight loop too... ;-)

Do *not* take this as *the* definition of kbhit(), it isn't.
What it is, is just a demonstration of where you want to start,
and with a few hints about how to avoid some problems. Some of
the hints are not explicit, so when you go to change something
it would be very wise to think very hard about what the total
effect will be.

/*
 * kbhit(), a keyboard lookahead monitor
 * getch(), a blocking single character input from stdin
 *
 * Plus a demo main() to illustrate usage.
 */

#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>

int getch(void);
int kbhit(void);

#define CMIN 1

#ifdef CTIME
#undef CTIME
#endif

#define CTIME 1

/*
 * kbhit() -- a keyboard lookahead monitor
 *
 * returns the number of characters available to read.
 */
int
kbhit(void)
{
  int count = 0;
  int error;
  static struct termios Otty, Ntty;

  tcgetattr(STDIN_FILENO, &Otty);
  Ntty = Otty;

  Ntty.c_lflag &= ~ICANON; /* raw mode */
  Ntty.c_cc[VMIN] = CMIN; /* minimum chars to wait for */
  Ntty.c_cc[VTIME] = CTIME; /* minimum wait time */

  if (0 == (error = tcsetattr(STDIN_FILENO, TCSANOW, &Ntty))) {
    struct timeval tv;
    error += ioctl(STDIN_FILENO, FIONREAD, &count);
    error += tcsetattr(STDIN_FILENO, TCSANOW, &Otty);
    /* minimal delay gives up cpu time slice, and
     * allows use in a tight loop */
    tv.tv_sec = 0;
    tv.tv_usec = 10;
    select(1, NULL, NULL, NULL, &tv);
  }

  return (error == 0 ? count : -1 );
}

/*
 * getch() -- a blocking single character input from stdin
 *
 * Returns a character, or -1 if an input error occurs.
 *
 * Conditionals allow compiling with or without echoing of
 * the input characters, and with or without flushing
 * pre-existing existing buffered input before blocking.
 *
 */
int
getch(void)
{
  char ch;
  int error;
  static struct termios Otty, Ntty;

  fflush(stdout);
  tcgetattr(STDIN_FILENO, &Otty);
  Ntty = Otty;

  Ntty.c_lflag &= ~ICANON; /* line settings */

#if 1
  /* disable echoing the char as it is typed */
  Ntty.c_lflag &= ~ECHO; /* disable echo */
#else
  /* enable echoing the char as it is typed */
  Ntty.c_lflag |= ECHO; /* enable echo */
#endif

  Ntty.c_cc[VMIN] = CMIN; /* minimum chars to wait for */
  Ntty.c_cc[VTIME] = CTIME; /* minimum wait time */

#if 1
/*
 * use this to flush the input buffer before
 * blocking for new input
 */
#define FLAG TCSAFLUSH
#else
/*
 * use this to return a char from the current input buffer,
 * or block if no input is waiting.
 */
#define FLAG TCSANOW

#endif

 if (0 == (error = tcsetattr(STDIN_FILENO, FLAG, &Ntty))) {
   /* get a single character from stdin */
   error = read(STDIN_FILENO, &ch, 1 );
   /* restore old settings */
   error += tcsetattr(STDIN_FILENO, FLAG, &Otty);
  }

  return (error == 1 ? (int) ch : -1 );
}

/*
 * a cutsie main() to demo how getch() and kbhit() work.
 */

#include <ctype.h>

int
main(void)
{
  int ch, count;
  static struct termios Otty, Ntty;

  tcgetattr(STDIN_FILENO, &Otty);
  Ntty = Otty;
  Ntty.c_lflag &= ~ECHO;

  printf("You must enter 10 characters to get\n");
  printf("this program to continue: ");
  fflush(stdout);
  /* collect 10 characters */
  while (1) {
    if (10 <= (count = kbhit())) {
      break;
    }
  }

  tcsetattr(STDIN_FILENO, TCSANOW, &Ntty); /* disable echoing */
  printf("\nSTOP!");
  fflush(stdout);
  printf("\nNow type <Enter> to continue!");
  fflush(stdout);
  ch = getchar();
  tcsetattr(STDIN_FILENO, TCSANOW, &Otty); /* enable echoing */
  printf("\n\nThe first five characters are: \"");
  /*
   * print a few chars, note that calling getch() will flush
   * remaining buffered input.
   */
  count = 4;
  do {
    printf("%c", ch);
    ch = getchar();
  } while (--count);
  printf("%c\"\n\n", ch);
  
  printf("\n\n *** Demo Menu ***\n\n");
  printf(" Option Action\n");
  printf(" A Exit and print an A word\n");
  printf(" B Exit and print a B word\n");
  printf(" C Exit and print a C word\n");
  printf("\n Enter your choice: ?\b");
  fflush(stdout);

  while(1) {
    switch (ch = getch()) {
    case 'a': case 'A':
      printf("%c\n\nThat is an *awesome* function!\n",
             toupper(ch));
      break;
    case 'b': case 'B':
      printf("%c\n\nThat is a *beneficial* function!\n",
             toupper(ch));
      break;
    case 'c': case 'C':
      printf("%c\n\nThat is a *critical* function!\n",
             toupper(ch));
      break;
    default:
      continue;
    }
    break;
  }
  return 0;
}

-- 
Floyd L. Davidson           <http://web.newsguy.com/floyd_davidson>
Ukpeagvik (Barrow, Alaska)                         floyd@barrow.com


Relevant Pages

  • Re: pressed 2 keys
    ... By installing your own keyboard handler interrupt. ... character value. ... public INPC ... INT 16H;COPY BYTE AL, CODE AH ...
    (comp.lang.asm.x86)
  • PC-like keyboard for console
    ... ALT-<keyboard character> does not produce the character ... My ALT key is working, ... I installed the emacs keyboard and now the NUM ... # code base shift cntrl shift alt shift cntrl shift state ...
    (comp.unix.bsd.freebsd.misc)
  • In a timeline pinch (Suspense: 25Jul05) pleading for assistance - Q1
    ... It will print out the computer's internal code for the character '7' ... It is never necessary to use an int for character processing. ... making an assignment to a pointer variable. ...
    (comp.lang.c)
  • [PATCH 36/70] ftdi_sio: Coding style
    ... unsigned short int divisor; ... /* Write an event character directly to the FTDI register. ... BmRequestType: 0100 0000B ... Configuration Descriptor ...
    (Linux-Kernel)
  • Re: K&R exercise 1-18
    ... _without_ the string terminator and has MAXIMUM+1 array elements. ... char lineis in this context equivalent to char *line ... int main ... You lose one character by overwriting it with '\n'. ...
    (comp.lang.c)