/*
 *  Unix Single Character Keyboard Input
 *  Copyright 2003 by Floyd L. Davidson, floyd@barrow.com
 *
 *  This program is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation;
 *  either version 2, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for details.
 *
 *  getch.c  --  A demo for POSIX single char input.
 *
 *  $Id: getch.c,v 1.1.0.0 2003/11/05 19:27:59 floyd Exp floyd $
 */

/*
 *  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 <time.h>
#include <sys/ioctl.h>
#include <sys/types.h>

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

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

  tcgetattr(STDIN_FILENO, &otty);
  ntty = otty;
  ntty.c_lflag 	 &= ~ICANON; /* raw mode */

  if (0 == (error = tcsetattr(STDIN_FILENO, TCSANOW, &ntty))) {
    error        += ioctl(STDIN_FILENO, FIONREAD, &count);
    error        += tcsetattr(STDIN_FILENO, TCSANOW, &otty);
    /* minimal delay gives up cpu time slice,
     * allows use in a tight loop */
    tv.tv_sec     = 0;
    tv.tv_nsec    = 10;
    nanosleep(&tv, NULL);
  }

  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)
{
  unsigned char		ch;
  int			error;
  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]  = 1;      /* block for input  */
  ntty.c_cc[VTIME] = 0;      /* timer is ignored */

#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;

  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;
    }
  }

   /* disable echoing keyboard input */
  tcgetattr(STDIN_FILENO, &otty);
  ntty = otty;
  ntty.c_lflag &= ~ECHO;
  tcsetattr(STDIN_FILENO, TCSANOW, &ntty);

  printf("\nSTOP!");
  fflush(stdout);
  printf("\nNow type <Enter> to continue!");
  fflush(stdout);
  ch = getchar();

  /* re-enable echoing */
  tcsetattr(STDIN_FILENO, TCSANOW, &otty);
  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;
}