Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C non-blocking keyboard input

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 ctrl-c keypress to do cleanup before the program closes instead of non-blocking io?

like image 496
Zxaos Avatar asked Jan 15 '09 23:01

Zxaos


People also ask

How do I make Getchar not blocked?

Re: Non Blocking getchar()? You need to do 2 things. The first is to put the input (stdin) into RAW mode. (this also suppresses echo) The second is to call ioctl with the FIONREAD parameter which will return the number of bytes available to be read.

What is non blocking IO in C?

Sometimes it's convenient to have I/O that doesn't block i.e we don't want a read call to block on one in case of input from the other. Solution for this is the given function: To specify non-blocking option: #include<fcntl. h> int fd; fcntl(fd, F_SETFL, O_NONBLOCK);


2 Answers

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 <unistd.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_ZERO(&fds);     FD_SET(0, &fds);     return select(1, &fds, NULL, NULL, &tv) > 0; }  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 */ } 
like image 103
Alnitak Avatar answered Sep 20 '22 08:09

Alnitak


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.

like image 40
Norman Ramsey Avatar answered Sep 18 '22 08:09

Norman Ramsey