[BRLTTY] Proof of concept code: Filter keyboard keypresses on Linux

Mario Lang mlang at debian.org
Sat Dec 15 11:17:47 EST 2007


Hi.

Virtual console screen readers that wanted to use the
normal keyboard for review functionality always had the
problem on linux that some sort of a non-standardized kernel
patch was needed to get keysniffing to work.

I've been thinking about a method that allows to do this completely
from within userspace without any kernel patching.  Now that
I had actually found another use case for this for me personally,
I went ahead and implemented the idea to see if it actually works.

And yes, it does!

The plan is as follows:
 * We iterate the input event devices in /dev/input/event* to find
   one with criteria that we can use (we can test for bustype, usb product/vendor ID,
   or for the presence of individual keys).
 * We then create a "uinput device" to feed keypresses back to the kernel.
 * We "grab" the keyboard, i.e., keypresses are no longer sent to the
   system, but to us (in userspace) exclusively.
 * Whenever we read in a key from our keyboard, we check if
   we handle that key, if not, we pass it back to the uinput device,
   which in turn will pass it back to the system.

The example code below is written for a Sun type 6 USB
keyboard which I happen to own.  This keyboard has some extra keys which
I'd like to use.  Most of them are not bound in Linux itself,
but two (MUTE and VOLUMEDOWN) do in fact insert garbage if pressed.
So I had to write this tool to be able to use these keys for
my own purpuses while avoiding them to generate garbage input when used.

The same method could be used in brltty to achieve the following
things we've identified in the past as worthwhile to implement:
 * Support braille displays without navigation keys (I've heard
   some japanese models are built that way).
 * Provide screen review for speech only situations.
 * And last but not least, provide better bindings for displays
   that do have very few navigation keys (the vario or bookworm comes to mind).

Since keyboards can have many different keys these days (think
USB multimedia keyboards), I guess the best way
to impelement this in brltty is to couple it with
some configuration file parsing that would allow the user
to define which keyboard actions are bound to which brltty commands.

I am open to all input here.  As usual, I am not the best code designer,
so some starting help for getting this into brltty core would
be very appreciated?  Dave?

P.S.: The following code checks for a specific keyboard.
We will of course want to do something much more generic here,
like test for a keyboard that actually has the keys we'd like to watch out for.
The function has_event() can be used to do this, a call like
has_event(filedescriptor, EV_KEY, KEY_ESC) will return 1 if the keyboard
associated with filedescriptor does have an ESC key...

#include <dirent.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

static int
has_event(int fd, unsigned short type, unsigned short code)
{
#define MAX(a,b) a>b?a:b
  const unsigned int len = sizeof(unsigned long)*8;
  const unsigned int max = MAX(EV_MAX,
			       MAX(KEY_MAX,
				   MAX(REL_MAX,
				       MAX(ABS_MAX,
					   MAX(LED_MAX,
					       MAX(SND_MAX, FF_MAX))))));
  unsigned long bits[(max+len-1)/len];
  if (ioctl(fd, EVIOCGBIT(type, max), bits)) {
    return (bits[code/len] >> (code%len)) & 1;
  }
  return 0;
#undef MAX
}

static int
constructKeyboard (char *name, struct input_id *id, unsigned long *keymask)
{
  int fd;
  if ((fd = open("/dev/input/uinput", O_WRONLY)) > 0) {
    struct uinput_user_dev description;

    memset(&description, 0, sizeof(description));
    strncpy(description.name, name, UINPUT_MAX_NAME_SIZE);
    memcpy(&description.id, id, sizeof(description.id));
    if (write(fd, &description, sizeof(description)) == sizeof(description)) {
      int key;

      ioctl(fd, UI_SET_EVBIT, EV_KEY);
      ioctl(fd, UI_SET_EVBIT, EV_REP);

      for (key=KEY_RESERVED; key<=KEY_MAX; key++) {
        if ((keymask[key/(sizeof(unsigned long)*8)] >> (key%(sizeof(unsigned long)*8)))&1) {
	  ioctl(fd, UI_SET_KEYBIT, key);
	}
      }
      if (ioctl(fd, UI_DEV_CREATE) != -1) {
	return fd;
      } else {
	perror("ioctl UI_DEV_CREATE");
      }
    } else {
      perror("write (struct uinput_user_dev)");
    }
  } else {
    perror("open /dev/input/uinput");
  }
  return -1;
}

