守护进程(即后台)进程是否可以从 USB 键盘查找按键?

守护进程(即后台)进程是否可以从 USB 键盘查找按键?

我正在开发一个嵌入式 Linux 项目,我将开发一个程序,该程序将在启动时自动运行并通过字符显示和某种按钮阵列与用户交互。如果我们使用一个简单的 GPIO 按钮阵列,我可以轻松编写程序来查找这些 GPIO 线路上的按键。但是,我们的想法之一是使用 USB 数字键盘设备来代替用户输入。我的理解是,这些设备将作为 USB 键盘呈现给操作系统。如果按照这种方式,是否有办法让我的程序在 Linux 中查找此 USB 键盘上的输入,请记住没有虚拟终端或 VGA 显示器。插入 USB 键盘时,'/dev' 中是否会出现一个实体,我可以为其打开文件描述符?

答案1

设备很可能会获得一个/dev/input/名为的文件eventN,其中 N 是各种设备,例如鼠标、键盘、插孔、电源按钮等。

ls -l  /dev/input/by-{path,id}/

应该给你一个提示。

另请参阅:

cat /proc/bus/input/devices

其中Sysfsvalue 是 下的路径/sys

您可以通过例如进行测试

cat /dev/input/event2 # if 2 is kbd.

要实现使用 ioctl 并检查设备+监视器。

编辑2:

好的。我根据所/dev/input/eventN使用的假设来扩展这个答案。

一种方法可能是:

  1. 在启动时循环event在 中找到的所有文件/dev/input/。用于ioctl()请求事件位:

    ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
    

    然后检查EV_KEY-bit 是否已设置。

  2. IFF 设置然后检查密钥:

    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), &keybit);
    

    例如,如果数字键有趣,则检查KEY_0-KEY9KEY_KP0to 的位是否KEY_KP9

  3. 找到 IFF 键然后开始监视线程中的事件文件。

  4. 回到1。

这样您就可以监控所有符合所需标准的设备。您不仅可以检查EV_KEY电源按钮是否设置了此位,但显然不会设置KEY_A等。

已经看到异国钥匙的误报,但是对于普通按键这应该足够了。监视例如电源按钮或插孔的事件文件没有直接危害,但您不会发出有问题的事件(也称为错误代码)。

下面有更详细的介绍。


编辑1:

关于“解释一下最后一句话……”。过去在堆栈溢出降落在这里……但是:

一个快速而肮脏的 C 示例。您必须实现各种代码来检查您是否确实获得了正确的设备、转换事件类型、代码和值。通常是按键按下、按键抬起、按键重复、按键代码等。

没有时间(这里太多了)来添加其余部分。

查看linux/input.h诸如dumpkeys内核代码之类的程序以获取映射代码。例如dumpkeys -l

无论如何:

运行例如:

# ./testprog /dev/input/event2

代码:

#include <stdio.h>

#include <string.h>     /* strerror() */
#include <errno.h>      /* errno */

#include <fcntl.h>      /* open() */
#include <unistd.h>     /* close() */
#include <sys/ioctl.h>  /* ioctl() */

#include <linux/input.h>    /* EVIOCGVERSION ++ */

#define EV_BUF_SIZE 16

int main(int argc, char *argv[])
{
    int fd, sz;
    unsigned i;

    /* A few examples of information to gather */
    unsigned version;
    unsigned short id[4];                   /* or use struct input_id */
    char name[256] = "N/A";

    struct input_event ev[EV_BUF_SIZE]; /* Read up to N events ata time */

    if (argc < 2) {
        fprintf(stderr,
            "Usage: %s /dev/input/eventN\n"
            "Where X = input device number\n",
            argv[0]
        );
        return EINVAL;
    }

    if ((fd = open(argv[1], O_RDONLY)) < 0) {
        fprintf(stderr,
            "ERR %d:\n"
            "Unable to open `%s'\n"
            "%s\n",
            errno, argv[1], strerror(errno)
        );
    }
    /* Error check here as well. */
    ioctl(fd, EVIOCGVERSION, &version);
    ioctl(fd, EVIOCGID, id); 
    ioctl(fd, EVIOCGNAME(sizeof(name)), name);

    fprintf(stderr,
        "Name      : %s\n"
        "Version   : %d.%d.%d\n"
        "ID        : Bus=%04x Vendor=%04x Product=%04x Version=%04x\n"
        "----------\n"
        ,
        name,

        version >> 16,
        (version >> 8) & 0xff,
        version & 0xff,

        id[ID_BUS],
        id[ID_VENDOR],
        id[ID_PRODUCT],
        id[ID_VERSION]
    );

    /* Loop. Read event file and parse result. */
    for (;;) {
        sz = read(fd, ev, sizeof(struct input_event) * EV_BUF_SIZE);

        if (sz < (int) sizeof(struct input_event)) {
            fprintf(stderr,
                "ERR %d:\n"
                "Reading of `%s' failed\n"
                "%s\n",
                errno, argv[1], strerror(errno)
            );
            goto fine;
        }

        /* Implement code to translate type, code and value */
        for (i = 0; i < sz / sizeof(struct input_event); ++i) {
            fprintf(stderr,
                "%ld.%06ld: "
                "type=%02x "
                "code=%02x "
                "value=%02x\n",
                ev[i].time.tv_sec,
                ev[i].time.tv_usec,
                ev[i].type,
                ev[i].code,
                ev[i].value
            );
        }
    }

fine:
    close(fd);

    return errno;
}

