Information sur le Langage C Linux et utilisation de Geany et CodeBlock
EN CONSTRUCTION...
Buts de cette page Web.
Le but de cette page et des vidéos qui lui sont associées n'est pas d'aborder les bases de la programmation en Langage C,
mais d'aborder des points particuliers, tels que ceux décrits dans la table des matières.
1) Quelques références de sites Web et de vidéos Top
Pour la numérotation des lignes, j'ai utilisé le site suivant :
https://jsfiddle.net/tovic/AbpRD/.
J'ai repris le code et je l'ai simplifié.
Pour la coloration syntaxique du code en langage C,
L'outil de coloration syntaxique du site suivant est utilisable : https://highlightjs.org/.
Autre information sur : highlight.js
Vu qu'il ne faisait pas exactement ce que je voulais,
il est trop généraliste et trop compliqué pour moi,
j'ai écrit un petit programme en javascript qui fait la coloration syntaxique.
Le code javascript est disponible ici.
Regardez aussi le code source de cette page pour les styles CSS associés,
mis en début du code html.
Regardez en fin de code pour l'appelle aux fonctions de coloration et de numérotation.
Je me suis rapproché de la coloration syntaxique par défaut de l'éditeur de textes geany.
J'ai ajouté une coloration pour les symboles + - * / ( ) = [ ] { }
Pour les bases de la programmation en langage C, voici de très bonnes vidéos.
Publié sur Youtube par FormationVidéo
Il a une série de 29 vidéos d'introduction, plus d'autres vidéos, sur le langage C.
Il a également des vidéos d'introductions au C++ et sur
d'autres langages.
Geany est un éditeur de texte,
multi usage, que j'utilise souvent pour de petites modifications de code HTML, CSS, javascript
et pour des petits programmes en langage C ou C++.
C'est lui que j'ai utilisé pour les exemples qui suivent.
Code::Blocks est un IDE "Integrated Development Environment" "Environnement de Développement Intégré".
Je le trouve pratique pour de gros projets.
Pour de petits tests, je lui préfère geany.
Je ne l'utiliserai pas par la suite.
Le site Web de référence de Code::Blocks.
Pour installer Code::Blocks, taper dans un Terminal :
sudo apt install codeblocks
Votre mot de passe administrateur vous sera demandé.
Il existe beaucoup d'autres Environnement de Développement,
Qt, Visual studio, Netbeans, Eclipse, Atome, Brackets, etc.
J'utilise un dérivé de la
notation hongroise
Chaque variable commence par des minuscules qui indiquent le type de la variable,
suivit d'une majuscule qui est le début du nom de la variable.
Voici mes conventions de notation hongroise :
préfixes description
n int (entier sur 4 octets, ou 2 sur certains systèmes)
l long int (entier long)
v float ou double (nombre à virgule flottante, 4 ou 8 octets)
c char (caractère un octet)
b byte (caractère non signé) == uc (unsigned char)
f flag = boolean (booléen true/false un octet)
w word (mot = double octet ou quadruple entier)
sz zero-terminated string (chaine de caractères terminée par un char zéro)
o object (objet)
str string (chaine de caractères)
modificateurs description
u unsigned (non signé)
p pointer (pointeur)
a array (tableau)
g_ global variable (variable globale)
exemple :
paulExemple1 pointeur sur un tableau d'entiers long non signés.
Chez moi, l'indentation de la fermeture des blocs d'instruction }
est décalée pour se trouver à la suite du bloc,
cela me parrait plus naturel.
Pour moi, la lisibilité du code prime sur sa longueur du code et même parfois sur son efficacité.
Par exemple, les commandes suivantes son à bannir,
c'est à mon avis une erreur qu'elles ne provoquent pas d'erreur.
if (a = b == 1) { ... }
if (a++ == 1) { ... } // L'incrémentation est faite après le test.
if (++b == 1) { ... } // L'incrémentation est faite avant le test.
Remplacer le premier exemple par :
a = b;
if (b == 1) { ... }
C'est une ligne de plus, mais plus lisible et évite des risques d'erreurs.
Remplacer l'autre exemple par :
a++; // Ou a += 1:
if (a == 2) { ... }
b++; // Ou b += 1:
if (b == 1) { ... }
Je compile tous mes code avec le but d'obtenir 0 warnings.
Tout "warning" doit être considéré comme une erreur.
6) Un premier exemple, "Hello world" avec suppléments. Top
Il est coutume d'écrire un premier programme qui affiche "Hello world".
Le jeux de caractères par défaut est codé en utf-8, donc les caractères accentués sont affichés.
J'ai ajouté les fonctionnalités suivantes :
Il affiche le nombre de paramètres transmis au programme depuis le Terminal.
Il affiche le premier paramètre, qui est toujours le nom du programme et son répertoire.
Il affiche tous les paramètres fourni au programme.
Il indique le répertoire absolu contenant le programme.
Il indique en commentaire que la variable $? contient la valeur retournée par le "main" du programme.
Le programme "Hello world" :
/*
ex0010_hello_world.c
Voici comment compiler ce simple programme.
Dans un Terminal :
° pour Compiler : gcc -Wall -o ex0010_hello_world ex0010_hello_world.c
° pour exécuter : ./ex0010_hello_world
° pour récupérer le nombre retourné par le programme : echo $?
-Wall demande d'afficher tous les warnings. Un bon programme devrait en avoir aucun.
-o nom_de_fichier indique le nom du fichier exécutable généré.
le dernier nom de fichier est celui que l'on veut compiler
Dans Geany :
Construire > "Définir les commandes de construction"
° Compile qui correspond à F8 : gcc -Wall -o "%e" "%f"
° Build qui correspond à F9 : pour l'instant, identique à "Compile"
° Execute qui correspond à F5 : "./%e"
%e = le nom de fichier, sans extension
%f = le nom de fichier, avec extension
%d = le nom complet du répertoire, sans le nom de fichier.
%p = si un projet est ouvert, le nom complet du répertoire de base du projet
%l = le numéro de ligne où se trouve le curseur
C.f. : file:///usr/share/doc/geany/html/index.html#substitutions-in-commands-and-working-directories
Dans un Terminal, depuis le répertoire contenant l'exécutable, taper :
./ex0010_hello_world premier et troisième
On peut voir qu'il y a 4 arguments, le 0ème étant le répertoire et nom de l'exécutable.
*/
#include <stdio.h> // Pour printf(...)
#include <unistd.h> // Pour getcwd(...) qui donne le chemin du répertoire courant.
int main(int argc, char *argv[]) {
//================================
printf("Bonjour tout le monde\n");
printf("Il y a %d argument(s)\n", argc);
printf("Le premier arguments est le chemin relativement au répertoire"
" courant et le nom du fichier : %s\n", argv[0]);
printf("Liste des arguments :\n");
for (int nn=0; nn<argc; nn++) {
printf("%2d %s\n", nn, argv[nn]);
}
char acBuffer[1024]; //Buffer contiendra le nom du répertoire de votre programme
// Réf. ; https://www.man7.org/linux/man-pages/man3/getcwd.3.html
printf("\nRépertoire courant = getcwd(acBuffer, 1024) =\n"
"%s\n", getcwd(acBuffer, 1024)); // getwd(...) est "deprecated", doit être abondonné.
// printf("get_current_dir_name() = %s\n", get_current_dir_name()); NE fonctionne pas, pas utile, vu que getcwd(...) fonctionne.
return 176;
// Dans un Terminal, la variable $? contiendra la valeur retournée.
// Écrivez : echo $? et 176 sera affiché dans le Terminal.
// Écrivez une deuxième fois : echo $? et 0 sera affiché dans le Terminal, car l'instruction précédente n'a pas provoqué d'erreur.
} // main
7) printf, les "specifiers" ou les "data type". Top
/*
ex0015_printf.c
Exemples d'affichages avec printf
Voici comment compiler ce simple programme.
Dans un Terminal :
° pour Compiler : gcc -Wall -o ex0015_printf ex0015_printf.c
° pour exécuter : ./ex0015_printf
° pour récupérer le nombre retourné par le programme : echo $?
-Wall demande d'afficher tous les warnings. Un bon programme devrait en avoir aucun.
-o nom_de_fichier indique le nom du fichier exécutable généré.
le dernier nom de fichier est celui que l'on veut compiler
Dans Geany :
Construire > "Définir les commandes de construction"
° Compile qui correspond à F8 : gcc -Wall -o "%e" "%f"
° Build qui correspond à F9 : pour l'instant, identique à "Compile"
° Execute qui correspond à F5 : "./%e"
%e = le nom de fichier, sans extension
%f = le nom de fichier, avec extension
%d = le nom complet du répertoire, sans le nom de fichier.
%p = si un projet est ouvert, le nom complet du répertoire de base du projet
%l = le numéro de ligne où se trouve le curseur
C.f. : file:///usr/share/doc/geany/html/index.html#substitutions-in-commands-and-working-directories
*/
#include <stdio.h> // Pour printf(...)
#include <string.h> // Pour la gestion des string
int main(int argc, char *argv[]) {
//================================
char c1 = 'a'; // C'est un nombre signé sur 8 bits, allant de -128 à 127
char c2 = '{';
unsigned char b1; // C'est un byte, un octet, sur 8 bits, allant de 0 à 255.
int nn, kk;
long long int llnGrand = 0; // long long int = long long = long int, de taille 8 octets, 64 bits.
double vNbVirgule = 3141592.653589793; // Le type double est codé sur 64 bits. Le type float, codé sur 32 bits, n'est presque plus utilisé.
//long double lvNb = 1.0 + 1e-16; // Je n'ai pas vu d'amélioration de précision, c.f. : https://en.wikipedia.org/wiki/Long_double
printf("Exemples d'affichages avec printf(...)\n");
// printf("sizeof(llnGrand)=%ld %ld %ld\n", sizeof(llnGrand), sizeof(vNbVirgule), sizeof(lvNb)); printf("%25Lg\n", (lvNb - 1.0));
printf("%%c %%3d %%03d %%x %%X\\n : %c %3d %03d %x %X\n", c1, c1, c1, c1, c1); // %c %3d %x %X\n : a 97 097 61 61
printf("%%c %%3d %%03d %%x %%X\\n : %c %3d %03d %x %X\n", c2, c2, c2, c2, c2); // %c %3d %x %X\n : { 123 123 7b 7B
c1 = 160;
printf("%%c %%d %%u %%x %%X\\n : %c %3d %x %X\n", c1, c1, c1, c1); // %c %d %u %x %X\n : � -96 ffffffa0 FFFFFFA0
b1 = c1;
printf("%%c %%3d %%3u %%x %%X\\n : %c %3d %u %x %X\n", b1, b1, b1, b1, b1); // %c %3d %3u %x %X\n : � 160 160 a0 A0
c1 = -1;
printf("%%d %%u %%x %%ld %%lu %%lX\\n : %d %u %x %ld %lu %lX\n",
c1, c1, c1, (long int)c1, (long int)c1, (long int)c1); // %d %u %x %ld %lu %lX\n : -1 4294967295 ffffffff -1 18446744073709551615 FFFFFFFFFFFFFFFF
c1 = -128; // c1 = -129 ou c1 = 128 génère un Warning, car on dépasse les capacité du nombre.
printf("%%d %%u %%x %%ld %%lu %%lX\\n : %d %u %x %ld %lu %lX\n",
c1, c1, c1, (long int)c1, (long int)c1, (long int)c1); // %d %u %x %ld %lu %lX\n : -128 4294967168 ffffff80 -128 18446744073709551488 FFFFFFFFFFFFFF80
c1 = -129; // Génère un Warning, car on dépasse les capacité du nombre (c1 == 127), mais la compilation est OK
printf("%%d %%u %%x\\n : %d %u %x\n", c1, c1, c1); // %d %u %x\n : 127 127 7f
c1 = 128; // Ne génère pas de Warning, mais on dépasse les capacité du nombre et on aura c1 == -128.
printf("%%d %%u %%x\\n : %d %u %x\n", c1, c1, c1); // %d %u %x\n : -128 4294967168 ffffff80
// Environ 17 chiffres sont stockés de manière correctes.
printf("%%f %%12.3f %%18.9f %%18.15e\\n : %f %12.3f %18.9f %18.15e\n",
vNbVirgule, vNbVirgule, vNbVirgule, vNbVirgule); // %f %12.3f %18.9f %18.15e\n : 3141592.653590 3141592.654 3141592.653589793 3.141592653589793e+06
vNbVirgule = 0.1; // On peut remarquer que le nombre 0.1 n'est pas stocké de manière exacte en nombre à virgule.
// Ceci vient du fait que ce n'est pas une somme finie de fractions n'ayant que des puissances de 2 au dénominateur.
printf("%%f %%12.3f %%24.21f\\n : %f %12.3f %24.21f\n",
vNbVirgule, vNbVirgule, vNbVirgule); // %f %12.3f %24.21f\n : 0.100000 0.100 0.100000000000000005551
vNbVirgule = 0.125; // On peut remarquer que le nombre 0.125 est stocké de manière exacte en nombre à virgule, car il vaut 1/2^3.
printf("%%f %%12.3f %%24.21f\\n : %f %12.3f %24.21f\n",
vNbVirgule, vNbVirgule, vNbVirgule); // %f %12.3f %24.21f\n : 0.125000 0.125 0.125000000000000000000
//----------------------------------------------------------------------
char strS[] = "Une chaîne avec é à ç"; // Les caractères accentués prendront plus que 1 octet, ici 2 octets, car ils sont codés en utf-8
printf("%%s\\n : %s\n", strS); // %s\n : Une chaîne avec é à ç
strcpy(strS, "éàç"); // Les caractères accentués prendront plus que 1 octet, ici 2 octets, car ils sont codés en utf-8
printf("%%s %%ld\\n : %s %ld\n", strS, strlen(strS)); // %s %ld\n : éàç 6
// Donne : Code utf-8 de "éàç" = c3 a9 c3 a0 c3 a7 0
printf("Code utf-8 de \"%s\" = ", strS);
for (nn=0; nn<=strlen(strS); nn++) {
printf("%x ", (unsigned char)strS[nn]);
}
printf("\n");
// Le plus grand nombre entier est : 9223372036854775807
// Le suivant est -9223372036854775808
// Dernier affichage : %20lld %20lld long=%d\n : 9223372036854775807 -9223372036854775808 long=19
llnGrand = 2;
kk = 2;
while (llnGrand > 0) {
if (llnGrand > 3000000000000000ul) {
sprintf(strS, "%lld", llnGrand);
nn = strlen(strS);
printf("%%3d %%20lld %%20lld long=%%d\\n : 2^%3d -1 = %20lld %20lld long=%d\n", kk, 2*llnGrand-1, 2*llnGrand, nn);
}
llnGrand *= 2; kk++;
}
return 7576;
// Dans un Terminal, la variable $? contiendra la valeur retournée.
// Écrivez : echo $? et 7576 sera affiché dans le Terminal.
// Écrivez une deuxième fois : echo $? et 0 sera affiché dans le Terminal, car l'instruction précédente n'a pas provoqué d'erreur.
} // main
string.h, la bibliothèque de manipulation de chaines de caractères en C. sprintf, conversion de nombre et plus en string.
8) None blocking "getc()", lecture d'un caractère au clavier, sans arrêter l'exécution. Top
Le programme getc(), non blocant
/*
ex0020_getc_no_stop.c
Permet de lire si un caractère est présent,
sans arrêter l'exécution du programme.
Le but est de simuler la fonction de Windows : GetKeyState function (winuser.h)
Simule _kbhit() _getch() et sous Windows // system("stty raw"); sous OSX
Il faut une fenêtre qui reçoit les stdin pour que cela fonctionne.
Pour chaque caractère tapé, affiche en hexadécimal son code UTF-8,
qui est le code ASCII pour les caractères non accentués,
puis affiche le caractère. Fonctionne avec tous les caractères (ayant un unicode et un codage UTF-8).
Pour la différence entre getc() et getchar()
En résumé, getchar() == getc(stdin)
C.f. : https://fr.sawakinome.com/articles/programming/difference-between-getc-and-getchar.html
Dans un Terminal :
° pour Compiler : gcc -Wall -o ex0020_getc_no_stop ex0020_getc_no_stop.c
° pour exécuter : ./ex0020_getc_no_stop
° pour récupérer le nombre retourné par le programme : echo $?
Dans Geany, Construire > Définir les commande de construction > Build pour F9
gcc -Wall -o "%e" "%f"
Dans CodeBlocks, dans Settings > Environment > general Settings, mettre
xterm -T $TITLE -geometry '200x50+1922+1' -e pour la 2ème fenêtre
xterm -T $TITLE -geometry '200x50+1+1' -e pour une seule fenêtre
Défini la position (2; 15) et la taille en caractères (80 caractères 60 lignes)
xfce4-terminal --geometry=80x60+2+15 -e "/bin/sh %c"
Pour lancer dans un terminal, de dimension données :
xfce4-terminal --geometry=30x40+2+15 -x ./ex0020_getc_no_stop
Fenêtre de :
30 colonnes de caractères
40 lignes de texte
2 pixels du bord gauche de l'écran
15 pixels du bord haut de l'écran
*********************************************************************/
#include <stdio.h> // http://www.cplusplus.com/reference/cstdio/
// Juste pour la fonction kbhit(), ci-dessous
#include <termios.h> // Pour : tcgetattr(STDIN_FILENO, &old_tio); // c.f. http://man7.org/linux/man-pages/man3/termios.3.html
#include <unistd.h> // Pour STDIN_FILENO qui vaut 0
#include <fcntl.h> // Pour F_GETFL, O_NONBLOCK
// c.f. https://www.tutorialspoint.com/c_standard_library/time_h.htm
#include <time.h> // Pour des mesures de temps
void kbhit_echo(int nOption, struct termios *pOld_tio) {
//======================================================
// C.f. : https://cboard.cprogramming.com/c-programming/63166-kbhit-linux.html
// C.f. : http://stackoverflow.com/questions/9547868/is-there-a-way-to-get-user-input-without-pressing-the-enter-key
// C.f. : http://shtrom.ssji.net/skb/getc.html
// Si pOld_tio == NULL utilise l'état par défaut d'entrée.
// Si nOption == 0, lit l'état par défaut d'entrée, met un état d'entrée sans ECHO et sans nécessité d'entrer ENTER pour lire un caractère
// et retourne la valeur par défaut d'entrée.
// Si nOption == 1, remet l'état par défaut d'entrée, pOld_tio étant celui retourné lors de l'appelle avec nOption == 0
// force l'ECHO et le ICANON
// Si nOption == 2, remet l'état par défaut d'entrée, pOld_tio étant celui retourné lors de l'appelle avec nOption == 0
// ne force pas l'ECHO et le ICANON, laisse pOld_tio telle qu'il est transmis.
// ICANON = canonical mode, allow input without having to type the Enter key.
// ICANON = mode cannonique, permet l'entrée de caractères sans devoir presser sur le touche Enter.
// ECHO = enable/disable local echo
// ECHO = active/désactive l'affichage des caractères que l'on tape au clavier
if ( (nOption == 0) || (pOld_tio == NULL) ) {
// Lit et mémorise les paramètres actuels de l'entrée stdin
tcgetattr(STDIN_FILENO, pOld_tio); // STDIN_FILENO == 0 par convention
}
if (nOption != 0) {
// Remet l'ancien état d'entrée, pour afficher à l'écran les caractères tapés et qu'une entrée doive se terminer par ENTER.
if (nOption == 1) pOld_tio->c_lflag |= ICANON | ECHO;// pour assurer d'avoir un Echo et que l'on doive presser la touche enter pour terminer une entrée.
tcsetattr(STDIN_FILENO, TCSANOW, pOld_tio); // Remet l'état des paramètres stdin à sa valeur par défaut
return;
}
struct termios new_tio; // Pour les paramètres d'entrée de stdin
// Copie les paramètres
new_tio = *pOld_tio; // *pOld_tio.c_lflag == 0x8a3b
new_tio.c_lflag &= (~ICANON & ~ECHO);
// new_tio.c_lflag = 0x8a31 ICANON == 2 ECHO == 8
// Définit de nouveau paramètres d'entrée stdin
// C.f. : https://man7.org/linux/man-pages/man2/fcntl.2.html
tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
} // kbhit_echo
int kbhit(int nOption) {
//======================
// C.f. : https://cboard.cprogramming.com/c-programming/63166-kbhit-linux.html
// C.f. : http://stackoverflow.com/questions/9547868/is-there-a-way-to-get-user-input-without-pressing-the-enter-key
// C.f. : http://shtrom.ssji.net/skb/getc.html
// Retourne 0 s'il n'y a pas de caractères dans le buffer s'entrée stdin
// retourne le code du caractère s'il y en a un.
// Si nOption == 0, retourne le code du buffer d'entrée, s'il y en a un et l'enlève du buffer d'entrée.
// Si nOption == 1, laisse inchangé le buffer d'entrée (stdin).
// Si nOption == 2, vide le buffer d'entrée (stdin) et retourne le nombre de caractères enlevés du buffer d'entrée.
// _kbhit() _getch() et sous Windows // system("stty raw"); sous OSX
int ch;
int oldf;
int nCompte = 0; // Compte le nombre de caractères qu'il y avait dans le buffer si nOption == 2
oldf = fcntl(STDIN_FILENO, F_GETFL, 0); // Mémorise l'état des conditions d'exécution de la fonction "getch()"
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); // Pour ne pas arrêter l'exécution au : "ch = getchar()"
ch = getchar(); // Lit un code (ou caractère) du buffer s'entrée (stdio), sans arrêter le programme s'il n'y en a pas.
if (nOption == 2) {
// Vide le buffer d'entrée
while (ch != EOF) { ch = getchar(); nCompte++; }
}
fcntl(STDIN_FILENO, F_SETFL, oldf & ~O_NONBLOCK); // Remet l'état des conditions d'exécution de la fonction "getch()" à sa valeur mémorisée.
if (nOption == 2) return nCompte;
if(ch != EOF) {
if (nOption == 1) ungetc(ch, stdin); // Remet le caractère dans le buffer d'entrée
return (int)ch;
}
return 0;
} // kbhit
int main() {
//==========
// Tester si un caractère a été tapé ou non, sans l'afficher lorsqu'il est tapé sur le clavier
// et que le programme continue dans tous les cas.
struct termios Old_tio; // Pour les paramètres d'entrée de stdin
char cDisp; // le caractère à afficher
time_t timeLast, timeNow;
double vDiffTime;
int nCpt = 0;
char acStr[8]; // Pour afficher des caractères codés en utf-8, comme les accentués
int nn = 0;
kbhit_echo(0, &Old_tio); // Mémorise l'état d'entrée du clavier et permet l'entrée sans ECHO, ni devoir presser sur ENTER.
timeNow = clock(); // heure du processeur
do {
cDisp = kbhit(0); // cDisp = getchar(); // cDisp = getc(stdin);
if (cDisp == '\n') printf("\n");
else if ((int)cDisp > 0) printf("%x", (unsigned char)cDisp);
if ((cDisp == 'q') || (cDisp == 'Q') || (cDisp == 27)) break; // 27 est la code ASCII de Escape
if ((cDisp >= ' ') && (cDisp <= '~')) printf("_%c", cDisp);
if ((int)cDisp < 0) {
// Code Utf-8 du caractère
// Essayer Alt Gr+Shift+ un chiffre ou un caractère.
nn = 0;
while (cDisp != 0) {
printf("%x", (unsigned char)cDisp);
acStr[nn] = cDisp;
cDisp = kbhit(0);
nn++;
}
acStr[nn] = 0; // Fin du string
printf("_%s%d", acStr, nn); // Pour afficher le caractère codé en utf-8
} // while
nCpt++;
if (nCpt >= 100) {
nCpt = 0;
printf(".");
}
// Attente de quelques dixièmes de secondes.
timeLast = timeNow;
do {
timeNow = clock(); // heure du processeur
vDiffTime = difftime(timeNow, timeLast); // Différence de temps en micro-secondes
} while (vDiffTime < 10000.0);
} while (1);
kbhit_echo(1, &Old_tio); // Remet l'état d'entrée du clavier normal, avec ECHO et en devant presser sur ENTER.
printf("FIN\n");
return 0;
} // main
Pour l'utiliser, copier - coller ce texte dans un fichier avec l'extension .py
Rendez-le exécutable, en regardant ses propriétés ou en tapant :
chmod u+x nom_du_fichier.py
Cela le rendra eXécutable pour l'Utilisateur.
À chaque exécution du programme,
9) Lecture des touches pressées du clavier, sans arrêter l'exécution. Top
Permet de voir les différents codes des touches.
Chaque touche a un code.
À partir de ce code, on peut connaître le symbole qui lui est associé.
Ce symbole est modifié en fonction de l'état des touches Shift, Alt Gr, Ctrl, et Caps Lock.
On obtient également le UNICODE du symbole correspondant, s'il y en a un.
On obtient également le code UTF-8 du symbole correspondant, s'il y en a un.
Tous les caractères ont un UNICODE et un code UTF-8,
mais certains symboles, comme "HOME", "END, "Page Up", ...
n'ont pas de UNICODE ni de code UTF-8.
Pour des convertisseurs : UNICODE - UTF-8 - caractères, voici trois liens :
Convertisseur de code Unicode 13.0/ISO 10646:2020, très bien, beaucoup de codage.
Quelques tables de caractères
UTF-8 encoding table and Unicode characters
Wikipedia sur UTF-8, explique clairement le lien entre UNICODE et UTF-8.
Il est assez simple de convertir un UNICODE en UTF-8 et inversément.
Le codage UTF-32 est égale à l'UNICODE, c'est son avantage, mais il prend beaucoup de place.
Si le nombre UNICODE < 256, alors il correspond au code ISO/CEI 8859-1, utilisé par défaut sous Windows.
Le programme Keyboard_state(), non blocant
/*
ex0030_keyboard_state.c
Pour la compilation :
gcc -Wall -o ex0030_keyboard_state ex0030_keyboard_state.c -lX11 -lxkbcommon
Dans Geany, Construire > Définir les commande de construction > Build pour F9
gcc -Wall -o "%e" "%f" -lX11 -lxkbcommon
Pour X11 c.f. /usr/include/X11
Pour xkbcommon c.f. /usr/include/xkbcommon
Il cherche les fichiers dans :
/usr/lib/x86_64-linux-gnu/libxkbcommon avec l'extension .so ou .a
/usr/lib/x86_64-linux-gnu/libX11 avec l'extension .so ou .a
Voir :
/usr/include/linux/input.h
/usr/include/stdio.h c.f. https://man7.org/linux/man-pages/man3/stdio.3.html
Pour les code clavier
/usr/include/linux/input-event-codes.h
Il y a un décalage de 8 entre le keycode défini ici et celui qu'il faut transmettre à XkbKeycodeToKeysym !?!
https://www.kernel.org/doc/man-pages/ Manuel
La fonction XQueryKeymap retourne un vecteur de bits indiquant l'état du clavier.
c.f. :
https://linux.die.net/man/3/xquerykeymap
https://www.systutorials.com/docs/linux/man/3-XQueryKeymap/
https://tronche.com/gui/x/xlib/function-index.html
Un keycode est le code d'une touche clavier
un keysym est le code du symbole qui lui est associé.
un keyunicode est le unicode du caractère
un keyutf8 est la séquence d'octets du code utf-8 du caractère
Il y a aussi le nom du symbole associé
Finalement, il y a le caractère lui-même.
La page suivante donne des explications sur le codage UTF-8,
en particulier, comment passer de UNICODE à UTF-8 dans les deux sens. C'est assez simple.
https://fr.wikipedia.org/wiki/UTF-8
Pour voir les caractères associés au keysym :
https://www.commentcamarche.net/faq/6988-redefinir-les-touches-du-clavier autre réf. en français, intéressante.
Commande à taper dans un Terminal pour voir les codes des touches :
xev !!!!!!!!!!!!!!!!!!
Commande permetant depuis un Terminal de connaître l'état des touches et de les modifier.
xset
c.f. : https://www.computerhope.com/unix/uxset.htm
Autre commande permettant d'agir sur le clavier :
xdotool
// Pour tkinter, c.f. la table complète de correspondance des <bind Key-keysym> :
// https://man.cx/keysyms
*********************************************************************/
#include <stdio.h> // Pour : printf(...)
#include <X11/XKBlib.h> // Pour XkbKeycodeToKeysym(...)
// Pour des mesures de temps, non essentiel, mais utilisé dans ce programme.
#include <time.h>
// C.f. : https://github.com/xkbcommon/libxkbcommon/tree/master/src
// Pour les fonctions :
// xkb_keysym_to_utf8, qui retourne le code utf-8 de la touche lorsqu'elle représente un caractère
// xkb_keysym_to_utf32, qui retourne le code unicode de la touche lorsqu'elle représente un caractère
// Il faut inclure l'option de compilation : -lxkbcommon
#include <xkbcommon/xkbcommon.h> // Inclu <stdio.h>, donc il n'est pas indispensable d'include <stdio.h> dans ce programme.
// Pour afficher les caractères selon leur code utf-8 ou unicode
// Pour la fonction : setlocale(LC_ALL, "");
#include <locale.h>
// C.f. : https://fr.wikipedia.org/wiki/ISO/CEI_10646
// La norme ISO/CEI 10646 tente de définir un système de codage universel pour tous les systèmes d'écriture.
// C'est le fondement du standard Unicode.
// C'est le même codage que l'UTF-32, qui code chaque caractère par son Unicode.
// L'UTF-8 code chaque caractère sur 1, 2, 3 ou 4 octets, suivant le caractère. Les caractères ASCII sont codés sur un octet, il y a compatibilité.
// Test que __STDC_ISO_10646 est défini
#ifndef __STDC_ISO_10646__
#error "Oops, our wide chars are not Unicode codepoints, sorry!"
#endif
int convertUnicode_utf8(int nOption, uint32_t *pwUnicode, unsigned char *acUtf8) {
//================================================================================
// Conversion de UNICODE en UTF-8 et inverse.
// La fonction est inutile, car ces conversions existent déjà en C, c.f. plus bas.
// C.f. : https://fr.wikipedia.org/wiki/UTF-8
// Si nOption == 0, conversion de UNICODE vers UTF-8
// Sinon, conversion de UTF-8 vers UNICODE
// acUtf8 doit être un tableau de longueur au moins 5 pour la conversion vers UTF-8. Il terminera par 0
// Sinon, c'est lui qui contient le code UTF-8 à convertir en UNICODE.
// L'UNICODE < 255 correspond au codage ISO/CEI 8859-1 C.f. : https://fr.wikipedia.org/wiki/Unicode#Plan_multilingue_de_base_(PMB,_0000_à_FFFF)
// Retourne 0 si la conversion c'est bien produite, 1 s'il y a eu une erreur.
if (nOption == 0) { // Conversion vers UTF-8
if (*pwUnicode <= 0x7F) { // Code ASCII
// Codage UTF-8 sur 1 octet
acUtf8[0] = (char)*pwUnicode;
acUtf8[1] = 0;
return 0;
}
if (*pwUnicode <= 0x7FF) { // 0x7FF = 2047
// Codage UTF-8 sur 2 octets
acUtf8[2] = 0;
acUtf8[1] = (char)(0x80 + *pwUnicode % 0x40); // 6 bits de poids faible, bits 0 à 5
acUtf8[0] = (char)(0xC0 + *pwUnicode / 0x40); // 5 bits de poids fort, bits 6 à 11 == 6 bits de poids fort vu que le bit_11 == 0
return 0;
}
if (*pwUnicode <= 0xFFFF) { // 0xFFFF = 65'535
// Codage UTF-8 sur 3 octets
acUtf8[3] = 0;
acUtf8[2] = (char)(0x80 + *pwUnicode % 0x40); // 6 bits de poids faible, bits 0 à 5
acUtf8[1] = (char)(0x80 + (*pwUnicode / 0x40) % 0x40); // bits 6 à 11
acUtf8[0] = (char)(0xE0 + (*pwUnicode / 0x1000) % 0x40); // bits 12 à 15
return 0;
}
if (*pwUnicode <= 0x10FFFF) { // 0x10FFFF = 1'114'111
// Codage UTF-8 sur 4 octets
acUtf8[4] = 0;
acUtf8[3] = (char)(0x80 + *pwUnicode % 0x40); // 6 bits de poids faible, bits 0 à 5
acUtf8[2] = (char)(0x80 + (*pwUnicode / 0x40) % 0x40); // bits 6 à 11
acUtf8[1] = (char)(0x80 + (*pwUnicode / 0x1000) % 0x40); // bits 12 à 17 (0x40 * 0x40 = 0x1000)
acUtf8[0] = (char)(0xF0 + (*pwUnicode / 0x40000) % 0x40); // bits 18 à 21 (0x40 * 0x40 * 0x40 = 0x40000)
return 0;
}
}
else { // Conversion vers UNICODE
if ((acUtf8[0] & 0x80) == 0) { // Code ASCII
// Codage UTF-8 sur 1 octet
*pwUnicode = acUtf8[0];
return 0;
}
if ((acUtf8[0] & 0xE0) == 0xC0) {
// Codage UTF-8 sur 2 octets
*pwUnicode = (acUtf8[0] & 0x3F) * 0x40 + (acUtf8[1] & 0x3F); // 2 paquets de 6 bits. Lit bit n°11 (celui de gauche) est toujours à 0.
return 0;
}
if ((acUtf8[0] & 0xF0) == 0xE0) {
// Codage UTF-8 sur 3 octets
*pwUnicode = (acUtf8[0] & 0x0F) * 0x1000 + (acUtf8[1] & 0x3F) * 0x40 + (acUtf8[2] & 0x3F); // 1 paquet de 4 bits et 2 paquets de 6 bits.
return 0;
}
if ((acUtf8[0] & 0xF0) == 0xF0) {
// Codage UTF-8 sur 4 octets
*pwUnicode = (acUtf8[0] & 0x07) * 0x40000 + (acUtf8[1] & 0x3F) * 0x1000 + (acUtf8[2] & 0x3F) * 0x40 + (acUtf8[3] & 0x3F); // 1 paquet de 3 bits et 3 paquets de 6 bits.
return 0;
}
}
return 1;
} // convertUnicode_utf8
int main() {
//==========
//time_t tNow = time(NULL); // Temps en secondes, pas assez précis pour moi
clock_t tNow = clock(); // Maintenant, en nombre de cycles d'horloge
clock_t tLast = clock(); // Temps du dernier affichage, nombre de cycles
unsigned char abKeys_return[32]; // Tableau de bits représentant l'état des touches du clavier
XKeyboardState structKeyboardState; // État du Caps Lock, du Num Lock et du Scroll Lock. C'est un "struct",
// c.f. : https://linux.die.net/man/3/xkeyboardcontrol et https://doc.servo.org/x11/xlib/struct.XKeyboardState.html
int nKeyCode = 0;
int nKeyCodeMem = 0; // Code de la touche pressée, correspondant à une autre touche que Shift, Ctrl, Alt, Alt Gr, Super (Super = la touche Windows).
unsigned int wEventMask = 0; // ShiftMask==1; LockMask==2; CtrlMask==4; Mod1Mask==8 (Alt); Mod2Mask==16; Mod3Mask==32;
// C.f. : /usr/include/X11/X.h Mod4Mask==64; Mod5Mask==128
// Pour indiquer les touches Shift, Ctrl, Alt, Alt Gr, Super pressées.
Bool fShift_L = False;
Bool fShift_R = False;
Bool fCtrl_L = False;
Bool fCtrl_R = False;
Bool fAlt_L = False;
// Bool fAlt_R = False; N'existe pas
Bool fAlt_Gr = False;
Bool fSuper_L = False;
Bool fSuper_R = False;
Bool fCaps_Lock = False;
Bool fNum_Lock = False;
KeySym ulKeysym; // KeySym du caractère du clavier. Si < 255, correspond au UNICODE du caractère, mais pas sinon.
uint32_t wKeyUnicode; // UNICODE du caractère
uint32_t wKeyUnicode2; // UNICODE du caractère, pour éviter l'affichage des caractères de contrôle
char acUtf8_string[13]; // Pour avoir la suite d'octets du codage UTF-8 du caractère.
unsigned char abUtf8_code[8]; // Pour avoir le codage UTF-8 d'un caractère, utile pour imprimer le caractère.
int nLenUTF8 = 0; // Longueur du codage UTF-8, de 1 à 4 octets. ==1 octet pour les caractères ASCII
// Pour que les caractères UNICODE non ASCII puissent s'afficher.
setlocale(LC_ALL, "");
printf("\ec"); // Efface le contenu du Terminal
//printf("XK_Shift_L = %xd", XK_Shift_L); // = 0xffe1 C.f. /usr/include/X11/keysmdef.h
printf("\n\n");
printf("\e[s"); // sauve la position courante du curseur
while (1) { // Boucle infinie
// Attend de quelques centièmes de secondes
// 1.0*(tNow - tLast)/CLOCKS_PER_SEC) donne le temps en secondes, en virgule flottante
tLast = clock();
while (1.0*(tNow - tLast)/CLOCKS_PER_SEC < 0.05) { tNow = clock(); }
// Lecture de l'état des touches du clavier
Display* dpy = XOpenDisplay(NULL);
XQueryKeymap( dpy, (char*)abKeys_return ); // c.f. https://tronche.com/gui/x/xlib/input/XQueryKeymap.html
// Lecture de l'état du Caps Lock et du Num Lock
XGetKeyboardControl(dpy, &structKeyboardState);
// structKeyboardState.led_mask & 1 != 0 si Caps Lock est ON
// structKeyboardState.led_mask & 2 != 0 si Num Lock est ON
// structKeyboardState.led_mask & 4'294'961'148 != 0 si Scroll Lock est ON 4'294'961'148 == 2^32 - 6142 == 2^32 - 2^12 - 2^11 + 4
// Parcours toutes les touches du clavier, pour déterminer celles qui sont pressées.
// Tiens compte de Shift, Alt Gr, Ctrl et Windows key pour mémoriser une touche qui a une chance de correspondre à un caractère.
// Si plusieurs touche de caractères sont pressées simultanément,
// affiche le code de chacune des touches, mais ne traite entièrement que celle de plus grand code (nKeyCodeMem).
wEventMask = 0;
nKeyCode = 0;
nKeyCodeMem = 0;
fShift_L = False; fShift_R = False; fCtrl_L = False; fCtrl_R = False; fAlt_L = False; fAlt_Gr = False;
fSuper_L = False; fSuper_R = False; fCaps_Lock = False; fNum_Lock = False;
for (int nn=0; nn<32; nn++) {
for (int kk=0; kk<8; kk++) {
nKeyCode = 0;
if ((abKeys_return[nn] & (1<<kk)) > 0) nKeyCode = 8*nn + kk;
if (nKeyCode == 50) { // Shift Gauche pressée
fShift_L = True;
wEventMask |= ShiftMask;
}
else if (nKeyCode == 62) { // Shift Droite pressée
fShift_R = True;
wEventMask |= ShiftMask;
}
else if (nKeyCode == 108) { // Alt Gr pressée
fAlt_Gr = True;
wEventMask |= 2;
}
else if (nKeyCode == 37) { // Ctrl Gauche pressée
fCtrl_L = True;
}
else if (nKeyCode == 64) { // Alt Gauche pressée
fAlt_L = True;
}
else if (nKeyCode == 105) { // Ctrl Gauche pressée
fCtrl_R = True;
}
else if (nKeyCode == 133) { // Super Gauche pressée // Windows key ( == Super key )
fSuper_L = True;
}
else if (nKeyCode == 134) { // Super Droit pressée
fSuper_R = True;
}
else if (nKeyCode != 0) nKeyCodeMem = nKeyCode;
} // for
} // for
// Change l'état majuscule - minuscule, mais seulement pour des lettres.
if ( (structKeyboardState.led_mask & 1) > 0) {
// Caps Lock est ON
fCaps_Lock = True;
ulKeysym = XkbKeycodeToKeysym(dpy, nKeyCodeMem, 0, 0); // Lecture du caractère correspondant à la touche pressée.
// Pas de distinction majuscule - minucules à cet endroit.
if ( (ulKeysym >= 'a') && (ulKeysym <= 'z') ) {
if ( (wEventMask & ShiftMask) == 0 ) wEventMask |= ShiftMask;
else wEventMask &= ~ShiftMask;
}
}
if ( (structKeyboardState.led_mask & 2) > 0) fNum_Lock = True;
// Conversion de l'état du clavier en un caractère correspondant
ulKeysym = XkbKeycodeToKeysym(dpy, nKeyCodeMem, 0, wEventMask);
XCloseDisplay(dpy);
// Numérotation
for (int nn=0; nn<16; nn++) printf("%4x", nn);
// Affichage de l'état du Caps Lock (1) et du Num Lock (2)
printf(" Caps + Num Lock = %ld \n", structKeyboardState.led_mask);
// Affichage des 16 premier octets d'état du clavier
for (int nn=0; nn<16; nn++) printf("%4d", (unsigned char)abKeys_return[nn]);
printf("\n\n");
// Numérotation
for (int nn=16; nn<32; nn++) printf("%4d", nn);
printf("\n");
// Affichage des 16 premier octets d'état du clavier
for (int nn=16; nn<32; nn++) printf("%4x", (unsigned char)abKeys_return[nn]);
printf("\n");
// Pour effacer deux lignes
printf(" \n");
printf(" \n\e[2A"); // "\e[2A" pour remonter de deux lignes
// Affichage des codes clavier keycode des touches pressées.
// Il peut y en avoir plusieurs, en particulier Shift, Ctrl, Alt, Alt Gr et la touche windows.
// Pour la correspondance entre la touche et le symbole, pour le clavier Américain, c.f. :
// /usr/include/linux/input-event-code.h Il y a un décalage de 8. C.f le programme xev pour comparer.
for (int nn=0; nn<32; nn++) {
for (int kk=0; kk<8; kk++) {
if ((abKeys_return[nn] & (1<<kk)) > 0) printf("%4d", 8*nn + kk);
} // for
} // for
// Affiche l'état des touches Shift, Ctrl, Alt, Alt Gr, Super, Caps Lock et Num Lock
printf("\nShift-Crtl-alt-super Lock(Caps, Num) =");
if (fShift_L) printf(" S_L");
if (fShift_R) printf(" S_R");
if (fAlt_Gr) printf(" A_Gr");
if (fCtrl_L) printf(" C_L");
if (fCtrl_R) printf(" C_R");
if (fAlt_L) printf(" A_L");
if (fSuper_L) printf(" Sup_L");
if (fSuper_R) printf(" Sup_R");
printf(" ");
if (fCaps_Lock) printf(" Caps_Lock");
if (fNum_Lock) printf(" Num_Lock");
printf("\n");
// Affichage du UNICODE de symbole correspondant à la touche pressée, ainsi que du symbole lui-même.
if (ulKeysym != 0){
// Converti le code du caractère en son UNICODE
wKeyUnicode = xkb_keysym_to_utf32((xkb_keysym_t)ulKeysym);
// Pour avoir le code UTF-8 du caractère tapé au clavier abUtf8_code doit être de taille au moins 7.
// c.f. https://xkbcommon.org/doc/0.2.0/group__keysyms.html
// Convertit le code du caractère (ulLeysym) en son codage UTF-8 (abUtf8_code) et retourne le nombre d'octets UTF-8 du codage.
nLenUTF8 = xkb_keysym_to_utf8((xkb_keysym_t)ulKeysym, (char*)abUtf8_code, 8); // abUtf8_code[nLenUTF8-1] == 0
if (nLenUTF8 == 2)
sprintf(acUtf8_string, " %2x", abUtf8_code[0]);
else if (nLenUTF8 == 3)
sprintf(acUtf8_string, " %2x %2x", abUtf8_code[0], abUtf8_code[1]);
else if (nLenUTF8 == 4)
sprintf(acUtf8_string, " %2x %2x %2x", abUtf8_code[0], abUtf8_code[1], abUtf8_code[2]);
else if (nLenUTF8 == 5)
sprintf(acUtf8_string, "%2x %2x %2x %2x", abUtf8_code[0], abUtf8_code[1], abUtf8_code[2], abUtf8_code[3]);
printf("Keycode Keysym UNICODE(dec, hex) UTF-8(hex.) Symbole Caractère Shift & Alt Gr\n");
printf(" \n\e[1A"); // Pour effacer la ligne suivante et la remplacer
wKeyUnicode2 = wKeyUnicode;
if (wKeyUnicode2 < 32) wKeyUnicode2 = 32; // Remplace les caratères de contrôle par un espace
printf("%7d %7ld %7u, %#7x %12s %12s %lc ", nKeyCodeMem, ulKeysym, wKeyUnicode, wKeyUnicode, acUtf8_string, XKeysymToString(ulKeysym), wKeyUnicode2);
if (fShift_L) printf(" S_L");
if (fShift_R) printf(" S_R");
if (fAlt_Gr) printf(" A_Gr");
if (fCtrl_L) printf(" C_L");
if (fCtrl_R) printf(" C_R");
//convertUnicode_utf8(0, &wKeyUnicode, abUtf8_code); printf(" %x %x %x %s", abUtf8_code[0], abUtf8_code[1], abUtf8_code[2], abUtf8_code); // Pour vérification
//convertUnicode_utf8(1, &wKeyUnicode, abUtf8_code); printf(" %#x", wKeyUnicode); // Pour vérification
printf("\n");
}
else printf("\n\n");
//printf("\e[9A"); // remonte le curseur de 9 lignes. La commande suivante est meilleure
printf("\e[u"); // restore la position du curseur
if (abKeys_return[1] == 2) break; // ESC pressé, arrêt du programme
} // while
} // main