1 pontos por GN⁺ 2025-08-16 | 1 comentários | Compartilhar no WhatsApp
  • 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 GhosttyConfig GObject com contagem de referência que encapsula a struct Config em Zig, e as notificações de mudança de propriedade propagam naturalmente as alterações por todo o aplicativo
  • 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 WeakRef de 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
  • 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

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

    • No passado, lembro de ter achado desconfortável usar bindings de GTK para linguagens específicas. Em 98% dos casos tudo funcionava bem, mas nos 2% restantes havia situações do tipo “esta função recebe ou não uma referência do objeto dependendo de outro argumento”, e isso tornava a análise do ciclo de vida dos objetos bem complicada
    • Além de agradecer, queria dizer que, ao tentar escrever código C# para Godot com bom desempenho, já sofri bastante com as muitas conversões entre tipos do engine, o que também gerava alocações repetidas. Fiquei curioso se você também encontrou esse tipo de problema ao criar bindings
    • Eu não sabia que havia um projeto em andamento para criar bindings de Godot em Zig. Gosto muito tanto de Godot quanto de Zig, então isso me anima bastante. Vou acompanhar de perto
  • 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

    • O motivo de o GTK ter essa estrutura é justamente o contexto em que o Vala nasceu. O Vala foi inspirado em C#, usa GObject e transpila o código para C. Por isso, um número considerável de apps GTK é escrito em Vala. Às vezes fica a sensação de que teria sido melhor usar D. Em vários aspectos, D parece um C# compilado
    • Ceder a um sistema ruim não é algo bom; é uma escolha prática
  • 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

    • Concordo com a parte de que o estilo fortemente opinativo de GTK e GObject também não combina muito com a minha forma de pensar. Também sinto bastante desalinhamento com a direção do ecossistema Gnome. Usar GTK no Ghostty para Linux é uma escolha bastante prática. O objetivo do Ghostty de ser nativo à plataforma, especialmente no Linux, está definido aqui. GTK é o mais usado no Linux e o que mais naturalmente se encaixa na maioria dos ecossistemas de apps, então essa decisão acaba sendo inevitável. No futuro, espero que o libghostty permita o surgimento de vários frontends de terceiros. Um exemplo é o Wraith, um frontend nativo em Wayland para Ghostty. Muito legal
    • Acho que o principal motivo de o GTK ser tão amplamente usado no Linux é justamente ter “bindings em C”. Assim, bindings para praticamente qualquer linguagem já existem ou são fáceis de gerar automaticamente. Já o Qt é ligado demais a C++ e Python, o que reduz bastante a acessibilidade. É importante encontrar o desenvolvedor onde ele está, independentemente da linguagem que usa. Além disso, ao escrever apps desktop complexos, toolkits de UI imperativos e mais antigos acabam sendo mais práticos, têm muitos widgets já comprovados e padrões familiares. As abordagens mais recentes fazem você ter que montar tudo do zero, e basta a complexidade aumentar um pouco para tudo ficar bem mais difícil
    • Fiquei curioso sobre que tipo de oposição você costuma encontrar em relação à ideia de “eu posso escolher não usar GTK”, já que algumas pessoas parecem tratar isso como se não fosse realmente uma escolha. Para mim, um diferencial importante do GTK parece ser o bom suporte a acessibilidade e entrada de texto não romana, áreas às quais desenvolvedores que fazem tudo por conta própria normalmente não dão a devida atenção
  • 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

    • Na prática, esse problema parece vir não do próprio GTK, mas do XInput2. Claro, o GTK poderia contornar isso com heurísticas, como o Chromium faz aqui, mas na origem é um problema da camada de cima, o XInput2
    • Ao ler esse relatório de bug e os issues vinculados, dá para ver que houve várias tentativas de corrigir, mas inevitavelmente foi preciso depender de algumas heurísticas, e isso seguia causando efeitos colaterais piores do que o problema original. No fim, como a raiz do problema está no X11, parece que qualquer melhoria real depende de uma correção fundamental nessa base. Mas como o X11 hoje está praticamente em modo de manutenção, enquanto os fãs continuarem dizendo “está funcionando perfeitamente, não precisa mexer”, não dá para esperar muito. No fim das contas, o que resta é esperar a migração para 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

    • Eu mesmo, ao usar C e C++, sempre rodei valgrind periodicamente. Os erros que valgrind e asan capturam normalmente não aparecem como crashes imediatos; eles ficam escondidos e depois viram bugs intermitentes muito difíceis de diagnosticar. No meio deles também podem existir vulnerabilidades de segurança. E quando pequenos vazamentos de memória vão se acumulando até virar um problema realmente sério, fica ainda mais difícil descobrir a causa porque já há uma pilha enorme de vazamentos menores. Por isso vale a pena usar essas ferramentas de forma proativa
    • Tenho usado Valgrind, especialmente memcheck, de forma proativa para capturar primeiro os problemas fáceis de corrigir antes de mergulhar no debugging detalhado de relatórios de bug. O maior problema é o overhead de desempenho, que torna a experiência interativa ruim. Ainda assim, acho extremamente valioso rodar os testes com Valgrind de vez em quando
    • Dito isso, o Valgrind é muito lento e custoso para colocar diretamente no ciclo editar-compilar-testar. Dá para usar em ciclos de teste como nightly e automação, mas uma integração bem feita exige trabalho extra
  • 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

    • Como substituto de terminal, o Ghostty me satisfaz em 99%, mas o problema de copiar e colar é realmente irritante e aparece todo dia
    • Desde que passei a usar o Ghostty como terminal padrão no meu computador novo, a maior falta que sinto é a ausência de busca. Costumo usar muito o atalho para procurar algo específico na saída, e isso não existe. Na prática, esse é um dos problemas mais citados no issue
    • Ao acessar remotamente um Ubuntu, o nano nem chega a rodar dentro do Ghostty
      $ nano
      Error opening terminal: xterm-ghostty.
      
      No mesmo ambiente, funciona normalmente no Terminal do macOS e no terminal integrado do VSCode
    • Isso realmente pode ser um bug, então recomendo reportar
    • O mais crítico é não ter busca de comandos tipo Cmd+F
  • 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

    • Com Rust, um dos problemas teria sido evitado, mas o restante seria basicamente igual. Como você apontou, tudo girava em torno da fronteira com a API C e da semântica dela, então a segurança real depende da qualidade do wrapper. Rust já tem um ecossistema de wrappers bem mais amadurecido, então nesse ponto talvez fosse menos arriscado do que um wrapper próprio em Zig, mas no essencial não mudaria tanto. Por exemplo, o acesso indefinido à memória que Rust provavelmente teria impedido foi justamente o que foi corrigido neste PR. Na prática, uma memória incorreta foi copiada no primeiro frame, mas não chegou a ser usada nem enviada para lugar nenhum, então não foi grave. Ainda assim, claramente estava errado
    • Rust também exige gerenciamento manual de memória e ciclo de vida na fronteira FFI com C/GObject. O borrow checker do Rust não consegue verificar o uso de memória por código externo
    • Um dos pontos do texto é justamente que a combinação zig + valgrind resultou em muito menos problemas de memória do que se esperava
    • Escrever bindings C em Rust é bem mais difícil. Então talvez nem fosse viável fazer bindings de GTK em Rust
  • 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

    • Eu também tive uma experiência parecida no Wayland. No X11, ao desativar os efeitos de composição, tanto a 1050Ti quanto uma placa AMD antiga, que exige o driver radeon, funcionam sem problemas. Já no Wayland tive travamentos, crashes, tela corrompida e coisas do tipo
  • 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

    • A UI do Ghostty não tem só abas, mas também divisões, banner de “processo encerrado”, diálogo de confirmação de fechamento, diálogo para mudar o título, detecção de colagem insegura, sino animado de notificação, terminal dropdown, barra de progresso e mais coisas do que parece à primeira vista. No Mac, também há integração com Apple Shortcuts e Spotlight. Claro, mesmo sem tudo isso ainda daria para implementar a UI inteiramente por conta própria sem um toolkit GUI, mas a missão do Ghostty é usar os toolkits nativos de cada plataforma para que o app pareça “realmente nativo”. Se você não gosta dessa abordagem, usar algo como o Kitty, com abas em texto, também é uma ótima escolha. São prioridades e valores diferentes. No futuro, já há planos para várias expansões de GUI e integrações mais profundas com recursos nativos de cada plataforma, como sincronização com iCloud
    • Sobre a afirmação de que “Kovid implementou mais rápido”, é bom tomar cuidado, porque há um histórico que faz suspeitar que essa conta possa ser do próprio Kovid. Já vi no HN e no Reddit mensagens apresentando o Kitty de forma aparentemente neutra enquanto criticavam o desenvolvedor. Foi até deixado este link para consultar comentários anteriores
    • Além da explicação positiva acima, também acho que o surgimento do libghostty é um divisor de águas. É uma implementação de terminal poderosa, estilo WebKit, que qualquer um pode simplesmente encaixar e usar imediatamente
    • Eu também rodei bastante em busca de um terminal, e o Ghostty não é o ideal perfeito para mim, mas acabei migrando para ele quando não encontrava nada que fosse razoavelmente satisfatório. Só isso já foi significativo para mim. No geral, o fato de eu continuar usando não é por um “motivo decisivo”, mas justamente por ele não ter grandes problemas, e isso por si só já é uma vantagem
    • As fontes ficam muito mais bonitas no Ghostty do que no Kitty. O Neovide é ainda mais bonito, mas ainda não tem suporte a abas e também consome mais bateria