4

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

recursion.ninja
  • 5,377
  • 7
  • 46
  • 78
  • Related: http://stackoverflow.com/questions/149860/how-to-prevent-fgets-blocks-when-file-stream-has-no-new-data – beatgammit Nov 17 '12 at 01:00
  • possible duplicate of [C non-blocking keyboard input](http://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input) – fvu Nov 17 '12 at 01:02
  • If your program has a tty for stdin, a user typing a key will not provide any input to the program. The tty will not send anything to your program until the user hits 'enter' or sends a signal via ^C or ^] or ^Z. Clarify your question. Do you want to respond to input, or do you want to respond to a keyboard event. The two are different. – William Pursell Nov 17 '12 at 01:03
  • @WilliamPursell Which is easier to implement, input or a keyboard event? – recursion.ninja Nov 17 '12 at 01:05
  • Trying to capture keyboard events is more work. Your problem is simplest if you simply require the user to hit 'enter'/'return', and probably easiest to just require ^C and handle the `SIGINT`. – William Pursell Nov 17 '12 at 01:06
  • Hitting enter/return as an interrupt satisfied my requirements for breaking the loop. The input can be key specific, I just don't want to use a signal to terminate the loop. – recursion.ninja Nov 17 '12 at 01:09
  • 1
    You could use the `poll(2)` syscall. – Basile Starynkevitch Nov 17 '12 at 01:09
  • @BasileStarynkevitch could you provide an answer in which `poll(2)` is used with the sample code above to create a loop that does not wait for user input? – recursion.ninja Nov 17 '12 at 01:28

1 Answers1

4

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;
}
William Morris
  • 3,554
  • 2
  • 23
  • 24