I haven't worked with GTK, but what you are describing here sounds reminiscent of what we have been dealing with trying to build Godot bindings in Zig with a nice API. the project is in mid-flight, but Godot:
- has tons of OOP concepts: classes, virtual methods, properties, signals, etc
- a C API to work with all of those concepts, define your own objects, properties, and so on
- manages the lifetimes of any engine objects (you can attach userdata to any of them)
- a whole tree of reference counted objects
it's a huge headache trying to figure out how to tie it into Zig idioms in a way that is an optimal API (specifically, dealing with lifetimes). we've come pretty far, but I am wondering if you have any additional insights or code snippets I should look at.
also.. now I want to attempt to write a Ghostty frontend as a Godot extension
crawshaw · 1h ago
Nice example of how good programming is often about meeting systems where they are:
Whatever your feelings are about OOP and memory management, the reality is that if you choose GTK, you're forced into interfacing in some way with the GObject type system. You can't avoid it.
Well you can avoid it and we did avoid it. And it leads to a mess trying to tie the lifetimes of your non-reference-counted objects to the reference counted ones. There was an entire class of bug that kept popping up in the Ghostty GTK application that could basically be summed up as: the Zig memory or the GTK memory has been freed, but not both.
Footnote7341 · 44m ago
I managed to write a fairly large GTK application without the GTK type system encroaching on my code at all. It just meant hooking a bunch of lambdas in where they want you to be inheriting from and extending their own classes to allow all the parts to communicate together.
In the end it wasn't that messy, but probably confusing for anyone used to writing dogmatic GTK applications.
Lerc · 1h ago
>Whatever your feelings are about OOP and memory management, the reality is that if you choose GTK, you're forced into interfacing in some way with the GObject type system. You can't avoid it.
In the past this has also been my assessment of GTK. It lead me to decide to take the other path, to never directly use GTK. I appreciate the value in having a unified user interface between applications, but I have always thought the features that GTK provides were not worth the penalties paid. I have worked around the edges of GTK in open source apps that are GTK based. That lead me to think that GTK and the GObject system is opinionated in a way that are not terribly compatible with my own opinions.
I don't hate that GTK exists, It is my choice not to use it and I am fine with that. However I also have encountered people who seem to think it is not my choice not to use it. There are a million and one other GUI toolkits out there, of which GTK is one of the most polished. I can't shake the feeling that if GTK were less dominant, some of the resources that go to polishing GTK might have been spent polishing a different framework with a nicer underlying architecture.
Of course what I consider nicer might not be what others consider nicer. Of those who use GTK, how many use it begrudgingly, and how many feel like it is the best tool for the job?
mitchellh · 1h ago
> That lead me to think that GTK and the GObject system is opinionated in a way that are not terribly compatible with my own opinions.
This might be amusing for me to say but... I also feel this way. I disagree a lot with the Gnome ecosystem's point of view. Funny!
Using GTK for Linux was a pragmatic choice. A goal of Ghostty is to be "platform-native" (defined here because there's no such thing on Linux: https://ghostty.org/docs/about#native). GTK is by various definitions the most popular, widespread GUI toolkit on Linux that makes your app fit into _most_ ecosystems. So, GTK it is.
I hope `libghostty` will give rise to other apprts (maintained by 3rd parties, it's hard enough for me to maintain macOS and GTK) so that you aren't forced into it. See https://ghostty.org/docs/about#libghostty For example Wraith is a Wayland-native Ghostty frontend (no GTK): https://github.com/gabydd/wraith Awesome.
LambdaComplex · 32m ago
I like Linux because it gives me the freedom to make my system behave how I want. The GNOME devs seem to think that GNOME should only behave how they want. For example, last time I checked, GNOME required a third-party plugin just to move the clock from the center of the status bar to the side.
I'm not at all surprised to see that this mindset extends to GTK.
jcastro · 27m ago
> just to move the clock from the center of the status bar to the side.
And I like linux because there are plenty of people who also do not care about this and want to just use their computer.
AndyKelley · 29m ago
Oh, I would love it if Wayland provided a standard UI toolkit (server-side)! Is that a thing that happened when I wasn't looking?
king_geedorah · 3m ago
No, there are no protocols intended to implement such a thing at this time. I'm not aware of anybody attempting to spec out such a protocol either, but I do think it's a really interesting idea.
cosmic_cheese · 12m ago
In my view the primary reason for GTK’s prominence on Linux rides on one thing primarily: it’s got C bindings, and thus it has decent bindings for just about every other language under the sun too. If you’re not trying for anything fancy, these bindings can even be auto-generated.
The runner up Qt is much more tightly tied to C++ and Python to its detriment. You really need to meet devs where they’re at instead of insisting that they adopt a particular language to use your UI toolkit.
Aside from that, at the end of the day, if you’re building a full fat complex desktop app an old style imperative UI toolkit is probably one of the more practical choices you can make. They have an exhaustive set of battle tested, accessible widgets built in and their pitfalls are well known. Newer approaches expect you to write or import everything and start requiring increased contortions from the developer past a certain point of complexity.
schmichael · 1h ago
I'm curious if Rust would have prevented the memory correctness errors assuming it replaced Zig in this scenario. It sounds like the vast majority were due to Zig/C interactions which makes me believe Rust would have had the same issues, but as a Go developer I am only guessing. I'm curious if there is a language that provides more tools to ensure correctness even when you're interacting with a huge amount of C.
mitchellh · 1h ago
Hi @schmichael ;) Rust would've prevented one. The rest Rust wouldn't have prevented since as you already noticed, it was in the boundary layer and semantics of a C API. It would've only been as safe as the Rust wrapper. One argument is that the richer, more proven ecosystem of wrapper libraries may have prevented it versus my DIY wrappers.
The one Rust would've prevented was a simple undefined memory access: https://github.com/ghostty-org/ghostty/pull/7982 (At least, I'm pretty sure Rust would've caught this). In practice, it meant that we were copying garbage memory on the first rendered frame, but that memory wasn't used or sent anywhere so in practice it was mostly safe. Still, its not correct!
pornel · 46m ago
Rust has safe and reliable GTK bindings. They used gir to auto-generate the error-prone parts of the FFI based on schemas and introspection: https://gtk-rs.org/gir/book/
Rust's bindings fully embrace GTK's refcounting, so there's no mismatch in memory management.
mitchellh · 44m ago
We also use gir to auto-generate our bindings. But stuff like this is not represented in gir: https://github.com/ghostty-org/ghostty/commit/7548dcfe634cd9... It could EASILY be represented in a wrapper (e.g. with a Drop trait) but that implies a well-written wrapper, which is my argument. It's not inherent in the safety Rust gives you.
So the safety does rely on the human, not the machine.
ericbarrett · 44m ago
Do you mean gtk-rs (https://gtk-rs.org/)? I have done a bit of programming with it. I respect the work behind it, but it is a monumental PITA - truly a mismatch of philosophies and design - and I would a thousand times rather deal with C/C++ correctness demons than attempt it again, unless I had hard requirements for soundness. Even then, if you use gtk-rs you are pulling in 100+ crate dependencies and who knows what lurks in those?
schmichael · 57m ago
Hey Mitchell! Leave it to me to bait the comments. :)
Thanks for validating my assumption that once you introduce a big blob o' C all bets are off and you're back to Valgrind (or similar tooling).
> One argument is that the richer, more proven ecosystem of wrapper libraries may have prevented it
Yeah but where's the fun in that? ;)
WD-42 · 1h ago
I think one of the main points of the article was about how shockingly few memory issues they have encountered. He talks about how great of a fit zig + valgrind is.
schmichael · 59m ago
Indeed, but I'm curious about the second class of memory correctness issue he mentions:
> 2. All other memory issues revolved around C API boundaries.
Is this something Rust, or any other language, has the ability to prevent any more than any other language? Or once you introduce a C API boundary is it back to tools like Valgrind?
NobodyNada · 1m ago
In general: whenever you call into C, you get all the safety of C, because C code can trivially have memory corruption bugs that corrupt memory "belonging" to Rust as well, inducing undefined behavior across your whole program [0].
In addition, it's often considered permissible for C APIs to exhibit undefined behavior if their API contracts are violated. This means that a bug in Rust code that calls into a C API incorrectly can (indirectly) cause undefined behavior. For this reason, all FFI calls are marked unsafe in Rust.
The typical approach to using C libraries from Rust is to create a "safe wrapper" around the unsafe FFI calls that uses Rust's type system and lifetimes to enforce the safety invariants. Of course it's possible to mess up this wrapper and have accidental undefined behavior, but you're much less likely to do so through a safe wrapper than if you use the unsafe FFI calls directly (or use C or Zig) for a couple reasons:
- Writing the safe wrapper forces you to sit down and think about safety invariants instead of glossing over it.
- Once you're done, the compiler will check the safety invariants for you every time you use the API -- no chance of making a mistake or forgetting to read a comment.
[0]: This could be avoided/mitigated with some kind of lightweight in-process sandboxing (e.g. Intel MPK + seccomp) to prevent C libraries from accessing memory that they don't own or performing syscalls they shouldn't. There's some academic research on this (and I experimented with it myself for a masters thesis project), but it generally requires some (minimal) performance overhead and code changes at language boundaries.
wofo · 21m ago
Having worked on a few Rust projects that interface with C libraries, I think your last question is a good summary: once you introduce a C API boundary it's back to tools like Valgrind.
Maybe you could even say Zig is at an advantage there, because tools like Valgrind are considered part of the game, whereas in Rust they are way less commonly used (pure-Rust codebases don't need anything of the sort, unless you are using `unsafe`).
sbt567 · 10m ago
I vaguely remember that there is a new language that promises "Safety across FFI boundary", but can't recall the name
ripley12 · 23m ago
> This has already led to more easily introducing GUI features like a new GTK titlebar tabs option
We had a similar experience to this at work. We wrote a google cloud service that interfaced with firestore using F# and found it painful because the firestore library is meant to be used with C#. It worked decently well but it was hard to write idiomatic F# without having a bunch of wrapping functions.
8f2ab37a-ed6c · 1h ago
Been a happy Ghostty user for a couple of months, it's my daily driver now as far as macOS terminals go. Thanks for all the hard work Mitchell & team.
WD-42 · 1h ago
Very nice writeup! I was actually just wondering what was going on with Ghostty, I've been daily driving it since initial release but I haven't noticed any updates since then (not that anything is particularly lacking, it's a great terminal!)
Good to hear the Mitchell and the team are still hacking away at it! Thanks for the great software!
mitchellh · 45m ago
Hold onto your butts cause 1.2 is weeks away and the release notes if printed would cause a [larger than we already have] deforestation problem.
WD-42 · 24m ago
Looking forward to it!
LeSaucy · 24m ago
I'll take QObject over GObject any day of the week.
corsica · 1h ago
I don't get the hype around this application. The only UI Ghostty has is tabs and the context menu, is it really worth the integration pain and now this rewrite?
Maybe they're planning for more, like those GUI configuration dialogs that iterm2 has?
Kitty uses OpenGL for everything and draws its own tabs, they're fully customizable and can be made to look however you want. By not wasting time on integrating with massive frameworks for drawing tabs, Kovid was able to quickly implement really useful things that Ghostty is sorely missing, like wrapping the output of the last command in a pager (run 'ps -auxf' and press Ctrl+Shift+G — this thing so useful it's hard to go without it now. It also works for remote shells across SSH sessions.)
mitchellh · 1h ago
> The only UI Ghostty has is tabs and the context menu
- Tabs
- Splits
- "this process has exited" banner
- Close confirmation dialogs
- Change title dialog
- Unsafe paste detection dialogs
- Context menus
- Animated bells (opt in)
- "Quake-style" dropdown terminals (cross platform but different mechanisms)
- Progress bars (ConEmu OSC 9;4)
- macOS: Apple Shortcuts Integration
- macOS: Spotlight Integration
Probably more I'm not thinking of. It's unfair to say it's just tabs. Could we have done this without a GUI toolkit? Of course! But the whole mission statement of this project was always to use platform-native (for various definitions) toolkits so that it _feels_ native.
That's not for everyone, and that's the great thing about the wonderful vibrant terminal ecosystem we have.
> is it really worth the integration pain and now this rewrite?
There's definitely a lot more on the way.
The first goal and primary focus of the project was to build a stable, feature rich (terminal sequences) terminal emulator. We're basically there. Next up, we're expanding GUI functionality significantly, including having more escape sequences result in more native GUI elements. But also traditional things like preferences GUIs, yes.
We're also integrating a lot more deeply with native features provided by each platform (somewhat related to the GUI toolkit choice), such as automatic iCloud syncing of your configuration on macOS. Now that the terminal is stable, we can start to really lean in to application features.
This isn't for everyone. Some people like Kitty's textual tabs. That's fine! It's a tradeoff. That's the beauty of choice. :) Kitty is a great terminal, if you prefer it, please use it. But it has completely different tradeoffs than Ghostty.
LambdaComplex · 30m ago
I know I'm about to show my ignorance regarding GUI programming here, but: would SDL2 be a suitable choice? Or would that be too low-level? Or just...the wrong sort of library?
Lerc · 1h ago
I went on a big meandering hunt for a terminal application that did what I wanted. I have tried many and while nothing perfectly met my needs, Ghostty is the one I am using now.
That counts for something.
Perhaps it is just it lacks an obvious reason to move away from it. Usually, the thing that made me try another terminal was because of something I couldn't do. It wasn't a matter of listing all the pros and cons and going with the best one. It has just found a home with me because it hasn't outstayed its welcome.
alberth · 58m ago
In addition to what others have said (positively), ‘libghostty’ is also a game changer.
It’s like the “WebKit” for terminal, as I understand it.
Anyone could drop-in libghostty, and immediately have a fully functional terminal.
hyperbolablabla · 1h ago
Agreed, seems like a lot of unnecessary girating just to implement something that would've been much simpler + cross-platform with a custom UI toolkit and something like opengl. Tabs are like UI 101
WD-42 · 52m ago
Luckily the UI and the core (libghostty) are separate so you can girate out your own UI 101 version without GTK if you'd like.
Easier integration with the desktops accessibility and input stack? When using GTK, you would probably also get out the box better performance/power on Wayland since it can take care of partial surface updates etc for you.
do_not_redeem · 1h ago
Well... a terminal is a GUI app, so they had to choose some GUI framework. On Linux GTK is as good a choice as any. (Yes I know you can skip the framework and talk to X11/Wayland directly, like xterm/foot do, but that's a pain all of its own.)
akulbe · 13m ago
What about Qt? Would that have been a better experience? Or would you experience different issues?
working on this problem produced this library, which I am not proud of: https://github.com/gdzig/oopz
here's a snippet that kind of demonstrates the state of the API at the moment: https://github.com/gdzig/gdzig/blob/master/example/src/Signa...
also.. now I want to attempt to write a Ghostty frontend as a Godot extension
In the end it wasn't that messy, but probably confusing for anyone used to writing dogmatic GTK applications.
In the past this has also been my assessment of GTK. It lead me to decide to take the other path, to never directly use GTK. I appreciate the value in having a unified user interface between applications, but I have always thought the features that GTK provides were not worth the penalties paid. I have worked around the edges of GTK in open source apps that are GTK based. That lead me to think that GTK and the GObject system is opinionated in a way that are not terribly compatible with my own opinions.
I don't hate that GTK exists, It is my choice not to use it and I am fine with that. However I also have encountered people who seem to think it is not my choice not to use it. There are a million and one other GUI toolkits out there, of which GTK is one of the most polished. I can't shake the feeling that if GTK were less dominant, some of the resources that go to polishing GTK might have been spent polishing a different framework with a nicer underlying architecture.
Of course what I consider nicer might not be what others consider nicer. Of those who use GTK, how many use it begrudgingly, and how many feel like it is the best tool for the job?
This might be amusing for me to say but... I also feel this way. I disagree a lot with the Gnome ecosystem's point of view. Funny!
Using GTK for Linux was a pragmatic choice. A goal of Ghostty is to be "platform-native" (defined here because there's no such thing on Linux: https://ghostty.org/docs/about#native). GTK is by various definitions the most popular, widespread GUI toolkit on Linux that makes your app fit into _most_ ecosystems. So, GTK it is.
I hope `libghostty` will give rise to other apprts (maintained by 3rd parties, it's hard enough for me to maintain macOS and GTK) so that you aren't forced into it. See https://ghostty.org/docs/about#libghostty For example Wraith is a Wayland-native Ghostty frontend (no GTK): https://github.com/gabydd/wraith Awesome.
I'm not at all surprised to see that this mindset extends to GTK.
And I like linux because there are plenty of people who also do not care about this and want to just use their computer.
The runner up Qt is much more tightly tied to C++ and Python to its detriment. You really need to meet devs where they’re at instead of insisting that they adopt a particular language to use your UI toolkit.
Aside from that, at the end of the day, if you’re building a full fat complex desktop app an old style imperative UI toolkit is probably one of the more practical choices you can make. They have an exhaustive set of battle tested, accessible widgets built in and their pitfalls are well known. Newer approaches expect you to write or import everything and start requiring increased contortions from the developer past a certain point of complexity.
The one Rust would've prevented was a simple undefined memory access: https://github.com/ghostty-org/ghostty/pull/7982 (At least, I'm pretty sure Rust would've caught this). In practice, it meant that we were copying garbage memory on the first rendered frame, but that memory wasn't used or sent anywhere so in practice it was mostly safe. Still, its not correct!
Rust's bindings fully embrace GTK's refcounting, so there's no mismatch in memory management.
EDIT:
I looked it up because I was curious, and a Drop trait is exactly what they do: https://github.com/gtk-rs/gtk-rs-core/blob/b7559d3026ce06838... and as far as I can tell this is manually written, not automatically generated from gir.
So the safety does rely on the human, not the machine.
Thanks for validating my assumption that once you introduce a big blob o' C all bets are off and you're back to Valgrind (or similar tooling).
> One argument is that the richer, more proven ecosystem of wrapper libraries may have prevented it
Yeah but where's the fun in that? ;)
> 2. All other memory issues revolved around C API boundaries.
Is this something Rust, or any other language, has the ability to prevent any more than any other language? Or once you introduce a C API boundary is it back to tools like Valgrind?
In addition, it's often considered permissible for C APIs to exhibit undefined behavior if their API contracts are violated. This means that a bug in Rust code that calls into a C API incorrectly can (indirectly) cause undefined behavior. For this reason, all FFI calls are marked unsafe in Rust.
The typical approach to using C libraries from Rust is to create a "safe wrapper" around the unsafe FFI calls that uses Rust's type system and lifetimes to enforce the safety invariants. Of course it's possible to mess up this wrapper and have accidental undefined behavior, but you're much less likely to do so through a safe wrapper than if you use the unsafe FFI calls directly (or use C or Zig) for a couple reasons:
- Writing the safe wrapper forces you to sit down and think about safety invariants instead of glossing over it.
- Once you're done, the compiler will check the safety invariants for you every time you use the API -- no chance of making a mistake or forgetting to read a comment.
[0]: This could be avoided/mitigated with some kind of lightweight in-process sandboxing (e.g. Intel MPK + seccomp) to prevent C libraries from accessing memory that they don't own or performing syscalls they shouldn't. There's some academic research on this (and I experimented with it myself for a masters thesis project), but it generally requires some (minimal) performance overhead and code changes at language boundaries.
Maybe you could even say Zig is at an advantage there, because tools like Valgrind are considered part of the game, whereas in Rust they are way less commonly used (pure-Rust codebases don't need anything of the sort, unless you are using `unsafe`).
Yes! This is huge, I previously gave up on Ghostty because the title bar wasted so much space on my laptop screen: https://bsky.app/profile/reillywood.bsky.social/post/3lebapf...
I found the PR in case anyone else is curious what the new functionality looks like: https://github.com/ghostty-org/ghostty/pull/8166
Good to hear the Mitchell and the team are still hacking away at it! Thanks for the great software!
Maybe they're planning for more, like those GUI configuration dialogs that iterm2 has?
Kitty uses OpenGL for everything and draws its own tabs, they're fully customizable and can be made to look however you want. By not wasting time on integrating with massive frameworks for drawing tabs, Kovid was able to quickly implement really useful things that Ghostty is sorely missing, like wrapping the output of the last command in a pager (run 'ps -auxf' and press Ctrl+Shift+G — this thing so useful it's hard to go without it now. It also works for remote shells across SSH sessions.)
- Tabs
- Splits
- "this process has exited" banner
- Close confirmation dialogs
- Change title dialog
- Unsafe paste detection dialogs
- Context menus
- Animated bells (opt in)
- "Quake-style" dropdown terminals (cross platform but different mechanisms)
- Progress bars (ConEmu OSC 9;4)
- macOS: Apple Shortcuts Integration
- macOS: Spotlight Integration
Probably more I'm not thinking of. It's unfair to say it's just tabs. Could we have done this without a GUI toolkit? Of course! But the whole mission statement of this project was always to use platform-native (for various definitions) toolkits so that it _feels_ native.
That's not for everyone, and that's the great thing about the wonderful vibrant terminal ecosystem we have.
> is it really worth the integration pain and now this rewrite?
There's definitely a lot more on the way.
The first goal and primary focus of the project was to build a stable, feature rich (terminal sequences) terminal emulator. We're basically there. Next up, we're expanding GUI functionality significantly, including having more escape sequences result in more native GUI elements. But also traditional things like preferences GUIs, yes.
We're also integrating a lot more deeply with native features provided by each platform (somewhat related to the GUI toolkit choice), such as automatic iCloud syncing of your configuration on macOS. Now that the terminal is stable, we can start to really lean in to application features.
This isn't for everyone. Some people like Kitty's textual tabs. That's fine! It's a tradeoff. That's the beauty of choice. :) Kitty is a great terminal, if you prefer it, please use it. But it has completely different tradeoffs than Ghostty.
That counts for something.
Perhaps it is just it lacks an obvious reason to move away from it. Usually, the thing that made me try another terminal was because of something I couldn't do. It wasn't a matter of listing all the pros and cons and going with the best one. It has just found a home with me because it hasn't outstayed its welcome.
It’s like the “WebKit” for terminal, as I understand it.
Anyone could drop-in libghostty, and immediately have a fully functional terminal.