From 52d4ee4136f5118e90f55bb3654a19d8a751d74f Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 25 May 2026 13:16:20 -0500 Subject: [PATCH] qt/wayland: detach subsurface buffer on hide so inactive tabs don't ghost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- qt/src/GhosttySurface.cpp | 8 ++++++++ qt/src/wayland/SubsurfacePresenter.cpp | 10 ++++++++++ qt/src/wayland/SubsurfacePresenter.h | 8 ++++++++ 3 files changed, 26 insertions(+) diff --git a/qt/src/GhosttySurface.cpp b/qt/src/GhosttySurface.cpp index c4635a9fe..5c8892b28 100644 --- a/qt/src/GhosttySurface.cpp +++ b/qt/src/GhosttySurface.cpp @@ -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); diff --git a/qt/src/wayland/SubsurfacePresenter.cpp b/qt/src/wayland/SubsurfacePresenter.cpp index bb8c4f5e4..d2a9c6427 100644 --- a/qt/src/wayland/SubsurfacePresenter.cpp +++ b/qt/src/wayland/SubsurfacePresenter.cpp @@ -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 diff --git a/qt/src/wayland/SubsurfacePresenter.h b/qt/src/wayland/SubsurfacePresenter.h index 50e3b0f00..c94d1642b 100644 --- a/qt/src/wayland/SubsurfacePresenter.h +++ b/qt/src/wayland/SubsurfacePresenter.h @@ -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.