
FAQ CConsultez toutes les FAQ
Nombre d'auteurs : 28, nombre de questions : 175, création le 11 janvier 2013
Sommaire→Les entrées/sorties→Gestion du clavier et de l'écran en mode console- Pourquoi faut-il valider les caractères tapées par Entrée ?
- Qu'est-ce que le caractère '\n' ?
- Qu'est-ce que le caractère '\r' ?
- La touche Entrée envoie-t-elle le caractère '\r', '\n' ou '\r' suivi de '\n' ?
- A quoi sert la fonction fflush ?
- Comment lire une ligne de manière securisée ?
- Comment vider le buffer clavier ?
- Comment générer EOF avec le clavier ?
- Pourquoi ne faut-il pas utiliser fflush(stdin) pour vider le buffer clavier ?
- Qu'est-ce qu'un terminal en mode brut (UNIX) ?
- Comment faire passer un terminal en mode brut (UNIX) ?
- Comment faire pour lire un caractère sans attendre la frappe d'Entrée ?
- Comment intercepter une touche sans bloquer le programme (DOS/Windows) ?
- Comment simuler la fonction _kbhit sous UNIX ?
- Comment gérer les touches étendues (F1..F12, flèches) ?
- Comment se positionner dans une console ?
- Comment effacer l'écran ?
- Où trouver des fonctions portables de gestion du clavier et de l'écran ?
- Où trouver une bibliothèque de gestion graphique ?
Les caractères tapés ne sont pas directement transmis au programme mais placés (par le système) dans un tampon. Le système ne transmettra le contenu de ce tampon que lorsque l'utilisateur aura émis le caractère de validation à savoir '\n'. En langage C, avec un clavier plus ou moins conventionnel, ce caractère est provoqué par la touche Entrée.
En langage C, ce caractère, appelé également Line Feed (en abrégé LF), indique la fin d'une ligne. Aussi bien en lecture (scanf, ...) qu'en écriture (printf, ...), le système requiert une ligne complète avant de réellement considérer les données, sinon elles seront tout simplement placées dans un tampon. Si on tente de l'afficher, il provoque le passage à la ligne suivante.
Ce caractère, appelé également Carriage Return (en abrégé CR), si l'on tente de l'afficher, provoque le retour en début de la ligne courante.
La touche Entrée (Retour Chariot) envoie le caractère '\r'. Cependant, cette touche sert en réalité à terminer la ligne plutôt qu'à retourner au début de celle-ci.
Or, selon le système, il est possible qu'une ligne doit être terminée non pas par un '\r' mais par un '\n' (à l'exemple d'UNIX) ou un '\r' suivi d'un '\n' (DOS/Windows) par exemple.
Ainsi, avant que ce caractère n'atteigne le programme, il aura déjà été converti en "caractère" de fin de ligne (qui peut être '\n', '\r' suivi de '\n', etc.). Cette conversion
se faisant de manière totalement opaque pour le programme, on a bien l'impression, dans le programme, que la touche Entrée provoque l'émission du caractère de fin de ligne.
Par contre, si le programme effectue une lecture directe au clavier (à l'aide de la fonction _getch() de DOS/Windows par exemple) plutôt qu'une lecture de haut niveau (getchar()),
on obtient bien le caractère '\r' et non le caractère de fin de ligne quand l'utilisateur appuie sur Entrée.
D'autre part, en langage C, les entrées/sorties se font par défaut en mode texte. Dans ce mode, le caractère de fin de ligne est toujours représenté par le caractère '\n'. Plus précisément,
quand une fonction de lecture recontre une fin de ligne, elle la convertit en '\n' avant de la retourner au programme. Quand le programme demande l'écriture de '\n', il sera tout d'abord converti
en caractère de fin de ligne avant d'être effectivement écrit. En mode binaire, ces conversions n'ont pas lieu.
A noter que pour l'écran, bien qu'il faut normalement écrire '\r' (Retour Chariot)
suivi de '\n' (Nouvelle ligne) pour commencer une nouvelle ligne, il est possible, toujours en fonction du système utilisé, que '\n' seulement suffise pour y arriver. Mais dans tout le cas,
l'affichage de '\r' seulement provoque le retour au début de la ligne courante.
Cette fonction sert à forcer l'écriture physique des données se trouvant dans le tampon associé à un flux sortant. Par exemple :
#include <stdio.h>
int main(void)
{
int n;
printf("Entrez un nombre entier : ");
/* En langage C, une ligne doit etre terminee par le caractere '\n'. Tant que */
/* la ligne n'est pas terminee et que le tampon associe au fichier n'est pas plein, */
/* les caracteres transmis ne seront pas effectivement ecrits mais tout simplement */
/* places dans le tampon. On peut cependant forcer le vidage de ce tampon a l'aide */
/* de la fonction fflush. */
fflush(stdout);
scanf("%d", &n);
printf("Merci pour : %d\n", n);
return 0;
}
Cependant, la norme ajoute que le tampon associé à un flux sortant doit être également vidé lorsqu'une opération de lecture nécessite l'émission de ces caractères. Dans de nombreuses implémentations, une demande de lecture au clavier provoque le vidage du tampon associé à l'écran. Le 'fflush(stdout);' juste après notre printf serait donc automatiquement appelé au moment du scanf et dans ce cas, on peut tout simplement l'omettre.
En utilisant fgets.
fgets(char * s, int n, FILE * flux);
fgets stoppe la lecture si la ligne a été lue dans son intégralité (c'est-à-dire si le caractère '\n' a été lu) ou si elle a lu n - 1 caractères
(le n-ième étant reservé pour le caractère de fin de chaîne, '\0'). Lorsque fgets() lit une ligne complète, le caractère '\n' est présent dans le tampon,
il faut donc prévoir sa suppression.
Voici une fonction effectuant la lecture d'une ligne en limitant la taille et en supprimant le caractère '\n' s'il est présent :
#include <stdio.h>
#include <string.h>
char * read_stdin(char * buffer, size_t taille)
{
char * result = fgets(buffer, taille, stdin);
if (result != NULL)
{
char * lf = strchr(buffer, '\n'); /* On cherche le caractere '\n'. */
if (lf != NULL) /* S'il est present, ... */
*lf = '\0'; /* ... on le supprime */
else
{
/*
* Le '\n' n'est pas present. Ca signifie qu'il reste au moins un
* caractere dans stdin. On peut choisir de les ignorer et de vider
* stdin ou d'agrandir le buffer si c'est possible (realloc()) et de
* rappeler fgets() autant de fois que necessaire...
*
* Si on ne fait rien, la prochaine lecture sur stdin se fera sans attente
* et recuperera ce qui n'a pas ete lu ...
*/
}
}
return result;
}
N.B. : le caractère '\n' n'est pas présent dans deux cas :
- La saisie est plus longue que taille demandée, dans ce cas les caractères supplémentaires restent présents.
- La fin de fichier est rencontrée.
Lien : Comment vider le buffer clavier ?
Lien : Pourquoi gets est-elle dépréciée en faveur de fgets ?
La méthode la plus sûre pour vider le buffer clavier consiste à consommer tout les caractères présents dans ce buffer jusqu'à ce qu'il soit vide :
#include <stdio.h>
void clean_stdin(void)
{
int c;
do {
c = getchar();
} while (c != '\n' && c != EOF);
}
Cela dépend du système. Par exemple, il suffit de taper, en début de ligne, Ctrl + Z sous DOS/Windows et Ctrl + D sous UNIX, puis de valider par Entrée.
Dans certaines implémentations, lorsque stdin est attaché au clavier, fflush(stdin) supprime tous les caractères encore présents dans le buffer du clavier.
Appliquer fflush sur un flux entrant, comme stdin, n'est cependant pas portable car la norme ne précise
l'effet de fflush que sur un flux sortant.
Lien : A quoi sert la fonction fflush ?
Lien : Comment vider le buffer clavier ?
Un terminal a deux modes de fonctionnement. Dans le mode normal (cooked), on saisit la ligne tranquillement, avec possibilité de l'éditer
avec Backspace, avant de l'envoyer en enfonçant Entrée. Le programme n'a pas du tout connaissance de la ligne avant que l'utilisateur
enfonce Entrée.
Dans le mode brut (raw), aucune interprétation n'est faite. Les effets sont notamment les suivants :
- getchar() lit réellement un caractère sans attente la frappe d'Entrée.
- fgets() lit exactement le nombre de caractères demandés.
- Il n'y a pas d'écho local des caractères tapés.
- Si on tape Backspace, le programme reçoit '\b' (code 8) ou DEL (code 127), selon le système.
- Ctrl-C (interruption), Ctrl-D (EOF), Ctrl-Z (suspension) et les autres combinaisons de contrôle ne sont pas interprétées, mais le code ASCII correspondant est retourné.
- Si on tape Entrée, le programme reçoit '\r' (au lieu de '\n' en mode cooked).
- Si on tente d'afficher '\n', le programme saute une ligne sans retourner au début de la ligne (il faut donc utiliser '\r' suivi de '\n' pour revenir en début de ligne puis aller à la ligne suivante).
Voici une fonction qui permet de passer d'un mode à l'autre. On appelle mode_raw(1) pour activer le mode raw et mode_raw(0) pour revenir en mode cooked. Pensez à toujours revenir en mode normal à la fin du programme.
#include <termios.h>
#include <unistd.h>
void mode_raw(int activer)
{
static struct termios cooked;
static int raw_actif = 0;
if (raw_actif == activer)
return;
if (activer)
{
struct termios raw;
tcgetattr(STDIN_FILENO, &cooked);
raw = cooked;
cfmakeraw(&raw);
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
}
else
tcsetattr(STDIN_FILENO, TCSANOW, &cooked);
raw_actif = activer;
}
Sous DOS/Windows, avec la fonction _getch(), déclarée dans conio.h. Cette fonction n'émet pas d'écho. Si vous voulez que le caractère tapé soit également affiché, utilisez la fonction getche(). getch et getche retournent toutes le code du caractère tapé. Avec un compilateur C/C++ moderne, préférez les noms _getch et _getche aux anciens noms getch et getche.
#include <stdio.h>
#include <conio.h>
int main(void)
{
printf("Hello, world.\n");
printf("Appuyez sur une touche pour continuer ...");
fflush(stdout);
_getch();
return 0;
}
Sous UNIX, il faut passer le terminal en mode brut (raw).
Lien : Que signifie l'underscore (_) en début du nom d'une fonction ou d'une macro, etc. ?
Lien : Comment faire passer un terminal en mode brut (UNIX) ?
Il arrive des fois qu'on ait besoin de savoir si une touche est présente, mais sans bloquer le déroulement du programme. La fonction
kbhit() permet de savoir si une touche est disponible ou non dans le buffer du clavier. Si une touche est présente, alors une lecture par
la fonction getch() suffit. Avec un compilateur C/C++ moderne, préférez les noms _kbhit et _getch aux anciens noms kbhit et getch.
Voici une source intégrant la lecture de touches simple et double code.
#include <stdio.h>
#include <conio.h>
int main(void)
{
int touche;
while (!_kbhit()) /* On boucle jusqu'a ce que l'utilisateur appuie sur une touche */
{
printf("Appuyez sur n'importe quelle touche !\n");
}
/* Une touche a enfin ete frappee. Lire son code. */
touche = _getch();
if (touche >= 32) /* code d'un caractere imprimable */
printf("Vous avez appuye sur '%c'.\n", touche);
else
{
/* C'est un caractere special. Ca peut etre un caractere de controle */
/* (ECHAP, ENTREE, etc.) ou encore une touche etendue (F1 .. F12, Fleches). */
printf("Vous avez appuye sur une touche.\n");
}
return 0;
}
Lien : Comment faire pour lire un caractère sans attendre la frappe d'Entrée ?
Lien : Comment gérer les touches étendues (F1..F12, flèches) ?
Lien : Que signifie l'underscore (_) en début du nom d'une fonction ou d'une macro, etc. ?
La fonction _kbhit() permet, sous DOS et Windows, de savoir si une touche est disponible ou non dans le buffer du clavier. La fonction ci dessous fournit le même service sous un environnement de type UNIX.
#include <unistd.h>
#include <sys/time.h>
int unix_text_kbhit(void)
{
struct timeval tv = { 0, 0 };
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
return select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv) == 1;
}
Les caractères détectés peuvent être lus ensuite par fgets() ou getchar().
Attention, cette fonction ne détectera des caractères tapés que s'ils ont été suivis par Entrée. Pour détecter l'arrivée de caractères
dès leur frappe, comme avec _kbhit, il faut que le terminal fonctionne en mode brut (raw).
Lien : Comment faire passer un terminal en mode brut (UNIX) ?
Les touches F1 à F12 et les touches fléchées sont des touches qui, lorsque l'on appuie dessus, renvoient 2 codes l'un à la suite de
l'autre. Ce sont ce que l'on appelle des touches étendues.
Pour gérer ce type de touches, il faut lire un premier caractère du buffer clavier, détecter qu'il s'agit d'un code de touche étendue,
et relire un second caractère pour identifier la touche. Ce premier 'caractère' lu dépend du système et peut également varier selon les
touches étendues (généralement il a la valeur 0 ou 224). A noter que les codes étendus, c'est-à-dire celui qui vient en seconde position,
est différent suivant les plateformes.
Voici un extrait de code qui fonctionne sous DOS/Windows, la fonction getch n'étant définie que par ces systèmes.
int c = getch();
if (c == 0 || c == 224) /* Si c'est une touche etendue */
{
c = getch();
/* c contient maintenant le code de la touche etendue */
}
Lien : Comment faire pour lire un caractère sans attendre la frappe d'Entrée ?
Si la console accepte les séquences d'échappement ANSI (il s'agit d'une spécification de combinaisons de caractères qu'un terminal doit interpréter comme une commande et non comme de simples caractères à imprimer ...), il est possible de les utiliser pour positionner le curseur au point de coordonnées (x, y) dans cette console.
#include <stdio.h>
void goto_x_y(unisgned y, unsigned x)
{
printf("\033[%u;%uH", y, x);
}
Cela fonctionne sous DOS et de nombreux environnements de type UNIX mais malheureusement pas sous Windows.
Sous Windows, la solution la plus naturelle consiste à appeler la fonction SetConsoleCursorPosition() en lui fournissant un handle sur la console et les coordonnées de la position souhaitée.
#include <windows.h>
void GotoXY(SHORT x, SHORT y)
{
/* STD_OUTPUT_HANDLE fait reference a la sortie standard du programme qui est par defaut la console */
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
COORD Pos;
Pos.X = x;
Pos.Y = y;
SetConsoleCursorPosition(hConsole, Pos);
}
Normalement, cela peut simplement se faire en appelant une commande système comme cls sous DOS/Windows et clear sous UNIX.
Cette technique n'est cependant pas la plus efficace à cause des limitations connues de la fonction system. Il n'existe pas non plus hélas
de fonction standard qui permette de le faire. Voici donc quelques fonctions que vous pouvez utiliser pour arriver à vos fins.
MS-DOS :
#include <dos.h>
void dos_clear_screen(void)
{
/* Il suffit de reinitialiser le mode video (appeler la fonction 00h de l'interruption 10h) */
union REGS inregs, outregs;
inregs.h.ah = 0x00; /* Fonction 00h : Change de mode video */
inregs.h.al = 0x03; /* Mode = 03h : 80x25 caracteres, 16 couleurs */
int86(0x10, &inregs, &outregs); /* INT 10h */
}
Windows :
#include <windows.h>
void windows_clear_screen(void)
{
HANDLE hConsole;
CONSOLE_CONS_BUFFER_INFO Info;
DWORD NbOctetsEcrits; /* Requis par FillConsoleOutputCharacter */
COORD Debut = {0, 0};
/* STD_OUTPUT_HANDLE fait reference a la sortie standard du programme qui est par defaut la console */
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
/* Lit les infos sur le buffer de l'ecran */
GetConsoleScreenBufferInfo(hConsole, &Info);
/* Remplit l'ecran avec le caractere espace */
FillConsoleOutputCharacter(hConsole, ' ', Info.dwSize.X*Info.dwSize.Y, Debut, &NbOctetsEcrits);
/* Remet le curseur au debut de l'ecran */
SetConsoleCursorPosition(hConsole, Debut);
}
Linux :
#include <ncurses.h>
void unix_clear_screen(void)
{
clear();
move(0, 0);
}
Le matériel étant une notion complètement inconnue du langage C, il faut normalement toujours faire appel aux fonctions
du système pour y avoir accès. CONIO (conio.h) sous DOS et Windows et ncurses sous UNIX mettent à disposition du programmeur
des routines permettant de gérer le clavier et l'écran en mode console à un niveau inférieur à celui que permet le C standard
mais comme nous venons de le dire, aucune des deux bibliothèques n'est portable (il peut cependant exister des portages ...).
PDCurses, un sous-ensemble de ncurses, est une bibliothèque portable de gestion du clavier et de l'écran en mode console.
La création d'interfaces graphiques est un domaine non couvert par le C. Pour développer des applications graphiques (c'est-à-dire des fenêtres, des boutons, etc.), vous pouvez soit utiliser
directement les fonctions de votre système (c'est-à-dire
l'API Windows sous Windows,
le protocole X sous UNIX,
etc.), soit faire appel à une bibliothèque éventuellement portable comme
GTK+.
Si vous voulez par contre faire du graphisme (jeux 2D ou 3D ...), vous devriez plutôt vous tournez vers des bibliothèques spécialisées comme
allegro,
SDL ou encore
OpenGL à moins que vous ayez
envie de réinventer la roue en utilisant les fonctions de très très bas niveau du système.



