Обнаружение нажатия и отпускания клавиш клавиатуры в Linux - PullRequest
3 голосов
/ 13 октября 2019

Я пытаюсь написать простую программу, которая выполняет определенную задачу, когда ни одна клавиша не нажата, и выполняет другую задачу, когда нажата любая клавиша. Это то, что я имею до сих пор:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

int kbhit (void);

int main()
{
    char c;

    while(1)
    {
        while (!kbhit())
        {
            printf("%d", 0);
        }
        c = getchar();
        printf("%c", c);

        if(c == 'x' || c == 'X')
            break;

        sleep(0);
    }

    return 0;
}

int kbhit (void)
{
    struct timeval tv;
    fd_set rdfs;

    tv.tv_sec = 0;
    tv.tv_usec = 1000;

    FD_ZERO(&rdfs);
    FD_SET (STDIN_FILENO, &rdfs);

    select(STDIN_FILENO+1, &rdfs, NULL, NULL, &tv);
    return FD_ISSET(STDIN_FILENO, &rdfs);
}

Эта реализация хороша для обнаружения нажатия одной клавиши снова и снова, но если вы удерживаете нажатой клавишу клавиатуры, между обнаружением нажатия клавиши имеются значительные промежутки. Несмотря на то, что клавиша постоянно нажата, в любом случае на консоль выводится 0. Например, вывод будет выглядеть примерно так, если я буду держать клавишу «W» нажатой: 000000000000000WWWWWWWW00000000000000000WWWWWWWW00000000...
Как я могу это исправить, чтобы при удерживании нажатой клавиши клавиатуры kbhit () всегда возвращала 0?

1 Ответ

2 голосов
/ 13 октября 2019

Ваш kbhit (и, по-видимому, даже оригинальный Windows kbhit) не определяет, нажата ли клавиша, а только, есть ли что-то новое для чтения на stdin. Это будет иметь место только 25 раз в секунду или около того, в зависимости от настроек автоповтора. Отключение буферизации stdout в вашем примере кода сделает это более очевидным (000000W00000000W000000000W)

Как я могу это исправить, чтобы при удерживании нажатой клавиши клавиатуры kbhit () всегда возвращал 0?

Это невозможно сделать переносимо. В Linux это можно сделать с помощью файлов устройства в /dev/input См. Пример программы ниже. Он зарегистрирует все ключи, даже одинокие SHIFT Обратите внимание, что это по сути кейлоггер, поэтому вам придется запускать с правами root (или настроить программу). Затем он зарегистрирует все нажатия клавиш, даже если у него нет фокуса клавиатуры. Yikes!

Примечание: приведенный ниже пример основан на keystate.c Кевина Кокса

#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);
  }
}       
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...