Skip to content

Commit 65e1853

Browse files
committed
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.
1 parent 4ef6c19 commit 65e1853

4 files changed

Lines changed: 130 additions & 92 deletions

File tree

src/core/shellhandler.cpp

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -603,8 +603,10 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface)
603603
self->m_pendingAppIdResolveToplevels.indexOf(raw);
604604
if (idx < 0)
605605
return; // removed before callback
606-
self->fetchInitialProperties(raw, appId);
607606
self->m_pendingAppIdResolveToplevels.removeAt(idx);
607+
self->ensureXwaylandWrapper(raw, appId);
608+
if (auto current = surface.data())
609+
self->fetchInitialProperties(current);
608610
});
609611
if (started) {
610612
qCDebug(lcTlShell)
@@ -619,8 +621,10 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface)
619621
}
620622
}
621623
}
622-
// Async path not taken: directly fetch properties then match/create
623-
fetchInitialProperties(raw, QString());
624+
// Async path not taken: create/match first so the wrapper observes
625+
// the initial map lifecycle, then fetch optional xprops.
626+
ensureXwaylandWrapper(raw, QString());
627+
fetchInitialProperties(raw);
624628
});
625629
surface->safeConnect(&WXWaylandSurface::aboutToDissociate, this, [this, surface] {
626630
auto wrapper = m_rootSurfaceContainer->getSurface(surface);
@@ -657,50 +661,53 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface)
657661
});
658662
}
659663

660-
void ShellHandler::fetchInitialProperties(WXWaylandSurface *surface, const QString &appId)
664+
void ShellHandler::fetchInitialProperties(WXWaylandSurface *surface)
661665
{
666+
if (!m_imCandidatePanelManager)
667+
return;
668+
669+
auto *wrapper = m_rootSurfaceContainer->getSurface(surface);
670+
if (!wrapper || wrapper->isIMCandidatePanel())
671+
return;
672+
662673
auto *xwayland = surface->xwayland();
663-
if (!xwayland) {
664-
ensureXwaylandWrapper(surface, appId);
674+
if (!xwayland)
665675
return;
666-
}
667676

668677
auto windowId = surface->handle()->handle()->window_id;
669-
QVector<WXWayland::AsyncPropRequest> requests;
670-
if (m_imCandidatePanelManager) {
671-
requests.append({ m_imCandidatePanelManager->imCandidatePanelAtom(), XCB_ATOM_CARDINAL });
672-
}
673-
674-
if (requests.isEmpty()) {
675-
ensureXwaylandWrapper(surface, appId);
678+
auto atom = m_imCandidatePanelManager->imCandidatePanelAtom();
679+
if (atom == XCB_ATOM_NONE)
676680
return;
677-
}
681+
682+
QVector<WXWayland::AsyncPropRequest> requests;
683+
requests.append({ atom, XCB_ATOM_CARDINAL });
678684

679685
xwayland->readAsyncProperties(
680686
windowId,
681687
requests,
682688
50,
683689
[self = QPointer<ShellHandler>(this),
684-
surface = QPointer<WXWaylandSurface>(surface),
685-
appId](xcb_window_t, const QMap<xcb_atom_t, QByteArray> &result) {
690+
surface = QPointer<WXWaylandSurface>(surface)](xcb_window_t,
691+
const QMap<xcb_atom_t, QByteArray> &result) {
686692
auto *raw = surface.data();
687693
if (!raw || !self)
688694
return;
689-
self->onInitialPropertiesReady(raw, appId, result);
695+
self->onInitialPropertiesReady(raw, result);
690696
});
691697
}
692698

693699
void ShellHandler::onInitialPropertiesReady(WXWaylandSurface *surface,
694-
const QString &appId,
695700
const QMap<xcb_atom_t, QByteArray> &result)
696701
{
697702
if (m_imCandidatePanelManager) {
698703
bool value = IMCandidatePanelManager::parseIMCandidatePanelProperty(
699704
result,
700705
m_imCandidatePanelManager->imCandidatePanelAtom());
701706
surface->setProperty("imCandidatePanel", value);
707+
auto *wrapper = m_rootSurfaceContainer->getSurface(surface);
708+
if (wrapper)
709+
m_imCandidatePanelManager->checkAndApplyIMCandidatePanel(wrapper, surface);
702710
}
703-
ensureXwaylandWrapper(surface, appId);
704711
}
705712

