Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/surface/surfacewrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,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)
Expand Down Expand Up @@ -537,6 +538,10 @@ void SurfaceWrapper::setup()
&WXWaylandSurface::belowChanged,
this,
&SurfaceWrapper::updateXWaylandStackingState);
connect(xwaylandSurface,
&WXWaylandSurface::x11MapCompleted,
this,
&SurfaceWrapper::syncPrelaunchXWaylandStacking);
updateX11SkipFlags();
updateXWaylandStackingState();
}
Expand Down Expand Up @@ -606,6 +611,12 @@ void SurfaceWrapper::setActivate(bool activate)
Q_ASSERT(!activate || hasActiveCapability());
m_isActivated = activate;

// Retry a deferred prelaunch X11 stack sync on activation (not only from the
// splash handoff): a map that happened while deactivated is repaired here,
// mirroring the raising-activation policy of Helper::setActivatedSurface.
if (activate)
syncPrelaunchXWaylandStacking();

if (m_attention && m_isActivated)
setAttention(false);

Expand Down Expand Up @@ -805,6 +816,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)) {
Expand Down Expand Up @@ -833,6 +846,60 @@ 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<WXWaylandSurface *>(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<WXWaylandSurface *>(m_shellSurface);
if (m_wrapperAboutToRemove || m_isProxy || m_type != Type::XWayland || !xwaylandSurface
|| xwaylandSurface->isBypassManager()) {
m_pendingPrelaunchXWaylandStackSync = false;
return;
}

if (!m_isActivated || !xwaylandSurface->isX11Mapped())
return;

// Clear before restacking so a re-entrant activation during the child
// reorder cannot repeat the restack.
m_pendingPrelaunchXWaylandStackSync = false;

// 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, mirroring the stacking
// direction updateXWaylandStackingState() gives to _NET_WM_STATE_ABOVE/BELOW windows.
if (!xwaylandSurface->isAbove() && xwaylandSurface->isBelow()) {
stackToFirst();
xwaylandSurface->restack(nullptr, WXWaylandSurface::XCB_STACK_MODE_BELOW);
} else {
stackToLast();
xwaylandSurface->restack(nullptr, WXWaylandSurface::XCB_STACK_MODE_ABOVE);
}
qCDebug(lcTlSurface)
<< "Synchronized active prelaunch XWayland stacking after X11 map for" << appId();
}

WSurface *SurfaceWrapper::surface() const
{
if (!m_shellSurface)
Expand Down Expand Up @@ -2701,10 +2768,19 @@ void SurfaceWrapper::updateXWaylandStackingState()
const bool below = !above && xwaylandSurface->isBelow();
setAlwaysOnBottom(below);
setAlwaysOnTop(above);

// Guards are mandatory: wlroots only puts a surface into its stack list
// after the map handler, and restack() asserts the window is not
// override-redirect.
const bool canRestack = xwaylandSurface->isX11Mapped() && !xwaylandSurface->isBypassManager();
if (above) {
stackToLast();
if (canRestack)
xwaylandSurface->restack(nullptr, WXWaylandSurface::XCB_STACK_MODE_ABOVE);
} else if (below) {
stackToFirst();
if (canRestack)
xwaylandSurface->restack(nullptr, WXWaylandSurface::XCB_STACK_MODE_BELOW);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/surface/surfacewrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,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);
Expand Down Expand Up @@ -592,6 +594,7 @@ public Q_SLOTS:

bool m_socketEnabled{ false };
bool m_windowAnimationEnabled{ true };
bool m_pendingPrelaunchXWaylandStackSync{ false };
const QString m_appId;
};

Expand Down
2 changes: 2 additions & 0 deletions waylib/src/server/protocols/private/wxwaylandsurface_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,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
Expand Down
44 changes: 40 additions & 4 deletions waylib/src/server/protocols/wxwayland.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -89,17 +90,38 @@ 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<const xcb_property_notify_event_t *>(event);
auto *self = WXWayland::fromHandle(xwayland);

if (!self)
return false;

auto *d = self->d_func();

// XSendEvent can forge MAP_NOTIFY/UNMAP_NOTIFY to the root window
// (SubstructureNotifyMask). Only genuine server notifications may drive
// the X11 map state machine; wlroots still processes the event below.
const bool synthetic = (event->response_type & 0x80) != 0;

// This handler runs before wlroots handles the event; the queued completion
// in WXWaylandSurface::setX11Mapped therefore lands after wlroots restacked
// the newly mapped window below. Do not make that signal synchronous.
if (!synthetic && response_type == XCB_MAP_NOTIFY) {
auto *me = reinterpret_cast<const xcb_map_notify_event_t *>(event);
d->setX11Mapped(me->window, true);
return false;
}

if (!synthetic && response_type == XCB_UNMAP_NOTIFY) {
auto *ue = reinterpret_cast<const xcb_unmap_notify_event_t *>(event);
d->setX11Mapped(ue->window, false);
return false;
}

if (response_type != XCB_PROPERTY_NOTIFY)
return false;

auto *pe = reinterpret_cast<const xcb_property_notify_event_t *>(event);

// Trigger async property reading infrastructure if this window is being tracked.
if (!d->asyncProps.isEmpty()) {
d->xcbPollReplies();
Expand Down Expand Up @@ -139,6 +161,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<WXWaylandSurface> guard(surface);
if (!guard)
continue;
if (guard->handle()->window_id != windowId)
continue;

guard->setX11Mapped(mapped);
break;
}
}

