1 points par GN⁺ 2025-08-16 | 1 commentaires | Partager sur WhatsApp
  • 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 GhosttyConfig GObject avec comptage de références qui encapsule la structure Zig Config, et les notifications de changement de propriété propagent naturellement les modifications à l’ensemble de l’application
  • 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 WeakRef GObject 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
  • 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

 
GN⁺ 2025-08-16
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

    • À l’époque, j’avais des souvenirs pénibles d’utilisation directe de bindings GTK pour un langage. Dans 98 % des cas, tout allait bien, mais dans les 2 % restants, on tombait sur des cas du genre « le fait que cette fonction prenne ou non une référence à l’objet dépend des autres arguments », ce qui rendait l’analyse du cycle de vie des objets particulièrement pénible
    • Merci, et au passage, en essayant d’écrire du code Godot C# performant, j’ai parfois souffert des conversions incessantes avec les types du moteur, qui entraînaient aussi des allocations répétées. Je me demande si vous avez rencontré ce genre de problème en créant les bindings
    • Je ne savais pas qu’un projet de bindings Godot en Zig était en cours. J’aime à la fois Godot et Zig, donc j’ai hâte de voir ça. Je vais suivre ça de près
  • 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

    • La raison historique de cette structure de GTK, c’est justement la naissance de Vala. Vala s’inspire de C#, s’appuie sur GObject et transpile en C. C’est pour cela qu’un très grand nombre d’applications GTK sont en réalité écrites en Vala. On peut aussi regretter qu’ils n’aient pas plutôt choisi le langage D. D donne souvent l’impression d’un C# compilé sur bien des aspects
    • Se soumettre à un mauvais système n’est pas une bonne chose, c’est un choix pragmatique
  • 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

    • Je partage l’idée que le côté fortement prescriptif de GTK et GObject s’accorde mal avec ma manière de voir les choses. Je me sens aussi souvent en décalage avec la direction prise par l’écosystème Gnome. Utiliser GTK pour Linux dans Ghostty est toutefois un choix très pragmatique. Les objectifs de Ghostty en matière de plateforme native, surtout sous Linux, sont définis ici. GTK est le plus utilisé sous Linux, et c’est ce qui s’intègre le plus naturellement dans la majorité de l’écosystème applicatif, donc cette décision était difficile à éviter. J’espère qu’à l’avenir, libghostty permettra à des tiers de proposer divers frontends. Il existe déjà par exemple Wraith, un frontend Ghostty natif Wayland. C’est très cool
    • À mon avis, la raison essentielle pour laquelle GTK est si répandu sous Linux, c’est précisément qu’il propose des « bindings C ». Du coup, il existe des bindings natifs ou facilement générables pour presque tous les langages. À l’inverse, Qt est trop étroitement lié au C++ et à Python, ce qui réduit nettement son accessibilité. L’important, c’est de s’adapter au langage qu’utilise déjà le développeur. Et puis, quand on construit des applications desktop complexes, les toolkits UI impératifs à l’ancienne restent souvent très pragmatiques, avec beaucoup de widgets éprouvés et des patterns familiers. Les approches plus récentes, au contraire, demandent souvent de tout façonner à la main dès les petites choses, et cela devient vite pénible dès que la complexité monte
    • Concernant le passage « on dit qu’on n’est pas obligé d’utiliser GTK, mais j’ai aussi rencontré des gens qui se comportent comme si ce choix ne m’appartenait pas », je serais curieux de savoir quels types d’objections vous avez le plus souvent rencontrés. De mon point de vue, GTK est plutôt bon sur des aspects que les développeurs qui réinventent la roue négligent souvent, comme l’accessibilité ou la saisie dans des écritures non romanes, et j’ai l’impression que c’est là son principal avantage concurrentiel
  • 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

    • En réalité, il semble que le problème vienne non pas de GTK lui-même, mais de XInput2. GTK pourrait certes contourner cela avec des heuristiques comme Chromium, mais fondamentalement c’est un problème de couche supérieure, à savoir XInput2
    • En lisant ce rapport de bug et les tickets liés, on voit bien qu’il y a eu plusieurs tentatives de correction, mais qu’elles ont inévitablement dû s’appuyer sur certaines heuristiques, avec à chaque fois des effets de bord pires encore que le problème initial. Au fond, comme le souci vient des couches basses de X11, il faudrait une correction à la racine pour que d’autres améliorations aient vraiment du sens. Mais X11 est désormais pratiquement en mode maintenance, donc tant que ses partisans continueront à affirmer que « tout marche parfaitement, inutile d’en faire plus », il ne faut sans doute pas trop espérer. Au final, il ne reste qu’à attendre la transition vers 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

    • Pour ma part, quand j’écris en C ou en C++, j’utilise toujours valgrind régulièrement. Les erreurs détectées par valgrind et asan ne provoquent généralement pas des crashes immédiats : ce sont plutôt des bugs discrets, difficiles à repérer, qui finissent par se manifester de façon intermittente et deviennent un cauchemar à diagnostiquer. Certaines de ces erreurs peuvent aussi être des failles de sécurité. Et quand de petites fuites mémoire s’accumulent au fil du temps jusqu’à produire un vrai gros problème, toutes ces innombrables petites fuites rendent ensuite l’identification de la cause bien plus difficile. C’est pourquoi mieux vaut les utiliser de manière proactive
    • J’ai aussi eu tendance à utiliser Valgrind, surtout memcheck, de façon proactive pour éliminer d’abord les problèmes faciles à corriger avant de me lancer dans le débogage détaillé d’un rapport de bug. Le principal problème reste toutefois l’énorme surcoût en performances, qui le rend peu agréable en usage interactif. En revanche, faire tourner les tests une fois sous Valgrind me paraît très rentable
    • Cela dit, Valgrind est extrêmement lent et coûteux, donc difficile à intégrer directement dans la boucle modifier-compiler-tester. On peut l’utiliser dans des cycles de test plus espacés, comme des nightly ou de l’automatisation, mais pour bien l’intégrer il faut du travail supplémentaire
  • 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

    • Ghostty me satisfait à 99 % comme remplaçant de terminal, mais les problèmes de copier-coller sont vraiment frustrants et je les rencontre tous les jours
    • Depuis que j’utilise Ghostty comme terminal par défaut sur un nouvel ordinateur, ce qui me manque le plus, c’est l’absence de fonction de recherche. J’utilise souvent le raccourci pour retrouver un élément précis dans la sortie, et là ce n’est pas possible. C’est d’ailleurs le point le plus souvent mentionné dans ce ticket
    • En connexion distante sur Ubuntu, impossible même de lancer nano dans Ghostty
      $ nano
      Error opening terminal: xterm-ghostty.
      
      Dans le même environnement, cela fonctionne pourtant très bien avec le terminal macOS ou le terminal intégré de VS Code
    • Cela ressemble vraiment à un bug, donc je recommanderais de le signaler
    • L’absence de recherche de commande façon Cmd+F est ce qu’il y a de plus pénalisant
  • 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

    • Avec Rust, un des problèmes aurait été évité, mais pour le reste cela aurait été pareil. Comme vous l’avez souligné, tout se jouait à la frontière de l’API C et dans sa sémantique, donc la sûreté effective dépendait surtout de la qualité des wrappers. Rust dispose déjà d’un écosystème de wrappers bien éprouvés, ce qui aurait réduit le risque par rapport à des wrappers faits maison en Zig, mais au fond cela n’aurait pas changé grand-chose. Par exemple, l’accès mémoire indéfini que Rust aurait probablement détecté a en fait été corrigé dans cette PR. En pratique, une mémoire erronée a bien été copiée dans la première frame, mais elle n’a jamais été utilisée ni envoyée ailleurs, donc ce n’était pas critique. Cela restait toutefois clairement incorrect
    • Rust aussi exige une gestion manuelle de la mémoire et des durées de vie à la frontière FFI avec C/GObject. Le borrow checker de Rust ne peut pas valider l’usage mémoire dans du code externe
    • L’un des points de l’article, c’est justement que la combinaison zig + valgrind a donné bien moins de problèmes mémoire que prévu
    • Écrire des bindings C en Rust est beaucoup plus difficile. Donc il est possible qu’écrire des bindings GTK en Rust n’aurait tout simplement pas marché
  • 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 eu une expérience similaire sous Wayland. Sous X11, en désactivant les effets de composition, tout fonctionne très bien aussi bien avec une 1050Ti qu’avec une vieille carte AMD nécessitant le pilote radeon. En revanche, sous Wayland, j’ai eu des saccades, des crashes, des artefacts d’affichage et d’autres problèmes du même genre
  • 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

    • L’UI de Ghostty ne se limite pas aux onglets : il y a aussi les splits, la bannière « le processus s’est terminé », la boîte de dialogue de confirmation de fermeture, la boîte de dialogue de changement de titre, la détection de collage non sécurisé, la cloche animée, le terminal déroulant, la barre de progression, etc. Sur Mac, il y a aussi l’intégration Apple Shortcuts et Spotlight. Bien sûr, tout cela aurait pu être implémenté sans GUI toolkit, mais la mission de Ghostty est d’utiliser les toolkits natifs de chaque plateforme pour que l’application donne l’impression d’être « vraiment native ». Si cette approche ne plaît pas, utiliser des onglets textuels comme Kitty reste une très bonne option. On peut choisir selon les valeurs et priorités recherchées. D’autres extensions GUI sont prévues à l’avenir, ainsi qu’une intégration plus poussée avec les fonctionnalités natives propres à chaque plateforme, comme la synchronisation iCloud
    • À propos de l’affirmation « Kovid a implémenté les fonctionnalités plus rapidement », il faut rester prudent, car ce compte a un historique qui fait soupçonner qu’il pourrait s’agir de Kovid lui-même. Je l’ai déjà vu sur HN et Reddit présenter Kitty sous un angle apparemment neutre tout en critiquant le développeur. Il a même fourni un lien vers d’anciens commentaires pour référence
    • En plus de la description positive ci-dessus, je pense que l’arrivée de ‘libghostty’ change complètement la donne. Comme WebKit, c’est une implémentation de terminal puissante qu’on peut intégrer en drop-in et avoir immédiatement quelque chose qui fonctionne
    • Moi aussi, j’ai erré d’un terminal à l’autre à la recherche du bon. Ghostty n’est pas parfaitement idéal, mais quand je n’ai rien trouvé de pleinement satisfaisant, c’est celui vers lequel j’ai fini par migrer. Pour moi, cela suffit déjà à lui donner de la valeur. Souvent, son avantage n’est pas un « facteur décisif » spectaculaire, mais simplement le fait de ne pas avoir de gros défauts qui me poussent à l’abandonner
    • Les polices sont beaucoup plus joliment rendues dans Ghostty que dans Kitty. Neovide est encore plus beau, mais n’a toujours pas de support des onglets et consomme davantage de batterie