Wednesday, February 10, 2010

polling key state on a Linux keyboard with EVIOCGKEY

Here's another quick Linux input snippet.  Sometimes you just want to check if a key is pressed down rather than using the event-driven interface from evdev.  That can be accomplished by issuing EVIOCGKEY, which asks the device for a bitmask of key states.

int is_key_pressed(int fd, int key)
{
        char key_b[KEY_MAX/8 + 1];

        memset(key_b, 0, sizeof(key_b));
        ioctl(fd, EVIOCGKEY(sizeof(key_b)), key_b);

        return !!(key_b[key/8] & (1<<(key % 8)));
}

...where 'key' can be one of the key codes specified in <linux/input.h>. For example KEY_F1 corresponds to key code 59.  'fd' is a file handle for a keyboard device.

Linux input event (evdev) snippet: find a keyboard by name

Here's a (hopefully useful) function for opening a keyboard (input event device) by name on Linux. For example, you want to open a device named "My Keyboard" but you don't know it's at /dev/input/event2. This function will open "/dev/input" and ask for the device name from each "eventN" device. It will return the file descriptor for the first device whose name matches (and the descriptor will be left open) or -1 if none matched.

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

int get_kb_fd(char *input_name)
{
        int fd;
        struct dirent *dp;
        char name[32];
        char dev[32];
        DIR *dir;

        dir = opendir("/dev/input");
        if (!dir)
                return 1;

        while ((dp = readdir(dir)) != NULL) {
                if (dp->d_name && !strncmp(dp->d_name, "event", 5)) {
                        snprintf(dev, sizeof(dev), "/dev/input/%s", dp->d_name);
                        fd = open(dev, O_RDONLY);
                        if (fd == -1)
                                continue;
                        if (ioctl(fd, EVIOCGNAME(32), name) < 0) {
                                close(fd);
                                continue;
                        }

                        if (!strcmp(name, input_name)) {
                                closedir(dir);
                                return fd;
                        } else { /* not what we wanted, check another */
                                close(fd);
                                fd = -1;
                        }
                }
        }

        closedir(dir);
        return -1;
}