Réécriture de l’application GTK de Ghostty
(mitchellh.com)- L’équipe de Ghostty a entièrement réécrit l’application GTK en exploitant activement le système de types GObject
- Dans ce processus, l’intégration avec le langage Zig et la vérification des problèmes mémoire avec Valgrind ont joué un rôle important
- L’adoption du système GObject a simplifié la gestion de la mémoire et l’implémentation de widgets personnalisés par rapport à l’existant
- L’usage de Valgrind a permis de constater une nette amélioration de la sûreté mémoire de Ghostty
- Le nouveau Ghostty GTK est devenu la base par défaut pour les builds depuis le code source et sera inclus dans la version 1.2
Introduction
- Ghostty est un émulateur de terminal multiplateforme prenant en charge macOS, Linux et FreeBSD
- Il se distingue en utilisant des frameworks GUI natifs selon chaque plateforme
- macOS : application de grande taille basée sur Swift et Xcode
- Linux et BSD : application basée sur GTK, avec intégration directe à X11/Wayland, etc.
- Le cœur commun est écrit en Zig et fournit une API compatible C ABI
- La raison de la réécriture de l’application GTK dans l’architecture précédente peut être consultée dans la PR d’origine
- Cet article se concentre surtout sur l’intégration avec le système de types GObject ainsi que sur les problèmes mémoire vérifiés avec Valgrind
Le système de types GObject et Zig
- Lorsqu’on utilise GTK, l’architecture impose d’interfacer de base avec le système de types GObject
- Par le passé, l’équipe évitait le système GObject et tentait d’aligner directement le cycle de vie des objets Zig sans comptage de références et des objets GObject, mais cela provoquait à répétition des problèmes de libération mémoire incorrecte
- Exemple : la mémoire côté Zig était libérée alors que la mémoire côté GTK vivait encore, ou l’inverse
- Cette approche posait non seulement des problèmes de correction, mais rendait aussi difficile l’usage des fonctionnalités propres à GTK (signaux d’événement, liaison de propriétés, actions)
- Un exemple concret concernait le rechargement de la structure de configuration (
config) : tous les éléments GUI liés devaient être mis à jour de manière cohérente, un processus complexe et sujet aux erreurs- Désormais, cela est géré via un
GhosttyConfigGObject avec comptage de références qui encapsule la structure ZigConfig, et les notifications de changement de propriété propagent naturellement les modifications à l’ensemble de l’application
- Désormais, cela est géré via un
- La création de widgets GObject personnalisés est aussi devenue plus simple, ce qui permet d’utiliser des technologies UI GTK modernes comme Blueprint
- Récemment, l’introduction de Blueprint a facilité l’ajout de nouvelles fonctions comme les onglets dans la barre de titre GTK et la bordure animée de la cloche
Valgrind, GTK et Zig
- Tout au long du développement, Valgrind a servi à vérifier systématiquement les fuites mémoire, les accès à de la mémoire non définie et d’autres problèmes du même type
- L’inspection Valgrind d’une application GTK est délicate et nécessite de gros fichiers de suppression (80 % pour GTK lui-même, le reste pour des bibliothèques tierces et les pilotes GPU)
- Des vérifications répétées permettent de détecter à l’avance certains bugs mémoire complexes qui ne surviennent que dans des cas particuliers
- Exemple : si un
WeakRefGObject n’est pas correctement initialisé, un accès à de la mémoire non définie peut survenir plus tard lorsque l’objet cible est libéré ; Valgrind a permis de le repérer en amont
- Exemple : si un
- D’après l’expérience réelle, les problèmes internes au code Zig n’ont été qu’au nombre de 2 au total (1 fuite, 1 accès non défini), et même ceux-là sont apparus lors de l’intégration avec des API C tierces
- L’allocateur de débogage de Zig et ses fonctions d’intégration avec Valgrind ont également démontré leur efficacité
- Les autres problèmes mémoire découverts provenaient majoritairement des frontières avec les API C et de la gestion complexe du cycle de vie dans le système GObject
- En conclusion, pour utiliser en sécurité l’API C de bibliothèques complexes, des outils comme Valgrind sont nécessaires
- Les fonctions d’assistance à la sûreté mémoire de Zig ont montré leur efficacité non seulement dans les discussions théoriques, mais aussi dans une expérience de projet concrète
Conclusion
- Il s’agit de la cinquième fois que la partie GUI de Ghostty est reconstruite depuis zéro
- Dans l’ordre : GLFW, macOS SwiftUI, macOS AppKit+SwiftUI, Linux GTK (procédural), Linux GTK + système de types GObject
- À chaque réécriture, ce processus itératif a apporté de nouveaux enseignements et une progression technique
- Une partie de cette expérience devrait aussi être appliquée au projet macOS
- La collaboration active de l’équipe chargée de la maintenance du système Ghostty GTK est également mise en avant
- La nouvelle application Ghostty GTK réécrite est désormais la valeur par défaut pour les builds depuis les sources et sera adoptée dans la version 1.2 stable
1 commentaires
Commentaire Hacker News
Je n’ai pas d’expérience directe avec GTK, mais d’après ce que vous décrivez, cela me rappelle énormément les problèmes que j’ai rencontrés en créant des bindings Godot en Zig. Godot repose énormément sur des concepts POO comme les classes, méthodes virtuelles, propriétés, signaux, etc. Il fournit aussi une API C qui permet de manipuler tous ces concepts et de créer des objets et propriétés personnalisés. Il faut gérer soi-même le cycle de vie des objets du moteur, et il existe aussi une structure arborescente d’objets à comptage de références. Rien que le fait d’essayer d’enrober les problèmes de durée de vie dans une API optimale et idiomatique pour Zig rend le tout extrêmement complexe. C’est en réfléchissant à tout cela que j’ai aussi créé la bibliothèque oopz. L’API en est encore à ce stade, et on peut voir un exemple réel ici. J’aimerais aussi essayer de faire un frontend Ghostty sous forme d’extension Godot
C’est un bon exemple du fait qu’une bonne programmation consiste au final à se conformer à la manière dont le système fonctionne. Peu importe ce que l’on pense de la POO ou de la gestion mémoire, si l’on utilise GTK, il faut d’une manière ou d’une autre interfacer avec le système de types GObject. On ne peut pas vraiment y échapper. Pourtant, nous avons essayé de l’éviter, et cela a produit un chaos total quand il a fallu lier la durée de vie d’objets à comptage de références avec celle d’objets sans comptage. Dans l’application GTK de Ghostty, on s’est retrouvés encore et encore avec des bugs où libérer la mémoire Zig ne libérait pas la mémoire GTK, ou l’inverse
Au-delà de ma position sur la POO et la gestion mémoire, je suis d’accord sur le fait qu’utiliser GTK implique inévitablement de se retrouver mêlé au système de types GObject. C’est précisément pour cela que j’ai décidé de ne pas utiliser GTK directement. Je reconnais la valeur d’un thème UI unifié, mais à mes yeux, les avantages de GTK ne sont pas assez importants pour justifier ce coût. À force d’avoir bricolé la périphérie de GTK dans des applis open source, j’ai acquis la conviction que la vision portée par GTK et GObject ne correspond pas bien à ma manière de penser. Cela ne me dérange pas que GTK existe. Je choisis simplement de ne pas l’utiliser, et cela me convient, mais je trouve étrange que certaines personnes considèrent que ce choix ne m’appartient pas. Ce n’est qu’une GUI toolkit parmi tant d’autres, et même si elle est techniquement très polie, je me demande si, avec un peu moins de part de marché, toute cette finition n’aurait pas pu profiter à d’autres toolkits structurellement meilleurs. Bien sûr, ce que je trouve bon ne l’est pas forcément pour tout le monde. Je me demande combien de gens utilisent GTK à contrecœur, et combien pensent réellement que c’est le meilleur outil
Fait intéressant : dans Ghostty et quelques autres applications GTK, si la souris sort de la fenêtre puis y revient, le premier clic de molette de défilement est ignoré. C’est dû à un très vieux bug signalé pour la première fois en 2015. Lien du bug. À ce jour, il n’est toujours pas prévu de le corriger, et les mainteneurs disent en gros d’attendre Wayland
Quand j’ai lu « j’ai validé chaque étape avec Valgrind », je me suis dit que c’était en fait très évident, mais en même temps je ne l’ai moi-même jamais fait, et j’ai rarement vu d’autres développeurs le faire non plus. En général, Valgrind n’était utilisé qu’au moment où un bug précis ou une régression de performances se manifestait. Si on utilisait de manière proactive, tout au long du développement, des outils comme Valgrind — surtout Memcheck et Helgrind —, la robustesse des logiciels augmenterait énormément, et beaucoup de bugs seraient détectés dès leur introduction, évitant d’avoir à fouiller des centaines de commits après coup
En utilisant Ghostty, ce qui m’ennuie beaucoup sur Mac, c’est de ne pas pouvoir coller correctement plusieurs lignes dans nano. Cela semble dépendre de la manière dont le terminal gère le « bracketed pasting », mais curieusement je n’ai pas ce problème avec iTerm2 ou Terminal
Je me demande si utiliser Rust au lieu de Zig aurait empêché les erreurs mémoire. Comme la plupart des problèmes venaient de l’interaction Zig/C, j’imagine que Rust aurait rencontré quelque chose de similaire. En tant que développeur Go, je me demande aussi s’il existe réellement un langage offrant davantage d’outils de sûreté lorsqu’il faut s’interfacer massivement avec du C
En utilisant des applications basées GPU comme Ghostty, Alacritty, WezTerm ou Zed, j’ai eu l’impression qu’elles étaient plus rapides et meilleures. Mais ironiquement, elles mettent aussi beaucoup plus en lumière les limites des pilotes Nvidia. Avant, j’utilisais très peu le GPU et je ne m’en rendais pas compte, mais que ce soit dans un environnement sans compositeur comme Regolith i3wm ou dans un environnement sway/wayland, les pilotes Nvidia étaient vraiment catastrophiques sur le partage d’écran, le retour de veille, les crashes, etc. J’ai essayé plusieurs versions (550/560/575/580), c’était pareil à chaque fois. Je réalise seulement maintenant à quel point c’était mauvais depuis longtemps
J’ai réussi à construire une grosse application sans que le système de types GTK n’influence le code. En contrepartie, au lieu de faire de l’héritage ou des extensions de classes, j’ai relié tous les composants entre eux uniquement en attachant des lambdas. Au final, ce n’était pas si sale, mais cela aurait probablement semblé déroutant pour des développeurs habitués au style GTK plus traditionnel
Je ne comprends pas l’engouement disproportionné autour de Ghostty. Avec une UI qui se limite à des onglets et un menu contextuel, je me demande si ce type d’intégration et de réécriture en vaut vraiment la peine. J’imagine qu’ils veulent aller vers un environnement GUI plus puissant, à la iTerm2. Kitty, lui, dessine directement ses onglets en OpenGL, permet une personnalisation complète et, en évitant de perdre du temps à s’intégrer à des frameworks compliqués, a pu implémenter rapidement des fonctions très pratiques, comme l’affichage du résultat de la dernière commande dans un pager. Kitty gère aussi très bien le distant