Est-il possible pour un processus démon (c'est-à-dire en arrière-plan) de rechercher des pressions sur un clavier USB?

13

Je travaille sur un projet Linux embarqué où je développerai un programme qui s'exécutera automatiquement au démarrage et interagira avec l'utilisateur via un affichage de caractères et une sorte de tableau de boutons. Si nous optons pour un simple tableau de boutons GPIO, je peux facilement écrire un programme qui recherchera des touches sur ces lignes GPIO. Cependant, une de nos réflexions était d'utiliser un pavé numérique USB à la place pour la saisie par l'utilisateur. Je crois comprendre que ces appareils se présenteront au système d'exploitation comme un clavier USB. Si vous suivez ce chemin, y a-t-il un moyen pour mon programme de rechercher des entrées sur ce clavier USB depuis Linux, en gardant à l'esprit qu'il n'y a pas de terminal virtuel ou d'affichage VGA. Lorsqu'un clavier USB est branché, y a-t-il une entité dans '/ dev' qui apparaît pour laquelle je peux ouvrir un descripteur de fichier?

KyleL
la source

Réponses:

24

Les périphériques obtiennent très probablement un fichier /dev/input/nommé eventNoù N correspond aux différents périphériques tels que la souris, le clavier, la prise, les boutons d'alimentation, etc.

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

devrait vous donner un indice.

Regardez aussi:

cat /proc/bus/input/devices

Où la Sysfsvaleur est le chemin sous /sys.

Vous pouvez tester par exemple

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

Pour implémenter, utilisez ioctl et vérifiez les périphériques + moniteur.

EDIT 2:

D'ACCORD. Je développe cette réponse en partant de l'hypothèse que l' /dev/input/eventNon utilise.

Une façon pourrait être:

  1. Au démarrage boucle tous les eventfichiers trouvés dans /dev/input/. Utilisez ioctl()pour demander des bits d'événement:

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

    puis vérifiez si EV_KEY-bit est défini.

  2. IFF réglé puis vérifiez les clés:

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

    Par exemple, si les touches numériques sont intéressantes, vérifiez si les bits pour KEY_0- KEY9et KEY_KP0pour KEY_KP9.

  3. Les clés IFF ont été trouvées, puis démarrent la surveillance du fichier d'événements dans le thread.

  4. Retour à 1.

De cette façon, vous devriez pouvoir surveiller tous les appareils qui répondent aux critères souhaités. Vous ne pouvez pas seulement vérifier car, EV_KEYpar exemple, le bouton d'alimentation aura ce bit défini, mais il n'aura évidemment pas KEY_Adéfini etc.

J'ai vu des faux positifs pour les clés exotiques, mais pour les clés normales, cela devrait suffire. Il n'y a pas de préjudice direct dans la surveillance, par exemple un fichier d'événements pour le bouton d'alimentation ou une prise, mais ceux-ci n'émettront pas les événements en question (aka. Mauvais code).

Plus en détail ci-dessous.


EDIT 1:

En ce qui concerne "Expliquez cette dernière déclaration…" . Aller ici dans le stackoverflow … mais:

Un exemple rapide et sale en C. Vous devrez implémenter divers codes pour vérifier que vous obtenez réellement le bon périphérique, traduire le type d'événement, le code et la valeur. Typiquement, clé en bas, clé en haut, répétition de clé, code de clé, etc.

Je n'ai pas le temps (et c'est trop ici) d'ajouter le reste.

Découvrez linux/input.h, des programmes comme dumpkeys, le code du noyau, etc. pour les codes de mappage. Par exempledumpkeys -l

De toute façon:

Exécuter comme par exemple:

# ./testprog /dev/input/event2

Code:

#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 (suite):

Notez que si vous regardez, /proc/bus/input/devicesvous avez une lettre au début de chaque ligne. Ici Bsignifie bitmap. C'est par exemple:

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

Chacun de ces bits correspond à une propriété de l'appareil. Ce qui signifie par bitmap, 1 indique qu'une propriété est présente, comme défini dans 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

Jetez un oeil /drivers/input/input.{h,c}dans l'arborescence des sources du noyau. Beaucoup de bon code là-bas. (Par exemple, les propriétés des appareils sont produites par cette fonction .)

Chacune de ces cartes de propriété peut être atteinte par ioctl. Par exemple, si vous souhaitez vérifier quelles propriétés de LED sont disponibles, dites:

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

Regardez la définition de struct input_devin input.hpour savoir comment ledbitsont définis.

Pour vérifier l'état des LED, dites:

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

Si le bit 1 dans ledbitest 1, le verrouillage numérique est allumé. Si le bit 2 est 1, le verrouillage des majuscules est allumé, etc.

input.h a les différentes définitions.


Remarques concernant la surveillance des événements:

Le pseudo-code de surveillance pourrait être quelque chose dans le sens 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

Quelques documents liés:

  1. Documentation/input/input.txt, esp. note section 5.
  2. Documentation/input/event-codes.txt, La description de divers événements , etc. Prenez note à ce qui est mentionné dans , par exemple à EV_SYNpropos deSYN_DROPPED
  3. Documentation/input ... lisez le reste si vous voulez.
Runium
la source
2

Vous pouvez le faire facilement en référençant /dev/input/by-id/usb-manufacturername_*serialnumber*. Ceux-ci apparaissent sous forme de liens symboliques que vous pouvez déréférencer en utilisant readlink -epour déterminer le périphérique de bloc associé. Ces liens sont cependant créés par udevlesquels peuvent ne pas être présents dans votre environnement intégré.

Ou .. Regardez dmesgaprès la connexion du périphérique USB. Il devrait vous donner le /devnœud.

Jeight
la source
1
Les entrées dans /dev/disk/by-id/sont à mon humble avis créées par udev- la question est de savoir si elles sont disponibles dans ce cas particulier (plateforme embarquée).
peterph
@peterph: Vous avez raison. Si vous n'utilisez pas udev, la première suggestion ne fonctionnera pas.
Jeight
@ Gilles: Je vois que vous avez édité la réponse et changé le chemin d'accès à l'entrée plutôt qu'au disque. Dans ce cas, je pense que ce serait entrée / par chemin d'accès et non disque / par id. Je soupçonne que l'un ou l'autre fonctionnerait.
Jeight
1
Non, by-idc'est correct. Par exemple, mon clavier USB est disponible en tant que /dev/input/by-id/usb-_USB_Keyboard-event-kbdet /dev/input/by-path/pci-0000:00:1d.2-usb-0:2:1.0-event-kbd.
Gilles 'SO- arrête d'être méchant'
@Jeight: Une fois que j'ai trouvé le nœud de périphérique correct, savez-vous comment accéder aux appuis sur les touches?
KyleL