I have some code which currently checks if the character "b" is being pressed:
#include <iostream>
#include <termios.h>
#include <unistd.h>
using namespace std;
char getKeyPress() {
struct termios oldt, newt;
char ch;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
int main() {
cout << "Press 'b' to see if it's detected. Press 'q' to quit." << endl;
while (true) {
char key = getKeyPress();
if (key == 'b') {
cout << "'b' is currently pressed." << endl;
}
if (key == 'q') {
cout << "Exiting..." << endl;
break;
}
}
return 0;
}
However in my real code base, I need to check for multiple characters being pressed. How could I extend this code to check if "b" or "n" are being pressed, and if both "b" and "n" are being pressed at the same time?
You need to change the terminal mode to raw or medium-raw whitch than sends mousedown-mouseup events, and then convert the events back to letters/symbols/keys
#include <sys/ioctl.h>
#include <unistd.h>
#include <termios.h>
#include <stdio.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
static const char *conspath[] = {
"/proc/self/fd/0",
"/dev/tty",
"/dev/tty0",
"/dev/vc/0",
"/dev/systty",
"/dev/console",
NULL
};
/*
* getfd.c
*
* Get an fd for use with kbd/console ioctls.
* We try several things because opening /dev/console will fail
* if someone else used X (which does a chown on /dev/console).
*/
static int
is_a_console(int fd)
{
char arg;
arg = 0;
return (isatty(fd) && ioctl(fd, KDGKBTYPE, &arg) == 0 && ((arg == KB_101) || (arg == KB_84)));
}
static int
open_a_console(const char *fnam)
{
int fd;
/*
* For setkbdmode we need write permissions
* and for getkbdmode we need read permissions
* so open with READ-WRITE
*/
fd = open(fnam, O_RDWR);
if (fd < 0)
return -1;
return fd;
}
int
getfd(const char *fnam)
{
int fd, i;
if (fnam) {
if ((fd = open_a_console(fnam)) >= 0) {
if (is_a_console(fd))
return fd;
close(fd);
}
return -1;
}
for (i = 0; conspath[i]; i++) {
if ((fd = open_a_console(conspath[i])) >= 0) {
if (is_a_console(fd))
return fd;
close(fd);
}
}
for (fd = 0; fd < 3; fd++)
if (is_a_console(fd))
return fd;
return -1;
}
int fd = getfd(NULL); // might need root permissions or sid bit in premissions (sudo chown root ./prog && sudo chmod u+s ./prog)
if (!fd) exit 0x12;
int old_kbdmode;
if (ioctl(fd, KDGKBMODE, &old_kbdmode)) {
throw("ioctl KDGKBMODE error");
}
int mode = K_MEDIUMRAW;
if ((err = ioctl(fd, KDSKBMODE, mode))) {
//* note - this might not work on ubuntu in a shared object file.
//* In a case like this just create a setkbdmode binary file
//* and use it from the dll
close(fd);
printf("ioctl KDSKBMODE error %d\n", errno);
return -1;
}
termios old_termios;
tcgetattr(STDIN_FILENO,&old_termios);
termios term_ios = old_termios;
term_ios.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &term_ios);
tcgetattr(fd,&old_fdterm);
term_ios = old_fdterm;
term_ios.c_lflag &= ~(ICANON | ECHO | ISIG);
term_ios.c_iflag = 0;
term_ios.c_cc[VMIN] = 0xff;
term_ios.c_cc[VTIME] = 1;
tcsetattr(fd, TCSANOW, &term_ios);
3.1(optional but usefull): define an enum class with keys for easeier usage: At the bottom of this file (just copy it)
3.2
// if you skipped 3.1 use [unsigned short] instead of [enum Key]
enum Key key_chart[MAX_NR_KEYMAPS][KEYBOARD_MAX]; // probably [255][255]
for (auto t = 0; t < MAX_NR_KEYMAPS; t++) {
if (t > UCHAR_MAX) {
exit(18);
}
for (auto i = 0; i < NR_KEYS; i++) {
if (i > UCHAR_MAX) {
exit(18);
}
struct kbentry ke;
ke.kb_table = (unsigned char) t;
ke.kb_index = (unsigned char) i;
ke.kb_value = 0;
if (ioctl(fd, KDGKBENT, (unsigned long)&ke)) {
exit(19);
}
if (!i && ke.kb_value == K_NOSUCHMAP)
break;
if (KTYP(ke.kb_value) == KT_LETTER) // weird kernel thing to show whitch writable characters can be acted by a capslock (just ignore it)
ke.kb_value = K(KT_LATIN, KVAL(ke.kb_value));
if (ke.kb_value == UINT16_MAX) {
// ? idk mabe just push back UNDEFIEND
exit(20);
}
key_chart[t][i] = static_cast<enum Key>(ke.kb_value);
}
}
inline constexpr int parse_input(const char * buf, int n) {
int out = 0;
for (int i = 0; i < n; i++) {
out = buf[i] & 0x7f; // set keycode
out += buf[i] & 0x80 ? 0x100 : 0x000; // add bit 0 if press bit 1 if release
}
return out;
};
4.2 Handle Keyboard
std::bitset<KEYBOARD_MAX> key_states(0);
int key_hit = -1;
int key_released = -1;
void HandleKeyboard(void) {
int bytes, parsed, len;
char buf[1];
key_hit = -1;
key_released = -1;
int old_hit = -1;
ioctl(fd, FIONREAD, &bytes);
if (!bytes) return;
ReadKeyboardAction:
len = read(fd, buf, sizeof(buf)); bytes -= len;
// TODO -> parse multi-byte sequences
//(not sure if they even exist - they don't in the en-us layout)
if (len <= 0) {
if (len < 0) {
fprintf(stderr, "\nread error: %d\n", errno);
exit(15);
}
return;
}
parsed = parse_input(buf,len);
if ( (!key_states[parsed % KEYBOARD_MAX]) && (!(parsed / KEYBOARD_MAX)) ) key_hit = parsed % KEYBOARD_MAX;
if ( key_states[parsed % KEYBOARD_MAX] && (parsed / KEYBOARD_MAX) ) key_released = parsed % KEYBOARD_MAX;
key_states[parsed % KEYBOARD_MAX] = !(parsed / KEYBOARD_MAX);
if (bytes > 0) goto ReadKeyboardAction;
bool IsKeyDown(enum Key key) {
for (auto i = 0; i < KEYBOARD_MAX; ++i)
if (key_states[i] && key_chart[0][i] == key)
return true;
return false;
}
enum Key KeyPressed(void) {
if (key_hit < 0) return Key::NONE;
return key_chart[0][key_hit];
}
enum Key KeyReleased(void) {
if (key_released < 0) return Key::NONE;
return key_chart[0][key_released];
}
void Fin(void) {
tcsetattr(fd,TCSANOW,&old_fdterm);
tcsetattr(STDIN_FILENO,TCSANOW,&old_termios);
ioctl(fd, KDSKBMODE, old_kbdmode)
close(fd);
}
This mean's that you should alway run this at exit:
// run this right after init
atexit(Fin);
at_quick_exit(Fin);
signal(SIGHUP, quick_exit);
signal(SIGINT, quick_exit);
signal(SIGQUIT, quick_exit);
signal(SIGILL, quick_exit);
signal(SIGTRAP, quick_exit);
signal(SIGABRT, quick_exit);
signal(SIGIOT, quick_exit);
signal(SIGFPE, quick_exit);
signal(SIGKILL, quick_exit);
signal(SIGUSR1, quick_exit);
signal(SIGSEGV, quick_exit);
signal(SIGUSR2, quick_exit);
signal(SIGPIPE, quick_exit);
signal(SIGTERM, quick_exit);
#ifdef SIGSTKFLT
signal(SIGSTKFLT, quick_exit);
#endif
signal(SIGCHLD, quick_exit);
signal(SIGCONT, quick_exit);
signal(SIGSTOP, quick_exit);
signal(SIGTSTP, quick_exit);
signal(SIGTTIN, quick_exit);
signal(SIGTTOU, quick_exit);
this ensures that if Ctrl+C, pkill, segmantetion fault, floating point error or anything really kill it, it won't make the machine have to shutdow
Example program using this:
#include "Keyboard.hpp" // the above utilities
#define IsCtrlDown() (IsKeyDown(Key::CTRL) || IsKeyDown(Key::CTRLL) || IsKeyDown(Key::CTRLR))
#include <iostream>
using namespace std;
int main() {
Init();
while(true) {
HandleKeyboard();
if (KeyPressed() == Key::q && IsCtrlDown()) break;
if (IsKeyDown(Key::k)) cout << "Key K is down\n";
if (IsKeyReleased(Key::l) cout << "The L ended" << endl;
}
cout << "Goodbye\n" << flush;
return 0;
}
If you want to know how to get toggled keys (capslock, numlock, scroll lock) than ask in a comment - I won't include it now 'cause this is already really long
I've got a program that uses this - you can look at it's source code here on github - the specific code for handling keyboard is in Console.cpp (It's multi-platform so you have to find the section #ifdef __linux__) [function Console::Init and Console::HandleKeyboard]