Je me suis pris la tête tout le week-end pour tenter de résoudre un soucis lié à la gestion du clavier pour une application que je voudrais faire avec mes élèves.
Cette partie étant la plus complexe, je trouve, c'est donc à moi de la mettre en place sous forme de librairie de fonctions que mes élèves intégreront à leur projet.
Nous sommes en train de développer un bomberman (simulation de) en mode console, sur Linux dans notre laboratoire.
Pour ce faire, nous avons besoin d'un gestionnaire de clavier qui détecte que la touche enfoncée est relevée à un moment avant de prendre en compte la touche appuyée.
Le concept est le suivant: je modifie le terminal linux pour que les entrées ne soient pas bloquantes, ainsi si il n'y a rien en entrée (aucune touche appuyée) la fonction retourne -1.
Cela me permet de savoir si une touche est maintenue enfoncée ou pas... si j'obtiens -1 à un moment dans la boucle de saisie, cela veut dire qu'on a relâché la touche.
Je ne vais pas revenir sur ma logique bizarroïde concernant la détection de la touche relevée et pourquoi je n'ai pas choisi d'utiliser SDL2 pour la gestion clavier: concernant SDL2, la gestion du clavier n'est pas indépendante
de l'environnement graphique, et pour mes débutants j'avais pas envie d'attaquer de suite SDL2 et le mode graphique.
Dans ma librairie de fonction j'ai nbgetchEx() (pour non bloquing getch Extended) définie comme ceci:
...qui ne fait pas l'écho des caractères une fois la touche appuyée.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 //***************************************************************************** // Fonction nbgetchEx (sans écho/affichage du caractère saisi) // // Entrée: néant // // Sortie: le caractère entré au clavier //***************************************************************************** #ifdef LINUX int nbgetchEx(void) { char Buffer; struct termios oldt; struct termios newt; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; // mode non bloquant... newt.c_cc[VMIN] = 0; newt.c_cc[VTIME] = 2; // 0 ça pousse le processeur à 100% (faire gaffe à ce que l'on fait) newt.c_lflag &= ~(ICANON|ECHOCTL|ECHO); // n'a pas besoin d'appuyer sur "entrée" (caractère non bloquant de la procédure) sans écho tcsetattr(STDIN_FILENO, TCSANOW, &newt); Buffer=getchar(); // retourne EOF (-1) si il y a rien dans le buffer // on restitue "l'ancienne configuration" tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return Buffer; } #endif
Ca fonctionne plutôt bien sauf que: vu la logique un peu scabreuse, comme ce "gestionnaire de clavier" est exécuté en parallèle avec le programme principal, j'ai besoin d'une synchronisation pour indiquer au processus père (la fonction principale dans mon cas) que un caractère a été saisi, lorsque je me suis assuré que la touche a été relevée. En effet, je considère qu'un caractère a été saisi si la séquence APPUI/RELEVE de la touche a été opérée et pas avant.
J'ai choisi les files de messages pour synchroniser le thread gestionnaire clavier avec la fonction principale.
J'ai créé deux structures pour échanger des messages entre les deux processus:
s_KeyEvent est une structure contenant des informations relatives à la touche appuyée, cKey contient le code de la touche et les autres champs indiquent si il s'agit d'un caractère multibyte ou une touche de fonction, je considère comme "multibyte" les séquences d'échappement ANSI qui ne sont pas des fonctions comme PAGEUP, HOME, les touches de direction, etc...
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 typedef struct s_KeyEvent { char cKey; wchar_t wKey; bool bMultiByte; bool bFunction; bool bStatus; // true: touche relevée, false: touche enfoncée }t_KeyEvent; typedef struct s_MSGQ_ACK { bool bAck; }t_MSGQ_ACK;
s_MSGQ_ACK est une structure me permettant d'indiquer au thread, à partir de la fonction principale, que le caractère (le message) a bien été reçu et que le thread peut continuer à détecter l'appui d'une touche.
Le "protocole" est le suivant:
THREAD: SI la touche appuyée est relevée ALORS envoyer message sur la file de message pour les données de type t_KeyEvent
MAIN: attendre qu'un message soit disponible dans la file de messageMAIN:traiter le messageTHREAD: se mettre en attente de l'ACK (acknowledgement) de la part de la fonction principale (MAIN)MAIN:indiquer que le message a été traité
THREAD: continuer à détecter une touche
Dans le code de la fonction main, je crée donc deux files de messages: l'une pour les données et l'autre pour l'ACK, j'ai voulu utiliser une seule file de message pour les deux synchronisations et je ne sais pour quelle raison
cela n'avait jamais fonctionné comme je l'entendais, mais peut-être était-ce lié au "bug" que j'ai découvert...
En effet, lorsque je place ma fonction msgget() qui permet de "se brancher" sur une file de message et d'obtenir un identifiant (censé être positif ou égal à zéro) en dehors de ma boucle où je me mets en attente
des messages provenant de la file de messages véhiculant les caractères appuyés, et qu'ensuite toujours dans cette boucle, je tente d'envoyer l'ACK, je ne sais pour quelle(s) raison(s) l'identifiant de la file de message
devient soudainement négatif, donc invalide et je me choppe un "invalid argument" lorsque je tente un msgsnd() pour envoyer l'ACK au thread.
Par contre, si je place le msgget() dans la boucle (ce que je trouve absurde), là je suis bon, l'identifiant ne deviendra jamais négatif et mon gestionnaire de clavier fonctionne à merveille.
Est-ce que j'aurais raté un épisode concernant la fonction msgget() et le mécanisme des files messages ou alors (et j'ai vérifié tant que verse peu) il y aurait un effet de bord qui modifie mon identifiant de file de message... ou
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 ... #ifdef BUGME server_ACK_queue=msgget(ftok(ACKQ,'A'),IPC_CREAT|0660); if(server_ACK_queue<0) { perror("[main] -S- Erreur de création de la file de messages "); return (EXIT_FAILURE); } #endif while(bRunning) { Log(logFile,"[main] attente..."); coderetour=msgrcv(client_TRN_queue,(void*)&c_SaisieClavier,sizeof(t_KeyEvent),0,0); // est censé bloquer le programme jusqu'à ce qu'un message arrive... if(coderetour==-1) { bRunning=false; continue; } ... // Si je ne déplace pas ce bloc ici, j'ai un bug --> server_ACK_queue devient négatif (???) server_ACK_queue=msgget(ftok(ACKQ,'A'),IPC_CREAT|0660); if(server_ACK_queue<0) { perror("[main] -S- Erreur de création de la file de messages "); return (EXIT_FAILURE); } ... coderetour=msgsnd(server_ACK_queue,(void*)&s_Ack,sizeof(t_MSGQ_ACK),0); if(coderetour==-1) { perror("[main] Erreur msgq snd -> "); break; } ... }
sommes nous dans un cas de figure qui nous laissera dans le flou le plus total ?
Partager