Question

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.

Was it helpful?

Solution

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.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top