From b15f16995c4b198fa5ff4d9627ea8af57a8a2d69 Mon Sep 17 00:00:00 2001 From: James Baumgarten Date: Fri, 25 Jul 2025 22:47:12 -0600 Subject: [PATCH 1/3] Fix i3 window border disappearing after fullscreen toggle When toggling a Ghostty window between fullscreen and windowed mode in the i3 window manager, window borders would disappear and not return. Root cause was that syncAppearance() was updating X11 properties on every call during window transitions, even when values hadn't changed. These redundant property updates interfered with i3's border management. The fix adds caching to syncBlur() and syncDecorations() to only update X11 properties when values actually change, eliminating unnecessary property changes during fullscreen transitions. --- src/apprt/gtk/winproto/x11.zig | 51 ++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/apprt/gtk/winproto/x11.zig b/src/apprt/gtk/winproto/x11.zig index 9dc273563..c73d4d482 100644 --- a/src/apprt/gtk/winproto/x11.zig +++ b/src/apprt/gtk/winproto/x11.zig @@ -173,6 +173,10 @@ pub const Window = struct { blur_region: Region = .{}, + // Cache last applied values to avoid redundant X11 property updates + last_applied_blur_region: ?Region = null, + last_applied_decoration_hints: ?MotifWMHints = null, + pub fn init( alloc: Allocator, app: *App, @@ -255,19 +259,34 @@ pub const Window = struct { const gtk_widget = self.apprt_window.as(gtk.Widget); const config = if (self.apprt_window.getConfig()) |v| v.get() else return; + // When blur is disabled, remove the property if it was previously set + const blur = config.@"background-blur"; + if (!blur.enabled()) { + if (self.last_applied_blur_region != null) { + try self.deleteProperty(self.app.atoms.kde_blur); + self.last_applied_blur_region = null; + } + return; + } + // Transform surface coordinates to device coordinates. const scale = gtk_widget.getScaleFactor(); self.blur_region.width = gtk_widget.getWidth() * scale; self.blur_region.height = gtk_widget.getHeight() * scale; - const blur = config.@"background-blur"; log.debug("set blur={}, window xid={}, region={}", .{ blur, self.x11_surface.getXid(), self.blur_region, }); - if (blur.enabled()) { + // Only update X11 properties when the blur region actually changes + const region_changed = if (self.last_applied_blur_region) |last| + !std.meta.eql(self.blur_region, last) + else + true; + + if (region_changed) { try self.changeProperty( Region, self.app.atoms.kde_blur, @@ -276,8 +295,7 @@ pub const Window = struct { .{ .mode = .replace }, &self.blur_region, ); - } else { - try self.deleteProperty(self.app.atoms.kde_blur); + self.last_applied_blur_region = self.blur_region; } } @@ -307,14 +325,23 @@ pub const Window = struct { .auto, .client, .none => false, }; - try self.changeProperty( - MotifWMHints, - self.app.atoms.motif_wm_hints, - self.app.atoms.motif_wm_hints, - ._32, - .{ .mode = .replace }, - &hints, - ); + // Only update decoration hints when they actually change + const hints_changed = if (self.last_applied_decoration_hints) |last| + !std.meta.eql(hints, last) + else + true; + + if (hints_changed) { + try self.changeProperty( + MotifWMHints, + self.app.atoms.motif_wm_hints, + self.app.atoms.motif_wm_hints, + ._32, + .{ .mode = .replace }, + &hints, + ); + self.last_applied_decoration_hints = hints; + } } pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void { From 47462ccc954e191506efac1f77389166ba1dcee3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 15 Dec 2025 09:38:36 -0800 Subject: [PATCH 3/3] clean up some blurring code --- src/apprt/gtk/winproto/x11.zig | 63 ++++++++++++++++------------------ 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/apprt/gtk/winproto/x11.zig b/src/apprt/gtk/winproto/x11.zig index c73d4d482..1e73c6139 100644 --- a/src/apprt/gtk/winproto/x11.zig +++ b/src/apprt/gtk/winproto/x11.zig @@ -173,7 +173,9 @@ pub const Window = struct { blur_region: Region = .{}, - // Cache last applied values to avoid redundant X11 property updates + // Cache last applied values to avoid redundant X11 property updates. + // Redundant property updates seem to cause some visual glitches + // with some window managers: https://github.com/ghostty-org/ghostty/pull/8075 last_applied_blur_region: ?Region = null, last_applied_decoration_hints: ?MotifWMHints = null, @@ -266,6 +268,7 @@ pub const Window = struct { try self.deleteProperty(self.app.atoms.kde_blur); self.last_applied_blur_region = null; } + return; } @@ -274,29 +277,26 @@ pub const Window = struct { self.blur_region.width = gtk_widget.getWidth() * scale; self.blur_region.height = gtk_widget.getHeight() * scale; + // Only update X11 properties when the blur region actually changes + if (self.last_applied_blur_region) |last| { + if (std.meta.eql(self.blur_region, last)) return; + } + log.debug("set blur={}, window xid={}, region={}", .{ blur, self.x11_surface.getXid(), self.blur_region, }); - // Only update X11 properties when the blur region actually changes - const region_changed = if (self.last_applied_blur_region) |last| - !std.meta.eql(self.blur_region, last) - else - true; - - if (region_changed) { - try self.changeProperty( - Region, - self.app.atoms.kde_blur, - c.XA_CARDINAL, - ._32, - .{ .mode = .replace }, - &self.blur_region, - ); - self.last_applied_blur_region = self.blur_region; - } + try self.changeProperty( + Region, + self.app.atoms.kde_blur, + c.XA_CARDINAL, + ._32, + .{ .mode = .replace }, + &self.blur_region, + ); + self.last_applied_blur_region = self.blur_region; } fn syncDecorations(self: *Window) !void { @@ -326,22 +326,19 @@ pub const Window = struct { }; // Only update decoration hints when they actually change - const hints_changed = if (self.last_applied_decoration_hints) |last| - !std.meta.eql(hints, last) - else - true; - - if (hints_changed) { - try self.changeProperty( - MotifWMHints, - self.app.atoms.motif_wm_hints, - self.app.atoms.motif_wm_hints, - ._32, - .{ .mode = .replace }, - &hints, - ); - self.last_applied_decoration_hints = hints; + if (self.last_applied_decoration_hints) |last| { + if (std.meta.eql(hints, last)) return; } + + try self.changeProperty( + MotifWMHints, + self.app.atoms.motif_wm_hints, + self.app.atoms.motif_wm_hints, + ._32, + .{ .mode = .replace }, + &hints, + ); + self.last_applied_decoration_hints = hints; } pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {