为什么`ioctl(fd,EVIOCGRAB,1)`有时会导致密钥垃圾邮件?

时间:2017-02-02 06:13:17

标签: c linux keyboard ubuntu-14.04

我正在尝试编写自己的“键盘驱动程序”(实际上没有编写内核模块), 通过抓取键盘,我认为是用户空间中最低级别的抽象:/dev/input/event*

如果您更改ioctl(fd, EVIOCGRAB, UNGRAB)的第一个错误,以下代码会抓取 到ioctl(fd, EVIOCGRAB, GRAB)

// gcc main.c -o main

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <linux/input.h>
#include <fcntl.h>
#include <errno.h>

#define EXIT_KEY  KEY_ESC
#define UNGRAB    0
#define GRAB      1

const char* kbd_device = "/dev/input/event4";

// ------------------------------------------------------------------------------------------------
int main(void){
  int fd = open(kbd_device, O_RDONLY);
  if(fd == -1){
    printf("Cannot open %s. %s.\n", kbd_device, strerror(errno));
    return -1;
  }

  if(ioctl(fd, EVIOCGRAB, UNGRAB))
    printf("Couldn't grab %s. %s.\n", kbd_device, strerror(errno));
  else
    printf("Grabbed %s!\n", kbd_device);

  while(1){
    struct input_event event;
    read(fd, &event, sizeof(event));
    if (event.type == EV_KEY && event.value >= 0 && event.value <= 2){
      printf("%d %3d\n", event.value, event.code);

      if(event.code == EXIT_KEY){
        ioctl(fd, EVIOCGRAB, UNGRAB);
        close(fd);
        return 0;
      }

    }
  }
}

问题

  • 如果我运行gcc main.c -o main && sudo ./main,一切都会按预期运行。
  • 如果先编译并然后我运行sudo ./main,则终端会不停地向下滚动,就像按住RETURN键一样。

为什么会这样?

备注

  • 我正在运行Ubuntu 14.04
  • 在我的平台上,/dev/input/event4恰好是键盘

动机

我正在尝试编写一个键盘“驱动程序”,它既适用于X,也适用于X(例如TTY)。

我理解X11的键盘库/扩展名是XKB。我认为TTY的键盘库是linux/divers/tty/vt/keyboard.csource), 它使用的初始键盘映射位于linux/drivers/tty/vt/defkeymap.mapsource),可以使用loadkeys(来源here)对其进行修改。如果我错了,请纠正我。

3 个答案:

答案 0 :(得分:5)

键入

gcc main.c -o main && sudo ./main ↵

GCC需要一些时间,所以键已经在./main运行时释放。

键入

sudo ./main ↵

终端按下后立即向shell发送换行符,然后开始执行./main。然后你的程序会看到释放的事件,但不是你的终端,因为你的程序已经抓住了输入设备。因此,对于终端,它看起来像,因此它继续产生换行符。

答案 1 :(得分:1)

这个问题已经被回答了,但是它仍然不能很好地解决这个问题。

我前一段时间实现的驱动程序也存在同样的问题,该驱动程序也需要捕获键盘。

我无法找到一种方法来强制内核在捕获设备之前识别出设备中的密钥释放,因此解决方案包括在您检测到所有密钥已被释放之前不抓取设备。可以通过在打开设备之前并抓住它之前用EVIOCGKEY ioctl监视设备来实现。

OBS:请注意,read循环中看似虚假的while函数是必要的,以避免繁忙的等待,这样循环将在输入设备发生每个事件后进行循环。另外请注意,必须将文件描述符配置为阻止I / O(默认设置)。

void waitReleaseAll(int fd) {

  struct input_event evt;
  unsigned char key_b[KEY_MAX/8 + 1];
  int i, nothing;

  while ( 1 ) {
    memset(key_b, 0, sizeof(key_b));
    ioctl(fd, EVIOCGKEY(sizeof(key_b)), key_b);
    for ( nothing = 1 , i = 0 ; i < KEY_MAX/8 + 1 ; i++ ) {
      if ( key_b[i] != 0 ) { nothing = 0; break; }
    }
    if ( nothing ) break;
    read(fd, &evt, sizeof(evt));
  }
  printf("All keys are now released\n");

}

答案 2 :(得分:0)

要解决您的问题,您应该在代码中使用SIGINT来识别用户的Ctrl-C击键。

代码中的SIGNAL实施例

static volatile sig_atomic_t stop = 0;
    
static void interrupt_handler(int sig)
{
    stop = 1;
} // Outside of the main function.

int main(int argc, char *argv[])
{
    signal(SIGINT, interrupt_handler);
    while (!stop) {
    //your code    
    }
    exit(0);
}