Reescrevendo o aplicativo GTK do Ghostty
(mitchellh.com)- A equipe do Ghostty reescreveu completamente o aplicativo GTK, passando a usar ativamente o sistema de tipos GObject
- Nesse processo, a integração com a linguagem Zig e a verificação de problemas de memória com o Valgrind tiveram papel importante
- Com a adoção do sistema GObject, a gestão de memória e a implementação de widgets personalizados ficaram mais simples do que antes
- Ao usar o Valgrind, a equipe constatou uma grande melhora na segurança de memória do Ghostty
- O novo Ghostty GTK se tornou o padrão para builds a partir do código-fonte e será incluído na versão 1.2
Introdução
- Ghostty é um emulador de terminal multiplataforma com suporte a macOS, Linux e FreeBSD
- Ele se diferencia por usar frameworks de GUI nativos em cada plataforma
- macOS: aplicativo de grande porte baseado em Swift e Xcode
- Linux e BSD: aplicativo baseado em GTK, com integração direta com X11/Wayland etc.
- O núcleo comum é escrito em Zig e fornece uma API compatível com C ABI
- O motivo para reescrever o aplicativo GTK na estrutura anterior pode ser consultado no PR original
- Este texto foca principalmente na integração com o sistema de tipos GObject e nos problemas de memória verificados com Valgrind
Sistema de tipos GObject e Zig
- Ao usar GTK, a estrutura naturalmente exige interface com o sistema de tipos GObject
- No passado, a equipe tentou evitar o sistema GObject e alinhar manualmente o ciclo de vida entre objetos Zig sem contagem de referência e objetos GObject, mas isso repetidamente gerava problemas de liberação incorreta de memória
- Ex.: a memória do lado do Zig já havia sido liberada, mas a memória do lado do GTK ainda estava viva, ou o contrário
- Essa abordagem dificultava não só a correção, mas também o uso de recursos próprios do GTK como sinais de evento, associação de propriedades e ações
- Um exemplo concreto era o recarregamento da struct de configuração (
config): todos os elementos de GUI conectados precisavam ser atualizados de forma consistente, e esse processo era complexo e propenso a erros- Agora isso é gerenciado por meio de um
GhosttyConfigGObject com contagem de referência que encapsula a structConfigem Zig, e as notificações de mudança de propriedade propagam naturalmente as alterações por todo o aplicativo
- Agora isso é gerenciado por meio de um
- Também ficou mais fácil criar widgets GObject personalizados, permitindo o uso de tecnologias modernas de UI do GTK, como o Blueprint
- Recentemente, com a adoção do Blueprint, ficou mais fácil adicionar novos recursos como abas na barra de título do GTK e borda animada do sino
Valgrind, GTK e Zig
- Durante todo o processo de desenvolvimento, o Valgrind foi usado para verificar sistematicamente vazamentos de memória, acessos a memória indefinida e outros problemas
- Inspecionar aplicativos GTK com Valgrind é trabalhoso e exige um grande conjunto de arquivos de suppression (80% para o próprio GTK, o restante para bibliotecas de terceiros e drivers de GPU)
- As verificações repetidas permitiram encontrar antecipadamente bugs de memória complexos que só ocorriam em alguns casos
- Ex.: se um
WeakRefde GObject não for inicializado corretamente, pode ocorrer acesso a memória indefinida quando o objeto-alvo for liberado depois; o Valgrind permitiu detectar isso antes
- Ex.: se um
- Na experiência prática, houve apenas 2 problemas dentro do codebase Zig (1 vazamento e 1 acesso indefinido), e ambos surgiram durante a integração com APIs C de terceiros
- O alocador de depuração do Zig e seus recursos de integração com o Valgrind também provaram sua eficácia
- Os outros problemas de memória encontrados vieram, em sua maioria, da fronteira com APIs C e do complexo gerenciamento de ciclo de vida do sistema GObject
- Em resumo, para usar com segurança a API C de bibliotecas complexas, são necessárias ferramentas como o Valgrind
- Os recursos auxiliares de segurança de memória do Zig mostraram eficácia não apenas em discussões teóricas, mas também na experiência prática de um projeto real
Conclusão
- Esta foi a quinta vez que a parte de GUI do Ghostty foi refeita do zero
- Na ordem: GLFW, macOS SwiftUI, macOS AppKit+SwiftUI, Linux GTK (procedural), Linux GTK+sistema de tipos GObject
- Em cada reescrita repetida, houve novas lições e crescimento técnico
- Há planos de aplicar parte dessa experiência também ao projeto no macOS
- Também foi destacada a colaboração ativa da equipe de manutenção do sistema Ghostty GTK
- O aplicativo Ghostty GTK recém-reescrito agora se tornou o padrão para builds a partir do código-fonte e será aplicado no lançamento oficial 1.2
1 comentários
Comentários do Hacker News
Nunca trabalhei diretamente com GTK, mas, pelo que você descreveu, isso parece muito parecido com os problemas que enfrento ao criar bindings de Godot em Zig. O Godot tem muitos conceitos de OOP: classes, métodos virtuais, propriedades, sinais etc. E fornece uma API em C que lida com todos esses conceitos e permite criar objetos e propriedades personalizados. Também há gerenciamento manual do ciclo de vida dos objetos do engine e uma estrutura em árvore de objetos com contagem de referência. Quando tento empacotar os problemas de ciclo de vida em uma API ideal e idiomática para Zig, isso fica extremamente complexo. Pensando nisso, acabei criando também a biblioteca oopz. A API ainda está nesse estado, e um exemplo real pode ser visto aqui. Também gostaria de tentar fazer o frontend do Ghostty como uma extensão do Godot
Este é um ótimo exemplo de como boa programação, no fim, significa se adequar à forma como o sistema foi feito. Independentemente do que se pense sobre OOP ou gerenciamento de memória, se você usa GTK então inevitavelmente precisa montar alguma interface com o sistema de tipos GObject. Não dá para escapar disso. Só que nós tentamos escapar, e o resultado foi um caos enorme ao tentar vincular o ciclo de vida de objetos com contagem de referência e objetos sem contagem de referência. No app GTK do Ghostty, tivemos repetidamente bugs em que liberar a memória do Zig não liberava a memória do GTK, ou o contrário
Independentemente da minha posição sobre OOP e gerenciamento de memória, concordo que, ao usar GTK, você inevitavelmente acaba preso ao sistema de tipos GObject. Por isso, decidi simplesmente não usar GTK diretamente. Entendo o valor de um tema de UI unificado, mas, para mim, as vantagens do GTK não compensam o custo. Pela experiência que tive mexendo na periferia de apps open source com GTK, fiquei convencido de que a visão de GTK e GObject não combina muito com a minha. Não me incomoda que o GTK exista. Eu escolho não usar e tudo bem, mas acho estranho que algumas pessoas não enxerguem isso como um direito meu. É só um entre muitos toolkits de GUI e, embora tecnicamente seja um toolkit muito refinado, às vezes penso que, se a participação do GTK fosse um pouco menor, esse polimento talvez pudesse ter sido investido em outro toolkit estruturalmente melhor. Claro, o que eu considero bom não é necessariamente bom para todos. Tenho curiosidade de saber quantas pessoas que usam GTK o fazem a contragosto e quantas realmente o consideram o melhor toolkit
Um fato curioso: no Ghostty e em alguns outros apps GTK, se o mouse sai da janela e depois volta, o primeiro clique da roda de rolagem é ignorado. Isso acontece por causa de um bug muito antigo, relatado pela primeira vez em 2015. Link do bug. Até hoje não há plano para corrigir, e a posição do mantenedor é basicamente esperar pelo Wayland
Sobre a parte de “validei tudo com Valgrind”: parece algo óbvio demais, mas a verdade é que eu mesmo nunca fiz isso, e também quase nunca vi outros desenvolvedores fazerem. Normalmente o Valgrind só era usado quando aparecia algum bug específico ou degradação de desempenho. Se ferramentas como Valgrind, especialmente Memcheck e Helgrind, fossem usadas de forma proativa ao longo de todo o desenvolvimento, a estabilidade do software provavelmente melhoraria muito, e muitos bugs seriam pegos logo no momento em que fossem introduzidos, evitando depois o sofrimento de vasculhar centenas de commits
Usando o Ghostty, acho muito inconveniente no Mac não conseguir colar várias linhas no nano. Parece depender de como o terminal trata “bracketed pasting”, mas, curiosamente, isso não acontece no iTerm2 nem no Terminal
Fiquei pensando se usar Rust em vez de Zig teria evitado os erros de memória. Como a maioria dos problemas veio da interação Zig/C, imagino que com Rust seria parecido. Estou especulando do ponto de vista de um desenvolvedor Go, mas me pergunto se existe alguma linguagem que, ao se integrar pesadamente com C, ofereça de fato muito mais ferramentas de segurança
Usando o Ghostty e outros apps baseados em GPU, como Alacritty, WezTerm e Zed, senti que eles são mais rápidos e agradáveis. Mas, ironicamente, esses apps também deixam muito mais evidentes as limitações dos drivers da Nvidia. Antes, como eu quase não usava GPU, nem percebia. Mas tanto em ambientes sem compositor, como Regolith i3wm, quanto em sway/Wayland, os drivers da Nvidia se mostraram muito ruins em compartilhamento de tela, retomada do sleep, crashes e vários outros pontos. Troquei entre várias versões (550/560/575/580) e foi a mesma coisa em todas. Só recentemente percebi o quanto isso já era ruim fazia tempo
Já consegui fazer um app grande sem deixar o sistema de tipos do GTK influenciar o código. Mas, em vez de usar herança e extensões de classes, conectei todos os componentes apenas vinculando lambdas. No fim não ficou tão bagunçado, embora provavelmente fosse confuso para desenvolvedores acostumados ao estilo tradicional de GTK
Não consigo entender todo esse hype em torno do Ghostty. É uma UI que basicamente só tem abas e menu de contexto; questiono se vale mesmo a pena todo esse trabalho de integração e reescrita. Imagino que a ideia seja adicionar no futuro um ambiente GUI mais robusto, como o iTerm2. O Kitty desenha as próprias abas diretamente em OpenGL, então permite personalização total, e em vez de gastar tempo integrando com frameworks complexos, implementa rapidamente recursos muito práticos, como mandar a saída do último comando para um pager. O Kitty também oferece bom suporte remoto
libghosttyé um divisor de águas. É uma implementação de terminal poderosa, estilo WebKit, que qualquer um pode simplesmente encaixar e usar imediatamente