Search code examples
cinfinite-loopuser-interaction

How to make a C program stop a loop from any user input?


I would like to writing a small C program that runs an infinite loop until the user presses a key on the keyboard (ie: there is a char in the stdin buffer). I am running into trouble breaking the loop on user input. I have tried using fgetc but that does not behave as expected. The code below waits for user input rather then running until user input.

Sample C Code:

while((c=fgetc(stdin) == EOF) {
  /* Does stuff for infinite loop here */
  printf("Example work in the loop\n");
}
printf("Out of the loop!\n");

How do I write a loop that executes until user intervention? Pressing any key or a specific key could be the intervention trigger.

Note 1: I am writing this for a Unix console in case of platform specific solutions

Note 2: Do not suggest Ctrl + C/X/Z as the user intervention trigger


Solution

  • This seems to work for me:

    #include <fcntl.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/uio.h>
    #include <unistd.h>
    
    static void set_non_blocking(int fd)
    {
        int flags  = fcntl(fd, F_GETFL, 0 );
        flags |= O_NONBLOCK;
        flags = fcntl(fd, F_SETFL, flags);
    }
    
    
    int main(int argc, char ** argv)
    {
        int fd = fileno(stdin);
        char buf[10];
    
        set_non_blocking(fd);
    
        while (read(fd, buf, sizeof buf) < 0) {
            perror("read");
            sleep(1);
        }
        return 0;
    }
    

    or you could use select:

    int main(int argc, char ** argv)
    {
        int fd = fileno(stdin);
        struct timeval tv = {0,0};
        fd_set fdset;
        int s;
    
        do {
            sleep(1);
            FD_ZERO(&fdset);
            FD_SET(fd, &fdset);
    
        } while ((s = select(fd+1, &fdset, NULL, NULL, &tv)) == 0);
    
        if (s < 0) {
            perror("select");
        }
        return 0;
    }
    

    Poll works too :-)

    int main(int argc, char ** argv)
    {
        struct pollfd pfd;
        int s;
    
        pfd.fd = fileno(stdin);
        pfd.events = POLLRDNORM;
    
        while ((s = poll(&pfd, 1, 0)) == 0) {
            perror("polling");
            sleep(1);
        }
        if (s < 0) {
            perror("poll");
        }
        return 0;
    }
    

    One last way is to set the terminal in 'raw' mode. Note that this upsets output to the terminal (at least on mine on OS-X) in that \r becomes necessary after \n. Note also that it needs to be undone at the end (the terminating tcsetattr call). This is the only one that does not need a \n (ie any keypress will do)

    #include <poll.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <termios.h>
    
    
    static void set_non_blocking(int fd)
    {
        int flags = fcntl(fd, F_GETFL, 0) | O_NONBLOCK;
    
        if (fcntl(fd, F_SETFL, flags) < 0) {
            perror("fcntl");
            exit(EXIT_FAILURE);
        }
    }
    
    
    int main(int argc, char ** argv)
    {
        struct termios params;
        struct termios params_orig;
        char buf[10];
        int fd = fileno(stdin);
    
        if (tcgetattr(fd, &params) < 0) {
            perror("tcgetattr");
            exit(EXIT_FAILURE);
        }
        params_orig = params;
    
        cfmakeraw(&params);
    
        if (tcsetattr(fd, TCSANOW, &params) < 0) {
            perror("tcsetattr");
            exit(EXIT_FAILURE);
        }
        set_non_blocking(fd);
    
        while (read(fd, buf, sizeof buf) < 0) {
            perror("\rread");
            sleep(1);
        }
    
        (void) tcsetattr(fd, TCSANOW, &params_orig);
        return 0;
    }