Accessing Keys from Linux Input Device

44,390

Open the input device,

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

static const char *const evval[3] = {
    "RELEASED",
    "PRESSED ",
    "REPEATED"
};

int main(void)
{
    const char *dev = "/dev/input/by-path/platform-i8042-serio-0-event-kbd";
    struct input_event ev;
    ssize_t n;
    int fd;

    fd = open(dev, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "Cannot open %s: %s.\n", dev, strerror(errno));
        return EXIT_FAILURE;
    }

and then read keyboard events from the device:

    while (1) {
        n = read(fd, &ev, sizeof ev);
        if (n == (ssize_t)-1) {
            if (errno == EINTR)
                continue;
            else
                break;
        } else
        if (n != sizeof ev) {
            errno = EIO;
            break;
        }

The above snippet breaks out from the loop if any error occurs, or if the userspace receives only a partial event structure (which should not happen, but might in some future/buggy kernels). You might wish to use a more robust read loop; I personally would be satisfied by replacing the last break with continue, so that partial event structures are ignored.

You can then examine the ev event structure to see what occurred, and finish the program:

        if (ev.type == EV_KEY && ev.value >= 0 && ev.value <= 2)
            printf("%s 0x%04x (%d)\n", evval[ev.value], (int)ev.code, (int)ev.code);

    }
    fflush(stdout);
    fprintf(stderr, "%s.\n", strerror(errno));
    return EXIT_FAILURE;
}

For a keypress,

  • ev.time: time of the event (struct timeval type)

  • ev.type: EV_KEY

  • ev.code: KEY_*, key identifier; see complete list in /usr/include/linux/input.h

  • ev.value: 0 if key release, 1 if key press, 2 if autorepeat keypress

See Documentation/input/input.txt in the Linux kernel sources for further details.

The named constants in /usr/include/linux/input.h are quite stable, because it is a kernel-userspace interface, and the kernel developers try very hard to maintain compatibility. (That is, you can expect there to be new codes every now and then, but existing codes rarely change.)

Share:
44,390
Senkwich
Author by

Senkwich

Updated on January 06, 2020

