From b15f16995c4b198fa5ff4d9627ea8af57a8a2d69 Mon Sep 17 00:00:00 2001 From: James Baumgarten Date: Fri, 25 Jul 2025 22:47:12 -0600 Subject: [PATCH] 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 {