views:

6170

answers:

8

I'm trying to write a program in C (on linux) that loops until the user presses a key, but shouldn't require a keypress to continue each loop.

Is there a simple way to do this? I figure I could possibly do it with select() but that seems like a lot of work.

Alternatively, is there a way to catch a control-c keypress to do cleanup before the program closes instead of non-blocking io?

+3  A: 

On UNIX systems, you can use sigaction call to register a signal handler for SIGINT signal which represents the Control+C key sequence. The signal handler can set a flag which will be checked in the loop making it to break appropriately.

Mehrdad Afshari
I think I'll probably end up doing this for ease, but I'm going to accept the other answer since it is closer to what the title asks.
Zxaos
+1  A: 

There is no portable way to do this, but select() might be a good way. See http://c-faq.com/osdep/readavail.html for more possible solutions.

Nate879
this answer is incomplete. without terminal manipulation with tcsetattr() [see my answer] you won't get anything on fd 0 until you press enter.
Alnitak
+1  A: 

The curses library can be used for this purpose. Of course, select() and signal handlers can be used too to a certain extent.

PolyThinker
curses is both the name of the library and what you'll mutter when you use it ;) However, since I was going to mention it +1 to you.
Wayne Werner
A: 

You probably want kbhit();

//Example will loop until a key is pressed
#include 
#include 

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}

this may not work on all environments. A portable way would be to create a monitoring thread and set some flag on getch();

Jon Clegg
definitely not portable. kbhit() is a DOS/Windows console IO function.
Alnitak
It's still a valid answer many uses. The OP didn't specify portability or any particular platform.
AShelly
they didn't. however use of terms such as "non-blocking" suggests Unix.
Alnitak
Alnitak: I wasn't aware it implied a platform - what's the equivalent Windows term?
Zxaos
you can do non-blocking IO on windows too, but the concept (along with use of select) is more familiar to Unix programmers (IMHO)
Alnitak
+4  A: 

As already stated, you can use sigaction to trap ctrl-c, or select to trap any standard input.

Note however that with the latter method you also need to set the TTY so that it's in character-at-a-time rather than line-at-a-time mode. The latter is the default - if you type in a line of text it doesn't get sent to the running program's stdin until you press enter.

You'd need to use the tcsetattr() function to turn off ICANON mode, and probably also disable ECHO too. From memory, you also have to set the terminal back into ICANON mode when the program exits!

Just for completeness, here's some code I've just knocked up (nb: no error checking!) which sets up a Unix TTY and emulates the DOS <conio.h> functions kbhit() and getch():

#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <termios.h>

struct termios orig_termios;

void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}

void set_conio_terminal_mode()
{
    struct termios new_termios;

    /* take two copies - one for now, one for later */
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));

    /* register cleanup handler, and set the new terminal mode */
    atexit(reset_terminal_mode);
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}

int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}

int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0) {
     return r;
    } else {
     return c;
    }
}

int main(int argc, char *argv[])
{
    set_conio_terminal_mode();

    while (!kbhit()) {
     /* do some work */
    }
    (void)getch(); /* consume the character */
}
Alnitak
+2  A: 

select() is a bit too low-level for convenience. I suggest you use the ncurses library to put the terminal in cbreak mode and delay mode, then call getch(), which will return ERR if no character is ready:

WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);

At that point you can call getch without blocking.

Norman Ramsey
I thought about using curses too, but the potential problem with that is that the initscr() call clears the screen, and it also maybe gets in the way of using normal stdio for screen output.
Alnitak
Yeah, curses is pretty all or nothing.
Norman Ramsey
A: 

If you are happy just catching Control-C, it's a done deal. If you really want non-blocking I/O but you don't want the curses library, another alternative is to move lock, stock, and barrel to the AT&T sfio library. It's nice library patterned on C stdio but more flexible, thread-safe, and performs better. (sfio stands for safe, fast I/O.)

Norman Ramsey
A: 

Re: primary answer, add FD_ZERO(&fds); before FD_SET to avoid premature wakeup due to uninitialized variable with random value, and another fd that happens to be ready for input.

Glenn Kasten
You're better putting this as a comment to the primary answer rather than a new answer. :)
Zxaos