From 7340ee70afdd2aa49cab030a4bbce5ae68d23906 Mon Sep 17 00:00:00 2001 From: LFRon Date: Sat, 20 Jun 2026 14:57:53 +0800 Subject: [PATCH] fix: create XWayland wrappers before initial xprop reads caused by #948 Create and match XWayland SurfaceWrapper instances as soon as the surface is associated, then read optional initial X11 properties afterwards. This keeps mapped state, activation, animations, focus and input setup from depending on a best-effort async xprop read. Make WXWayland async property reads independent per request and complete from XCB replies without waiting for PropertyNotify. Keep cancellation scoped by X window so pending reads are discarded when the surface dissociates. Also handle the late-wrapper case by applying the current mapped state when a mapped surface receives its container after the original mapped signal was missed. --- src/core/shellhandler.cpp | 47 ++++--- src/core/shellhandler.h | 6 +- src/surface/surfacewrapper.cpp | 5 + waylib/src/server/protocols/wxwayland.cpp | 164 +++++++++++++--------- 4 files changed, 130 insertions(+), 92 deletions(-) diff --git a/src/core/shellhandler.cpp b/src/core/shellhandler.cpp index 72521e125..37e0ed3d1 100644 --- a/src/core/shellhandler.cpp +++ b/src/core/shellhandler.cpp @@ -603,8 +603,10 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface) self->m_pendingAppIdResolveToplevels.indexOf(raw); if (idx < 0) return; // removed before callback - self->fetchInitialProperties(raw, appId); self->m_pendingAppIdResolveToplevels.removeAt(idx); + self->ensureXwaylandWrapper(raw, appId); + if (auto current = surface.data()) + self->fetchInitialProperties(current); }); if (started) { qCDebug(lcTlShell) @@ -619,8 +621,10 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface) } } } - // Async path not taken: directly fetch properties then match/create - fetchInitialProperties(raw, QString()); + // Async path not taken: create/match first so the wrapper observes + // the initial map lifecycle, then fetch optional xprops. + ensureXwaylandWrapper(raw, QString()); + fetchInitialProperties(raw); }); surface->safeConnect(&WXWaylandSurface::aboutToDissociate, this, [this, surface] { auto wrapper = m_rootSurfaceContainer->getSurface(surface); @@ -657,41 +661,42 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface) }); } -void ShellHandler::fetchInitialProperties(WXWaylandSurface *surface, const QString &appId) +void ShellHandler::fetchInitialProperties(WXWaylandSurface *surface) { + if (!m_imCandidatePanelManager) + return; + + auto *wrapper = m_rootSurfaceContainer->getSurface(surface); + if (!wrapper || wrapper->isIMCandidatePanel()) + return; + auto *xwayland = surface->xwayland(); - if (!xwayland) { - ensureXwaylandWrapper(surface, appId); + if (!xwayland) return; - } auto windowId = surface->handle()->handle()->window_id; - QVector requests; - if (m_imCandidatePanelManager) { - requests.append({ m_imCandidatePanelManager->imCandidatePanelAtom(), XCB_ATOM_CARDINAL }); - } - - if (requests.isEmpty()) { - ensureXwaylandWrapper(surface, appId); + auto atom = m_imCandidatePanelManager->imCandidatePanelAtom(); + if (atom == XCB_ATOM_NONE) return; - } + + QVector requests; + requests.append({ atom, XCB_ATOM_CARDINAL }); xwayland->readAsyncProperties( windowId, requests, 50, [self = QPointer(this), - surface = QPointer(surface), - appId](xcb_window_t, const QMap &result) { + surface = QPointer(surface)](xcb_window_t, + const QMap &result) { auto *raw = surface.data(); if (!raw || !self) return; - self->onInitialPropertiesReady(raw, appId, result); + self->onInitialPropertiesReady(raw, result); }); } void ShellHandler::onInitialPropertiesReady(WXWaylandSurface *surface, - const QString &appId, const QMap &result) { if (m_imCandidatePanelManager) { @@ -699,8 +704,10 @@ void ShellHandler::onInitialPropertiesReady(WXWaylandSurface *surface, result, m_imCandidatePanelManager->imCandidatePanelAtom()); surface->setProperty("imCandidatePanel", value); + auto *wrapper = m_rootSurfaceContainer->getSurface(surface); + if (wrapper) + m_imCandidatePanelManager->checkAndApplyIMCandidatePanel(wrapper, surface); } - ensureXwaylandWrapper(surface, appId); } void ShellHandler::ensureXwaylandWrapper(WXWaylandSurface *surface, const QString &targetAppId) diff --git a/src/core/shellhandler.h b/src/core/shellhandler.h index 362cb8772..ad8f3a39b 100644 --- a/src/core/shellhandler.h +++ b/src/core/shellhandler.h @@ -162,11 +162,9 @@ private Q_SLOTS: // Unified parent/container update for Xdg & XWayland toplevel wrappers. void updateWrapperContainer(SurfaceWrapper *wrapper, WAYLIB_SERVER_NAMESPACE::WSurface *parentSurface); - // Async X11 property fetch for XWayland surfaces - void fetchInitialProperties(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface, - const QString &appId); + // Async X11 property fetch for XWayland surfaces after wrapper creation. + void fetchInitialProperties(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface); void onInitialPropertiesReady(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface, - const QString &appId, const QMap &result); WAYLIB_SERVER_NAMESPACE::WXdgShell *m_xdgShell = nullptr; diff --git a/src/surface/surfacewrapper.cpp b/src/surface/surfacewrapper.cpp index 54bbd2a96..2334ee0c6 100644 --- a/src/surface/surfacewrapper.cpp +++ b/src/surface/surfacewrapper.cpp @@ -2124,6 +2124,11 @@ void SurfaceWrapper::setHasInitializeContainer(bool value) // m_prelaunchSplash can't get mapped signal createNewOrClose(OPEN_ANIMATION); } + + if (!m_prelaunchSplash && value && surface() && surface()->mapped() + && !m_hasActiveCapability.testFlag(ActiveControlState::MappedOrSplash)) { + onMappedChanged(); + } } void SurfaceWrapper::disableWindowAnimation(bool disable) diff --git a/waylib/src/server/protocols/wxwayland.cpp b/waylib/src/server/protocols/wxwayland.cpp index a1c79b79c..fe012c476 100644 --- a/waylib/src/server/protocols/wxwayland.cpp +++ b/waylib/src/server/protocols/wxwayland.cpp @@ -55,18 +55,22 @@ class Q_DECL_HIDDEN WXWaylandPrivate : public WWrapObjectPrivate // Async property reading struct PerWindowProps { + xcb_window_t windowId = XCB_WINDOW_NONE; QVector requests; QMap results; QVector cookies; + QVector done; std::function &)> callback; - QTimer *timer = nullptr; - bool propNotifySeen = false; + QTimer *timeoutTimer = nullptr; + QTimer *pollTimer = nullptr; }; - QMap asyncProps; + QMap asyncProps; + quint64 nextAsyncPropRequestId = 1; void xcbPollReplies(); - void xcbAsyncTimeoutForWindow(xcb_window_t windowId); + void xcbAsyncTimeoutForRequest(quint64 requestId); + void cleanupAsyncRequest(QMap::iterator it, bool discardPending); W_DECLARE_PUBLIC(WXWayland) @@ -101,28 +105,9 @@ bool xwayland_user_event_handler(wlr_xwayland *xwayland, xcb_generic_event_t *ev auto *d = self->d_func(); - // Trigger async property reading infrastructure if this window is being tracked. + // Progress any pending async property reads while X11 events are flowing. if (!d->asyncProps.isEmpty()) { d->xcbPollReplies(); - auto it = d->asyncProps.find(pe->window); - if (it != d->asyncProps.end()) { - auto &props = it.value(); - props.propNotifySeen = true; - // Resend requests on PROPNOTIFY to get the latest value after the change. - xcb_connection_t *conn = self->xcbConnection(); - props.cookies.clear(); - for (const auto &req : std::as_const(props.requests)) { - auto cookie = xcb_get_property_unchecked(conn, - false, - pe->window, - req.atom, - req.type, - 0, - 1024); - props.cookies.append(cookie); - } - xcb_flush(conn); - } } const auto &list = self->surfaceList(); @@ -477,8 +462,10 @@ void WXWayland::readAsyncProperties( } WXWaylandPrivate::PerWindowProps props; + props.windowId = windowId; props.requests = requests; props.callback = std::move(callback); + props.done.resize(requests.size()); props.cookies.reserve(requests.size()); for (const auto &req : std::as_const(requests)) { @@ -488,15 +475,24 @@ void WXWayland::readAsyncProperties( } xcb_flush(conn); - // Create per-window timer. - props.timer = new QTimer(this); - props.timer->setSingleShot(true); - connect(props.timer, &QTimer::timeout, this, [d, windowId]() { - d->xcbAsyncTimeoutForWindow(windowId); + const quint64 requestId = d->nextAsyncPropRequestId++; + if (d->nextAsyncPropRequestId == 0) + d->nextAsyncPropRequestId = 1; + + props.timeoutTimer = new QTimer(this); + props.timeoutTimer->setSingleShot(true); + connect(props.timeoutTimer, &QTimer::timeout, this, [d, requestId]() { + d->xcbAsyncTimeoutForRequest(requestId); + }); + props.timeoutTimer->start(timeoutMs); + + props.pollTimer = new QTimer(this); + connect(props.pollTimer, &QTimer::timeout, this, [d]() { + d->xcbPollReplies(); }); - props.timer->start(timeoutMs); + props.pollTimer->start(1); - d->asyncProps.insert(windowId, std::move(props)); + d->asyncProps.insert(requestId, std::move(props)); } void WXWaylandPrivate::xcbPollReplies() @@ -508,16 +504,21 @@ void WXWaylandPrivate::xcbPollReplies() xcb_connection_t *conn = q->xcbConnection(); - QList toRemove; + struct CompletedRequest + { + xcb_window_t windowId = XCB_WINDOW_NONE; + QMap results; + std::function &)> callback; + }; + QVector completed; - for (auto it = asyncProps.begin(); it != asyncProps.end(); ++it) { - xcb_window_t windowId = it.key(); + for (auto it = asyncProps.begin(); it != asyncProps.end();) { auto &props = it.value(); bool windowDone = true; for (int i = 0; i < props.cookies.size(); ++i) { - if (props.results.contains(props.requests[i].atom)) - continue; // already got this one + if (props.done[i]) + continue; void *replyPtr = nullptr; xcb_generic_error_t *err = nullptr; @@ -528,6 +529,8 @@ void WXWaylandPrivate::xcbPollReplies() continue; } + props.done[i] = true; + if (ret == 1 && replyPtr) { auto *reply = static_cast(replyPtr); if (reply->type != 0 && reply->value_len > 0) { @@ -541,33 +544,28 @@ void WXWaylandPrivate::xcbPollReplies() } } - // Fire callback when all replies received AND propNotifySeen. - if (windowDone && props.propNotifySeen) { - auto resultCopy = props.results; - props.callback(windowId, resultCopy); - - toRemove.append(windowId); + if (windowDone) { + CompletedRequest request; + request.windowId = props.windowId; + request.results = props.results; + request.callback = std::move(props.callback); + completed.append(std::move(request)); + cleanupAsyncRequest(it, false); + it = asyncProps.erase(it); + } else { + ++it; } } - // Cleanup completed windows. - for (auto windowId : std::as_const(toRemove)) { - auto it = asyncProps.find(windowId); - if (it != asyncProps.end()) { - if (it->timer) { - it->timer->stop(); - delete it->timer; - } - asyncProps.erase(it); - } - } + for (auto &request : completed) + request.callback(request.windowId, request.results); } -void WXWaylandPrivate::xcbAsyncTimeoutForWindow(xcb_window_t windowId) +void WXWaylandPrivate::xcbAsyncTimeoutForRequest(quint64 requestId) { W_Q(WXWayland); - auto it = asyncProps.find(windowId); + auto it = asyncProps.find(requestId); if (it == asyncProps.end()) return; @@ -577,11 +575,12 @@ void WXWaylandPrivate::xcbAsyncTimeoutForWindow(xcb_window_t windowId) if (conn) { // Drain remaining replies. for (int i = 0; i < props.cookies.size(); ++i) { - if (props.results.contains(props.requests[i].atom)) + if (props.done[i]) continue; void *replyPtr = nullptr; xcb_generic_error_t *err = nullptr; int ret = xcb_poll_for_reply64(conn, props.cookies[i].sequence, &replyPtr, &err); + props.done[i] = true; if (ret == 1 && replyPtr) { auto *reply = static_cast(replyPtr); if (reply->type != 0 && reply->value_len > 0) { @@ -595,31 +594,60 @@ void WXWaylandPrivate::xcbAsyncTimeoutForWindow(xcb_window_t windowId) free(replyPtr); if (err) free(err); + if (ret == 0 && props.cookies[i].sequence) + xcb_discard_reply(conn, props.cookies[i].sequence); } } } + auto windowId = props.windowId; auto resultCopy = props.results; - props.callback(windowId, resultCopy); + auto callback = std::move(props.callback); + cleanupAsyncRequest(it, false); + asyncProps.erase(it); + callback(windowId, resultCopy); +} + +void WXWaylandPrivate::cleanupAsyncRequest(QMap::iterator it, + bool discardPending) +{ + W_Q(WXWayland); + auto &props = it.value(); + + if (discardPending) { + auto *conn = q->xcbConnection(); + if (conn) { + for (int i = 0; i < props.cookies.size(); ++i) { + if (!props.done[i] && props.cookies[i].sequence) + xcb_discard_reply(conn, props.cookies[i].sequence); + } + } + } - // Cleanup. - if (props.timer) { - delete props.timer; + if (props.timeoutTimer) { + props.timeoutTimer->stop(); + props.timeoutTimer->deleteLater(); + props.timeoutTimer = nullptr; + } + + if (props.pollTimer) { + props.pollTimer->stop(); + props.pollTimer->deleteLater(); + props.pollTimer = nullptr; } - asyncProps.erase(it); } void WXWayland::cancelAsyncProperties(xcb_window_t windowId) { W_D(WXWayland); - auto it = d->asyncProps.find(windowId); - if (it == d->asyncProps.end()) - return; - - if (it->timer) { - delete it->timer; + for (auto it = d->asyncProps.begin(); it != d->asyncProps.end();) { + if (it.value().windowId == windowId) { + d->cleanupAsyncRequest(it, true); + it = d->asyncProps.erase(it); + } else { + ++it; + } } - d->asyncProps.erase(it); } bool WXWayland::event(QEvent *ev)