2006-03-25

kbdfr dk 6.0

Et voilà le résultat de plusieurs mois de travail, une nouvelle version de mon pilote de clavier français amélioré avec un logiciel d'installation sympathique ^^

Les améliorations sont de deux ordres :

  • des ajouts et des modifications concernant les caractères affectés aux touches ;
  • un logiciel d'installation amélioré.

Parmi les modifications sur les touches, certains seront heureux de retrouver les chiffres sur la première rangée, même si la combinaison pour les obtenir est un peu laborieuse.

Toujours dans le but d'accéder à l'ensemble des caractères de la langue française, j'ai ajouté le tiret demi-cadratin, utilisé pour faire des listes et des incises. J'ai aussi ajouté le tiret cadratin qui est utilisé pour des dialogues. Enfin, j'ai mis l'espace fine insécable, utilisée avant le point d'interrogation, le point d'exclamation et le point virgule.

Concernant l'espace fine insécable, hélas, peu de polices de caractères semblent la supporter. On obtient bien souvent un carré symbolisant l'inexistance du glyph. Disons qu'au moins le kbdfr dk est prêt pour un meilleur support à l'avenir.

Pour davantage de détails sur les caractères, vous pourrez consulter l'illustration ou lire le fichier « lisez-moi » dans l'archive à télécharger.

Concernant le logiciel d'installation amélioré, c'est cette tâche qui m'a pris ces fameux mois de travail. Avant d'en dire davantage, voici les améliorations en question :

  • l'installation est « system friendly » à présent : il n'écrase plus l'ancien pilote de clavier français, il se déclare gentiment auprès de Windows ; vous pourrez même le voir apparaitre dans le panneau de configuration des options régionales (sous le nom « Français (dk 6.0) » ;
  • l'installation se fait pour l'utilisateur courant uniquement ; donc si votre ordinateur est utilisé par plusieurs personnes, elles ne seront pas dans l'obligation d'utiliser le kbdfr-dk ;
  • on peut désinstaller le pilote de façon automatique, ainsi vous pouvez vous en débarrasser en deux clics s'il ne vous plait pas ;
  • il n'y a pas besoin de redémarrer l'ordinateur, ni de redémarrer les applications en cours ; les changements sont pris en compte immédiatement.

(oui mes fenêtres sont roses en ce moment, c'est pour aller avec ma skin Winamp PowerPuff Girl... hum :))

Ces tâches furent difficiles à réaliser en raison de la façon dont sont gérés les pilotes de clavier sous Windows. Là où sous d'autres OS il suffit de copier un fichier dans le bon dossier, ici il faut créer pleins de clés bizarres dans la base de registre et utiliser des fonctions du système non documentées ou mal documentées.

Sur le site de Microsoft comme ailleurs sur le Web, difficile de trouver de la documentation sur l'installation d'un nouveau pilote de clavier. J'ai essentiellement étudié le comportement de MSKLC, le logiciel de Microsoft qui permet de créer ses propres pilotes de clavier. Cela m'a donné une piste sur les valeurs à mettre dans la base de registre ainsi que leur signification. C'est d'une complexité aberrante, mais je pense avoir saisi l'essentiel. Je n'ai pas le choix de toute façon.

Pour ceux que ça intéresse, la clé déclarant le kbdfr-dk-6.0 est la suivante : HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Keyboard Layouts/b060040c

Quant à l'activation à la volée du nouveau pilote installé, aucune information disponible... rien... Il faut dire que ça ne doit pas être une tâche courante chez les programmeurs. En revanche je savais qu'il s'agissait d'un but réalisable puisque le Shell de Windows, « explorer » de son petit nom, sait le faire à l'aide du panneau de configuration des options régionales.

Il m'a donc tout simplement fallu récupérer le code source de la DLL qui gère le panneau de configuration des options régionales et étudier le procédé magique employé... Mouahahah, non je plaisante, Windows c'est du code source fermé, par conséquent j'ai dû trouver un autre moyen :p

Comment étudier le comportement d'un logiciel sans en avoir le code source ? Pour le panneau de configuration des options régionales, ça s'est passé comme ça (attention, longue lecture) :

1) Rechercher le nom du fichier qui gère ce panneau, il s'agit de « intl.cpl ».

Les fichiers CPL ce sont des DLL spéciales faisant office de plugin pour le Shell de Windows. J'ai donc dû apprendre à gérer ces DLL spéciales, afin de démarrer « intl.cpl » à partir d'un programme à ma sauce, une application d'analyse.

