diff --git a/src/surface/surfacewrapper.cpp b/src/surface/surfacewrapper.cpp index 5dde1367a..3e816b766 100644 --- a/src/surface/surfacewrapper.cpp +++ b/src/surface/surfacewrapper.cpp @@ -193,6 +193,7 @@ void SurfaceWrapper::invalidate() { Q_ASSERT_X(!m_wrapperAboutToRemove, Q_FUNC_INFO, "Can't call `invalidate` twice!"); m_wrapperAboutToRemove = true; + m_pendingPrelaunchXWaylandStackSync = false; Q_EMIT aboutToBeInvalidated(); if (!m_skipDockPreView) @@ -479,6 +480,10 @@ void SurfaceWrapper::setup() updateX11SkipFlags(); updateSizeCapabilities(); }); + connect(xwaylandSurface, + &WXWaylandSurface::x11MapCompleted, + this, + &SurfaceWrapper::syncPrelaunchXWaylandStacking); updateX11SkipFlags(); } // Connect DConfig windowRadius change so QML bindings re-evaluate radius() @@ -546,6 +551,8 @@ void SurfaceWrapper::setActivate(bool activate) Q_ASSERT(!activate || hasActiveCapability()); m_isActivated = activate; + if (activate) + syncPrelaunchXWaylandStacking(); if (m_attention && m_isActivated) setAttention(false); @@ -732,6 +739,8 @@ void SurfaceWrapper::completeSplashTransition(const QSizeF &targetImplicitSize, m_decoration->stackBefore(m_surfaceItem); } + requestPrelaunchXWaylandStackSync(); + m_surfaceItem->setVisible(true); if (m_type == Type::XWayland && (m_surfaceState == State::Maximized || m_surfaceState == State::Fullscreen)) { @@ -760,6 +769,51 @@ void SurfaceWrapper::completeSplashTransition(const QSizeF &targetImplicitSize, updateHasActiveCapability(ActiveControlState::MappedOrSplash, surface() && surface()->mapped()); } +void SurfaceWrapper::requestPrelaunchXWaylandStackSync() +{ + if (m_isProxy || m_type != Type::XWayland) + return; + + auto *xwaylandSurface = qobject_cast(m_shellSurface); + if (!xwaylandSurface || xwaylandSurface->isBypassManager()) + return; + + m_pendingPrelaunchXWaylandStackSync = true; + if (!xwaylandSurface->isX11Mapped()) { + qCDebug(lcTlSurface) + << "Deferring prelaunch XWayland stacking until X11 map completes for" << appId(); + return; + } + + syncPrelaunchXWaylandStacking(); +} + +void SurfaceWrapper::syncPrelaunchXWaylandStacking() +{ + if (!m_pendingPrelaunchXWaylandStackSync) + return; + + auto *xwaylandSurface = qobject_cast(m_shellSurface); + if (m_wrapperAboutToRemove || m_isProxy || m_type != Type::XWayland || !xwaylandSurface + || xwaylandSurface->isBypassManager()) { + m_pendingPrelaunchXWaylandStackSync = false; + return; + } + + if (!m_isActivated || !xwaylandSurface->isX11Mapped()) + return; + + // wlroots initially places a managed XWayland window at the bottom of the native X11 + // stack. A prelaunch wrapper is already activated, so the normal activation path cannot + // observe a wrapper change and raise the newly attached X11 window. Synchronize both + // stacks after wlroots has finished handling XCB_MAP_NOTIFY. + stackToLast(); + xwaylandSurface->restack(nullptr, WXWaylandSurface::XCB_STACK_MODE_ABOVE); + m_pendingPrelaunchXWaylandStackSync = false; + qCDebug(lcTlSurface) + << "Synchronized active prelaunch XWayland stacking after X11 map for" << appId(); +} + WSurface *SurfaceWrapper::surface() const { if (!m_shellSurface) diff --git a/src/surface/surfacewrapper.h b/src/surface/surfacewrapper.h index b443c5827..8e2d450e2 100644 --- a/src/surface/surfacewrapper.h +++ b/src/surface/surfacewrapper.h @@ -435,6 +435,8 @@ public Q_SLOTS: void updateActivateCapability(); void updateFocusCapability(); void completeSplashTransition(const QSizeF &targetImplicitSize, bool hideDecoration = false); + void requestPrelaunchXWaylandStackSync(); + void syncPrelaunchXWaylandStacking(); // wayland set by treeland-dde-shell, x11 set by bypassManager/windowTypes void setSkipDockPreView(bool skip); @@ -518,6 +520,7 @@ public Q_SLOTS: bool m_socketEnabled{ false }; bool m_windowAnimationEnabled{ true }; + bool m_pendingPrelaunchXWaylandStackSync{ false }; const QString m_appId; }; diff --git a/waylib/src/server/protocols/private/wxwaylandsurface_p.h b/waylib/src/server/protocols/private/wxwaylandsurface_p.h index 48dcedec2..4713feb76 100644 --- a/waylib/src/server/protocols/private/wxwaylandsurface_p.h +++ b/waylib/src/server/protocols/private/wxwaylandsurface_p.h @@ -67,6 +67,8 @@ class Q_DECL_HIDDEN WXWaylandSurfacePrivate : public WToplevelSurfacePrivate uint minimized:1; uint fullscreen:1; uint activated:1; + bool x11Mapped = false; + quint64 x11MapGeneration = 0; private: // XWayland owns this handle and destroys it after notifying the diff --git a/waylib/src/server/protocols/wxwayland.cpp b/waylib/src/server/protocols/wxwayland.cpp index 2c1860c9f..ea316193a 100644 --- a/waylib/src/server/protocols/wxwayland.cpp +++ b/waylib/src/server/protocols/wxwayland.cpp @@ -67,6 +67,7 @@ class Q_DECL_HIDDEN WXWaylandPrivate : public WWaylandResourcePrivate void xcbPollReplies(); void xcbAsyncTimeoutForWindow(xcb_window_t windowId); + void setX11Mapped(xcb_window_t windowId, bool mapped); W_DECLARE_PUBLIC(WXWayland) @@ -89,10 +90,6 @@ bool xwayland_user_event_handler(wlr_xwayland *xwayland, xcb_generic_event_t *ev return false; const uint8_t response_type = event->response_type & ~0x80; - if (response_type != XCB_PROPERTY_NOTIFY) - return false; - - auto *pe = reinterpret_cast(event); auto *self = WXWayland::fromHandle(xwayland); if (!self) @@ -100,6 +97,23 @@ bool xwayland_user_event_handler(wlr_xwayland *xwayland, xcb_generic_event_t *ev auto *d = self->d_func(); + if (response_type == XCB_MAP_NOTIFY) { + auto *me = reinterpret_cast(event); + d->setX11Mapped(me->window, true); + return false; + } + + if (response_type == XCB_UNMAP_NOTIFY) { + auto *ue = reinterpret_cast(event); + d->setX11Mapped(ue->window, false); + return false; + } + + if (response_type != XCB_PROPERTY_NOTIFY) + return false; + + auto *pe = reinterpret_cast(event); + // Trigger async property reading infrastructure if this window is being tracked. if (!d->asyncProps.isEmpty()) { d->xcbPollReplies(); @@ -139,6 +153,20 @@ bool xwayland_user_event_handler(wlr_xwayland *xwayland, xcb_generic_event_t *ev return false; } +void WXWaylandPrivate::setX11Mapped(xcb_window_t windowId, bool mapped) +{ + for (auto *surface : std::as_const(surfaceList)) { + QPointer guard(surface); + if (!guard) + continue; + if (guard->handle()->window_id != windowId) + continue; + + guard->setX11Mapped(mapped); + break; + } +} + void WXWaylandPrivate::init() { W_Q(WXWayland); diff --git a/waylib/src/server/protocols/wxwaylandsurface.cpp b/waylib/src/server/protocols/wxwaylandsurface.cpp index f7c190d17..c21233db3 100644 --- a/waylib/src/server/protocols/wxwaylandsurface.cpp +++ b/waylib/src/server/protocols/wxwaylandsurface.cpp @@ -58,6 +58,7 @@ void WXWaylandSurfacePrivate::init() }); q->listeners()->add(&m_handle->events.dissociate, this, [this, q] (void *) { Q_ASSERT(surface); + q->setX11Mapped(false); Q_EMIT q->aboutToDissociate(); delete surface; surface = nullptr; @@ -486,6 +487,12 @@ bool WXWaylandSurface::isBypassManager() const return d->handle()->override_redirect; } +bool WXWaylandSurface::isX11Mapped() const +{ + W_DC(WXWaylandSurface); + return d->x11Mapped; +} + WXWaylandSurface::WindowTypes WXWaylandSurface::windowTypes() const { W_DC(WXWaylandSurface); @@ -605,4 +612,34 @@ void WXWaylandSurface::restack(WXWaylandSurface *sibling, StackMode mode) wlr_xwayland_surface_restack(handle(), nullptr, static_cast(mode)); } +void WXWaylandSurface::setX11Mapped(bool mapped) +{ + W_D(WXWaylandSurface); + + if (d->x11Mapped == mapped) + return; + + d->x11Mapped = mapped; + const quint64 generation = ++d->x11MapGeneration; + if (!mapped) + return; + + // wlroots invokes the user event handler before its own XCB_MAP_NOTIFY handler. + // Defer notification so consumers run after wlroots performs the initial X11 restack. + QPointer guard(this); + QMetaObject::invokeMethod( + this, + [guard, generation] { + if (!guard) + return; + + const auto *d = guard->d_func(); + if (!d->x11Mapped || d->x11MapGeneration != generation) + return; + + Q_EMIT guard->x11MapCompleted(); + }, + Qt::QueuedConnection); +} + WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/protocols/wxwaylandsurface.h b/waylib/src/server/protocols/wxwaylandsurface.h index 211419245..818c64456 100644 --- a/waylib/src/server/protocols/wxwaylandsurface.h +++ b/waylib/src/server/protocols/wxwaylandsurface.h @@ -10,6 +10,7 @@ WAYLIB_SERVER_BEGIN_NAMESPACE class WXWayland; +class WXWaylandPrivate; class WSeat; class WXWaylandSurfacePrivate; class WAYLIB_SERVER_EXPORT WXWaylandSurface : public WToplevelSurface @@ -108,6 +109,7 @@ class WAYLIB_SERVER_EXPORT WXWaylandSurface : public WToplevelSurface ConfigureFlags requestConfigureFlags() const; bool isBypassManager() const; + bool isX11Mapped() const; WindowTypes windowTypes() const; DecorationsFlags decorationsFlags() const; @@ -127,6 +129,8 @@ public Q_SLOTS: void associated(); // Emitted before WXWaylandSurfacePrivate handles notify_dissociate cleanup. void aboutToDissociate(); + // Emitted asynchronously after wlroots finishes handling XCB_MAP_NOTIFY. + void x11MapCompleted(); void parentXWaylandSurfaceChanged(); void childrenChanged(); @@ -139,6 +143,10 @@ public Q_SLOTS: void requestConfigure(QRect geometry, ConfigureFlags flags); void requestActivate(); + +private: + friend class WXWaylandPrivate; + void setX11Mapped(bool mapped); }; WAYLIB_SERVER_END_NAMESPACE