É possível que um processo de daemon (ou seja, em segundo plano) procure pressionamentos de teclas em um teclado USB?

13

Estou trabalhando em um projeto Linux incorporado no qual desenvolverei um programa que será executado automaticamente na inicialização e interagirá com o usuário por meio de uma exibição de caracteres e algum tipo de matriz de botões. Se formos com uma simples matriz de botões GPIO, posso escrever facilmente um programa que procurará pressionar as teclas nessas linhas GPIO. No entanto, um de nossos pensamentos era usar um dispositivo de teclado numérico USB em vez da entrada do usuário. Meu entendimento é que esses dispositivos se apresentarão ao sistema operacional como um teclado USB. Se seguir esse caminho, existe um caminho para o meu programa procurar entrada neste teclado USB no Linux, tendo em mente que não há terminal virtual ou monitor VGA. Quando um teclado USB é conectado, existe uma entidade em '/ dev' que parece que eu posso abrir um descritor de arquivo?

KyleL
fonte

Respostas:

24

Os dispositivos provavelmente recebem um arquivo /dev/input/nomeado eventNonde N são os vários dispositivos, como mouse, teclado, tomada, botões liga / desliga etc.

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

deve lhe dar uma dica.

Veja também:

cat /proc/bus/input/devices

Onde o Sysfsvalor está no caminho /sys.

Você pode testar por exemplo

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

Para implementar, use ioctl e verifique devices + monitor.

EDIT 2:

ESTÁ BEM. Estou expandindo essa resposta com base no pressuposto de que /dev/input/eventNé usado.

Uma maneira poderia ser:

  1. No loop de inicialização, todos os eventarquivos encontrados em /dev/input/. Use ioctl()para solicitar bits de evento:

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

    depois verifique se EV_KEY-bit está definido.

  2. IFF definido e verifique as chaves:

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

    Por exemplo, se as teclas numéricas são interessantes, verifique se os bits são para KEY_0- KEY9e KEY_KP0para KEY_KP9.

  3. As chaves IFF localizadas e iniciam o monitoramento do arquivo de eventos no encadeamento.

  4. Voltar para 1.

Dessa forma, você deve monitorar todos os dispositivos que atendem aos critérios desejados. Você não pode apenas verificar EV_KEYcomo, por exemplo, o botão liga / desliga terá esse bit definido, mas obviamente não terá o KEY_Aconjunto etc.

Vimos falsos positivos para chaves exóticas, mas para chaves normais isso deve ser suficiente. Não há nenhum dano direto no monitoramento, por exemplo, arquivo de evento para botão liga / desliga ou conector, mas você não emitirá eventos em questão (também conhecido como código incorreto).

Mais detalhadamente abaixo.


EDIT 1:

No que diz respeito a "Explique a última declaração ..." . Indo até o stackoverflow aqui ... mas:

Uma amostra rápida e suja em C. Você precisará implementar vários códigos para verificar se realmente obtém o dispositivo correto, traduz o tipo, o código e o valor do evento. Normalmente key-down, key-up, key-repeat, código-chave, etc.

Não há tempo (e é demais aqui) para adicionar o resto.

Confira linux/input.hprogramas como dumpkeyscódigo do kernel etc. para códigos de mapeamento. Por exemplodumpkeys -l

De qualquer forma:

Executar como por exemplo:

# ./testprog /dev/input/event2

Código:

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

EDIT 2 (continuação):

Observe que, se você olhar, /proc/bus/input/devicestem uma letra no início de cada linha. Aqui Bsignifica mapa de bits. Isso é por exemplo:

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

Cada um desses bits corresponde a uma propriedade do dispositivo. Que por meio bit-mapa, 1 indicam uma propriedade está presente, conforme definido no 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

Dê uma olhada na /drivers/input/input.{h,c}árvore de fontes do kernel. Muito código bom lá. (Por exemplo, as propriedades do dispositivo são produzidas por esta função .)

Cada um desses mapas de propriedades pode ser alcançado por ioctl. Por exemplo, se você quiser verificar quais propriedades de LED estão disponíveis, diga:

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

Veja a definição de struct input_devin input.hpara saber como ledbitsão definidos.

Para verificar o status dos LEDs, diga:

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

Se o bit 1 in ledbitfor 1, o num-lock estará aceso. Se o bit 2 for 1, o caps lock estará aceso etc.

input.h tem os vários define.


Observa quando se trata de monitoramento de eventos:

O pseudocódigo para monitoramento pode ser algo na direção de:

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

Alguns documentos relacionados:

  1. Documentation/input/input.txt, esp. veja a seção 5.
  2. Documentation/input/event-codes.txt, descrição de vários eventos etc. Observe o que é mencionado em, por exemplo, EV_SYNsobreSYN_DROPPED
  3. Documentation/input ... leia o resto, se quiser.
Runium
fonte
2

Você pode fazer isso facilmente fazendo referência /dev/input/by-id/usb-manufacturername_*serialnumber*. Eles aparecem como links simbólicos que podem ser desreferenciados readlink -epara determinar o dispositivo de bloco associado. No entanto, esses links são criados pelos udevquais podem não estar presentes no seu ambiente incorporado.

Ou .. Veja dmesgdepois de conectar o dispositivo USB. Deve fornecer o /devnó.

Jeight
fonte
1
As entradas em /dev/disk/by-id/são criadas por udev- a questão é se isso está disponível neste caso parcial (plataforma incorporada).
Peterph
@ Peter: Você está correto. Se não estiver usando o udev, a primeira sugestão não funcionará.
Jeight
@ Gilles: Vejo que você editou a resposta e alterou o caminho para entrada, em vez de disco. Nesse caso, acredito que seria entrada / por caminho e não disco / por ID. Eu suspeito que ambos funcionariam.
Jeight
1
Não, by-idestá correto. Por exemplo, meu teclado USB está disponível como /dev/input/by-id/usb-_USB_Keyboard-event-kbde /dev/input/by-path/pci-0000:00:1d.2-usb-0:2:1.0-event-kbd.
Gilles 'SO- stop be evil'
@ Jaeight: Depois de encontrar o nó correto do dispositivo, você sabe como posso acessar as teclas pressionadas?
KyleL