Getting Character-At-A-Time Input in C


You normally can't read input a character at a time from the terminal. By default, the terminal is configured to be in line-by-line mode, so that the editing characters (like the backspace key) can be used by the user when entering a line.

The man page for tty in section 4 of the manual discusses how to change the terminal settings to do things like put the terminal in character-by-character mode so that you can read each character as it is typed. To view this man page, type:

man 4 tty
Below is an example of how to use the functions described in that man page. It is a function called keypressed, which turns on character-by-character mode, then uses another special function call (the FIONREAD ioctl call) to ask the system how many characters are waiting to be read, then turns off character-by-character mode (or, actually, puts it back to whatever it was before the function started); the net result is a determination of whether or not a key has been pressed. Note that this function would not work if it did not turn on character-by-character mode, because FIONREAD would continue to return 0 until the user hit return.
/* Procedure to check for waiting input on the tty.
 * Does not actually read the input.
 */
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <stdio.h>

int
keypressed()
{
     /* These are for ioctl */
     struct sgttyb tty, ntty;
     int ttyset, stat, arg;

     ttyset = 0;

     /*
      * The TIOCGETP ioctl call gets the tty information 
      * structure. See tty(4) for details about the contents
      * of that structure.
      */
     stat = ioctl(0, TIOCGETP, &tty);
     if (stat == -1) {
          perror("ioctl");
          return(-1);
     }

     /*
      * CBREAK is the status flag that controls character 
      * by character input mode.  This if statement checks
      *  to see if CBREAK is already enabled and only enables
      *  it if it is not.
      */
     if (! (tty.sg_flags & CBREAK)) {
          ntty = tty;
          ttyset = (! ttyset);
          /* OR'ing the status bits with CBREAK turns it on. */
          ntty.sg_flags |= CBREAK;
          /* TIOCSETN changes the terminal characteristics, 
           * without discarding pending data.
           */
          stat = ioctl(0, TIOCSETN, &ntty);
          if (stat == -1) {
               perror("ioctl");
               return(-1);
          }
     }

     /* FIONREAD returns the number of characters of waiting
      * input 
      */
     stat = ioctl(0, FIONREAD, &arg);
     if (stat == -1) {
          perror("ioctl");
          return(-1);
     }

     if (ttyset) {
          /* put the tty characteristics back to their 
           * original form 
           */
          stat = ioctl(0, TIOCSETN, &tty);
          if (stat == -1) {
               perror("ioctl");
               return(-1);
          }
     }

     return(arg);
}
last updated: 6/12/95