J'ai donc essayé d'accéder à la saisie au clavier sous Linux. Plus précisément, je dois pouvoir accéder aux pressions sur les touches de modification sans appuyer sur d'autres touches. De plus, je veux pouvoir faire cela sans un système X en cours d'exécution.
Donc, en bref, mes exigences sont les suivantes:
0 = not pressed
, 1 = currently pressed
pour me faire savoir si la touche est maintenue enfoncée lors de la vérification du clavierMa machine Linux normale est sur un camion vers mon nouvel appartement; donc je n'ai qu'un Macbook Air pour travailler en ce moment. Par conséquent, j'utilise Linux dans un VM pour tester cela.
Machine virtuelle dans VirtualBox
Tout ce qui suit a été fait dans cet environnement. J'ai essayé à la fois avec X en cours d'exécution et dans l'un des autres ttys.
Je changerai cela si quelqu'un peut me corriger.
J'ai fait pas mal de lecture pour me rendre compte que les bibliothèques de niveau supérieur ne fournissent pas ce type de fonctionnalité. Les touches de modification sont utilisées avec d'autres touches pour fournir un autre code de touche. Accéder aux touches de modification elles-mêmes via une bibliothèque de haut niveau sous Linux n'est pas aussi facile. Ou plutôt, je n'ai pas trouvé d'API de haut niveau pour cela sur Linux.
Je pensais que libtermkey serait la réponse, mais il ne semble pas prendre en charge la touche de modification Shift mieux que la récupération normale des touches. Je ne sais pas non plus si cela fonctionne sans X.
Pendant que je travaillais avec libtermkey (avant de me rendre compte que le changement n'était pas possible dans des cas comme Shift-Return), je prévoyais d'écrire un démon qui s'exécuterait pour rassembler les événements du clavier. L'exécution de copies du programme démon acheminerait simplement les demandes de données de clavier et recevrait les données de clavier en réponse. Je pourrais utiliser cette configuration pour que quelque chose fonctionne toujours en arrière-plan, au cas où je ne pourrais pas vérifier les états des codes clés à des moments spécifiques (je dois recevoir les codes clés lorsqu'ils se produisent).
Voici mes deux tentatives pour écrire un programme qui peut lire à partir du clavier Linux. J'ai également inclus mon petit chèque pour m'assurer d'avoir le bon appareil.
J'ai essayé d'accéder directement au clavier, mais je rencontre des problèmes. J'ai essayé la suggestion ici qui se trouve dans un autre thread Stack Overflow. Cela m'a donné un défaut de segmentation; donc je l'ai changé de fopen en 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);
// ...
Bien qu'il n'y ait eu aucun défaut de segmentation, il n'y avait aucun indicateur de pression sur une touche (pas seulement des touches de modification). J'ai testé cela en utilisant:
./foo && echo "TRUE" || echo "FALSE"
Je l'ai utilisé pour tester des codes de retour réussis à partir de commandes; donc je sais que ça va. J'ai également sorti la clé (toujours 0) et le masque (0100) pour vérifier. Il ne semble tout simplement pas détecter quoi que ce soit.
À partir d'ici, j'ai pensé essayer une approche légèrement différente. Je voulais comprendre ce que je faisais mal. Après la page this fournissant un extrait montrant l'impression des codes clés, je l'ai regroupé dans un programme:
#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;
}
Auparavant, j'avais la taille à 16 octets au lieu de 128 octets. Je devrais honnêtement passer un peu plus de temps à comprendre ioctl et EVIOCGKEY. Je sais juste qu'il est censé mapper des bits à des touches spécifiques pour indiquer des pressions, ou quelque chose comme ça ( corrigez-moi si je me trompe, s'il vous plaît! ).
Je n'avais pas non plus de boucle au départ et je maintiendrais simplement différentes touches pour voir si un code de clé était apparu. Je n'ai rien reçu; donc, je pensais qu'une boucle pourrait rendre la vérification plus facile à tester au cas où quelque chose manquerait.
Je l'ai testé en exécutant cat
sur le périphérique d'entrée. Plus précisément:
$ Sudo cat /dev/input/by-path/platform-i8042-serio-0-event-kbd
Garbage ASCII a été envoyé à mon terminal lors de la pression des touches et des événements de libération commençant par la touche retour (entrée) lorsque j'ai commencé la sortie à l'aide de cat. Je sais également que cela semble fonctionner correctement avec les touches de modification comme la touche Maj, le contrôle, la fonction et même la touche de commande d'Apple sur mon Macbook exécutant une machine virtuelle Linux. libéré.
Donc, même si mon approche n'est peut-être pas la bonne (je suis prêt à entendre toute alternative ), l'appareil semble fournir ce dont j'ai besoin.
De plus, je sais que cet appareil n'est qu'un lien pointant vers/dev/input/event2 depuis son exécution:
$ ls -l /dev/input/by-path/platform-i8042-serio-0-event-kbd
J'ai essayé les deux programmes ci-dessus avec/dev/input/event2 et je n'ai reçu aucune donnée. L'exécution de cat sur/dev/input/event2 a fourni la même sortie qu'avec le lien.
Ouvrez le périphérique d'entrée,
#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;
}
puis lire les événements clavier de l'appareil:
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;
}
L'extrait ci-dessus sort de la boucle si une erreur se produit ou si l'espace utilisateur ne reçoit qu'une structure d'événement partielle (ce qui ne devrait pas se produire, mais pourrait dans certains noyaux futurs/bogués). Vous souhaiterez peut-être utiliser une boucle de lecture plus robuste; Personnellement, je serais satisfait en remplaçant le dernier break
par continue
, de sorte que les structures d'événements partielles soient ignorées.
Vous pouvez ensuite examiner la structure d'événement ev
pour voir ce qui s'est produit et terminer le programme:
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;
}
Pour une touche,
ev.time
: heure de l'événement (struct timeval
type)
ev.type
: EV_KEY
ev.code
: KEY_*
, identifiant de clé; voir la liste complète dans /usr/include/linux/input.h
ev.value
: 0
si libération de clé, 1
si la touche est enfoncée, 2
si répétition automatique des touches
Voir Documentation/input/input.txt dans les sources du noyau Linux pour plus de détails.
Les constantes nommées dans /usr/include/linux/input.h
sont assez stables, car il s'agit d'une interface noyau-espace utilisateur, et les développeurs du noyau s'efforcent de maintenir la compatibilité. (Autrement dit, vous pouvez vous attendre à ce qu'il y ait de nouveaux codes de temps en temps, mais les codes existants changent rarement.)