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
  • Narrow it down: do you just mean terminal input like shown or the wide world of arbitrary evdev input devices? – genpfault Commented Nov 22, 2024 at 13:53
  • 1 Just terminal input, but if there is a better alternative I would like to know as well – Tom McLean Commented Nov 22, 2024 at 14:02
  • I don't know if curses support it, but I would rather use it anyway than doing all the terminal handling myself. – Some programmer dude Commented Nov 22, 2024 at 14:32
  • 2 What do you mean by "at the same time"? A key going down sends an event. A key being released sends an event. Do you mean that the "key pressed" event occurs simultaneously with the others, or do you mean that "key pressed" for one key happens before "key released" for the other key? – William Pursell Commented Nov 22, 2024 at 14:43
  • 1 The answer will probably depend on the terminal handler -- the solution with X will be different than that with iOS or when running under WSL, for example, and it's quite possible that some terminal handlers do not provide a means of accessing the individual events (key down and key up). Also, it's probable that whatever the method, it won't return ASCII values like '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 a w or a z. – James Kanze Commented Nov 22, 2024 at 15:19
 |  Show 4 more comments

1 Answer 1

Reset to default 4 +500

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

  1. 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>
  1. 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;
} 
  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);
  1. 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);
   }
}

  1. 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]

本文标签: