c - 从Linux输入设备访问键

标签 c linux keyboard-events modifier-key

我想做什么

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

简而言之,我的要求是:

  • 在Linux上工作
  • 不需要X11
  • 可以按检索修饰键,而无需按下其他任何键
  • 这包括以下键:
  • Shift
  • 控制
  • Alt
  • 我需要的只是一个简单的0 = not pressed1 = currently pressed让我知道是否
    选中键盘时按住该键

  • 我的电脑设定

    我的普通Linux机器正在卡车上驶向我​​的新公寓。因此,我现在只能使用Macbook Air。因此,我正在VM中运行Linux进行测试。

    VirtualBox中的虚拟机
  • 操作系统:Linux Mint 16
  • 桌面环境:XFCE

  • 下面的所有操作都是在此环境中完成的。我已经尝试过在X和其他tty之一中运行。

    我的想法

    如果有人可以纠正我,我将更改此设置。

    我已经读了很多书,以认识到高级库不提供这种功能。修饰键与其他键一起使用以提供备用键代码。通过Linux中的高级库本身访问修饰键并不容易。或者,相反,我还没有在Linux上找到用于此目的的高级API。

    我以为libtermkey是答案,但是似乎不比普通的击键检索更好地支持Shift修饰键。我也不确定如果没有X,它是否可以工作。

    在使用libtermkey时(在意识到Shift-Return之类的情况下没有发生移位之前),我打算编写一个守护程序来运行以收集键盘事件。运行守护程序的副本将仅通过管道传送对键盘数据的请求并作为响应接收键盘数据。我可以使用此设置使某些内容始终在后台运行,以防万一我无法在特定时间检查键码状态(必须及时接收键码状态)。

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

    尝试#1

    我试图直接访问键盘设备,但是遇到问题。我已经尝试了另一个堆栈溢出线程中的建议here。这给了我分割错误。因此,我将其从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

    从这里开始,我想我会尝试一种稍微不同的方法。我想弄清楚我做错了什么。在this页面提供了演示打印出关键代码的代码段之后,我将其 bundle 到程序中:
    #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的Command键之类的修饰键一起使用,似乎效果很好。按下按键时出现输出,从按住该按键发出的后续信号开始 swift 出现,并在释放按键时输出更多数据。

    因此,尽管我的方法可能不正确(我愿意听到的任何替代方案),但该设备似乎满足了我的需求。

    此外,我知道此设备只是从运行指向/dev/input/event2的链接:
    $ ls -l /dev/input/by-path/platform-i8042-serio-0-event-kbd
    

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

    最佳答案

    打开输入设备,

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

    如果发生任何错误,或者如果用户空间仅接收部分事件结构(不应发生,但可能在将来的/笨拙的内核中发生),则以上代码段将从循环中中断。您可能希望使用更强大的读取循环;我个人将用break替换最后一个continue感到满意,从而忽略了部分事件结构。

    然后,您可以检查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.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中的命名常量非常稳定,因为它是内核-用户空间接口(interface),内核开发人员非常努力地维护兼容性。 (也就是说,您可以期望偶尔会有新的代码,但是现有代码很少更改。)

    关于c - 从Linux输入设备访问键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20943322/

    相关文章:

    c - Nvidia Tesla : No platforms found 上的 OpenCL

    c - linux 函数 inet_ntoa 会导致 dns 查询吗?

    linux - 来自终端的 EVIOCGRAB 换行符

    google-chrome - 如何像 Vim 一样将 "go back"Chrome(ALT+向左箭头)重新映射为 ALT+H?

    连接 2 个 unicode 字符串 - 如何做到这一点?

    带文件的 C 项目

    python - 如何使用树莓派 PREEMPT_RT 补丁让进程实时运行?

    python - 使用套接字 : address already in use 监听 ip 地址

    linux - 键盘输入解析算法的引用资料?

    c - 这是 Vernam 密码的 C 代码,如何将数字而不是字符串作为输入消息?