IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Demande information sur getchar


Sujet :

C

  1. #1
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 434
    Points : 43 068
    Points
    43 068
    Par défaut Demande information sur getchar
    Bonjour,

    Je cherche à lire un caractère sur la console. Je me suis tourné vers getchar(). Mon soucis est que des caractères s’affichent jusqu'à l'appui de la touche entrée. La fonction n'est elle pas censée lire un seul caractère ? Le retour dans l'int correspond au code ASCII de la première touche appuyée, les autres touches n'écrasent donc pas mon int mais les caractères suivant sont "perdus" je pense. Et comment gérer la touche entrée si celle-ci arrête getchar ? Et comment gérer les touches spéciales ? exemple ctrl+a : J'ai trouvé un exemple avec la touche flèche vers le haut. Ceci envoi une séquence de codes ASCII suivants :27,91,65 (test avec petit code c trouvé)

    Par ailleurs les caractères s'affichent à l'écran en echo. J'ai vu que pour contourner ceci il fallait appeler "/bin/stty raw" et "/bin/stty cooked" mais ceci sera valable pour tout ce qu'il se passe sur le tty et donc pas forcément que pour mon process je pense.

    Dois-je utiliser autre chose que getchar ?

    Merci pour votre aide.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  2. #2
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par chrtophe Voir le message
    Bonjour,
    Bien le bonjour.

    Je cherche à lire un caractère sur la console. Je me suis tourné vers getchar(). Mon soucis est que des caractères s’affichent jusqu'à l'appui de la touche entrée. La fonction n'est elle pas censée lire un seul caractère ?
    getchar() va lire le premier élément disponible dans le stdin et le retourner.

    Le retour dans l'int correspond au code ASCII de la première touche appuyée, les autres touches n'écrasent donc pas mon int mais les caractères suivant sont "perdus" je pense.
    Ils ne sont pas perdu, ils sont toujours présent dans le stdin. Une fonction comme scanf() par exemple n'est pour ainsi dire qu'une boucle sur un getchar(), visant a vider le buffer stdin.

    Et comment gérer la touche entrée si celle-ci arrête getchar ? Et comment gérer les touches spéciales ? exemple ctrl+a : J'ai trouvé un exemple avec la touche flèche vers le haut. Ceci envoi une séquence de codes ASCII suivants :27,91,65 (test avec petit code c trouvé)
    getchar() gère de lui-même la touche "enter".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <stdio.h>
     
    int main(void) {
     
    	int ret = getchar();
     
    	printf("ret : %d",ret,ret);
    	return 0;
    }
    Avec uniquement "enter" affiche bien 10. Selon la table ascii, 10 représente bien une LN : Line feed -- nouvelle ligne.
    Par contre, je ne pourrais te dire pour les caractères spéciaux et pour les combinaisons de touche. Il me semble qu'il faille traiter chaque caractère séparément.

    Par ailleurs les caractères s'affichent à l'écran en echo. J'ai vu que pour contourner ceci il fallait appeler "/bin/stty raw" et "/bin/stty cooked" mais ceci sera valable pour tout ce qu'il se passe sur le tty et donc pas forcément que pour mon process je pense.
    Je ne connais pas cette partie, donc ne pourrai m'étendre dessus.

    Dois-je utiliser autre chose que getchar ?
    Sur windows, il y a la fonction propriétaire getch() qui a le même comportement que le getchar(), excepté que celui-ci sort de la fonction dès réception d'un caractère sur le stdin. Il n'est donc pas dépendant de l'appui sur touche "enter".
    De mémoire, il n'y a pas d'équivalent dans le monde Unix, il faut donc le bricoler soi-même (mais je ne saurai dire comment).

    Merci pour votre aide.
    De rien

  3. #3
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    852
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 852
    Points : 2 298
    Points
    2 298
    Par défaut
    Citation Envoyé par archMqx. Voir le message
    Sur windows, il y a la fonction propriétaire getch() qui a le même comportement que le getchar(), excepté que celui-ci sort de la fonction dès réception d'un caractère sur le stdin. Il n'est donc pas dépendant de l'appui sur touche "enter".
    De mémoire, il n'y a pas d'équivalent dans le monde Unix, il faut donc le bricoler soi-même (mais je ne saurai dire comment).
    J'aurais pensé à une utilisation de select allié à read.

  4. #4
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par imperio Voir le message
    J'aurais pensé à une utilisation de select allié à read.
    Je note et testerai ça dans l'après-midi. Merci à toi !

    EDIT :

    Alors, après test de ceci (basé sur l'exemple de test dans le man select) :

    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
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
     
    int
    main(void)
    {
       fd_set rfds;
       struct timeval tv;
       int retval;
       char ret;
     
       /* Surveiller stdin (fd 0) en attente d'entrées */
       FD_ZERO(&rfds);
       FD_SET(0, &rfds);
       /* Pendant 5 secondes maxi */
       tv.tv_sec = 5;
       tv.tv_usec = 0;
     
       retval = select(1, &rfds, NULL, NULL, &tv);
       /* Considerer tv comme indéfini maintenant ! */
     
       if(retval){
     
           printf("Données disponibles maintenant\n");
           fread(&ret, 1, sizeof(char), stdin);
           printf("char dispo : %c -- %d ",ret,ret);
     
       }else{
     
           printf("Pas de données depuis 5 secondes\n");
     
       }
     
       exit(0);
    }
    Problème, on reste dépendant de ce que le terminal nous envoie. Celui-ci attends enter pour valider la chaine et l'envoyer au programme.
    Donc, j'ai été voir du côté des fonctions termios :

    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
    int my_getch()
    {
    	struct termios info;
    	int retval;
    	// Récupère les infos sur stdin
    	tcgetattr(STDIN_FILENO,&info);
     
    	// Rends stdin en mode raw
    	info.c_lflag &= ~ICANON;
     
    	// On effectue les modif immediatement
    	tcsetattr(STDIN_FILENO, TCSANOW, &info);
     
    	retval = getchar();
     
    	// On repasse en mode normal
    	info.c_lflag |= ICANON;
    	// On effectue les modif immediatement
    	tcsetattr(STDIN_FILENO, TCSANOW, &info);
     
     
    	return retval;
    }
    Le passage du terminal en mode raw permet de recevoir les caractères un à un. Ce qui au final réponds à l'attente.
    Dernière modification par Invité ; 02/07/2015 à 17h35.

  5. #5
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 622
    Points
    23 622
    Par défaut
    Bonjour,

    Citation Envoyé par chrtophe Voir le message
    Je cherche à lire un caractère sur la console. Je me suis tourné vers getchar(). Mon soucis est que des caractères s’affichent jusqu'à l'appui de la touche entrée. La fonction n'est elle pas censée lire un seul caractère ? Le retour dans l'int correspond au code ASCII de la première touche appuyée, les autres touches n'écrasent donc pas mon int mais les caractères suivant sont "perdus" je pense. Et comment gérer la touche entrée si celle-ci arrête getchar ? Et comment gérer les touches spéciales ? exemple ctrl+a : J'ai trouvé un exemple avec la touche flèche vers le haut. Ceci envoi une séquence de codes ASCII suivants :27,91,65 (test avec petit code c trouvé)
    C'est un problème classique : ce comportement n'est pas dû à ton programme C, ni à la fonction getchar() en particulier, ni mêmes aux arcanes du système UNIX qui chapeaute tout cela : il est dû au fait que ton entrée standard provient par défaut d'un terminal. Un « terminal » étant par définition l'équipement de transmission qui vient prendre place au bout d'une ligne (de communication). Ça peut être une console de travail (donc un combiné écran + clavier) mais ça peut aussi désigner un téléphone mobile dans le cas du GSM, par exemple.

    Ceci veut dire qu'en réalité, l'ordinateur central se trouvait dans une grosse pièce isolée de laquelle partait un grand nombre de lignes séries distinctes (à la manière des RS/232) qui aboutissaient chacune à un poste de travail, lequel était formé d'un terminal « bête ». C'est-à-dire que la plupart du temps, ce terminal se contentait d'envoyer par la ligne le caractère saisi par l'utilisateur et d'afficher à l'écran tout caractère qui lui parvenait par la même ligne. Le Minitel était un exemple-type de terminal et si d'aventure, tu en as encore un qui traîne dans un coin, tu t'apercevras qu'il fonctionne exactement de la même façon.

    Un terminal était donc une « console » dans le sens où on s'asseyait devant et où il formait un pupitre de travail. C'est par extension que l'on a utilisé le même terme pour définir le mot « console de jeux », là encore dans le sens où l'on venait se tenir devant l'appareil et que celui-ci était fait pour proposer directement les jeux sans avoir à faire de manipulation particulière ou à donner d'ordre.

    C'est aussi pour les mêmes raisons que les 32 premiers « caractères » du code ASCII sont des « codes de contrôle » : comme il n'avait pas d'autre moyen d'intervenir sur le terminal que de lui envoyer des caractères, il fallait dès le départ définir des codes spéciaux qui servaient à le piloter et à le placer dans certains modes. C'est aussi pour cela que l'ASCII propose le fameux code ESC (« Escape », 27 en décimal, 33 en octal, 1B en hexadécimal) pour pouvoir « sortir » du flux normal et envoyer des séquences hors-code, ce qui permettait d'une certaine manière de « l'étendre ».

    Tout ceci pour dire, donc, que les « terminaux virtuels » que tu utilises aujourd'hui sous Unix à travers les sessions X-Window, tout comme les consoles virtuelles en mode texte de Linux (Alt-F1, F2, F3…) ne sont pas de simples « boîtes DOS » : il s'agit en réalité d'émulateurs reproduisant fidèlement le comportement des vrais terminaux physiques distants. Ça veut dire aussi que comme ces machines sont censées être complètement détachées de la machine sur laquelle ton logiciel fonctionne, celui-ci ne peut pas connaître directement l'état des touches en question.

    Or, à des fins d'efficacité, et pour essayer de « factoriser » les utilisations les plus fréquentes, certains dispositifs ont été mis en place. D'une part sur les terminaux et même et, d'autre part, sur les contrôleurs de ligne ou sur les dispositifs d'entrée, ce qui permettait de définir des « disciplines de ligne ». Côté terminal, il y a l'écho local : comme le terminal envoie par la ligne ce que l'utilisateur tape et n'affiche à l'écran que ce qu'il reçoit, alors l'ordinateur est obligé de répéter ce qu'il reçoit du terminal pour que celui-ci l'affiche. Pour éviter du trafic inutile, il est possible de demander au terminal d'afficher directement ce que l'utilisateur tape. Avantage : plus de temps de latence à l'affichage, un trafic considérablement réduit, et une grande simplicité du côté logiciel car autrement, c'est l'application elle-même qui devrait gérer tout cela à chaque fois qu'elle ferait une saisie utilisateur.

    Côté discipline de ligne, il est généralement possible de gérer en amont de l'application, soit directement les champs de saisie, soit au moins des buffers de ligne : il se passe à peu près la même chose qu'avec setbuf() et printf(), à ceci près que ces fonctions travaillent en sortie et sont gérées par l'application, via la bibliothèque standard, qu'elle utilise. Dans le cas des terminaux, ça veut dire que tout ce qui est saisi par l'utilisateur va être mis en attente par le système, jusqu'à ce que l'utilisateur en question valide sa saisie avec Enter ou Return. C'est seulement alors que l'intégralité de la ligne arrivera à stdin. C'est pourquoi tu ne parviens, par défaut, à ne lire aucun caractère avant que l'utilisateur ait validé et c'est également pour cela qu'au moment où les caractères te parviennent, tu reçois d'abord le premier.

    Ce comportement a beaucoup d'avantages également : il permet non seulement d'économiser beaucoup de trafic, encore une fois, mais également de minimiser les overheads liés à cet envoi (autrement, si ta liaison transitait par une connexion TCP/IP par exemple, il faudrait envoyer un paquet entier pour chaque caractère saisi) mais cela permet également à l'utilisateur de se corriger : par exemple, sous Shell, on peut taper Ctrl-U pour effacer la ligne entière (plus précisément : tout ce qui se trouve avant le curseur). Ce n'est pas qu'un simple raccourci clavier : c'est un code spécial attribué à certaines fonctions du terminal et qui est perçu comme un ordre lorsqu'il est saisi. Ça te permet par exemple d'effacer tout un mot de passe si tu t'es trompé en le saisissant, et ce même si tu ne le vois pas. Et tout cela à l'insu de l'application, ce qui est une bonne chose car autrement, elle devrait non seulement gérer l'écho local mais aussi, et d'une manière générale, toutes les facilités d'édition permettant à l'utilisateur de corriger sa frappe.

    À noter que cela se fait sur tous les terminaux électroniques, mais se faisait également sur les dernières générations de machine à écrire dans les bureaux, avant que tout le monde passe au traitement de texte : ces machines étaient devenues électro-mécaniques, avec des touches qui commandaient le lancement d'un marteau mû électriquement, d'une boule ou d'une marguerite. Elles disposaient d'un petit écran LCD 16 ou 20 caractères qui permettait d'avoir un aperçu de la ligne courante et de la corriger. Le texte était physiquement imprimé qu'au moment du retour à la ligne.

    Par ailleurs les caractères s'affichent à l'écran en echo. J'ai vu que pour contourner ceci il fallait appeler "/bin/stty raw" et "/bin/stty cooked"
    Plus précisément, raw et cooked sont des aliases qui veulent dire « brut » (de décoffrage) ou « cuisiné » (aux petits oignons) et qui servent à positionner plusieurs flags d'un coup.

    Ceux qui nous intéressent ici sont :


    • stty ±echo
    • stty ±icanon


    La man page Linux de stty est le premier endroit où chercher mais elle est trop imprécise (et mériterait d'être ré-écrite). Celle d'AIX IBM est meilleure : https://www-01.ibm.com/support/knowl...cmds5/stty.htm

    Tu peux utiliser termios pour le faire directement depuis le C, à condition de sauvegarder l'état initial au début de ton programme et de le restaurer en sortant, par exemple avec atexit().

    mais ceci sera valable pour tout ce qu'il se passe sur le tty et donc pas forcément que pour mon process je pense.
    C'est vrai mais ton terminal ne sera exploité que par un seul processus à la fois.
    Par contre, il est effectivement important de restaurer l'état initial du terminal en fin de programme.

    Dois-je utiliser autre chose que getchar ? Merci pour votre aide.
    Non. Tu as le droit d'utiliser la fonction qui te plaît mais le problème sera le même puisqu'il se situe en amont.

    Citation Envoyé par archMqx. Voir le message
    getchar() gère de lui-même la touche "enter".

    Avec uniquement "enter" affiche bien 10. Selon la table ascii, 10 représente bien une LN : Line feed -- nouvelle ligne.
    Par contre, je ne pourrais te dire pour les caractères spéciaux et pour les combinaisons de touche. Il me semble qu'il faille traiter chaque caractère séparément.
    Attention au piège : ce n'est pas getchar() qui traite Enter elle-même, c'est la manière dont les retours à la ligne sont représentés sous Unix. « 10 » uniquement sous Unix, et « 13 10 » dans le monde Microsoft. « 13 » correspond au retour chariot et au code '\r', ce qui ramène le curseur au début de la ligne courante. C'est également le code exact renvoyé par la touche Return et par Ctrl+M, laquelle combinaison peut se substituer à cette touche (si, si, essayez !). « 10 » correspond à Line Feed (LF), destiné initialement aux imprimantes et qui fait avancer le papier de la hauteur d'une ligne exactement (sans déplacer la tête). C'est à mettre en parallèle avec Form Feed (FF) qui fait avancer « au formulaire suivant », ce qui en pratique consiste à éjecter la feuille en cours et à mettre la suivante en position (au début, en haut de la page), ou à faire avancer le papier au début du feuillet prédécoupé suivant dans le cas d'une alimentation en continu.

    Sur un écran, un LF se traduit donc par la descente du curseur à la ligne suivante et un FF à l'effacement complet de l'écran avec retour du curseur en haut de l'écran. C'est ce qui se passe avec le Vidéotex entre autres, mais FF correspond au code 12, soit « Ctrl+L ». Ce n'est donc pas un hasard si c'est cette combinaison de touches qui permet d'effacer un terminal depuis un Shell UNIX.

    « 13 10 » est donc la combinaison qui décrit le mieux ce qui se passe lorsque l'on tape à la machine et que l'on va à la ligne : on pousse le chariot à la main pour le ramener en début de ligne et là, un petit levier permet de faire défiler la page d'une ligne en continuant sur sa lancée. Seulement, deux caractères, c'est très pénible à gérer, surtout quand on utilise justement getchar() pour détecter le retour à la ligne. Donc, par convention, la « nouvelle ligne » a été confondue avec le saut de ligne vertical seul, car il est très fréquent de revenir au début de la ligne courante sur un terminal (par exemple, pour mettre l'affichage à jour, dans le cas d'un compteur ou d'une barre de progression) alors que sauter une ligne SANS revenir au début est relativement rare.

    Il n'empêche que c'est encore une fois les terminaux qui font cette conversion eux-mêmes et qu'il est possible, via les mêmes outils, de leur demander de ne pas le faire : c'est pour cela qu'on voit parfois certains terminaux déréglés (par exemple suite à l'affichage du contenu d'un fichier binaire), se mettre à sauter des lignes sans revenir au début.

    Sur windows, il y a la fonction propriétaire getch() qui a le même comportement que le getchar(), excepté que celui-ci sort de la fonction dès réception d'un caractère sur le stdin. Il n'est donc pas dépendant de l'appui sur touche "enter".
    On est bien d'accord que ce n'est possible qu'à partir du moment où l'on a une vue directe sur le clavier : si l'utilisateur se connecte à travers un ssh, par exemple, le problème sera le même : il faudra demander d'une façon ou d'une autre au client d'envoyer directement les caractères pour que l'on puisse les lire.

    De mémoire, il n'y a pas d'équivalent dans le monde Unix, il faut donc le bricoler soi-même (mais je ne saurai dire comment).
    Soit on paramètre le terminal comme décrit au dessus et on utilise les fonctions standard, soit on considère qu'on travaille en local et il faut alors lire le périphérique clavier en particulier. Le mieux, si on travaille sous X-Window, étant de se baser sur les événements KeyPress plutôt que sur l'entrée standard.

    Citation Envoyé par imperio Voir le message
    J'aurais pensé à une utilisation de select allié à read.
    Ça ne te donnera rien de plus dans ce cas précis, mais ce sera nécessaire si tu veux faire d'autres tâches en l'absence de sollicitations de la part de l'utilisateur.

  6. #6
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    852
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 852
    Points : 2 298
    Points
    2 298
    Par défaut
    @Obsidian: Tout à fait, je ne sais pas pourquoi mais je pensais que le terminal était en mode raw (donc même s'il n'y a rien, il retourne si je ne me trompe pas dans le nom). Si ce n'est pas le cas, on est toujours dépendant de l'appui de la touche entrée par l'utilisateur comme l'a signalé @archMqx. (merci à toi d'ailleurs ).

  7. #7
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    Bonjour,
    Bien le bonjour,
    Je te remercie énormément pour ce complément d'information, vraiment très instructif !


    Citation Envoyé par Obsidian Voir le message
    ....
    Tu peux utiliser termios pour le faire directement depuis le C, à condition de sauvegarder l'état initial au début de ton programme et de le restaurer en sortant, par exemple avec atexit().
    ...
    Soit on paramètre le terminal comme décrit au dessus et on utilise les fonctions standard, soit on considère qu'on travaille en local et il faut alors lire le périphérique clavier en particulier. Le mieux, si on travaille sous X-Window, étant de se baser sur les événements KeyPress plutôt que sur l'entrée standard.
    ...
    J'ai une petite confusion quant au propos tenu ici.
    Quel est donc le moyen le plus conseillé/meilleur ?
    Si je comprends, on utilise termios.h si notre programme est rattaché à un terminal précis. Sinon, on va utiliser les événements KeyPress. J'ai juste ?

    Citation Envoyé par imperio Voir le message
    @archMqx. (merci à toi d'ailleurs ).
    Nom : 10977-7eb6.gif
Affichages : 1467
Taille : 3,0 Ko Au plaisir !

  8. #8
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 434
    Points : 43 068
    Points
    43 068
    Par défaut
    Merci pour vos retours.

    Donc qu'est ce qui est mieux utiliser termios ou select - read comme proposé ?

    on va utiliser les événements KeyPress
    Comment cela ?
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  9. #9
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    852
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 852
    Points : 2 298
    Points
    2 298
    Par défaut
    select - read ne fonctionne que si le terminal est en mode non bloquant, donc il te faudrait passer par termios.

  10. #10
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 434
    Points : 43 068
    Points
    43 068
    Par défaut
    ok merci pour les infos.

    Je vais tester et voir les comportements.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  11. #11
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 622
    Points
    23 622
    Par défaut
    Citation Envoyé par imperio Voir le message
    @Obsidian: Tout à fait, je ne sais pas pourquoi mais je pensais que le terminal était en mode raw (donc même s'il n'y a rien, il retourne si je ne me trompe pas dans le nom). Si ce n'est pas le cas, on est toujours dépendant de l'appui de la touche entrée par l'utilisateur comme l'a signalé @archMqx. (merci à toi d'ailleurs ).
    Attention : Le mode raw, c'est une configuration particulière du terminal entier. La man page précise :

    Citation Envoyé par man stty
    raw: Identique à -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -opost -isig -icanon -xcase min 1 time 0
    Avec « - » : identique à cooked.

    Ce qui nous intéresse ici, c'est le mode canonique : c'est de lui que va dépendre la mise en application des disciplines de ligne et la gestion des codes spéciaux permettant l'édition. C'est donc le flag « ±icanon » qui va, dans les faits, provoquer ou non l'envoi immédiat des caractères, ou plus précisément leur « mise à disposition ».

    Voir cette page : http://en.wikipedia.org/wiki/POSIX_terminal_interface

    Après, il peut effectivement être intéressant — ou pas — de passer directement en mode raw : le principal avantage à mon avis étant la spécification de « min 1 time 0 », dont on ne saisit pas forcément l'intérêt sur le coup, mais qui devient clair à la lecture de la section « non canonical mode » de la page ci-dessus.

    On rappelle enfin que raw n'est pas un flag : c'est un raccourci pour mettre tous les flags concernés à la valeur voulue. C'est pour cela qu'on ne le trouvera pas dans termios. Par contre, les BSD définissent quand même la macro cfmakeraw() pour arriver au même résultat.

    Citation Envoyé par archMqx. Voir le message
    J'ai une petite confusion quant au propos tenu ici.
    Quel est donc le moyen le plus conseillé/meilleur ?
    Si je comprends, on utilise termios.h si notre programme est rattaché à un terminal précis. Sinon, on va utiliser les événements KeyPress. J'ai juste ?
    Non : termios.h définit les appels système et les structures C qui leur sont associées pour pouvoir paramétrer un terminal depuis un programme C. C'est sur eux que s'appuie la commande stty qui, elle, permet à l'utilisateur de faire cela depuis le shell.

    Les événements KeyPress n'ont rien à voir, en revanche : ils font partie de la programmation d'applications graphiques X-Window en particulier (un peu comme le mode console ou graphique des applications Windows) : toutes les interfaces graphiques, pour ainsi dire, fonctionnent à base d'événements, et KeyPress est l'un d'eux, mais cela n'a rien à voir avec le C standard, ni avec la programmation système UNIX, ni avec les terminaux en mode texte. Par contre, à partir du moment où ces événements sont réputés exister et que l'on travaille dans l'environnement concerné, c'est eux qu'il faut exploiter, surtout qu'il ont le bon goût de nous prévenir quand une touche est enfoncée et qu'elle est relâchée, contrairement à la lecture d'un flux de caractère qui nous dit juste que ce caractère a été saisi, peu importe la façon.

    Attention au piège, là encore : un x-term est un émulateur de terminal qui fonctionne sous X-Window, mais l'application qui y est reliée (la tienne, et éventuellement via le shell) n'en a aucune conscience.

    Citation Envoyé par chrtophe Voir le message
    Merci pour vos retours.
    Donc qu'est ce qui est mieux utiliser termios ou select - read comme proposé ?
    Ce sont deux choses qui n'ont rien à voir : select() est intéressant quand on veut surveiller plusieurs flux à la fois.

    Citation Envoyé par imperio Voir le message
    select - read ne fonctionne que si le terminal est en mode non bloquant, donc il te faudrait passer par termios.
    Pas forcément : à la base, le mode bloquant ou non bloquant d'un flux en lecture n'est absolument pas lié au terminal, mais à la gestion des fichiers POSIX : il est tout-à-fait possible d'ouvrir un fichier en mode non-bloquant ou de positionner ce flag a posteriori s'il est déjà ouvert. Par contre, il est possible d'émuler un comportement similaire grâce aux paramètres TIME et MIN du terminal. Mais cela n'est de toutes façons valable que si on travaille en mode non canonique.

  12. #12
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 434
    Points : 43 068
    Points
    43 068
    Par défaut
    Voici ou j'en suis :

    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
     
    // test.c
    #include <termios.h>
    #include <unistd.h>
    #include <stdio.h>
    int main()
    {
    	struct termios info;
    	tcgetattr(STDIN_FILENO,&info);
    	int svg=info.c_lflag;
    	info.c_lflag &= ~ECHO;
    	tcsetattr(STDIN_FILENO, TCSANOW, &info);
    	int entier=getchar();
    	printf("\nvaleur entier : %d\n",entier);
    	info.c_lflag = svg;
    	tcsetattr(STDIN_FILENO, TCSANOW, &info);
    	char chaine[250];
    	printf("lecture reste stdin : ");
    	scanf("%s",chaine);
    	printf("contenu de chaine : %s",chaine);
    	return 0;
    }
    Je neutralise bien l'echo. Et à l'appui de la flèche du haut par exemple, je retrouve dans entier la valeur 27 (séquence d'échappement) et les caractères "[A" récupérés dans le flux stdin par scanf. Je ne sais pas comment ça s'appele mais ça me fait penser aux scancodes/breakcodes du BIOS (je pense que ça te parles Obsidian) Si je n'est pas appuyé sur une touche spéciale, scanf attendra une saisie. (j'ai mis scanf juste pour tester la récupération des caractères spéciaux).

    à la base, le mode bloquant ou non bloquant d'un flux en lecture n'est absolument pas lié au terminal, mais à la gestion des fichiers POSIX : il est tout-à-fait possible d'ouvrir un fichier en mode non-bloquant ou de positionner ce flag a posteriori s'il est déjà ouvert.
    ça, ça m’intéresse et je vais regarder pour neutraliser l'obligation d'appui de la touche entrée à l'appel de getchar. Il me reste aussi à tester avec redirection du flux stdin depuis un fichier. Et à récuperer les codes suivant un échappement.

    Je serais alors en mesure de créer ma propre fonction "getch"

    J'ai vu aussi que je pouvais utiliser la bibliothèque curse, c'est une approche envisageable également.

    Les événements KeyPress n'ont rien à voir, en revanche : ils font partie de la programmation d'applications graphiques X-Window en particulier
    ça je connais avec X-Window, mais ça ne me parlait pas pour le terminal.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  13. #13
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 622
    Points
    23 622
    Par défaut
    Citation Envoyé par chrtophe Voir le message
    Je neutralise bien l'echo. Et à l'appui de la flèche du haut par exemple, je retrouve dans entier la valeur 27 (séquence d'échappement) et les caractères "[A" récupérés dans le flux stdin par scanf. Je ne sais pas comment ça s'appele mais ça me fait penser aux scancodes/breakcodes du BIOS (je pense que ça te parles Obsidian)
    En fait, « ESC [ » s'appelle « CSI » pour Control Sequence Introducer. C'est une séquence initialement définie par ECMA-48 que l'on a ensuite appelé les « codes ANSI » dans le langage courant. Les touches du curseur n'ont pas de caractère attitré dans le code ASCII, et le déplacement du curseur en soi non plus. Par contre, le standard ANSI a déterminé tout un tas d'action et de configuration qu'un terminal pouvait gérer. Ce sont les fameux terminaux DEC, VT-52, VT-100 et VT-510 entre autres qui ont été parmi les premiers à l'implémenter et qui sont devenus des références.

    Le Vidéotex, par contre, mappait les déplacements ←, →, ↓ et ↑ sur les caractères 8, 9, 10 et 11, soit Backspace, Tab, Line Feed (dont on a parlé) et Vertical Tab, ce qui se tient tout-à-fait. Ça faisait aussi Ctrl-H, Ctrl+I, Ctrl+J, Ctrl+K ce qui ressemblait « un peu » aux commandes VI, mais c'est une coïncidence (les flèches étaient imprimées sur les touches du clavier du concepteur). C'était donc vrai avec Télétexte, les Minitels et la famille des 8 bits Thomson qui avait choisi d'adopter ce standard plutôt que l'ANSI des PC. Il faut dire que le Vidéotex était adapté au format 40 colonnes et était nettement moins gourmand en caractères que son homologue, ce qui permettait même de faire des petites animations. Ceux qui trouvaient le Minitel lent n'ont pas essayé d'exploiter le mode Téléinformatique 80 colonnes (au standard ANSI) à travers la connexion 1200 bauds du modem intégré (ou 9600 au maximum sur Minitel 2 via la prise périphérique).

    Voir :
    http://www.ecma-international.org/pu...s/Ecma-048.htm
    http://en.wikipedia.org/wiki/ANSI_escape_code
    https://fr.wikipedia.org/wiki/VT100
    http://linux.die.net/man/4/console_codes

    Donc, "ESC [" est le début d'une séquence plus ou moins longue qui peut servir à n'importe quoi : soit de la part du terminal pour indiquer qu'il s'est passé quelque chose ou pour répondre à une interrogation, soit de l'initiative du serveur pour modifier le paramétrage du terminal en lui-même, par exemple pour changer la couleur du texte, lorsque c'est possible.

    ça, ça m’intéresse
    Regarde du côté de fcntl().

    et je vais regarder pour neutraliser l'obligation d'appui de la touche entrée à l'appel de getchar.
    Ça n'existe pas. On a déjà dit que c'était la façon dont Unix d'un côté et ses terminaux, de l'autre, géraient la chose. On a expliqué plus haut qu'il fallait valider la ligne pour qu'elle soit émise, et comme le code 13 en lui-même n'apparaissait pas, il a été supposé que c'était la fonction qui se chargeait de l'absorber, mais c'est faux.

    Il me reste aussi à tester avec redirection du flux stdin depuis un fichier.
    Absolument, mais tu vas être confronté à deux problèmes :

    • Ton entrée standard ne sera plus reliée à un terminal, donc termios n'aura plus d'objet. Ça ne t'empêchera cependant pas de lire les caractères ;
    • Les caractères en lecture provenant d'un fichier, ils seront donc forcément disponibles en permanence (sauf sur des périphériques vraiment… vraiment TRÈS lents) mais il faudra gérer le cas de la fin de fichier EOF).


    Et à récuperer les codes suivant un échappement. Je serais alors en mesure de créer ma propre fonction "getch"
    Ça, c'est un point intéressant. Il faut simplement être sûr de bien voir comment est écrite une séquence CSI de façon générale.

    J'ai vu aussi que je pouvais utiliser la bibliothèque curse, c'est une approche envisageable également.
    C'est ce qu'il y a de plus intelligent, en effet.

  14. #14
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 434
    Points : 43 068
    Points
    43 068
    Par défaut
    Voici ou j'en suis.

    J'aqi fait des essais avec ncurses. ncurses comporte une fonction getch. Par contre lors de l'appel de la fonction d’initialisation initscr, l'écran est effacé. Il sera restauré lors de l'appel à la fonction endwin. Il faut utiliser des fonctions spécifiques à ncurses telle que wprintw au lieu de printf. Il faut donc que le code soit modifié en conséquence. Ca ne me plait pas trop. Je suis donc plutôt parti sur l'utilisation de termio qui m'a été proposée.

    Voici mon code :

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
     
    #include <termios.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
     
    int my_getch()
    {
    struct 	termios info;
    char 	sequence[10];
    int		svg,pos,entier;
     
    	tcgetattr(STDIN_FILENO,&info);
    	svg=info.c_lflag;
    	info.c_lflag &= ~ECHO && ICANON; // ~ECHO=noecho ICANON=mode canonique (pas d'attente de entrée)
    	tcsetattr(STDIN_FILENO, TCSANOW, &info);
    	pos=0;
    	entier=getchar();
    	if (entier==27)
    	{
    		entier=getchar();
    		sequence[pos]=(char)entier;
    		++pos;
    		entier=getchar();
    		sequence[pos]=(char)entier;
    		++pos;
    		if (entier>=0x30 && entier<=0x39)
    		{
    			do
    			{
    				entier=getchar();
    				sequence[pos]=(char)entier;
    				++pos;
    			} while (entier=='~');
    			sequence[pos]='~';
    			++pos;
    		}
    		sequence[pos]='\0';
    		if (strcmp(sequence,"[A")==0) entier=0403; 		// fleche haute=27,[A 
    		if (strcmp(sequence,"[21~")==0) entier=0420; 	// touche F10=27,[21~
    	}
    	info.c_lflag = svg;
    	tcsetattr(STDIN_FILENO, TCSANOW, &info);
    	return entier;
    }
     
    int main()
    {
    	printf("Appuyer sur une touche : ");
    	int retour=my_getch();
    	printf("\nvaleur entier : %d\n",retour);
    	return 0;	
    }
    Le fonctionnement :

    J'appele une 1ère fois getchar. Si le retour correspond à 27, il s'agit d'un code escape, sinon je retourne la touche "normale".
    Je récupère ensuite la séquence "ECMA". Le caractère suivant est toujours "[". Le reste est de longueur non fixe. D'après ce que j'en ai compris, soit il y a un code suivant (exemple pour flèche haute : \0x27[A) soit la suite est finie par "~" (exemple touche F10 : \0x27[21~). Mon tableau de char séquence va donc contenir cette récupération. avec des if je compare ensuite les séquences. Mon code ne traite que la touche flèche haute et F10. En, cas de flèche haute je retourne 259. Pourquoi 259 ? car en regardant ncurses.h je trouve ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    #define KEY_UP		0403		/* up-arrow key */
    0403 est pour moi de l'octal et vaut en décimal 259. Il est à noter qu'un code ASCII est limité à 256 caractères (et à 128 en caractères non étendus), je suis donc au dessus. Je ne sais pas à quelle norme ce code correspond (si c'est toujours de l'ECMA). Je me sers de tcsetattr pour désactiver l'echo et l'obligation d'appui de retour chariot.

    Reste à gérer l'appui juste sur echap, ce que mon code ne fait pas. Il semblerait que pour cela, d'après ce que j'ai vu sur le net, on considère qu'une séquence est transmise dans un certain délai, donc si pas de caractère après un certain temps ... J'ai vu aussi que ça pouvait poser problème en cas de transmission lente. Si je veux traiter cela, je pense faire un getchar non bloquant avec getchar_unlocked ne bloquant pas le flux (ce qui pourrait aussi être une autre façon de ne pas attendre le retour chariot je pense) après la détection de l'appui d'echap et d'attendre un certain délai (en millisecondes)
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  15. #15
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    852
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 852
    Points : 2 298
    Points
    2 298
    Par défaut
    Oula, tu te compliques la tâche ! Fait tout simplement un read de 5 sur un terminal en mode canonique et affiche ce que tu reçois. De mémoire, le "caractère" ESC vaut : "^[", donc 2 chars. Encore une fois, il te suffit de tester et tu verras bien.

  16. #16
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 434
    Points : 43 068
    Points
    43 068
    Par défaut
    Oula, tu te compliques la tâche ! Fait tout simplement un read de 5 sur un terminal en mode canonique
    Tu veux dire que je lis 5 caractères ? Je ne connais pas le nombre maximal de caractères pour ECMA. Si j'ai 3 ou 1 caractères à lire comment cela comporte t'il ?

    Je vais tester.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  17. #17
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 622
    Points
    23 622
    Par défaut
    Ne cherches pas à lire toute la séquence d'un coup : si tu veux vraiment ré-implémenter tout cela toi-même, il faut faire une boucle et mettre en place un algorithme stateful, comme un automate type AFD. Ne serait-ce que parce que tu ne peux pas te fier à ton homologue : si pour une raison ou une autre, il t'envoie une séquence incorrecte, ou que la transmission est perturbée par un parasite, cela suffirait à faire planter ton programme qui se retrouverait dans un cas de figure indéfini.

    À la place, fais une boucle qui lit l'entrée standard caractère par caractère (même si tu peux faire en sorte de lire d'un coup un bloc de n caractères dans un buffer pour des raisons d'efficacité et travailler ensuite sur ce buffer). À côté de ça, tu définis une énumération qui contient tous les états possibles de ton automate, c'est-à-dire tous les phases du traitement dans lesquelles tu peux te trouver. Par exemple : { fluxnormal, escape, csi, parametrecsi, … }. Tu déclares ensuite une variable qui va conserver l'état dans lequel tu te trouves à un moment donné.

    Ensuite, tu choisis la façon dont tu vas traiter le caractère que tu viens de recevoir en fonction de l'état dans lequel tu te trouves :

    • Si tu es à ce moment dans « fluxnormal », alors tu vérifies si ton caractères n'est pas un caractères spécial comme ESC. S'il s'avère que c'est le cas, alors tu l'ignores mais tu le traites en conséquence. Et si c'est ESC en particulier, tu passes en état « escape » (en affectant la bonne valeur à ta variable). Sinon, tu restes dans l'état actuel et tu transmets le caractère tel quel à la suite du programme (par exemple, tu l'écris à l'écran) ;
    • Si tu es en mode « escape », alors tu vérifies si le caractère qui suit a un sens lorsqu'il suit un ESC, c'est-à-dire s'il forme une combinaison valide. Si ce n'est pas le cas, tu redescends en mode « fluxnormal » et tu envoie d'abord un code « ESC » puis le caractères que tu viens de recevoir. Si par contre, c'est une combinaison valide, alors tu agis en conséquence. Et si le caractère est un « [ », alors tu passes en « csi » ;
    • Si tu es en mode « csi », tu vérifies si le caractère correspond à une séquence CSI valide. Si ce n'est pas le cas, tu as deux choix : soit tu abandonnes la séquence, tu repasses là encore en « fluxnormal » et tu envoies tout ce que tu as reçu, soit tu traites quand même la séquence tant qu'elle reste syntaxiquement correcte et tu l'ignores à la fin. Sinon, tu vérifies si la séquence attend un paramètre éventuel. Le cas échéant, tu passes en « parametrecsi » ;
    • Si tu es en mode « parametrecsi », tu vérifies si ce que tu reçois est admissible pour un paramètre, lequel est généralement un nombre entier. Autrement dit, tu vérifies si c'est un chiffre de 0 à 9 et dans ce cas, tu mets à jour la valeur du paramètre en fonction de ce que tu viens de recevoir.


    Généralement, une séquence CSI, après ses paramètres, se termine avec « ; ».

    C'est à toi de parcourir ECMA-48 pour voir si c'est toujours le cas, s'il y a une définition formelle de ces séquences et si la norme leur prévoit une taille maximum. À défaut, tu peux prévoir un buffer suffisamment large (256 caractères par exemple) et t'en servir comme d'un buffer circulaire : de cette façon, la longueur de la séquence peut être potentiellement infinie. Comme tu traites les informations au fur et à mesure, tu ne conserves dans le buffer que celles dont tu as besoin.

  18. #18
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 434
    Points : 43 068
    Points
    43 068
    Par défaut
    Obsidian,

    ce que je fais :

    recup caractère
    si différent de 27 fin avec retour de celui ci
    sinon
    lecture caractère suivant (qui est censé être "[") et stockage dans tampon
    lecture caractère suivant et stockage dans tampon
    si le dernier caractère est numérique (0 à 9) lecture caractères avec stockage jusqu'à atteinte "~"
    test tampon avec chaine correspondant à flèche haute ou F10
    si comparaison retour avec codes

    Je sais pas si on peut considérer ça comme une machine à état.

    Les améliorations à faire : tester les valeurs comme tu l'as signalé (en cas de prob de transmission). Notamment, dans mon code, je ne teste pas si [ sius escape
    Je n'ai pas bien compris la longueur des séquences CSI, avec mes tests j'ai vu que soit c'était une lettre et rien d'autre derrière le [ soit des chiffres avec ~en terminaison. De ce que j'en ai vu c'est soit un ou deux chiffres.J'ai besoin de creuser.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  19. #19
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 622
    Points
    23 622
    Par défaut
    Citation Envoyé par chrtophe Voir le message
    Obsidian,

    ce que je fais :
    recup caractère
    si différent de 27 fin avec retour de celui ci
    lecture caractère suivant (qui est censé être "[")
    Non, c'est une erreur. Tu ne peux malheureusement pas faire cette supposition.

    « ESC » est le code d'échappement du flux standard défini par ASCII. Il peut être suivi de n'importe quoi. Par exemple, en Vidéotex (= sur le minitel), ESC était immédiatement suivi d'un code de commande qui, lui, ne prenait que très rarement un paramètre. Du coup, 95% de ces commandes spéciales occupaient deux à trois octets maximum, sachant que les plus concises étaient les plus utilisées. Ça rendait la chose très efficace. Ça paraissait déjà lent à l'utilisateur mais le standard ANSI exploité à travers la même connexion était intolérablement lent, alors même que les terminaux originaux travaillait en deçà de cette vitesse, encore. :-(

    Toujours est-il que même en s'en tenant au standard ANSI, il y a beaucoup de commandes « implicites » déclenchées immédiatement après un ESC. Par exemple « ESC c », avec un « c » minuscule, soit 1B 63, est la commande de réinitialisation globale du terminal. Ça provoque également l'effacement de l'écran et le retour du curseur à son point de départ (en haut à gauche), mais il n'est pas « autorisé » d'utiliser cette commande (pourtant bien pratique) à des seules fins d'effacement de l'écran.

    C'est d'ailleurs précisément pour cette raison que les séquences CSI ont été définis : on a choisi un caractère comme « introduction de séquence » (Control Sequence Introducer : CSI), servant à faire au niveau du terminal exactement ce que ESC sert à faire en soi au niveau ASCII, et de là, on peut exprimer une séquence selon un format défini et de la longueur nécessaire.

    et stockage dans tampon

    lecture caractère suivant et stockage dans tampon
    si le dernier caractère est numérique (0 à 9) lecture caractères avec stockage jusqu'à atteinte "~"
    test tampon avec chaine correspondant à flèche haute ou F10
    si comparaison retour avec codes

    Je sais pas si on peut considérer ça comme une machine à état.
    Pas vraiment, puisqu'une machine à états tient trace, par définition, de l'état courant dans lequel on se trouve, ce qui peut amener le programme à prendre une décision différente pour un même caractère au même endroit du programme. D'ailleurs, puisque tu en parles, tu multiplies les :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
      entier=getchar();
      sequence[pos]=(char)entier;
      ++pos;
    … un peu partout dans ton code. En principe, ce bloc ne devrait apparaître qu'une seule fois, en haut de ta boucle : c'est une fois le caractère lu et mis en sécurité dans ton buffer que tu peux l'examiner, en t'appuyant sur la valeur de « entier », par exemple.

    Les améliorations à faire : tester les valeurs comme tu l'as signalé (en cas de prob de transmission). Notamment, dans mon code, je ne teste pas si [ sius escape
    Dans ton cas de figure, tu entres dans un bloc « if » et tu lis un nouveau caractère pour pouvoir terminer la procédure en une seule fois. Au niveau d'un AFD, ce serait comme tout traiter pendant la transition. Parfois, on ne peut pas faire autrement, mais c'est alors beaucoup plus difficile de revenir en arrière et d'annuler la procédure quand on se rend compte que la séquence devient invalide au milieu de la transmission.

    Je n'ai pas bien compris la longueur des séquences CSI, avec mes tests j'ai vu que soit c'était une lettre et rien d'autre derrière le [ soit des chiffres avec ~en terminaison. De ce que j'en ai vu c'est soit un ou deux chiffres.J'ai besoin de creuser.
    Regarde la norme ECMA-48 à la section 5.4. Tout est écrit dedans.

    Une séquence CSI, c'est un motif de la forme « ESC [32;15m », où « ESC [ » est le CSI, 32 et 15 sont les paramètres et « m » le code de clôture qui définit également l'action à entreprendre, ici changer la couleur du texte. Plus précisément, le texte dit :
    • Le prologue CSI (1B 5B ou « ESC [ ») ;
    • Une suite optionnelle de caractères formant le ou les paramètres, compris entre 30 et 3F ;
    • Une suite optionnelle de caractères dits « intermédiaires », compris entre 20 et 2F ;
    • Le code de clôture, un caractère compris entre 40 et 7E ;


    … sachant que dans la table ASCII : « < = > ? »

    — l'intervalle 30-3F correspond à « 0 1 2 3 4 5 6 7 8 9 : ; < = > ? » ;
    — l'intervalle 20-2F correspond à « ! " # $ % & ' ( ) * + , - . / » (en commençant par Espace) ;
    — l'intervalle 40-7E correspond au reste des caractères affichables, c'est-à-dire les lettres majuscules et minuscules, plus les autres caractères spéciaux (crochets, slashes), qui viennent combler l'espace à la fin des deux colonnes normalement consacrées aux lettres.

    Il est également précisé que :

    • La suite des paramètres, si elle existe, doit forcément commencer dans l'intervalle 30-3B, soit les chiffres de 0 à 9, le « : » et le « ; ». Si elle commence par un caractère parmi « < = > ? », elle est considérée comme privée ou expérimentale (pratique pour les extensions propriétaires spécifiques à certaines implémentations des terminaux) ;
    • Si la séquence est bien au format standard, alors ses paramètres doivent être sous la forme de nombres décimaux entiers, séparés par des « ; » et en utilisant éventuellement « : » comme séparateur de sous-chaîne pour un paramètre donné (voir par exemple ceci à ce sujet).


    Donc, pour lire une séquence CSI entière, il suffit de détecter « ESC [ » et de lire tout le reste jusqu'à ce que l'on trouve un caractère qui soit supérieur ou égal à « @ » (40h). Ce caractère fait partie de la séquence et indique l'action à entreprendre. Tout ce qui suit ne fait plus partie de la séquence.

    Ton « ~ » est donc en fait l'une des nombreuses actions que la séquence de contrôle peut mener. Elle sert effectivement à représenter les touches « étendues » comme Home, Pg.Up, Pg.Down et autres caractères, qui sont surtout spécifiques au PC, en fait. En revanche, les touches du curseur qui existaient dès l'aube des terminaux font chacune l'objet d'une séquence CSI propre : sans paramètre, et terminées respectivement par « A », « B », « C » ou « D ».


    Dernier piège, parce que ce ne serait pas drôle sinon : sur PC, « ESC » est souvent représenté par « ^[ ». Le « ^ » est là pour représenter l'élévation de la touche provoqué par le modificateur (Ctrl) et est suivi de la lettre associée, car en principe, Ctrl ne peut être associé qu'avec une lettre de A à Z. « Ctrl » est la touche qui permet d'envoyer les caractères de contrôle de la table ASCII et c'est pour cela qu'on la trouve sur pratiquement tous les terminaux (Minitel compris, d'ailleurs, à partir du Minitel 1B). Cela permet donc d'envoyer les codes de 1 à 26 et c'est exactement pour cette raison que ESC prend la valeur 27 : pour ne pas être considéré (et émis) comme un code de contrôle ordinaire. Il se trouve que le caractère qui suit exactement la lettre Z dans la table ASCII est « [ », d'où cet affichage, pas du tout officiel.

    C'est peut-être une coïncidence, c'est peut-être volontaire, mais le fait est qu'il n'a rien à voir avec le vrai caractère 5B qui suit l'ESC de la séquence CSI : Si bien qu'un séquence CSI affichée sur l'écran d'un PC est souvent écrite « ^[[ ». Le premier crochet est un faux, le second est un vrai. :-)

  20. #20
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 434
    Points : 43 068
    Points
    43 068
    Par défaut
    Donc, pour lire une séquence CSI entière, il suffit de détecter « ESC [ » et de lire tout le reste jusqu'à ce que l'on trouve un caractère qui soit supérieur ou égal à « @ » (40h)
    Là c'est clair. J'ai modifié mon code en conséquence et il est plus simple.

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
     
    #include <termios.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
     
    int my_getch()
    {
    struct 	termios info;
    char 	sequence[10];
    int		svg,pos,entier;
     
    	tcgetattr(STDIN_FILENO,&info);
    	svg=info.c_lflag;
    	info.c_lflag &= ~ECHO && ICANON; // ~ECHO=noecho ICANON=mode canonique (pas d'attente de entrée)
    	tcsetattr(STDIN_FILENO, TCSANOW, &info);
    	pos=0;
    	entier=getchar();
    	if (entier==27)
    	{
    		entier=getchar();
    		sequence[pos]=(char)entier;
    		++pos;
    		if (entier=='[')
    		{
    			do
    			{
    				entier=getchar();
    				sequence[pos]=(char)entier;
    				++pos;
    			} while (entier<=0x40);
    			sequence[pos]='\0';
    			if (strcmp(sequence,"[A")==0) entier=0403; 		// fleche haute=27,[A 
    			else if (strcmp(sequence,"[21~")==0) entier=0420; 	// touche F10=27,[21~
    			else entier=0;
    		}
    		else
    		{
    			ungetc(entier,stdin);
    			entier=0;
    		}
    	}
    	info.c_lflag = svg;
    	tcsetattr(STDIN_FILENO, TCSANOW, &info);
    	return entier;
    }
     
    int main()
    {
    	printf("test - Appuyer sur une touche : ");
    	int retour=my_getch();
    	printf("\nvaleur entier de retour : %d\n",retour);
    	return 0;	
    }
    algorithme :
    lecture caractere
    si caractère lu n'est pas esc je le retourne
    sinon
      lecture caractère suivant et mise dans tampon
      si caractere="["
        lecture caractère et copie dans tampon jusqu'à ce que celui soit supérieur ou égal à 40h
        terminaison tampon par ajout "\0"
        analyse tampon (si égal à [A (flèche haute) retourne 0x259, si égal à [21~ (touche F10) retourne 0x272 sinon retourne 0) 
      fin si
      sinon
        remise caractère dans flux (je considère dans ce cas la lecture invalide)
        retourne 0
      fin sinon
    fin sinon
    Je n'utilise pas de machine à état,et mon code est à mon niveau opérationnel. Grosso-modo si la séquence est non reconnue (ne commençant pas par [ après esc ou n'étant pas flèche gauche ou F10 - les deux touches spéciales que je gère) j'ai un retour de 0.

    Pour info, les erreurs de transmissions sont négligeables car je travaille en terminal virtuel (pas de liaison sérielle ou réseau)
    Obsidian, je ne tiens pas compte de ton dernier paragraphe du moins dans mon code (gestion de "^"), mais ça n'en ai pas moins intéressant.

    Pour avoir un code fiable, il me faudrait gérer dans le cas d'une chaine CSI erronée sa longueur car le codee que j'ai fait tel que présenté présentera un buffer overflow en cas de non présence de caractère supérieur ou égal à 40h, et d'autre part, je ne gère pas l'appui unique sur la touche echap ce qui se présentera clairement. Mon code fonctionne aussi avec une redirection de stdin.

    Pour moi mon prob est résolu, et j'ai appris plein de trucs sur le terminal.
    Merci encore Obsidian pour le temps consacré et les explications détaillées. Merci également aux autres contributeurs.

    J'ai crée ma fonction getch() mais pour un réel projet , il vaudra mieux utiliser une bibliothèque qui gèrera beaucoup mieux que moi le terminal (cf ncurses ).

    Je laisse encore la discussion ouverte si qq1 à qq chose à ajouter.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Demande information sur le redimensionnement des fenetres
    Par stardeus dans le forum Général JavaScript
    Réponses: 7
    Dernier message: 07/06/2007, 13h33
  2. Demande informations sur la transparence
    Par Claude URBAN dans le forum Windows
    Réponses: 2
    Dernier message: 04/01/2007, 19h11
  3. Demande information sur "TansparentColorValue.."
    Par Claude URBAN dans le forum C++Builder
    Réponses: 12
    Dernier message: 19/12/2006, 16h59
  4. Demande Information sur "TransparentColorValue"
    Par Claude URBAN dans le forum Delphi
    Réponses: 2
    Dernier message: 14/12/2006, 14h05
  5. Demande information sur les librairie.
    Par argon dans le forum C
    Réponses: 2
    Dernier message: 29/03/2006, 16h22

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo