C non-blocking keyboard input
Solution 1
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 */
}
Solution 2
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.
Solution 3
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.
Solution 4
Another way to get non-blocking keyboard input is to open the device file and read it!
You have to know the device file you are looking for, one of /dev/input/event*. You can run cat /proc/bus/input/devices to find the device you want.
This code works for me (run as an administrator).
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
int main(int argc, char** argv)
{
int fd, bytes;
struct input_event data;
const char *pDevice = "/dev/input/event2";
// Open Keyboard
fd = open(pDevice, O_RDONLY | O_NONBLOCK);
if(fd == -1)
{
printf("ERROR Opening %s\n", pDevice);
return -1;
}
while(1)
{
// Read Keyboard Data
bytes = read(fd, &data, sizeof(data));
if(bytes > 0)
{
printf("Keypress value=%x, type=%x, code=%x\n", data.value, data.type, data.code);
}
else
{
// Nothing read
sleep(1);
}
}
return 0;
}
Solution 5
You probably want kbhit();
//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>
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();
mfonda
Updated on December 02, 2021Comments
-
mfonda over 2 years
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?
-
mfonda over 15 yearsI 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.
-
Alnitak over 15 yearsdefinitely not portable. kbhit() is a DOS/Windows console IO function.
-
Alnitak over 15 yearsthis answer is incomplete. without terminal manipulation with tcsetattr() [see my answer] you won't get anything on fd 0 until you press enter.
-
Sunilsingh over 15 yearsIt's still a valid answer many uses. The OP didn't specify portability or any particular platform.
-
Alnitak over 15 yearsthey didn't. however use of terms such as "non-blocking" suggests Unix.
-
mfonda over 15 yearsAlnitak: I wasn't aware it implied a platform - what's the equivalent Windows term?
-
Alnitak over 15 yearsyou can do non-blocking IO on windows too, but the concept (along with use of select) is more familiar to Unix programmers (IMHO)
-
Alnitak over 15 yearsI 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.
-
Norman Ramsey over 15 yearsYeah, curses is pretty all or nothing.
-
Wayne Werner almost 14 yearscurses 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.
-
Salepate almost 12 yearsIs getch() supposed to return which key was pressed, or is it simply supposed to consume the character?
-
Alnitak almost 12 years@Salepate it's supposed to return the character. It's the
(void)
bit that gets that character and then throws it away. -
Salepate almost 12 yearsoh I see, maybe i'm doing it wrong but when i use getch() on a printf("%c"); it displays weird characters on the screen.
-
jernkuan about 11 yearsSlight edit, you need to #include <unistd.h> for read() to work
-
azmeuk about 10 yearsIs there a simple way to read the whole line (for instance with 'getline') instead of a single character ?
-
MestreLion about 9 years@Alnitak why would "non-blocking", as a programming concept, be more familiar in Unix environment?
-
Alnitak about 9 years@MestreLion Maybe now (six years later) that's no longer the case. It was always there in POSIX, though.
-
MestreLion about 9 years@Alnitak: I mean I was familiar with the term "non-blocking call" long before touching any *nix.. so just like Zxaos I would never realize it would imply a platform.
-
luser droog about 8 yearsThis answer is incomplete. You need to set the terminal to non-canonical mode. Also
nfds
should be set to 1. -
étale-cohomology over 7 yearsThe switch statement is missing one bracket :)
-
ThorSummoner over 4 yearsfantastic example, but I ran into an issue where Xorg would keep receiving key events, when the event device stopped emitting when more than six keys were held :\ weird right?
-
Nathan B almost 4 yearswhat's the purpose of the non blocking kbhit()? just to know if there are characters left to consume by the blocking getch()? How long does the character pressed 'stay alive' before we should check its existence with kbhit()?
-
Alnitak almost 4 years@NadavB yes,
kbhit()
just tells you if there are any characters waiting to be read, and it'll remain valid forever, so long as that's true. -
Admin almost 3 yearsThe first reading is fine, but the following reads bring up two characters. The first I supposse is the enter of the previous one and the second is the key itself.
-
Max888 almost 3 yearsThis seems to remove the ability to exit the program with ctrl c, is it possible to amend the above to avoid this?
-
Alnitak almost 3 years@Max888 not portably, but you could exit the program yourself if the next retrieved character is ^c (0x03)
-
BlueChip over 2 yearsThanks for an elegant solution ...I have an interrupt timer running which causes
kbhit()
to occasionally return-1 == EINTR
...This meanskbhit()
returnstrue
when it should (arguably) returnfalse
...I have fixed this by changing thereturn
statement inkbhit()
toreturn (select(1, &fds, NULL, NULL, &tv) > 0);
-
Alnitak over 2 years@BlueChip good point - code updated accordingly!