706713
void ShellHandler::ensureXwaylandWrapper(WXWaylandSurface *surface, const QString &targetAppId)

src/core/shellhandler.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,9 @@ private Q_SLOTS:
162162
// Unified parent/container update for Xdg & XWayland toplevel wrappers.
163163
void updateWrapperContainer(SurfaceWrapper *wrapper,
164164
WAYLIB_SERVER_NAMESPACE::WSurface *parentSurface);
165-
// Async X11 property fetch for XWayland surfaces
166-
void fetchInitialProperties(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface,
167-
const QString &appId);
165+
// Async X11 property fetch for XWayland surfaces after wrapper creation.
166+
void fetchInitialProperties(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface);
168167
void onInitialPropertiesReady(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface,
169-
const QString &appId,
170168
const QMap<xcb_atom_t, QByteArray> &result);
171169

172170
WAYLIB_SERVER_NAMESPACE::WXdgShell *m_xdgShell = nullptr;

src/surface/surfacewrapper.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2114,6 +2114,11 @@ void SurfaceWrapper::setHasInitializeContainer(bool value)
21142114
// m_prelaunchSplash can't get mapped signal
21152115
createNewOrClose(OPEN_ANIMATION);
21162116
}
2117+
2118+
if (!m_prelaunchSplash && value && surface() && surface()->mapped()
2119+
&& !m_hasActiveCapability.testFlag(ActiveControlState::MappedOrSplash)) {
2120+
onMappedChanged();
2121+
}
21172122
}
21182123

21192124
void SurfaceWrapper::disableWindowAnimation(bool disable)

waylib/src/server/protocols/wxwayland.cpp

Lines changed: 96 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,22 @@ class Q_DECL_HIDDEN WXWaylandPrivate : public WWrapObjectPrivate
5555
// Async property reading
5656
struct PerWindowProps
5757
{
58+
xcb_window_t windowId = XCB_WINDOW_NONE;
5859
QVector<WXWayland::AsyncPropRequest> requests;
5960
QMap<xcb_atom_t, QByteArray> results;
6061
QVector<xcb_get_property_cookie_t> cookies;
62+
QVector<bool> done;
6163
std::function<void(xcb_window_t, const QMap<xcb_atom_t, QByteArray> &)> callback;
62-
QTimer *timer = nullptr;
63-
bool propNotifySeen = false;
64+
QTimer *timeoutTimer = nullptr;
65+
QTimer *pollTimer = nullptr;
6466
};
6567

66-
QMap<xcb_window_t, PerWindowProps> asyncProps;
68+
QMap<quint64, PerWindowProps> asyncProps;
69+
quint64 nextAsyncPropRequestId = 1;
6770

6871
void xcbPollReplies();
69-
void xcbAsyncTimeoutForWindow(xcb_window_t windowId);
72+
void xcbAsyncTimeoutForRequest(quint64 requestId);
73+
void cleanupAsyncRequest(QMap<quint64, PerWindowProps>::iterator it, bool discardPending);
7074

7175
W_DECLARE_PUBLIC(WXWayland)
7276

