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)