How to detect key presses in a Linux C GUI program without prompting the user?

You have to modify terminal settings using termios. See Stevens & Rago 2nd Ed ‘Advanced Programming in the UNIX Environment’ it explains why tcsetattr() can return successfuly without having set all terminal characteristcs, and why you see what looks to be redundant calls to tcsetattr().

This is ANSI C in UNIX:

#include <sys/types.h>
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

int checktty(struct termios *p, int term_fd)
{
    struct termios ck;
    return (
       tcgetattr(term_fd, &ck) == 0 &&
      (p->c_lflag == ck.c_lflag) &&
      (p->c_cc[VMIN] == ck.c_cc[VMIN]) &&
      (p->c_cc[VTIME] == ck.c_cc[VMIN])
    );
}


int
keypress(int term_fd)
{
     unsigned char ch;
   int retval=read(term_fd, &ch, sizeof ch);
   return retval;
}

int   /* TCSAFLUSH acts like fflush for stdin */
flush_term(int term_fd, struct termios *p)
{
   struct termios newterm;
   errno=0;
   tcgetattr(term_fd, p);  /* get current stty settings*/

   newterm = *p; 
   newterm.c_lflag &= ~(ECHO | ICANON); 
   newterm.c_cc[VMIN] = 0; 
   newterm.c_cc[VTIME] = 0; 

   return( 
       tcgetattr(term_fd, p) == 0 &&
       tcsetattr(term_fd, TCSAFLUSH, &newterm) == 0 &&
       checktty(&newterm, term_fd) != 0
   );
}
void 
term_error(void)
{
     fprintf(stderr, "unable to set terminal characteristics\n");
     perror("");                                                
     exit(1);                                                   
}


void
wait_and_exit(void)
{
    struct timespec tsp={0,500};  /* sleep 500 usec (or likely more ) */
    struct termios  attr;
    struct termios *p=&attr;
    int keepon=0;
    int term_fd=fileno(stdin);

    fprintf(stdout, "press any key to continue:");
    fflush(stdout);
    if(!flush_term(term_fd, p) )
       term_error();
    for(keepon=1; keepon;)
    {
        nanosleep(&tsp, NULL);
        switch(keypress(term_fd) )
        {
              case 0:
              default:
                 break;
            case -1:
                 fprintf(stdout, "Read error %s", strerror(errno));
                 exit(1);
                 break;
            case 1:       /* time to quit */
                 keepon=0;
                 fprintf(stdout, "\n");
                 break;                 
        } 
    }
    if( tcsetattr(term_fd, TCSADRAIN, p) == -1 && 
          tcsetattr(term_fd, TCSADRAIN, p) == -1 )
          term_error();
    exit(0);
}

int main()
{
      wait_and_exit();
      return 0;  /* never reached */
}

The nanosleep call is there to prevent the code from gobbling up system resources. You could call nice() and not use nanosleep(). All this does is sit and wait for a keystroke, then exit.

Leave a Comment