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

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    18 167
    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 : 18 167
    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 Expert
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    868
    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 : 868
    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 442
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    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 442
    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 Expert
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    868
    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 : 868
    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 : 1625
Taille : 3,0 Ko Au plaisir !

+ Répondre à la discussion
Cette discussion est résolue.

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