int
main(int argc, char *argv[])
{
  char root[] = "/dev/input";
  int rootLength = strlen(root);
  int foundKeyboard = 0;
  DIR *directory;

  if ((directory = opendir(root))) {
    struct dirent *entry;

    while ((entry = readdir(directory))) {
      size_t nameLength = strlen(entry->d_name);
      struct stat status;
      char path[rootLength + 1 + nameLength + 1];

      snprintf(path, sizeof(path), "%s/%s", root, entry->d_name);
      if (stat(path, &status) == -1) continue;
      if (S_ISCHR(status.st_mode)) {
        int fd;

        if ((fd = open(path, O_RDONLY)) > 0) {
          struct input_id id;
	  
          if (ioctl(fd, EVIOCGID, &id) != -1) {
            if (id.bustype == 3 &&
                id.vendor == 0X0430 && id.product == 0X0005 &&
		has_event(fd, EV_KEY, KEY_HELP)) {
	      unsigned long keymask[(KEY_MAX+(sizeof(unsigned long)*8)-1)/(sizeof(unsigned long)*8)];
              struct input_event event;

              foundKeyboard = 1;

	      if (ioctl(fd, EVIOCGBIT(EV_KEY, KEY_MAX), keymask)) {
		int uinput = constructKeyboard("test", &id, keymask);
	      
		if (uinput != -1) {
		  ioctl(fd, EVIOCGRAB, 1);
		  while (read(fd, &event, sizeof(event)) == sizeof(event)) {
		    if (event.type == EV_KEY) {
		      switch (event.code) {
		      case KEY_MUTE:
			printf("MUTE %s\n", event.value ? "press":"release");
			break;
		      case KEY_VOLUMEDOWN:
			printf("VOLUMEDOWN %s\n", event.value ? "press":"release");
			break;
		      case KEY_VOLUMEUP:
			printf("VOLUMEUP %s\n", event.value ? "press":"release");
			break;
		      case KEY_POWER:
			printf("POWER %s\n", event.value ? "press":"release");
			break;
		      case KEY_STOP:
			printf("STOP %s\n", event.value ? "press":"release");
			break;
		      case KEY_AGAIN:
			printf("AGAIN %s\n", event.value ? "press":"release");
			break;
		      case KEY_PROPS:
			printf("PROPS %s\n", event.value ? "press":"release");
			break;;
		      case KEY_UNDO:
			printf("UNDO %s\n", event.value ? "press":"release");
			break;;
		      default:
			write(uinput, &event, sizeof(event));
			printf("%d = %d\n", event.code, event.value);
		      }
		    }
		  }

		  ioctl(fd, EVIOCGRAB, 0);
		  close(uinput);
		}
	      }
	    }
          }
          close(fd);
        }
      }
    }
    closedir(directory);

    if (!foundKeyboard) {
      printf("No supported USB keyboard found.\n");
    }
  } else {
    fprintf(stderr, "Unable to open %s\n", root);
  }
  return 0;
}


-- 
CYa,
  Mario | Debian Developer <URL:http://debian.org/>
  .''`. | Get my public key via finger mlang at db.debian.org
 : :' : | 1024D/7FC1A0854909BCCDBE6C102DDFFC022A6B113E44
 `. `'
   `-      <URL:http://delysid.org/>  <URL:http://www.staff.tugraz.at/mlang/>


More information about the BRLTTY mailing list