从Linux输入设备访问密钥

Sen*_*ich 25 c linux keyboard-events modifier-key

我想做什么

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

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

  • 适用于Linux
  • 不需要X11
  • 无需按任何其他键 即可检索修改键
    • 这包括以下键:
      • 转移
      • 控制
      • Alt键
    • 我需要的只是一个简单的0 = not pressed,1 = currently pressed让我知道在检查键盘时是否按下了键

我的电脑设置

我正常的Linux机器在我的新公寓的卡车上; 所以,我现在只有Macbook Air可以使用.因此,我在VM中运行Linux来测试它.

VirtualBox中的虚拟机

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

以下所有内容都是在这种环境下完成的.我已经尝试过使用X运行和其他一个ttys.

我的想法

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

我已经做了一些阅读,意识到更高级别的库不提供这种功能.修饰符键与其他键一起使用以提供备用键代码.通过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);

// ...
Run Code Online (Sandbox Code Playgroud)

虽然没有分段错误,但没有任何按键的指示(不仅仅是修改键).我测试了这个:

./foo && echo "TRUE" || echo "FALSE"
Run Code Online (Sandbox Code Playgroud)

我已经用它来测试命令中成功返回的代码了不少; 所以,我知道那很好.我还输出了密钥(总是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;
}
Run Code Online (Sandbox Code Playgroud)

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

我最初也没有循环,只是按住各种键来查看密钥代码是否出现.我一无所获; 所以,我认为循环可能会使检查更容易测试,以防错过的东西.

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

我通过cat在输入设备上运行来测试它.特别:

$ sudo cat /dev/input/by-path/platform-i8042-serio-0-event-kbd
Run Code Online (Sandbox Code Playgroud)

当我使用cat开始输出时,垃圾ASCII在按键时发送到我的终端,并以return(enter)键开始释放事件.我也知道这似乎与修改键,如移位,控制,功能,甚至Apple运行Linux VM的Macbook上的命令键都能正常工作.按下按键时出现输出,通过按住按键发出的后续信号开始迅速出现,并在按键被释放时输出更多数据.

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

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

$ ls -l /dev/input/by-path/platform-i8042-serio-0-event-kbd
Run Code Online (Sandbox Code Playgroud)

我用/ dev/input/event2尝试了上面的两个程序并且没有收到任何数据.在/ dev/input/event2上运行cat提供与链接相同的输出.

Nom*_*mal 39

打开输入设备,

#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;
    }
Run Code Online (Sandbox Code Playgroud)

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

    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;
        }
Run Code Online (Sandbox Code Playgroud)

如果发生任何错误,或者如果用户空间仅接收部分事件结构(不应该发生,但可能在某些未来/错误的内核中),则上述片段从循环中突破.您可能希望使用更强大的读取循环; 我个人会被替换最后一个满足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;
}
Run Code Online (Sandbox Code Playgroud)

对于按键,

  • ev.time:活动时间(struct timeval类型)

  • ev.type: EV_KEY

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

  • ev.value:0如果按键释放,1如果按键,2如果是自动重复按键

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

命名常量/usr/include/linux/input.h非常稳定,因为它是一个内核用户空间接口,内核开发人员非常努力地保持兼容性.(也就是说,您可能会偶尔会有新代码,但现有代码很少会发生变化.)

  • @muman:你可以抓住(`ioctl(fd,EVIOCGRAB,1)`)输入事件设备来消耗按键(抓住它们,而不是仅仅观察它们).请参阅我的示例[here](http://stackoverflow.com/a/29956584/1475978),尤其是`barcode_open()`函数.您可以使用`uinput`设备重新插入任何按键(但出于明显的安全原因,这也需要特权). (4认同)