Detecting keyboard key press and release on Linux
Your kbhit
(and, apparently, even Windows' original kbhit
) doesn't detect whether a key is pressed, but only whether there is something new to read on stdin
. This will only be the case 25 times a second or so, depending on your autorepeat setting. Making stdout
unbuffered in your example code will make that more obvious (000000W00000000W000000000W
)
How can I fix this so that when a keyboard key is kept pressed, kbhit() always returns 0?
This is impossible to do portably. In Linux, it can be done using the device files under /dev/input
See the example program below. It will register all keys, even a lone SHIFT Note that this is effectively a keylogger, so you will have to run as root (or make the program setuid). It will then register all keystrokes, even when it doesn't have keyboard focus. Yikes!
Note: the example below is based on keystate.c by Kevin Cox
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <glob.h>
#include <linux/input.h>
#include <sys/stat.h>
#include <fcntl.h>
/* Return -1 if no key is being pressed, or else the lowest keycode
(c.f. linux/input-event-codes.h) of all the keys that are being pressed */
int keycode_of_key_being_pressed() {
FILE *kbd;
glob_t kbddev; // Glob structure for keyboard devices
glob("/dev/input/by-path/*-kbd", 0, 0, &kbddev); // Glob select all keyboards
int keycode = -1; // keycode of key being pressed
for (int i = 0; i < kbddev.gl_pathc ; i++ ) { // Loop through all the keyboard devices ...
if (!(kbd = fopen(kbddev.gl_pathv[i], "r"))) { // ... and open them in turn (slow!)
perror("Run as root to read keyboard devices");
exit(1);
}
char key_map[KEY_MAX/8 + 1]; // Create a bit array the size of the number of keys
memset(key_map, 0, sizeof(key_map)); // Fill keymap[] with zero's
ioctl(fileno(kbd), EVIOCGKEY(sizeof(key_map)), key_map); // Read keyboard state into keymap[]
for (int k = 0; k < KEY_MAX/8 + 1 && keycode < 0; k++) { // scan bytes in key_map[] from left to right
for (int j = 0; j <8 ; j++) { // scan each byte from lsb to msb
if (key_map[k] & (1 << j)) { // if this bit is set: key was being pressed
keycode = 8*k + j ; // calculate corresponding keycode
break; // don't scan for any other keys
}
}
}
fclose(kbd);
if (keycode)
break; // don't scan for any other keyboards
}
return keycode;
}
void main()
{
setvbuf(stdout, NULL, _IONBF, 0); // Set stdout unbuffered
while (1) {
int key = keycode_of_key_being_pressed();
printf((key < 0 ? "no key\n" : "keycode: %d\n"), key);
if (key == KEY_X)
exit(0);
}
}
Test whether key is pressed without blocking on Linux
I found a solution :
int khbit() const
{
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return FD_ISSET(STDIN_FILENO, &fds);
}
void nonblock(int state) const
{
struct termios ttystate;
tcgetattr(STDIN_FILENO, &ttystate);
if ( state == 1)
{
ttystate.c_lflag &= (~ICANON & ~ECHO); //Not display character
ttystate.c_cc[VMIN] = 1;
}
else if (state == 0)
{
ttystate.c_lflag |= ICANON;
}
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
}
bool keyState(int key) const //Use ASCII table
{
bool pressed;
int i = khbit(); //Alow to read from terminal
if (i != 0)
{
char c = fgetc(stdin);
if (c == (char) key)
{
pressed = true;
}
else
{
pressed = false;
}
}
return pressed;
}
int main()
{
nonblock(1);
int i = 0;
while (!i)
{
if (cmd.keyState(32)) //32 in ASCII table correspond to Space Bar
{
i = 1;
}
}
nonblock(0);
return 0;
}
It works well. Thanks for helping me. I hope it will help someone :)
Is there a way to detect if a key has been pressed?
I use the following function for kbhit()
. It works fine on g++ compiler in Ubuntu.
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
int kbhit(void)
{
struct termios oldt, newt;
int ch;
int oldf;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);
if(ch != EOF)
{
ungetc(ch, stdin);
return 1;
}
return 0;
}
Check if the Enter key is pressed in a linux bash script
The \n
at the end of the user's input is stripped off before assigning the value to the shell variable. If the user just presses ENTER
, the value read will be the empty string.
read VAR
if [[ -z $VAR ]]; then echo "User pressed ENTER with no input text"; fi
shell script respond to keypress
read -rsn1
Expect only one letter (and don't wait for submitting) and be silent (don't write that letter back).
Related Topics
What's the Point of Eval/Bash -C as Opposed to Just Evaluating a Variable
Bash Command Substitution on Remote Host
How to Have Simple and Double Quotes in a Scripted Ssh Command
Why Does Printf Overwrite the Ecx Register
Meaning of Tilde in Linux Bash (Not Home Directory)
How to Compile/Install Node.Js(Could Not Configure a Cxx Compiler!) (Ubuntu)
How to Perform a For-Each Loop Over All the Files Under a Specified Path
Switching Users Inside Docker Image to a Non-Root User
Node.Js: Cannot Find Module 'Request'
How to Run Nginx Within a Docker Container Without Halting
Vim Configuration for Linux Kernel Development
How to Edit /Etc/Sudoers from a Script
Get Yesterday's Date in Bash on Linux, Dst-Safe
Linux: Where Are Environment Variables Stored
Should I Use Libc++ or Libstdc++
Bash Script: Using "Script" Command from a Bash Script for Logging a Session
Bash Script to Remove 'X' Amount of Characters the End of Multiple Filenames in a Directory