@@ -101,28 +105,9 @@ bool xwayland_user_event_handler(wlr_xwayland *xwayland, xcb_generic_event_t *ev
101105

102106
auto *d = self->d_func();
103107

104-
// Trigger async property reading infrastructure if this window is being tracked.
108+
// Progress any pending async property reads while X11 events are flowing.
105109
if (!d->asyncProps.isEmpty()) {
106110
d->xcbPollReplies();
107-
auto it = d->asyncProps.find(pe->window);
108-
if (it != d->asyncProps.end()) {
109-
auto &props = it.value();
110-
props.propNotifySeen = true;
111-
// Resend requests on PROPNOTIFY to get the latest value after the change.
112-
xcb_connection_t *conn = self->xcbConnection();
113-
props.cookies.clear();
114-
for (const auto &req : std::as_const(props.requests)) {
115-
auto cookie = xcb_get_property_unchecked(conn,
116-
false,
117-
pe->window,
118-
req.atom,
119-
req.type,
120-
0,
121-
1024);
122-
props.cookies.append(cookie);
123-
}
124-
xcb_flush(conn);
125-
}
126111
}
127112

128113
const auto &list = self->surfaceList();
@@ -477,8 +462,10 @@ void WXWayland::readAsyncProperties(
477462
}
478463

479464
WXWaylandPrivate::PerWindowProps props;
465+
props.windowId = windowId;
480466
props.requests = requests;
481467
props.callback = std::move(callback);
468+
props.done.resize(requests.size());
482469

483470
props.cookies.reserve(requests.size());
484471
for (const auto &req : std::as_const(requests)) {
@@ -488,15 +475,24 @@ void WXWayland::readAsyncProperties(
488475
}
489476
xcb_flush(conn);
490477

491-
// Create per-window timer.
492-
props.timer = new QTimer(this);
493-
props.timer->setSingleShot(true);
494-
connect(props.timer, &QTimer::timeout, this, [d, windowId]() {
495-
d->xcbAsyncTimeoutForWindow(windowId);
478+
const quint64 requestId = d->nextAsyncPropRequestId++;
479+
if (d->nextAsyncPropRequestId == 0)
480+
d->nextAsyncPropRequestId = 1;
481+
482+
props.timeoutTimer = new QTimer(this);
483+
props.timeoutTimer->setSingleShot(true);
484+
connect(props.timeoutTimer, &QTimer::timeout, this, [d, requestId]() {
485+
d->xcbAsyncTimeoutForRequest(requestId);
486+
});
487+
props.timeoutTimer->start(timeoutMs);
488+
489+
props.pollTimer = new QTimer(this);
490+
connect(props.pollTimer, &QTimer::timeout, this, [d]() {
491+
d->xcbPollReplies();
496492
});
497-
props.timer->start(timeoutMs);
493+
props.pollTimer->start(1);
498494

499-
d->asyncProps.insert(windowId, std::move(props));
495+
d->asyncProps.insert(requestId, std::move(props));
500496
}
501497

502498
void WXWaylandPrivate::xcbPollReplies()
@@ -508,16 +504,21 @@ void WXWaylandPrivate::xcbPollReplies()
508504

509505
xcb_connection_t *conn = q->xcbConnection();
510506

511-
QList<xcb_window_t> toRemove;
507+
struct CompletedRequest
508+
{
509+
xcb_window_t windowId = XCB_WINDOW_NONE;
510+
QMap<xcb_atom_t, QByteArray> results;
511+
std::function<void(xcb_window_t, const QMap<xcb_atom_t, QByteArray> &)> callback;
512+
};
513+
QVector<CompletedRequest> completed;
512514

513-
for (auto it = asyncProps.begin(); it != asyncProps.end(); ++it) {
514-
xcb_window_t windowId = it.key();
515+
for (auto it = asyncProps.begin(); it != asyncProps.end();) {
515516
auto &props = it.value();
516517

517518
bool windowDone = true;
518519
for (int i = 0; i < props.cookies.size(); ++i) {
519-
if (props.results.contains(props.requests[i].atom))
520-
continue; // already got this one
520+
if (props.done[i])
521+
continue;
521522

522523
void *replyPtr = nullptr;
523524
xcb_generic_error_t *err = nullptr;
@@ -528,6 +529,8 @@ void WXWaylandPrivate::xcbPollReplies()
528529
continue;
529530
}
530531

532+
props.done[i] = true;
533+
531534
if (ret == 1 && replyPtr) {
532535
auto *reply = static_cast<xcb_get_property_reply_t *>(replyPtr);
533536
if (reply->type != 0 && reply->value_len > 0) {
@@ -541,33 +544,28 @@ void WXWaylandPrivate::xcbPollReplies()
541544
}
542545
}
543546

544-
// Fire callback when all replies received AND propNotifySeen.
545-
if (windowDone && props.propNotifySeen) {
546-
auto resultCopy = props.results;
547-
props.callback(windowId, resultCopy);
548-
549-
toRemove.append(windowId);
547+
if (windowDone) {
548+
CompletedRequest request;
549+
request.windowId = props.windowId;
550+
request.results = props.results;
551+
request.callback = std::move(props.callback);
552+
completed.append(std::move(request));
553+
cleanupAsyncRequest(it, false);
554+
it = asyncProps.erase(it);
555+
} else {
556+
++it;
550557
}
551558
}
552559

553-
// Cleanup completed windows.
554-
for (auto windowId : std::as_const(toRemove)) {
555-
auto it = asyncProps.find(windowId);
556-
if (it != asyncProps.end()) {
557-
if (it->timer) {
558-
it->timer->stop();
559-
delete it->timer;
560-
}
561-
asyncProps.erase(it);
562-
}
563-
}
560+
for (auto &request : completed)
561+
request.callback(request.windowId, request.results);
564562
}
565563

566-
void WXWaylandPrivate::xcbAsyncTimeoutForWindow(xcb_window_t windowId)
564+
void WXWaylandPrivate::xcbAsyncTimeoutForRequest(quint64 requestId)
567565
{
568566
W_Q(WXWayland);
569567

570-
auto it = asyncProps.find(windowId);
568+
auto it = asyncProps.find(requestId);
571569
if (it == asyncProps.end())
572570
return;
573571

@@ -577,11 +575,12 @@ void WXWaylandPrivate::xcbAsyncTimeoutForWindow(xcb_window_t windowId)
577575
if (conn) {
578576
// Drain remaining replies.
579577
for (int i = 0; i < props.cookies.size(); ++i) {
580-
if (props.results.contains(props.requests[i].atom))
578+
if (props.done[i])
581579
continue;
582580
void *replyPtr = nullptr;
583581
xcb_generic_error_t *err = nullptr;
584582
int ret = xcb_poll_for_reply64(conn, props.cookies[i].sequence, &replyPtr, &err);
583+
props.done[i] = true;
585584
if (ret == 1 && replyPtr) {
586585
auto *reply = static_cast<xcb_get_property_reply_t *>(replyPtr);
587586
if (reply->type != 0 && reply->value_len > 0) {
@@ -595,31 +594,60 @@ void WXWaylandPrivate::xcbAsyncTimeoutForWindow(xcb_window_t windowId)
595594
free(replyPtr);
596595
if (err)
597596
free(err);
597+
if (ret == 0 && props.cookies[i].sequence)
598+
xcb_discard_reply(conn, props.cookies[i].sequence);
598599
}
599600
}
600601
}
601602

603+
auto windowId = props.windowId;
602604
auto resultCopy = props.results;
603-
props.callback(windowId, resultCopy);
605+
auto callback = std::move(props.callback);
606+
cleanupAsyncRequest(it, false);
607+
asyncProps.erase(it);
608+
callback(windowId, resultCopy);
609+
}
610+
611+
void WXWaylandPrivate::cleanupAsyncRequest(QMap<quint64, PerWindowProps>::iterator it,
612+
bool discardPending)
613+
{
614+
W_Q(WXWayland);
615+
auto &props = it.value();
616+
617+
if (discardPending) {
618+
auto *conn = q->xcbConnection();
619+
if (conn) {
620+
for (int i = 0; i < props.cookies.size(); ++i) {
621+
if (!props.done[i] && props.cookies[i].sequence)
622+
xcb_discard_reply(conn, props.cookies[i].sequence);
623+
}
624+
}
625+
}
604626

605-
// Cleanup.
606-
if (props.timer) {
607-
delete props.timer;
627+
if (props.timeoutTimer) {
628+
props.timeoutTimer->stop();
629+
props.timeoutTimer->deleteLater();
630+
props.timeoutTimer = nullptr;
631+
}
632+
633+
if (props.pollTimer) {
634+
props.pollTimer->stop();
635+
props.pollTimer->deleteLater();
636+
props.pollTimer = nullptr;
608637
}
609-
asyncProps.erase(it);
610638
}
611639

612640
void WXWayland::cancelAsyncProperties(xcb_window_t windowId)
613641
{
614642
W_D(WXWayland);
615-
auto it = d->asyncProps.find(windowId);
616-
if (it == d->asyncProps.end())
617-
return;
618-
619-
if (it->timer) {
620-
delete it->timer;
643+
for (auto it = d->asyncProps.begin(); it != d->asyncProps.end();) {
644+
if (it.value().windowId == windowId) {
645+
d->cleanupAsyncRequest(it, true);
646+
it = d->asyncProps.erase(it);
647+
} else {
648+
++it;
649+
}
621650
}
622-
d->asyncProps.erase(it);
623651
}
624652

625653
bool WXWayland::event(QEvent *ev)

0 commit comments

Comments
 (0)