From 560b7ba8e853d1744a04915b9c62062f992310a0 Mon Sep 17 00:00:00 2001 From: Leorize Date: Wed, 15 Apr 2026 23:48:18 -0700 Subject: [PATCH] gtk/SurfaceScrolledWindow: wrap root child with another Adw.Bin Due to a known Gtk issue, the scrolled_window at the root of the template is free-ed twice on dispose. This causes crashes when used with GNOME 49 platform (Gtk 4.20, libadwaita 1.8.5). Workaround this issue by wrapping the root child in another Adw.Bin, similar to widgets like ResizeOverlay. LLM was used to perform discovery against a manually recorded Valgrind trace, and helped tracking down known fixes for this problem. Fixes https://github.com/ghostty-org/ghostty/discussions/12306 Assisted-by: OpenAI GPT-5.4 --- src/apprt/gtk/class/surface_scrolled_window.zig | 3 +-- src/apprt/gtk/ui/1.5/surface-scrolled-window.blp | 13 +++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/apprt/gtk/class/surface_scrolled_window.zig b/src/apprt/gtk/class/surface_scrolled_window.zig index dd4f17d11..6ccb0a0d2 100644 --- a/src/apprt/gtk/class/surface_scrolled_window.zig +++ b/src/apprt/gtk/class/surface_scrolled_window.zig @@ -163,8 +163,7 @@ pub const SurfaceScrolledWindow = extern struct { _: ?*anyopaque, ) callconv(.c) void { const priv = self.private(); - const child: *gtk.Widget = self.as(Parent).getChild().?; - const scrolled_window = gobject.ext.cast(gtk.ScrolledWindow, child).?; + const scrolled_window = self.private().scrolled_window.as(gtk.ScrolledWindow); scrolled_window.setChild(if (priv.surface) |s| s.as(gtk.Widget) else null); // Unbind old config binding if it exists diff --git a/src/apprt/gtk/ui/1.5/surface-scrolled-window.blp b/src/apprt/gtk/ui/1.5/surface-scrolled-window.blp index 75582a89f..8ba27cb64 100644 --- a/src/apprt/gtk/ui/1.5/surface-scrolled-window.blp +++ b/src/apprt/gtk/ui/1.5/surface-scrolled-window.blp @@ -3,9 +3,14 @@ using Adw 1; template $GhostttySurfaceScrolledWindow: Adw.Bin { notify::surface => $notify_surface(); - - Gtk.ScrolledWindow scrolled_window { - hscrollbar-policy: never; - vscrollbar-policy: bind $scrollbar_policy(template.config) as ; + // The double-nesting is required due to a GTK bug where you can't + // bind the first child of a builder layout. If you do, you get a double + // dispose. Easiest way to see that is simply remove this and see the + // GTK critical errors (and sometimes crashes). + Adw.Bin { + Gtk.ScrolledWindow scrolled_window { + hscrollbar-policy: never; + vscrollbar-policy: bind $scrollbar_policy(template.config) as ; + } } }