2) Chercher les fonctions utilisées dans intl.cpl en rapport avec le clavier. Ces fonctions sont dans la DLL système nommée « user32.dll ». On y trouve notamment :

  • ActivateKeyboardLayout (pas intéressant, car local au processus qui l'appelle) ;
  • GetKeyboardLayout (pas intéressant non plus, en rapport avec le processus appelant) ;
  • LoadKeyboardLayoutA (pas mal, mais ajoute une nouvelle langue dans le système, moi je ne veux que modifier le layout d'une langue existante, le français) ;
  • LoadKeyboardLayoutW (même remarque que LoadKeyboardLayoutA) ;
  • UnloadKeyboardLayout (on s'en servira peut-être pour la désinstallation) ;
  • LoadKeyboardLayoutEx ---> Aha ! C'est quoi cette fonction ? J'ai pas d'info dessus !

3) Déterminer le format de la fonction LoadKeyboardLayoutEx

Pour cela j'ai utilisé l'excellent PE Explorer (payant, mais il y a une période d'évaluation) pour déssasembler le code de user32.dll. J'ai découvert que cette fonction prend trois paramètres de type entier sur 32 bits.

4) Déterminer comment intl.cpl utilise toutes ces fonctions pour parvenir à ses fins

Pour ça j'ai utilisé le principe du trampoline : placer du code pour tracer l'exécution des fonctions entre l'appel par intl.cpl et le vrai code de user32.dll. La méthode ? Patcher user32 en mémoire, exécuter intl.cpl, faire ses manips, dépatcher user32, et enfin lire le joli fichier texte de traces généré.

Patcher user32 en mémoire... oui mais comment ? Pour le coup j'ai réutilisé mes connaissances en matière de format de fichier exécutable sous Windows, nommé « PE : Portable Executable », à l'époque où je souhaitais réaliser une petite démo en 4 Ko. J'ai créé une classe qui permet de remapper un EXE ou une DLL déjà chargé en mémoire, et d'en modifier la structure. Bien entendu, la structure est souvent protégée en écriture, mais Windows propose des fonctions sympas pour déprotéger les pages mémoires.

Les DLL ont en leur sein une table des fonctions exportées, qui contiennent des pseudos pointeurs vers chacunes des fonctions embarquées. Quand un EXE dépendant de cette DLL se charge, il remplit sa propre table de fonctions importées à l'aide de cette table de fonctions exportées. C'est compliqué et pas super optimisé je sais, d'ailleurs dans d'autres OS il arrive qu'un EXE travaille directement avec la table des fonctions de la DLL même (sous AmigaOS par exemple).

Bref, à l'aide de cette classe magique, dans mon application d'analyse du comportement de intl.cpl, je charge user32.dll pour récupèrer un handle dessus, je m'en sers pour obtenir un mapping mémoire du fichier PE correspondant grâce à ma classe magique, je déprotège la table des fonctions exportées, je la modifie pour que les fonctions qui m'intéressent pointent vers mes fonctions à moi.

Après ça, toujours dans la même application d'analyse, je démarre intl.cpl, qui ne verra que du feu en chargeant mes fonctions au lieu de celles de user32.dll.

  • intl : il y a anguille sous roche !
  • user32 : je t'assure chérie, je suis resté le même qu'avant...
  • intl : mais les enfants, tu as pensé aux enfants ?

J'ai rencontré une autre difficulté, car user32 est chargé à une adresse mémoire haute, alors que par défaut un EXE est chargé à une adresse mémoire basse. Cela impliquait l'utilisation de sauts négatifs dans la table d'export de user32 pour patcher les fonctions souhaitées. Et ça se passait mal à l'exécution. Comme disait notre ancien premier ministre en citant un philosophe célèbre : « il faut positiver ». J'ai donc changé l'adresse de base de user32 et remonté celle de mon application d'analyse, afin de n'avoir plus que des sauts positifs. Cependant, comment charger cette nouvelle DLL user32 alors qu'elle est déjà chargée par Windows ?

La solution fut de renommer cette DLL en user33.dll (nom du fichier, et nom interne), changer le checksum pour avoir une DLL valide (toujours grâce à PE Explorer), et modifier intl.cpl pour qu'il fasse référence à user33.dll et plus user32.dll.

Phieeeeeeeewwwww, ça m'a permis de comprendre comment foncionne intl.cpl.

Alors là j'ai pu écrire la première version du nouvel installeur, qui inscrit le nouveau pilote dans la base de registre, et l'active comme pilote par défaut. Malheureusement le changement ne semblait pas immédiat. C'est alors que j'ai dû retourner dans le code désassemblé de intl.cpl et tracer son exécution. Au bout de nombreuses lignes immondes d'assembleur x86 j'ai trouvé un appel à une fonction peu connue de Windows : SystemParametersInfo, utilisé notamment avec le paramètre « SPI_SETDEFAULTINPUTLANG ». Ça permet de réveiller un peu Windows sur le fait que le pilote de clavier a changé.

On avance, on avance. Mais il reste encore un truc embêtant. Quand j'ouvre Notepad avec le clavier X, que j'installe le kbdfr-dk, Notepad continue à utiliser le clavier X. En revanche si je lance une autre fenêtre Notepad, là le nouveau pilote est pris en compte. Gênant...

Là j'ai découvert qu'il convient d'envoyer un message Windows « WM_INPUTLANGCHANGEREQUEST » à toutes les fenêtres ouvertes. Qu'il en soit ainsi... En tout cas j'avais enfin atteint mon but avec mon programme d'installation. Il ne restait plus qu'à écrire le code de désinstallation, qui m'a aussi causé quelques soucis.

Et enfin, c'était fait, un nouveau programmme d'installation pour le kbdfr-dk ! Le jeu en valait-il la chandelle ? Dans le but de faciliter l'existence à l'utilisateur final, oui. En revanche je me retrouve avec la même conclusion qu'à l'époque où je souhaitais réaliser ma démo en 4K : quand on met le nez dans les entrailles d'un OS, on prend réllement conscience de l'élégance ou non du système. En l'occurrence là sous Windows, j'ai vraiment l'impression d'avoir à faire à un OS à l'architecture d'une complexité insensée. Du KISS, je veux du KISS !