gtk: honor suspended window state

GTK exposes the Wayland xdg_toplevel suspended state when the
compositor knows a window is not visible. Ghostty previously only used
widget map state, so it could continue rendering a mapped surface on an
inactive workspace or behind other windows.

Combine the mapped and suspended states for surface occlusion and update
all displayed surfaces whenever the toplevel suspension state changes.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fa965-aa5f-7099-85b4-a9679d2c8bd3
This commit is contained in:
Tim Culverhouse
2026-07-26 13:45:32 -05:00
parent 74f45b3219
commit 2f3814ca5e
2 changed files with 35 additions and 3 deletions

View File

@@ -3324,7 +3324,7 @@ pub const Surface = extern struct {
self: *Self,
) callconv(.c) void {
self.updateMapped(true);
self.updateOcclusion(true);
self.updateOcclusion();
}
fn glareaUnmap(
@@ -3332,7 +3332,7 @@ pub const Surface = extern struct {
self: *Self,
) callconv(.c) void {
self.updateMapped(false);
self.updateOcclusion(false);
self.updateOcclusion();
}
fn updateMapped(self: *Self, mapped: bool) void {
@@ -3341,13 +3341,23 @@ pub const Surface = extern struct {
self.as(gobject.Object).notifyByPspec(properties.mapped.impl.param_spec);
}
fn updateOcclusion(self: *Self, visible: bool) void {
/// Update the core surface visibility based on both GTK widget and
/// toplevel state. This is public so the window can call it when its
/// suspended state changes.
pub fn updateOcclusion(self: *Self) void {
const surface = self.core() orelse return;
const visible = self.private().mapped and !self.windowSuspended();
surface.occlusionCallback(visible) catch |err| {
log.warn("error in occlusion callback err={}", .{err});
};
}
fn windowSuspended(self: *Self) bool {
const native = self.as(gtk.Widget).getNative() orelse return false;
const window = gobject.ext.cast(gtk.Window, native) orelse return false;
return window.isSuspended() != 0;
}
fn glareaRender(
_: *gtk.GLArea,
_: *gdk.GLContext,

View File

@@ -1374,11 +1374,33 @@ pub const Window = extern struct {
}
}
// Notify every displayed surface when the compositor changes the
// Wayland xdg_toplevel suspended state.
_ = gobject.Object.signals.notify.connect(
self.as(gtk.Window),
*Self,
propSuspended,
self,
.{ .detail = "suspended" },
);
// When we are realized we always setup our appearance since this
// calls some winproto functions.
self.syncAppearance();
}
fn propSuspended(
_: *gtk.Window,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
const tab = self.getSelectedTab() orelse return;
const tree = tab.getSurfaceTree() orelse return;
var it = tree.iterator();
while (it.next()) |entry| entry.view.updateOcclusion();
}
fn btnNewTab(_: *adw.SplitButton, self: *Self) callconv(.c) void {
self.performBindingAction(.new_tab);
}