Linux input device events, how to retrieve initial state

4.6k views Asked by At

I am using the gpio-keys device driver to handle some buttons in an embedded device running Linux. Applications in user space can just open /dev/input/eventX and read input events in a loop.

My question is how to get the initial states of the buttons. There is an ioctl call (EVIOCGKEY) which can be used for this, however if I first check this and then start to read from /dev/input/eventX, there's no way to guarantee that the state did not change in between.

Any suggestions?

1

There are 1 answers

0
Phillip On BEST ANSWER

The evdev devices queue events until you read() them, so in most cases opening the device, doing the ioctl() and immediately starting to read events from it should work. If the driver dropped some events from the queue, it sends you a SYN_DROPPED event, so you can detect situations where that happened. The libevdev documentation has some ideas on how one should handle that situation; the way I read it you should simply retry, i.e. drop all pending events, and redo the ioctl() until there are no more SYN_DROPPED events.

I used this code to verify that this approach works:

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

#define EVDEV "/dev/input/event9"

int main(int argc, char **argv) {
    unsigned char key_states[KEY_MAX/8 + 1];
    struct input_event evt;
    int fd;

    memset(key_states, 0, sizeof(key_states));
    fd = open(EVDEV, O_RDWR);
    ioctl(fd, EVIOCGKEY(sizeof(key_states)), key_states);

    // Create some inconsistency
    printf("Type (lots) now to make evdev drop events from the queue\n");
    sleep(5);
    printf("\n");

    while(read(fd, &evt, sizeof(struct input_event)) > 0) {
        if(evt.type == EV_SYN && evt.code == SYN_DROPPED) {
            printf("Received SYN_DROPPED. Restart.\n");
            fsync(fd);
            ioctl(fd, EVIOCGKEY(sizeof(key_states)), key_states);
        }
        else if(evt.type == EV_KEY) {
            // Ignore repetitions
            if(evt.value > 1) continue;

            key_states[evt.code / 8] ^= 1 << (evt.code % 8);
            if((key_states[evt.code / 8] >> (evt.code % 8)) & 1 != evt.value) {
                printf("Inconsistency detected: Keycode %d is reported as %d, but %d is stored\n", evt.code, evt.value,
                        (key_states[evt.code / 8] >> (evt.code % 8)) & 1);
            }
        }
    }
}

After starting, the program deliberately waits 5 seconds. Hit some keys in that time to fill the buffer. On my system, I need to enter about 70 characters to trigger a SYN_DROPPED. The EV_KEY handling code checks if the events are consistent with the state reported by the EVIOCGKEY ioctl.