37

我想要做什么

所以,我一直在尝试在 Linux 中访问键盘输入。具体来说,我需要能够在按下其他键的情况下访问修饰键。此外,我希望能够在没有运行 X 系统的情况下执行此操作。

所以,简而言之,我的要求是:

  • 在 Linux 上工作
  • 不需要 X11
  • 可以在不按下任何其他键的 情况下检索修饰键按下
    • 这包括以下键:
      • 转移
      • 控制
      • Alt
    • 我只需要一个简单的0 = not pressed1 = currently pressed让我知道在检查键盘时是否按下了键

我的电脑设置

我的普通 Linux 机器正在开往我的新公寓的卡车上;所以,我现在只有一台 Macbook Air 可以使用。因此,我在虚拟机中运行 Linux 来测试这一点。

VirtualBox 中的虚拟机

  • 操作系统:Linux Mint 16
  • 桌面环境:XFCE

下面的一切都是在这种环境下完成的。我已经尝试过运行 X 和其他 tty 之一。

我的想法

如果有人可以纠正我,我会改变这个。

我做了很多阅读,意识到更高级别的库不提供这种功能。修饰键与其他键一起使用以提供备用键代码。通过 Linux 中的高级库访问修饰键本身并不容易。或者,更确切地说,我还没有在 Linux 上找到用于此的高级 API。

我认为libtermkey将是答案,但它似乎并不比普通的击键检索更好地支持 Shift 修饰键。我也不确定它是否在没有 X 的情况下工作。

在使用 libtermkey 时(在我意识到它在 Shift-Return 之类的情况下不会发生转变之前),我打算编写一个可以运行以收集键盘事件的守护程序。运行守护程序的副本将简单地通过管道传输对键盘数据的请求并接收键盘数据作为响应。我可以使用此设置让某些东西始终在后台运行,以防我无法在特定时间检查键码状态(必须在它们发生时接收键码)。

以下是我编写可以从 Linux 键盘设备读取的程序的两次尝试。我还附上了我的小支票,以确保我拥有正确的设备。

尝试#1

我曾尝试直接访问键盘设备,但遇到了问题。我在这里尝试了另一个 Stack Overflow 线程中的建议。它给了我一个分段错误;所以,我把它从 fopen 改为 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);

// ...

虽然没有分段错误,但没有任何按键指示(不仅仅是修饰键)。我使用以下方法对此进行了测试:

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

我已经用它来测试命令的成功返回码了。所以,我知道这很好。我还输出了要检查的密钥(始终为 0)和掩码(0100)。它似乎没有检测到任何东西。

尝试#2

从这里开始,我想我会尝试一种稍微不同的方法。我想弄清楚我做错了什么。在页面提供了一个演示打印关键代码的片段之后,我将其捆绑到一个程序中:

#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;
}

以前,我将大小设置为 16 字节而不是 128 字节。老实说,我应该多花一点时间了解 ioctl 和 EVIOCGKEY。我只知道它应该将位映射到特定键以指示按下或类似的东西(如果我错了,请纠正我!)。

我最初也没有循环,只需按住各种键即可查看是否出现键码。我什么也没收到;所以,我认为循环可能会使检查更容易测试,以防遗漏某些东西。

我怎么知道输入设备是正确的

我通过cat在输入设备上运行对其进行了测试。具体来说:

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

当我使用 cat 开始输出时,垃圾 ASCII 在按键和释放事件中被发送到我的终端,这些事件从返回(回车)键开始。我也知道这似乎与我运行 Linux VM 的 Macbook 上的修改键(如 shift、control、function 甚至 Apple 的命令键)配合得很好。输出在按键被按下时出现,从随后按键发出的信号开始迅速出现,在按键松开时输出更多的数据。

因此,虽然我的方法可能不是正确的(我愿意听到任何替代方案),但该设备似乎提供了我需要的东西。

此外,我知道这个设备只是一个指向 /dev/input/event2 运行的链接:

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

我已经使用 /dev/input/event2 尝试了上述两个程序,但没有收到任何数据。在 /dev/input/event2 上运行 cat 提供与链接相同的输出。

4

1 回答 1

56

打开输入设备,

#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;
    }

然后从设备中读取键盘事件:

    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;
        }

如果发生任何错误,或者如果用户空间仅接收到部分事件结构(这不应该发生,但在某些未来/有缺陷的内核中可能会发生),上述代码片段将从循环中中断。您可能希望使用更健壮的读取循环;我个人会满意将最后一个替换为breakcontinue从而忽略部分事件结构。

然后,您可以检查ev事件结构以查看发生了什么,并完成程序:

        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;
}

对于按键,

  • ev.time:事件的时间(struct timeval类型)

  • ev.typeEV_KEY

  • ev.code: KEY_*, 密钥标识符;见完整列表/usr/include/linux/input.h

  • ev.value0如果按键释放,1如果按键,2如果自动重复按键

有关详细信息,请参阅Linux 内核源代码中的Documentation/input/input.txt

其中的命名常量/usr/include/linux/input.h相当稳定,因为它是一个内核-用户空间接口,内核开发人员非常努力地保持兼容性。(也就是说,您可以期望不时出现新代码,但现有代码很少更改。)

于 2014-01-06T08:48:45.217 回答