Ghostty schreibt die GTK-Anwendung neu
(mitchellh.com)- Das Ghostty-Team hat die GTK-Anwendung vollständig neu geschrieben und das GObject-Typsystem aktiv genutzt
- Dabei spielten die Integration mit der Programmiersprache Zig und die Überprüfung von Speicherproblemen mit Valgrind eine wichtige Rolle
- Durch die Einführung des GObject-Systems wurden Speicherverwaltung und die Implementierung benutzerdefinierter Widgets gegenüber bisher vereinfacht
- Der Einsatz von Valgrind zeigte aus praktischer Erfahrung, dass sich die Speichersicherheit von Ghostty deutlich verbessert hat
- Das neue Ghostty GTK ist nun der Standard für Builds aus dem Quellcode und soll in Release 1.2 enthalten sein
Einleitung
- Ghostty ist ein plattformübergreifender Terminal-Emulator mit Unterstützung für macOS, Linux und FreeBSD
- Für jede Plattform nutzt es ein natives GUI-Framework als Unterscheidungsmerkmal
- macOS: umfangreiche Anwendung auf Basis von Swift und Xcode
- Linux und BSD: GTK-basierte Anwendung mit direkter Integration in X11/Wayland usw.
- Der gemeinsame Kern ist in Zig geschrieben und stellt eine API mit C-ABI-Kompatibilität bereit
- Warum die GTK-Anwendung in der bisherigen Struktur neu geschrieben wurde, kann im ursprünglichen PR nachgelesen werden
- Dieser Beitrag konzentriert sich insbesondere auf die Anbindung an das GObject-Typsystem sowie auf mit Valgrind verifizierte Speicherprobleme
GObject-Typsystem und Zig
- Wer GTK verwendet, muss strukturell mit dem GObject-Typsystem und seinen Interfaces arbeiten
- Früher wurde versucht, das GObject-System zu umgehen und den Lebenszyklus von Zig-Objekten ohne Reference Counting und GObject-Objekten direkt aufeinander abzustimmen, doch dabei traten wiederholt Probleme mit nicht korrekt freigegebenem Speicher auf
- Beispiel: Der Speicher auf Zig-Seite war bereits freigegeben, während der Speicher auf GTK-Seite noch lebte, oder umgekehrt
- Dieser Ansatz erschwerte nicht nur die Korrektheit, sondern auch die Nutzung GTK-spezifischer Funktionen wie Event-Signale, Property Binding und Actions
- Ein konkretes Beispiel war das Neuladen der Config-Struktur: Alle verbundenen GUI-Elemente mussten dabei konsistent aktualisiert werden, was komplex und fehleranfällig war
- Heute wird dies als reference-counted
GhosttyConfigGObject verwaltet, das die Zig-Config-Struktur umhüllt; Benachrichtigungen über Property-Änderungen propagieren Änderungen nun auf natürliche Weise durch die gesamte Anwendung
- Heute wird dies als reference-counted
- Auch das Erstellen benutzerdefinierter GObject-Widgets wurde einfacher, sodass moderne GTK-UI-Technologien wie Blueprint genutzt werden können
- In letzter Zeit wurde mit der Einführung von Blueprint die Umsetzung neuer Funktionen wie GTK-Tabs in der Titelleiste und animierte Bell-Ränder erleichtert
Valgrind, GTK und Zig
- Während des gesamten Entwicklungsprozesses wurde Valgrind eingesetzt, um Speicherlecks, Zugriffe auf undefinierten Speicher und ähnliche Probleme systematisch zu prüfen
- Die Prüfung einer GTK-Anwendung mit Valgrind ist anspruchsvoll und erfordert große Suppression-Dateien (80 % für GTK selbst, der Rest für Third-Party-Bibliotheken und GPU-Treiber)
- Durch wiederholte Prüfungen konnten komplexe Speicherfehler, die nur in bestimmten Fällen auftreten, frühzeitig entdeckt werden
- Beispiel: Wird ein GObject-
WeakRefnicht korrekt initialisiert, kann es später beim Freigeben des Zielobjekts zu Zugriffen auf undefinierten Speicher kommen; Valgrind erkannte dies im Voraus
- Beispiel: Wird ein GObject-
- In der Praxis gab es im Zig-Codebestand insgesamt nur zwei Probleme (ein Leak, ein undefinierter Zugriff), und selbst diese traten bei der Anbindung an eine Third-Party-C-API auf
- Auch Zigs Debug-Allocator und die Valgrind-Integrationsfunktionen haben ihre Wirksamkeit belegt
- Die übrigen gefundenen Speicherprobleme stammten überwiegend von den C-API-Grenzen und der komplexen Lebenszyklusverwaltung im GObject-System
- Das Fazit lautet: Um die C-API komplexer Bibliotheken sicher zu nutzen, sind Werkzeuge wie Valgrind erforderlich
- Die unterstützenden Funktionen von Zig für Speichersicherheit erwiesen sich nicht nur in theoretischen Diskussionen, sondern auch in konkreter Projekterfahrung als wirksam
Fazit
- Dies war das fünfte Mal, dass der GUI-Teil von Ghostty von Grund auf neu erstellt wurde
- In der Reihenfolge: GLFW, macOS SwiftUI, macOS AppKit+SwiftUI, Linux GTK (prozedural), Linux GTK + GObject-Typsystem
- Aus dem wiederholten Rewrite wurden jedes Mal neue Erkenntnisse und technisches Wachstum gewonnen
- Teile dieser Erfahrung sollen auch auf das macOS-Projekt übertragen werden
- Hervorgehoben wird außerdem die aktive Zusammenarbeit des Wartungsteams für das Ghostty-GTK-System
- Die neu geschriebene Ghostty-GTK-Anwendung ist nun der Standard für Source-Builds und soll mit dem offiziellen Release 1.2 eingeführt werden
1 Kommentare
Hacker-News-Kommentare
Ich habe nicht direkt mit GTK gearbeitet, aber nach deiner Beschreibung fühlt sich das sehr ähnlich zu den Problemen an, die ich beim Erstellen von Godot-Bindings für Zig hatte. Godot hat extrem viele OOP-Konzepte wie Klassen, virtuelle Methoden, Properties und Signale. Und es bietet eine C-API, die all diese Konzepte behandelt und es ermöglicht, benutzerdefinierte Objekte und Eigenschaften zu erstellen. Die Lebensdauer der Engine-Objekte muss direkt verwaltet werden, und es gibt auch Baumstrukturen mit referenzgezählten Objekten. Gerade die Lebensdauerprobleme in eine optimale API zu packen, die zu Zig-Idiomen passt, ist extrem komplex. Während ich darüber nachgedacht habe, habe ich auch die oopz-Bibliothek erstellt. Der API-Status ist noch ungefähr auf diesem Stand, und ein reales Beispiel gibt es hier. Ich würde den Ghostty-Frontend auch gern als Godot-Erweiterung bauen
Das ist ein gutes Beispiel dafür, dass gutes Programmieren letztlich bedeutet, sich der Art und Weise anzupassen, die das System vorgibt. Unabhängig davon, wie man über OOP oder Speicherverwaltung denkt: Wenn man GTK benutzt, muss man irgendwie mit dem GObject-Typsystem interagieren. Man kann ihm letztlich nicht ausweichen. Wir haben es trotzdem versucht, und das Ergebnis war ein enormes Chaos beim Verknüpfen der Lebensdauer referenzgezählter und nicht referenzgezählter Objekte. In der Ghostty-GTK-App traten immer wieder Bugs auf, bei denen Zig-Speicher freigegeben wurde, GTK-Speicher aber nicht — oder umgekehrt
Meine Haltung zu OOP und Speicherverwaltung sei dahingestellt, aber ich stimme zu, dass man sich mit dem GObject-Typsystem verheddert, sobald man GTK verwendet. Deshalb habe ich mich entschieden, GTK gar nicht erst direkt zu verwenden. Ich verstehe den Wert eines einheitlichen UI-Themas, aber aus meiner Sicht sind die Vorteile von GTK nicht groß genug, um diesen Preis zu zahlen. Nach meiner Erfahrung mit dem Randbereich von GTK in Open-Source-Apps bin ich überzeugt, dass die Sichtweisen von GTK und GObject nicht besonders gut zu meinen Vorlieben passen. Es stört mich nicht, dass GTK existiert. Ich entscheide mich einfach dagegen, und das ist für mich in Ordnung, aber es ist seltsam, dass manche Leute diese Entscheidung anscheinend nicht als mein Recht ansehen. Es ist nur eines von vielen GUI-Toolkits, und obwohl es technisch sehr ausgereift ist, frage ich mich manchmal, ob die Sorgfalt, die in GTK geflossen ist, nicht auch einem strukturell besseren Toolkit hätte zugutekommen können, wenn GTK nur ein wenig weniger Marktanteil hätte. Natürlich ist das, was ich gut finde, nicht für alle gut. Ich frage mich, wie viele GTK-Nutzer es eher widerwillig einsetzen und wie viele es wirklich für das beste Toolkit halten
Eine interessante Tatsache: In Ghostty und einigen anderen GTK-Apps gibt es das Phänomen, dass der erste Scroll-Klick ignoriert wird, wenn die Maus das Fenster verlässt und dann wieder hineinbewegt wird. Das liegt an einem uralten Bug, der erstmals 2015 gemeldet wurde. Link zum Bug. Bis heute ist keine Behebung geplant, und die Maintainer vertreten die Haltung, man solle auf Wayland warten
Bei der Aussage „Ich habe jeden Schritt mit Valgrind verifiziert“ dachte ich: Eigentlich ist das selbstverständlich, aber ich habe das in der Praxis nie selbst gemacht und auch selten gesehen, dass andere Entwickler es so handhaben. Normalerweise wurde Valgrind nur eingesetzt, wenn ein bestimmter Bug oder ein Performance-Einbruch auftrat. Wenn man Werkzeuge wie Valgrind, besonders Memcheck und Helgrind, während der gesamten Entwicklung aktiv nutzt, steigt die Stabilität des Tools enorm, und Bugs lassen sich direkt beim Einführen beheben, statt später mühsam Hunderte Commits durchsuchen zu müssen
Bei der Nutzung von Ghostty ist es auf dem Mac sehr störend, dass man in nano nicht mehrere Zeilen einfügen kann. Es scheint damit zusammenzuhängen, wie das Terminal „bracketed pasting“ behandelt, aber seltsamerweise gibt es dieses Problem bei iTerm2 oder Terminal nicht
Ich frage mich, ob Rust anstelle von Zig Speicherfehler verhindert hätte. Da das meiste aus der Zig/C-Interaktion entstand, wäre es wahrscheinlich ähnlich gewesen. Ich spekuliere hier als Go-Entwickler und frage mich, ob es überhaupt eine Sprache gibt, die bei umfangreicher Interaktion mit C deutlich mehr Sicherheitswerkzeuge bietet
Bei der Nutzung GPU-basierter Apps wie Ghostty, Alacritty, WezTerm oder Zed hatte ich den Eindruck, dass sie schneller und besser sind. Ironischerweise machen solche Apps aber die Grenzen der Nvidia-Treiber noch deutlicher sichtbar. Früher ist mir das nicht aufgefallen, weil ich kaum GPU verwendet habe, aber sowohl in Umgebungen ohne Compositor wie Regolith i3wm als auch unter sway/Wayland waren Bildschirmfreigabe, Aufwachen aus dem Sleep, Abstürze usw. mit dem Nvidia-Treiber wirklich miserabel. Ich habe verschiedene Versionen (550/560/575/580) ausprobiert, und es war überall dasselbe. Erst kürzlich ist mir klar geworden, dass das schon lange so schlecht ist
Ich konnte eine größere App bauen, ohne dass das GTK-Typsystem den Code beeinflusst hat. Statt Klassenvererbung und Erweiterung habe ich aber alle Komponenten nur dadurch verbunden, dass ich Lambdas gebunden habe. Dadurch war das Ergebnis nicht allzu chaotisch, aber Entwickler, die an den klassischen GTK-Stil gewöhnt sind, hätten es vielleicht verwirrend gefunden
Ich verstehe den übertriebenen Hype um Ghostty an sich nicht. Bei einer UI, die im Grunde nur Tabs und Kontextmenüs hat, frage ich mich, ob all diese Integrationsarbeit und Umschreibungen den Aufwand wert sind. Ich vermute, dass man vielleicht eine leistungsfähigere GUI-Umgebung wie bei iTerm2 hinzufügen will. Kitty zeichnet Tabs direkt mit OpenGL und erlaubt vollständige Anpassung, spart dadurch Zeit bei der Integration in komplexe Frameworks und implementiert sehr praktische Funktionen schneller, etwa die Ausgabe des letzten Befehls in einen Pager zu wrappen. Auch Remote-Unterstützung funktioniert in Kitty gut