23 Nov 2009

Getch() function in Linux

Some long time ago I was searching for GetCh() implementation on Linux, and I've entered google new groups and found some good stuff. Today durning some cleanings on my system I've found saved html page with this, so i will paste it here (this blog is my notepad recenty). Post wost starting like this: Anybody have any *GetCh*() style non-blocking code that works? I've seen some samples out there, have grabbed a few, but haven't had any luck. The one's I've tried have all used the curses library. I was successful with doing a basic intscr(), cbreak(), noecho(), and *getch*().. but when I use this method.. The X-term closes when the program exits. If I don't call *getch*() from within the program (just the startup and shutdown curses functions), it closes properly. Anyone? And the best anwser was posted by Floyd L. Davidson. Here is a similar variation on *getch*(), which includes a kbhit() function.
/*
 *  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                   cnt = 0;
  int                   error;
  static struct termios Otty, Ntty;

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

  Ntty.c_iflag          = 0;       /* input mode                */
  Ntty.c_oflag          = 0;       /* output mode               */
  Ntty.c_lflag         &= ~ICANON; /* line mode                 */
  Ntty.c_cc[VMIN]       = CMIN;    /* minimum time to wait      */
  Ntty.c_cc[VTIME]      = CTIME;   /* minimum characters to wait for */

  if (0 == (error = tcsetattr(0, TCSANOW, &Ntty))) {
    struct timeval      tv;
    error     += ioctl(0, FIONREAD, &cnt);
    error     += tcsetattr(0, TCSANOW, &Otty);
    tv.tv_sec  = 0;
    tv.tv_usec = 100;
    select(1, NULL, NULL, NULL, &tv);  /* a small time delay */
  }

  return (error == 0 ? cnt : -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(0, &Otty);
  Ntty = Otty;

  Ntty.c_iflag  =  0;           /* input mode           */
  Ntty.c_oflag  =  0;           /* output mode          */
  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 time to wait */
  Ntty.c_cc[VTIME] = CTIME;     /* minimum chars to wait for */

#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(0, FLAG, &Ntty))) {
    error  = read(0, &ch, 1 );                /* get char from stdin */
    error += tcsetattr(0, FLAG, &Otty);   /* restore old settings */
  }

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

}

/*
 * a cutsie main() to demo how *getch*() and kbhit() work.
 */
#include <ctype.h>

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

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

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

  tcsetattr(0, TCSANOW, &Ntty); /* disable echoing of further input */
  printf("\nSTOP!");
  fflush(stdout);
  printf("\nNow type <Enter> to continue!");
  fflush(stdout);
  ch = getchar();
  tcsetattr(0, TCSANOW, &Otty); /* enable echoing of further input */
  printf("\n\nThe first five characters are:  \"");
  /*
   * print a few chars, note that calling *getch*() will flush
   * remaining buffered input.
   */
  cnt = 4;
  do {
    printf("%c", ch);
    ch = getchar();
  } while  (--cnt);
  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;

}
Other recomendations:
  • W. Richard Stevens - "Advanced Programming in the Unix Environment"
  • man nodelay (a curses function)

1 comment:

  1. it's what I have been looking for, thanks!

    ReplyDelete