编辑 2(续):

请注意,如果您查看,/proc/bus/input/devices每行开头都有一个字母。这里的B意思是位图。例如:

B: PROP=0
B: EV=120013
B: KEY=20000 200 20 0 0 0 0 500f 2100002 3803078 f900d401 feffffdf ffefffff ffffffff fffffffe
B: MSC=10
B: LED=7

这些位中的每一个都对应于设备的一个属性。位图意味着 1 表示存在属性,如 中所定义linux/input.h。 :

B: PROP=0    => 0000 0000
B: EV=120013 => 0001 0010 0000 0000 0001 0011 (Event types sup. in this device.)
                   |   |               |   ||
                   |   |               |   |+-- EV_SYN (0x00)
                   |   |               |   +--- EV_KEY (0x01)
                   |   |               +------- EV_MSC (0x04)
                   |   +----------------------- EV_LED (0x11)
                   +--------------------------- EV_REP (0x14)
B: KEY=20... => OK, I'm not writing out this one as  it is a bit huge.

B: MSC=10    => 0001 0000
                   |
                   +------- MSC_SCAN
B: LED=7     => 0000 0111 , indicates what LED's are present
                      |||
                      ||+-- LED_NUML
                      |+--- LED_CAPSL
                      +---- LED_SCROLL

查看/drivers/input/input.{h,c}内核源代码树。那里有很多好的代码。 (例如,设备属性是由这个功能.)

这些属性图中的每一个都可以通过 获得ioctl。例如,如果您想检查哪些 LED 属性可用,请说:

ioctl(fd, EVIOCGBIT(EV_LED, sizeof(ledbit)), &ledbit);

查看struct input_devin的定义input.h了解如何ledbit定义。

要检查 LED 的状态,请执行以下操作:

ioctl(fd, EVIOCGLED(sizeof(ledbit)), &ledbit);

如果位 1 为ledbit1,则数字锁定灯亮起。如果位 2 为 1,则大写锁定指示灯亮起,等等。

input.h有各种定义。


事件监听时的注意事项:

用于监控的伪代码可能是以下方向的东西:

WHILE TRUE
    READ input_event
    IF event->type == EV_SYN THEN
        IF event->code == SYN_DROPPED THEN
            Discard all events including next EV_SYN
        ELSE
            This marks EOF current event.
        FI
    ELSE IF event->type == EV_KEY THEN
        SWITCH ev->value
            CASE 0: Key Release    (act accordingly)
            CASE 1: Key Press      (act accordingly)
            CASE 2: Key Autorepeat (act accordingly)
        END SWITCH
    FI
END WHILE

一些相关文件:

  1. Documentation/input/input.txt,特别是。请注意第 5 节。
  2. Documentation/input/event-codes.txt、各种事件的描述等。请注意下面提到的内容,例如EV_SYN关于SYN_DROPPED
  3. Documentation/input...如果您愿意,请阅读其余部分。

答案2

您可以通过引用轻松完成此操作/dev/input/by-id/usb-manufacturername_*serialnumber*。它们显示为符号链接,您可以使用它们取消引用readlink -e以确定关联的块设备。然而,这些链接是由其创建的udev,可能不存在于您的嵌入式环境中。

dmesg或者..连接USB设备后看一下。它应该给你/dev节点。

相关内容