void WXWaylandPrivate::init()
{
W_Q(WXWayland);
Expand Down
37 changes: 37 additions & 0 deletions waylib/src/server/protocols/wxwaylandsurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -519,6 +520,12 @@ bool WXWaylandSurface::isBelow() const
return d->handle()->below;
}

bool WXWaylandSurface::isX11Mapped() const
{
W_DC(WXWaylandSurface);
return d->x11Mapped;
}

bool WXWaylandSurface::isModal() const
{
W_DC(WXWaylandSurface);
Expand Down Expand Up @@ -662,4 +669,34 @@ void WXWaylandSurface::restack(WXWaylandSurface *sibling, StackMode mode)
wlr_xwayland_surface_restack(handle(), nullptr, static_cast<xcb_stack_mode_t>(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<WXWaylandSurface> 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
10 changes: 10 additions & 0 deletions waylib/src/server/protocols/wxwaylandsurface.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
WAYLIB_SERVER_BEGIN_NAMESPACE

class WXWayland;
class WXWaylandPrivate;
class WSeat;
class WXWaylandSurfacePrivate;
class WAYLIB_SERVER_EXPORT WXWaylandSurface : public WToplevelSurface
Expand Down Expand Up @@ -115,6 +116,7 @@ class WAYLIB_SERVER_EXPORT WXWaylandSurface : public WToplevelSurface
bool isBypassManager() const;
bool isAbove() const;
bool isBelow() const;
bool isX11Mapped() const;
bool isModal() const;
WindowTypes windowTypes() const;
DecorationsFlags decorationsFlags() const;
Expand All @@ -136,6 +138,10 @@ public Q_SLOTS:
void associated();
// Emitted before WXWaylandSurfacePrivate handles notify_dissociate cleanup.
void aboutToDissociate();
// Emitted asynchronously (queued) after wlroots has handled XCB_MAP_NOTIFY,
// and only while the X11 window is still mapped. Assumes the Wayland event
// loop runs on the single Qt main thread (see WServerPrivate).
void x11MapCompleted();

void parentXWaylandSurfaceChanged();
void childrenChanged();
Expand All @@ -150,6 +156,10 @@ public Q_SLOTS:

void requestConfigure(QRect geometry, ConfigureFlags flags);
void requestActivate();

private:
friend class WXWaylandPrivate;
void setX11Mapped(bool mapped);
};

WAYLIB_SERVER_END_NAMESPACE
Expand Down
Loading