admin管理员组文章数量:1122965
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?
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?
Share Improve this question edited Nov 22, 2024 at 14:08 Tom McLean asked Nov 22, 2024 at 13:37 Tom McLeanTom McLean 6,2651 gold badge20 silver badges47 bronze badges 9 | Show 4 more comments1 Answer
Reset to default 4 +500You 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
- header files (I might've missed some or written to much see: header file more than enough of them)
#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>
- function to get the file descriptor of the current console
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;
}
- Init: store old KBD_MODE and set it to K_MEDUIMRAW, than save old termios mode change the it to unbuffered input (not waiting for enter)
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);
- get keyboard layout mapping to convert keycodes to letters/numbers/symblols/function keys
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);
}
}
- Update Loop (I advise to make a function ex. HandleKeyboard, and if you do, remember to keep your variables global) 4.1 parse input function
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;
-
- Checking if a key is down/was just pressed or released:
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];
}
-
- Very important - end of program reset settings back to normal - if you don't the terminal might end up unsusable (it will if youre using linux without a graphical environment (raw console))
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]
本文标签:
版权声明:本文标题:How can I detect if multiple keyboard keys are being pressed at the same time in C++, in Linux, from the terminal? - Stack Overf 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736533831a1944351.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
'b'
, but rather the key code, which depends on the position of the key on the keyboard. Depending on the keyboard driver I have installed, for example, the same key may be aw
or az
. – James Kanze Commented Nov 22, 2024 at 15:19