Search code examples
clinuxkeyboardubuntu-14.04

Why does `ioctl(fd, EVIOCGRAB, 1)` cause key spam sometimes?


I'm trying to write my own "keyboard driver" (without actually writing a kernel module), by grabbing the keyboard at what I assume is the lowest level of abstraction in userland: /dev/input/event*.

The following code does the grabbing, provided you change the first ocurrence of ioctl(fd, EVIOCGRAB, UNGRAB) to 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;
      }

    }
  }
}

Problem

  • If I run gcc main.c -o main && sudo ./main, everything works as expected.
  • If first compile and then I run sudo ./main, however, the terminal scrolls down nonstop, as if the RETURN key was held down.

Why does happen?

Notes

  • I'm running Ubuntu 14.04
  • On my platform, /dev/input/event4 happens to be the keyboard

Motivation

I'm trying to write a keyboard "driver" that works both on X and not on X (eg. a TTY).

I understand X11's keyboard library/extension is XKB. I think the TTY's keyboard library is linux/divers/tty/vt/keyboard.c (source), the initial keyboard map it uses is in linux/drivers/tty/vt/defkeymap.map (source), and it can be modified by using loadkeys (source here). Do correct me if I'm wrong.


Solution

  • When you type

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

    GCC takes some time, so the key has been released by the time ./main runs.

    When you type

    sudo ./main ↵
    

    the terminal sends the shell a newline as soon as you push down , and starts executing ./main. Then the released event is seen by your program, but not by your terminal, because your program has grabbed the input device. Thus, to the terminal it looks like is stuck down, so it continues to produce newlines.