qt/wayland: detach subsurface buffer on hide so inactive tabs don't ghost

All panes' wl_subsurfaces share the top-level QWindow's wl_surface
as their parent (commit ef4df3b8d). When a tab switches, Qt
hides the inactive pane's GhosttySurface widgets but the
subsurfaces stayed attached to the top-level with their last
buffers — so the old tab's terminal pixels showed through wherever
the new tab's content didn't cover them.

Add SubsurfacePresenter::hide() which attaches a NULL buffer to
the child surface (= no contribution to compositor frame). Call it
from QEvent::Hide alongside the existing
ghostty_surface_set_occlusion(false) which already throttles
libghostty's renderer thread.

On the next Show + present, the buffer reattaches and the pane
becomes visible again. ghostty_surface_set_occlusion(true) on
Show re-enables the renderer so it produces that first frame.

Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
Nathan 2026-05-25 13:16:20 -05:00
parent ef4df3b8da
commit 52d4ee4136
3 changed files with 26 additions and 0 deletions

View File

@ -464,6 +464,14 @@ bool GhosttySurface::event(QEvent *e) {
}
} else if (e->type() == QEvent::Hide) {
ghostty_surface_set_occlusion(m_surface, false);
// Detach the subsurface buffer so this pane's last frame
// doesn't ghost on top of whatever the now-active tab is
// showing. The next Show + render reattaches a buffer and
// makes it visible again.
if (m_subsurfacePresenter) {
m_subsurfacePresenter->hide();
forceParentCommit();
}
}
}
return QWidget::event(e);

View File

@ -487,4 +487,14 @@ void SubsurfacePresenter::setPosition(int x, int y) {
wl_display_flush(m_display);
}
void SubsurfacePresenter::hide() {
if (!m_childSurface) return;
// Attach NULL = no buffer. After commit + parent commit, the
// subsurface contributes nothing to the compositor's frame.
// Caller is responsible for forceParentCommit on its side.
wl_surface_attach(m_childSurface, nullptr, 0, 0);
wl_surface_commit(m_childSurface);
wl_display_flush(m_display);
}
} // namespace wayland

View File

@ -134,6 +134,14 @@ public:
// position to apply. No-op if the position hasn't changed.
void setPosition(int x, int y);
// Detach the currently-attached buffer so the subsurface becomes
// invisible. Called when the owning GhosttySurface hides (tab
// switch) so the inactive pane's pixels don't ghost on top of
// whatever the active tab is showing in the same on-screen
// region. The next presentDmabuf call re-attaches a buffer and
// the subsurface becomes visible again.
void hide();
// Called from the wp_fractional_scale_v1.preferred_scale event.
// Public so the C-style listener struct at file scope in the .cpp
// can name it; not part of the API for other call sites.