Reescriben la aplicación GTK de Ghostty
(mitchellh.com)- El equipo de Ghostty reescribió por completo la aplicación GTK y aprovechó activamente el sistema de tipos GObject
- En este proceso, la integración con el lenguaje Zig y la verificación de problemas de memoria con Valgrind jugaron un papel importante
- Al adoptar el sistema GObject, se simplificaron más que antes la gestión de memoria y la implementación de widgets personalizados
- Al usar Valgrind, comprobaron en la práctica que la seguridad de memoria de Ghostty mejoró considerablemente
- El nuevo Ghostty GTK ya se convirtió en la opción predeterminada para compilar desde el código fuente y está previsto que se incluya en la versión 1.2
Introducción
- Ghostty es un emulador de terminal multiplataforma compatible con macOS, Linux y FreeBSD
- Se distingue por usar frameworks GUI nativos en cada plataforma
- macOS: aplicación de gran escala basada en Swift y Xcode
- Linux y BSD: aplicación basada en GTK, con integración directa con X11/Wayland, etc.
- El núcleo compartido está escrito en Zig y ofrece una API compatible con C ABI
- Para conocer el motivo de la reescritura de la aplicación GTK dentro de la estructura anterior, puede consultarse el PR original
- Este texto se centra especialmente en la integración con el sistema de tipos GObject y en los problemas de memoria verificados con Valgrind
El sistema de tipos GObject y Zig
- Al usar GTK, la estructura exige interactuar de forma predeterminada con el sistema de tipos GObject
- En el pasado intentaron evitar el sistema GObject y hacer coincidir manualmente el ciclo de vida entre objetos Zig sin conteo de referencias y objetos GObject, pero una y otra vez surgieron problemas de liberación incorrecta de memoria
- Ejemplo: la memoria del lado de Zig ya se había liberado, pero la del lado de GTK seguía viva, o ocurría la situación contraria
- Este enfoque no solo generaba problemas de corrección, sino que también dificultaba usar funciones propias de GTK (señales de eventos, binding de propiedades, acciones)
- Un ejemplo concreto fue que, al recargar la estructura de configuración (
config), todos los elementos GUI conectados debían actualizarse de forma consistente, pero ese proceso era complejo y propenso a errores- Ahora se gestiona mediante un
GhosttyConfigGObject con conteo de referencias que envuelve la estructuraConfigde Zig, y las notificaciones de cambio de propiedades propagan los cambios de manera natural por toda la aplicación
- Ahora se gestiona mediante un
- También se volvió más fácil crear widgets GObject personalizados, lo que permite usar tecnologías modernas de UI para GTK como Blueprint
- Recientemente, con la adopción de Blueprint, resultó más sencillo introducir nuevas funciones como pestañas en la barra de título de GTK y bordes animados para la campana
Valgrind, GTK y Zig
- Durante todo el proceso de desarrollo se validaron de forma sistemática, con Valgrind, problemas como fugas de memoria y accesos a memoria no definida
- Revisar una aplicación GTK con Valgrind es complicado, y requiere un gran volumen de archivos de suppressions (80% corresponde al propio GTK, y el resto a librerías de terceros y drivers GPU)
- Gracias a las verificaciones repetidas, fue posible descubrir de antemano errores de memoria complejos que solo ocurrían en ciertos casos
- Ejemplo: si no se inicializa correctamente un
WeakRefde GObject, cuando el objeto objetivo se libera más adelante puede producirse un acceso a memoria no definida; Valgrind permitió detectarlo antes
- Ejemplo: si no se inicializa correctamente un
- En la experiencia real, los problemas dentro del codebase de Zig fueron solo 2 en total (1 fuga, 1 acceso no definido), y aun esos ocurrieron durante la integración con APIs C de terceros
- También quedó demostrada la efectividad del asignador de depuración de Zig y de sus funciones de integración con Valgrind
- La mayoría de los demás problemas de memoria detectados provenían de los límites con APIs C y de la compleja gestión del ciclo de vida en el sistema GObject
- En conclusión, para usar con seguridad la API C de librerías complejas, se necesitan herramientas como Valgrind
- Las funciones de apoyo a la seguridad de memoria de Zig demostraron su efectividad no solo en lo teórico, sino también a través de experiencia empírica en proyectos reales
Conclusión
- Esta fue la quinta vez que rehacen desde cero la parte GUI de Ghostty
- GLFW, macOS SwiftUI, macOS AppKit+SwiftUI, Linux GTK (procedimental), Linux GTK+sistema de tipos GObject
- En el proceso de reescritura iterativa, cada vez obtuvieron nuevas lecciones y crecimiento técnico
- Planean aplicar parte de esta experiencia también al proyecto de macOS
- También destacan la colaboración activa del equipo de mantenimiento del sistema GTK de Ghostty
- La aplicación Ghostty GTK recién reescrita ya se convirtió en la opción predeterminada para compilar desde el código fuente, y se aplicará en la versión estable 1.2
1 comentarios
Opiniones en Hacker News
No tengo experiencia trabajando directamente con GTK, pero por lo que describes, siento que es muy parecido a los problemas que he tenido creando bindings de Godot en Zig. Godot tiene muchísimos conceptos de OOP: clases, métodos virtuales, propiedades, señales, etc. Y ofrece una API en C que maneja todos esos conceptos y te permite crear objetos y propiedades personalizadas. También hay que gestionar manualmente el ciclo de vida de los objetos del motor, y existe una estructura en árbol de objetos con conteo de referencias. Intentar encapsular esos problemas de ciclo de vida en una API óptima y acorde a los modismos de Zig se vuelve extremadamente complejo. Mientras lidiaba con eso, también hice la librería oopz. La API por ahora está en este estado, y un ejemplo real puede verse aquí. También me gustaría intentar hacer el frontend de Ghostty como una extensión de Godot
Este es un muy buen ejemplo de que programar bien al final significa alinearse con la forma en que el sistema fue diseñado. Da igual lo que uno piense sobre OOP o gestión de memoria: si usas GTK, inevitablemente tienes que diseñar la interfaz de alguna manera alrededor del sistema de tipos GObject. Aunque quieras evitarlo, no se puede esquivar. Nosotros intentamos evitarlo, y el resultado fue un caos enorme al intentar unir los ciclos de vida de objetos con conteo de referencias y objetos sin él. En la app GTK de Ghostty, se repetían bugs donde se liberaba la memoria de Zig pero no la de GTK, o al revés
Dejando de lado mi postura sobre OOP y la gestión de memoria, sí estoy de acuerdo en que si usas GTK no te queda otra que enredarte con el sistema de tipos GObject. Por eso decidí directamente no usar GTK. Entiendo el valor de tener un tema visual unificado, pero en mi opinión las ventajas de GTK no compensan el costo que implica usarlo. Por mi experiencia tocando la periferia de apps open source alrededor de GTK, terminé convencido de que la visión de GTK y GObject no encaja bien con mi forma de pensar. No me molesta que GTK exista. Yo elijo no usarlo y con eso estoy bien, pero me parece raro que algunas personas no consideren que esa sea una elección que me corresponde. No es más que una GUI toolkit entre muchas, y aunque técnicamente es una toolkit muy refinada, a veces pienso que si GTK hubiera tenido un poco menos de cuota, todo ese polish podría haberse invertido en otras toolkits estructuralmente mejores. Claro, lo que a mí me parece bueno no necesariamente lo es para todos. Me pregunto cuánta gente usa GTK a regañadientes y cuánta realmente la considera la mejor opción
Un dato curioso: en Ghostty y algunas otras apps GTK, si el mouse sale de la ventana y luego vuelve a entrar, el primer clic de scroll se ignora. Esto pasa por un bug antiquísimo reportado por primera vez en 2015. Enlace al bug. Hasta hoy no hay planes de arreglarlo, y la postura del mantenedor es que hay que esperar a Wayland
Sobre la parte de “verifiqué cada paso con Valgrind”, la verdad es que aunque parezca obvio, yo nunca lo he hecho y tampoco he visto que muchos otros desarrolladores lo hagan. Normalmente Valgrind se usa solo cuando ya apareció un bug concreto o una degradación de rendimiento. Si durante todo el desarrollo se usaran activamente herramientas como Valgrind, especialmente Memcheck y Helgrind, probablemente la estabilidad mejoraría muchísimo y muchos bugs se atraparían en el momento exacto en que se introducen, en vez de obligarte después a revisar cientos de commits
Usando Ghostty, me resulta muy incómodo que en Mac no se pueda pegar varias líneas en nano. Parece depender de cómo el terminal maneja el “bracketed pasting”, pero curiosamente eso no pasa en iTerm2 ni en Term
Me pregunto si usar Rust en vez de Zig habría evitado los errores de memoria. Como la mayoría venían de la interacción Zig/C, supongo que Rust habría terminado en una situación parecida. Lo digo especulando desde la perspectiva de un desarrollador Go, pero me da curiosidad si existe algún lenguaje que realmente ofrezca más herramientas de seguridad cuando se integra a gran escala con C
Usando apps basadas en GPU como Ghostty, Alacritty, WezTerm o Zed, sí sentí que iban más rápido y mejor. Pero irónicamente eso también hace mucho más evidentes las limitaciones de los drivers de Nvidia. Antes casi no usaba la GPU y no me daba cuenta, pero tanto en entornos sin compositor como Regolith i3wm como en sway/Wayland, los drivers de Nvidia fueron pésimos en cosas como compartir pantalla, volver del modo sleep o los crashes. Probé varias versiones, 550/560/575/580, y todas se comportaban igual. Solo recientemente me di cuenta de lo malos que han sido desde hace tiempo
Pude construir una app grande sin que el sistema de tipos de GTK afectara demasiado el código. Pero a cambio, conecté todos los componentes enlazando lambdas, en lugar de usar herencia o extensiones de clases. El resultado no fue tan desordenado, aunque seguramente habría confundido a desarrolladores acostumbrados al estilo tradicional de GTK
No entiendo todo el hype exagerado alrededor de Ghostty. Tiene una UI con poco más que pestañas y menú contextual, así que me cuesta ver si realmente vale la pena tanto trabajo de integración y reescritura. Supongo que tal vez quieran terminar construyendo un entorno GUI poderoso como iTerm2. Kitty, por ejemplo, dibuja sus propias pestañas directamente con OpenGL, lo que le permite personalización total, y además se ahorra tiempo integrándose con frameworks complejos, así que puede implementar antes funciones muy prácticas, como envolver la salida del último comando en un pager. Kitty también soporta bien el uso remoto
libghosttyfue un game changer. Es una implementación de terminal muy potente, tipo WebKit, donde cualquiera puede hacer un drop-in y tenerla funcionando de inmediato