Relatively simple port. A few cool things:
1. We use properties on `GhosttySurface` to set this now and standard
property listeners
2. We make `terminal.MouseShape` a GObject enum if we have gobject
available.
3. The property based approach means we don't have to manage
`*gdk.Cursor` memory anywhere anymore.
And, we're still Valgrind clean.
Found by Valgrind:
```
==265734== 320 bytes in 8 blocks are definitely lost in loss record 13,786 of 15,141
==265734== at 0x5A65810: malloc (in /nix/store/l431jn8ahj09g5c1arrl7q6wcxngg21q-valgrind-3.24.0/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==265734== by 0x3D799EB: onig_region_resize (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:923)
==265734== by 0x3D81BCA: onig_region_resize_clear (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:949)
==265734== by 0x3DA814F: search_in_range (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:5454)
==265734== by 0x3DA7F25: onig_search (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:5414)
==265734== by 0x382E7E8: regex.Regex.searchAdvanced (regex.zig:61)
==265734== by 0x382E974: regex.Regex.search (regex.zig:48)
==265734== by 0x382EC08: terminal.StringMap.SearchIterator.next (StringMap.zig:40)
==265734== by 0x39ADDCD: renderer.link.Set.matchSetFromLinks (link.zig:320)
==265734== by 0x39AE5C7: renderer.link.Set.matchSet (link.zig:95)
==265734== by 0x39BCCE1: renderer.generic.Renderer(renderer.OpenGL).rebuildCells (generic.zig:2307)
==265734== by 0x39C3BDB: renderer.generic.Renderer(renderer.OpenGL).updateFrame (generic.zig:1228)
==265734== by 0x3993E88: renderer.Thread.renderCallback (Thread.zig:607)
==265734== by 0x3993CFF: renderer.Thread.wakeupCallback (Thread.zig:524)
==265734== by 0x39C522E: callback (async.zig:679)
==265734== by 0x39C522E: watcher.async.AsyncEventFd(api.Xev(.io_uring,backend.io_uring)).waitPoll__anon_592685__struct_596870.callback (async.zig:181)
==265734== by 0x3970EAE: backend.io_uring.Completion.invoke (io_uring.zig:804)
==265734== by 0x3973AD8: backend.io_uring.Loop.tick___anon_586861 (io_uring.zig:193)
==265734== by 0x3973BCD: backend.io_uring.Loop.run (io_uring.zig:84)
==265734== by 0x3978673: dynamic.Xev(&.{ .io_uring, .epoll }[0..2]).Loop.run (dynamic.zig:172)
==265734== by 0x3978972: renderer.Thread.threadMain_ (Thread.zig:263)
==265734== by 0x3954580: renderer.Thread.threadMain (Thread.zig:202)
==265734== by 0x39279CA: Thread.callFn__anon_573552 (Thread.zig:488)
==265734== by 0x38F4594: Thread.PosixThreadImpl.spawn__anon_570448.Instance.entryFn (Thread.zig:757)
==265734== by 0x6E567EA: start_thread (pthread_create.c:448)
==265734== by 0x6ED9FB3: clone (clone.S:100)
==265734==
```
Found by Valgrind:
```
==265734== 320 bytes in 8 blocks are definitely lost in loss record 13,786 of 15,141
==265734== at 0x5A65810: malloc (in /nix/store/l431jn8ahj09g5c1arrl7q6wcxngg21q-valgrind-3.24.0/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==265734== by 0x3D799EB: onig_region_resize (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:923)
==265734== by 0x3D81BCA: onig_region_resize_clear (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:949)
==265734== by 0x3DA814F: search_in_range (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:5454)
==265734== by 0x3DA7F25: onig_search (.cache/zig/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c/src/regexec.c:5414)
==265734== by 0x382E7E8: regex.Regex.searchAdvanced (regex.zig:61)
==265734== by 0x382E974: regex.Regex.search (regex.zig:48)
==265734== by 0x382EC08: terminal.StringMap.SearchIterator.next (StringMap.zig:40)
==265734== by 0x39ADDCD: renderer.link.Set.matchSetFromLinks (link.zig:320)
==265734== by 0x39AE5C7: renderer.link.Set.matchSet (link.zig:95)
==265734== by 0x39BCCE1: renderer.generic.Renderer(renderer.OpenGL).rebuildCells (generic.zig:2307)
==265734== by 0x39C3BDB: renderer.generic.Renderer(renderer.OpenGL).updateFrame (generic.zig:1228)
==265734== by 0x3993E88: renderer.Thread.renderCallback (Thread.zig:607)
==265734== by 0x3993CFF: renderer.Thread.wakeupCallback (Thread.zig:524)
==265734== by 0x39C522E: callback (async.zig:679)
==265734== by 0x39C522E: watcher.async.AsyncEventFd(api.Xev(.io_uring,backend.io_uring)).waitPoll__anon_592685__struct_596870.callback (async.zig:181)
==265734== by 0x3970EAE: backend.io_uring.Completion.invoke (io_uring.zig:804)
==265734== by 0x3973AD8: backend.io_uring.Loop.tick___anon_586861 (io_uring.zig:193)
==265734== by 0x3973BCD: backend.io_uring.Loop.run (io_uring.zig:84)
==265734== by 0x3978673: dynamic.Xev(&.{ .io_uring, .epoll }[0..2]).Loop.run (dynamic.zig:172)
==265734== by 0x3978972: renderer.Thread.threadMain_ (Thread.zig:263)
==265734== by 0x3954580: renderer.Thread.threadMain (Thread.zig:202)
==265734== by 0x39279CA: Thread.callFn__anon_573552 (Thread.zig:488)
==265734== by 0x38F4594: Thread.PosixThreadImpl.spawn__anon_570448.Instance.entryFn (Thread.zig:757)
==265734== by 0x6E567EA: start_thread (pthread_create.c:448)
==265734== by 0x6ED9FB3: clone (clone.S:100)
==265734==
```
This introduces a new `GhosttyDialog` class that either inherits from
`adw.MessageDialog` or `adw.AlertDialog`, depending on the version of
libadwaita we compile against. This is the same logic we used
previously.
This lets us have a single libadw 1.2 blueprint file for all dialogs and
we just do the right thing at compile time!
This introduces a new `GhosttyDialog` class that either inherits from
`adw.MessageDialog` or `adw.AlertDialog`, depending on the version of
libadwaita we compile against. This is the same logic we used
previously.
This lets us have a single libadw 1.2 blueprint file for all dialogs and
we just do the right thing at compile time!
A small, simple change. This implements the `Surface.close` apprt
required function. After this PR, a process exiting within the terminal
will close the window properly (unless `wait-after-command` is set of
course!).
This doesn't yet show close confirmation. I'm working on some dialog
refactors on the side to see if we can simplify our Adw 1.2 vs. 1.5
dialogs and didn't want to include it here.
Close now works by the `GhosttySurface` class emitting the
`close-request` signal (similar to a `gtk.Window`) and the parent
container is responsible for closing it. This will let us reuse the
surface within different contexts: tabs, splits, etc.
This also remove the unused `shouldClose`, `setShouldClose` apprt APIs
which are a holdover from the glfw days!
This is a tiny addon from the recent gtk-ng work. We've moved redraw
requests into the apprt action system (the `render` action). I waited
until I had my macOS machine to verify that this fix could work. We can
now remove this completely.
We can probably remove the redraw inspector API too at some point but
I'm not there yet with the GTK backend so I'll just wait on it.
This is a tiny addon from the recent gtk-ng work. We've moved redraw
requests into the apprt action system (the `render` action). I waited
until I had my macOS machine to verify that this fix could work. We can
now remove this completely.
We can probably remove the redraw inspector API too at some point but
I'm not there yet with the GTK backend so I'll just wait on it.
This ports back all our event controllers back to the `GhosttySurface`.
With this PR, the terminal is now usable again at a very very simple
level!
This also brings back `winproto` but its still filled with
incompatibilities. I just need to bring that back so modifiers worked
properly. We'll fix that up in a future PR.
This also fixes one undefined memory access in debug modes found by
Valgrind.
This ports over the `new_window` functionality and shows a single
no-input, no-tab, no-split surface. The surface renders (e.g. the cursor
blinks) and it is a full Ghostty surface underneath so the shell prompt
shows up and everything. However, the surface doesn't respond to input.
I'm going to put this PR up in this state so that it isn't too much all
at once.
This work also required some core libghostty improvements that just
didn't fit the model being built here. They're extremely minimal,
however (basically going from struct fields to struct decls so we can do
some logic).
A couple new Valgrind suppressions had to be added to deal with GLAreas.
These suppressions were necessary before we ever hooked up our renderers
so they're caused by GTK itself. Only two, though! Other than that,
Ghostty runs **Valgrind clean**.
<img width="1726" height="714" alt="2025-07-18-131732_hyprshot"
src="https://github.com/user-attachments/assets/9c8bfe86-705c-4173-916b-df2b9b54dbfd"
/>
Even though the viewport pin isn't used unless the `viewport` is `pin`,
it's still possible to access undefined data through `clone`. Valgrind
found this:
```
==107091== Conditional jump or move depends on uninitialised value(s)
==107091== at 0x392B96A: terminal.PageList.clone (PageList.zig:540)
==107091== by 0x392C9A0: terminal.Screen.clonePool (Screen.zig:348)
==107091== by 0x392DF7A: terminal.Screen.clone (Screen.zig:330)
==107091== by 0x394E6D4: renderer.generic.Renderer(renderer.OpenGL).updateFrame (generic.zig:1129)
==107091== by 0x3919BF8: renderer.Thread.renderCallback (Thread.zig:607)
==107091== by 0x3919A6F: renderer.Thread.wakeupCallback (Thread.zig:524)
==107091== by 0x394FA6E: callback (async.zig:679)
==107091== by 0x394FA6E: watcher.async.AsyncEventFd(api.Xev(.io_uring,backend.io_uring)).waitPoll__anon_436371__struct_440666.callback (async.zig:181)
==107091== by 0x38F781E: backend.io_uring.Completion.invoke (io_uring.zig:804)
==107091== by 0x38FA448: backend.io_uring.Loop.tick___anon_431479 (io_uring.zig:193)
==107091== by 0x38FA53D: backend.io_uring.Loop.run (io_uring.zig:84)
==107091== by 0x38FEFE3: dynamic.Xev(&.{ .io_uring, .epoll }[0..2]).Loop.run (dynamic.zig:172)
==107091== by 0x38FF2E2: renderer.Thread.threadMain_ (Thread.zig:263)
==107091== by 0x38DDF80: renderer.Thread.threadMain (Thread.zig:202)
==107091== by 0x38B5C0A: Thread.callFn__anon_421402 (Thread.zig:488)
==107091== by 0x3888604: Thread.PosixThreadImpl.spawn__anon_418943.Instance.entryFn (Thread.zig:757)
==107091== by 0x6C6E7EA: start_thread (pthread_create.c:448)
==107091== by 0x6CF1FB3: clone (clone.S:100)
==107091==
```
Even though the viewport pin isn't used unless the `viewport` is `pin`,
it's still possible to access undefined data through `clone`. Valgrind
found this:
```
==107091== Conditional jump or move depends on uninitialised value(s)
==107091== at 0x392B96A: terminal.PageList.clone (PageList.zig:540)
==107091== by 0x392C9A0: terminal.Screen.clonePool (Screen.zig:348)
==107091== by 0x392DF7A: terminal.Screen.clone (Screen.zig:330)
==107091== by 0x394E6D4: renderer.generic.Renderer(renderer.OpenGL).updateFrame (generic.zig:1129)
==107091== by 0x3919BF8: renderer.Thread.renderCallback (Thread.zig:607)
==107091== by 0x3919A6F: renderer.Thread.wakeupCallback (Thread.zig:524)
==107091== by 0x394FA6E: callback (async.zig:679)
==107091== by 0x394FA6E: watcher.async.AsyncEventFd(api.Xev(.io_uring,backend.io_uring)).waitPoll__anon_436371__struct_440666.callback (async.zig:181)
==107091== by 0x38F781E: backend.io_uring.Completion.invoke (io_uring.zig:804)
==107091== by 0x38FA448: backend.io_uring.Loop.tick___anon_431479 (io_uring.zig:193)
==107091== by 0x38FA53D: backend.io_uring.Loop.run (io_uring.zig:84)
==107091== by 0x38FEFE3: dynamic.Xev(&.{ .io_uring, .epoll }[0..2]).Loop.run (dynamic.zig:172)
==107091== by 0x38FF2E2: renderer.Thread.threadMain_ (Thread.zig:263)
==107091== by 0x38DDF80: renderer.Thread.threadMain (Thread.zig:202)
==107091== by 0x38B5C0A: Thread.callFn__anon_421402 (Thread.zig:488)
==107091== by 0x3888604: Thread.PosixThreadImpl.spawn__anon_418943.Instance.entryFn (Thread.zig:757)
==107091== by 0x6C6E7EA: start_thread (pthread_create.c:448)
==107091== by 0x6CF1FB3: clone (clone.S:100)
==107091==
```
Every GObject class we're ever going to make has the same handful of
methods. This adds *just enough* noise to be annoying. This commit
extracts the common ones so far into a central mixin. Since Zig is
removing `usingnamespace` and has no other mixin mechanism, we must
forward the decls, but this is still cleaner imo than what we did
before.
I suspect longer term we can probably abstract more of the `zig-gobject`
boilerplate into a higher level abstraction but I'm not confident doing
that yet across the 4 classes we have so far.
Credit to @pluiedev for pointing this out.
Every GObject class we're ever going to make has the same handful of
methods. This adds *just enough* noise to be annoying. This commit
extracts the common ones so far into a central mixin. Since Zig is
removing `usingnamespace` and has no other mixin mechanism, we must
forward the decls, but this is still cleaner imo than what we did
before.
I suspect longer term we can probably abstract more of the `zig-gobject`
boilerplate into a higher level abstraction but I'm not confident doing
that yet across the 4 classes we have so far.
Ghostty has had support for a while (since PR #3124) for parsing
progress reports but never did anything with them. This PR adds the core
infrastructure and an implementation for GTK.
On GTK, the progress bar will show up as a thin bar along the top of the
terminal. Under normal circumstances it will use whatever you have set
as your accent color. If the progam sending the progress report
indicates an error, it will change to a reddish color.
Ghostty has had support for a while (since PR #3124) for parsing progress
reports but never did anything with them. This PR adds the core
infrastructure and an implementation for GTK.
On GTK, the progress bar will show up as a thin bar along the top of
the terminal. Under normal circumstances it will use whatever you have
set as your accent color. If the progam sending the progress report
indicates an error, it will change to a reddish color.
This ports the config errors dialog from `apprt/gtk` to `gtk-ng`.
The major change here is that we now use proper template bindings for
the content. To do this, a `ghostty.Config` is now wrapped in a GObject
`GhosttyConfig` to make it safe to pass around (ref count) and to
provide helpful properties like the diagnostics buffer we bind to.
As a minor change, I stripped the `Ghostty` prefix from our GObject
classes in Zig code. For templates its all still there as is the norm.
This retains the exact same version requirements and layout as the
existing one.
Looks like 52354b8 missed noting the outgoing screen selection state's
rectangle flag when setting the selection on mouse release, this was
causing the selection that was actually set to be
standard/wrap-selected. This corrects that by just shipping said flag
when calling `setSelection`.
Looks like 52354b8 missed noting the outgoing screen selection state's
rectangle flag when setting the selection on mouse release, this was
causing the selection that was actually set to be
standard/wrap-selected. This corrects that by just shipping said flag
when calling setSelection.