Comments

  • Senkwich
    Senkwich over 4 years

    What I am trying to do

    So, I have been trying to access keyboard input in Linux. Specifically, I need to be able to access modifier key presses without other keys being pressed. Furthermore, I want to be able to do this without an X system running.

    So, in short, my requirements are these:

    • Works on Linux
    • Does not need X11
    • Can retrieve modifier key press without any other keys being pressed
      • This includes the following keys:
        • Shift
        • Control
        • Alt
      • All I need is a simple 0 = not pressed, 1 = currently pressed to let me know if the key is being held down when the keyboard is checked

    My computer setup

    My normal Linux machine is on a truck towards my new apartment; so, I only have a Macbook Air to work with right now. Therefore, I am running Linux in a VM to test this out.

    Virtual Machine in VirtualBox

    • OS: Linux Mint 16
    • Desktop Environment: XFCE

    Everything below was done in this environment. I've tried both with X running and in one of the other ttys.

    My Thoughts

    I'll alter this if someone can correct me.

    I've done a fair bit of reading to realize that higher-level libraries do not provide this kind of functionality. Modifier keys are used with other keys to provide an alternate key code. Accessing the modifier keys themselves through a high-level library in Linux isn't as easy. Or, rather, I haven't found a high-level API for this on Linux.

    I thought libtermkey would be the answer, but it doesn't seem to support the Shift modifier key any better than normal keystroke retrieval. I'm also not sure if it works without X.

    While working with libtermkey (before I realized it didn't get shift in cases like Shift-Return), I was planning to write a daemon that would run to gather keyboard events. Running copies of the daemon program would simply pipe requests for keyboard data and receive keyboard data in response. I could use this setup to have something always running in the background, in case I cannot check key code statuses at specific times (have to be receive key codes as they happen).

    Below are my two attempts to write a program that can read from the Linux keyboard device. I've also included my small check to make sure I had the right device.

    Attempt #1

    I have tried to access the keyboard device directly, but am encountering issues. I have tried the suggestion here that is in another Stack Overflow thread. It gave me a segmentation fault; so, I changed it from fopen to open:

    // ...
    
    int fd;
    fd = open("/dev/input/by-path/platform-i8042-serio-0-event-kbd", O_RDONLY);
    
    char key_map[KEY_MAX/8 + 1];
    
    memset(key_map, 0, sizeof(key_map));
    ioctl(fd, EVIOCGKEY(sizeof key_map), key_map);
    
    // ...
    

    While there was no segmentation fault, there was no indicator of any key press (not just modifier keys). I tested this using:

    ./foo && echo "TRUE" || echo "FALSE"
    

    I've used that to test for successful return codes from commands quite a lot; so, I know that's fine. I've also outputted the key (always 0) and mask (0100) to check. It just doesn't seem to detect anything.

    Attempt #2

    From here, I thought I'd try a slightly different approach. I wanted to figure out what I was doing wrong. Following this page providing a snippet demonstrating printing out key codes, I bundled that into a program:

    #include <stdio.h>
    #include <stdint.h>
    #include <string.h>
    #include <fcntl.h>
    #include <linux/input.h>
    
    int main(int argc, char** argv) {
        uint8_t keys[128];
        int fd;
    
        fd = open("/dev/input/by-path/platform-i8042-serio-event-kbd", O_RDONLY);
        for (;;) {
            memset(keys, 0, 128);
            ioctl (fd, EVIOCGKEY(sizeof keys), keys);
    
            int i, j;
            for (i = 0; i < sizeof keys; i++)
                for (j = 0; j < 8; j++)
                    if (keys[i] & (1 << j))
                        printf ("key code %d\n", (i*8) + j);
        }
    
        return 0;
    }
    

    Previously, I had the size to 16 bytes instead of 128 bytes. I should honestly spend a bit more time understanding ioctl and EVIOCGKEY. I just know that it supposedly maps bits to specific keys to indicate presses, or something like that (correct me if I'm wrong, please!).

    I also didn't have a loop initially and would just hold down various keys to see if a key code appeared. I received nothing; so, I thought a loop might make the check easier to test in case a missed something.

    How I know the input device is the right one

    I tested it by running cat on the input device. Specifically:

    $ sudo cat /dev/input/by-path/platform-i8042-serio-0-event-kbd
    

    Garbage ASCII was sent to my terminal on key press and release events starting with the return (enter) key when I began the output using cat. I also know that this seems to work fine with modifier keys like shift, control, function, and even Apple's command key on my Macbook running a Linux VM. Output appeared when a key was pressed, began to appear rapidly from subsequent signals sent by holding the key down, and outputted more data when a key was released.

    So, while my approach may not be the right one (I'm willing to hear any alternative), the device seems to provide what I need.

    Furthermore, I know that this device is just a link pointing to /dev/input/event2 from running:

    $ ls -l /dev/input/by-path/platform-i8042-serio-0-event-kbd
    

    I've tried both programs above with /dev/input/event2 and received no data. Running cat on /dev/input/event2 provided the same output as with the link.

  • Senkwich
    Senkwich over 10 years
    Excellent! I had been glancing at the documentation you mentioned, but didn't want to delve much further in case I was looking in the wrong place. Great snippets and explanation! Testing the code you gave me works just the way I needed it to!
  • muman
    muman almost 9 years
    On my system I have to be super-user in order to access the keyboard device. Is there any other way? Also my console is echoing the output to the screen. Which way is recommended to avoid the echoing?
  • Nominal Animal
    Nominal Animal almost 9 years
    @muman: Well, you can add an udev rule to change the ownership and mode of the device. You definitely do NOT want input devices to be readable by everyone, because that'd make eavesdropping really easy. Normally, the input handling process/daemon is run with superuser privileges; with unprivileged helper utilities to communicate with it. Basic Unix design, in other words.
  • Nominal Animal
    Nominal Animal almost 9 years
    @muman: You can grab (ioctl(fd, EVIOCGRAB, 1)) the input event device to consume the keypresses (grab them, instead of just observing them). See my example here, especially the barcode_open() function. You can reinsert any keypresses using the uinput device (but that needs privileges, too, for obvious security reasons).
  • Naveen
    Naveen over 3 years
    how to detect multiple keypress events?
  • C. Lang
    C. Lang over 2 years
    list of key identifiers are in /usr/include/linux/input-event-codes.h