-
Notifications
You must be signed in to change notification settings - Fork 43
fix: create XWayland wrappers before initial xprop reads caused by #948 #1031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,20 +55,24 @@ | |
| // Async property reading | ||
| struct PerWindowProps | ||
| { | ||
| xcb_window_t windowId = XCB_WINDOW_NONE; | ||
| QVector<WXWayland::AsyncPropRequest> requests; | ||
| QMap<xcb_atom_t, QByteArray> results; | ||
| QVector<xcb_get_property_cookie_t> cookies; | ||
| QVector<bool> done; | ||
| std::function<void(xcb_window_t, const QMap<xcb_atom_t, QByteArray> &)> callback; | ||
| QTimer *timer = nullptr; | ||
| bool propNotifySeen = false; | ||
| QTimer *timeoutTimer = nullptr; | ||
| QTimer *pollTimer = nullptr; | ||
| }; | ||
|
|
||
| QMap<xcb_window_t, PerWindowProps> asyncProps; | ||
| QMap<quint64, PerWindowProps> asyncProps; | ||
| quint64 nextAsyncPropRequestId = 1; | ||
|
|
||
| void xcbPollReplies(); | ||
| void xcbAsyncTimeoutForWindow(xcb_window_t windowId); | ||
| void xcbAsyncTimeoutForRequest(quint64 requestId); | ||
| void cleanupAsyncRequest(QMap<quint64, PerWindowProps>::iterator it, bool discardPending); | ||
|
|
||
| W_DECLARE_PUBLIC(WXWayland) | ||
|
|
||
| xcb_screen_t *screen = nullptr; | ||
|
|
||
|
|
@@ -101,28 +105,9 @@ | |
|
|
||
| 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 @@ | |
| } | ||
|
|
||
| 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 @@ | |
| } | ||
| 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 @@ | |
|
|
||
| xcb_connection_t *conn = q->xcbConnection(); | ||
|
|
||
| QList<xcb_window_t> toRemove; | ||
| struct CompletedRequest | ||
| { | ||
| xcb_window_t windowId = XCB_WINDOW_NONE; | ||
| QMap<xcb_atom_t, QByteArray> results; | ||
| std::function<void(xcb_window_t, const QMap<xcb_atom_t, QByteArray> &)> callback; | ||
| }; | ||
| QVector<CompletedRequest> 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; | ||
|
Comment on lines
518
to
524
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): In the new |
||
|
|
@@ -528,6 +529,8 @@ | |
| continue; | ||
| } | ||
|
|
||
| props.done[i] = true; | ||
|
|
||
| if (ret == 1 && replyPtr) { | ||
| auto *reply = static_cast<xcb_get_property_reply_t *>(replyPtr); | ||
| if (reply->type != 0 && reply->value_len > 0) { | ||
|
|
@@ -541,33 +544,28 @@ | |
| } | ||
| } | ||
|
|
||
| // 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 @@ | |
| 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<xcb_get_property_reply_t *>(replyPtr); | ||
| if (reply->type != 0 && reply->value_len > 0) { | ||
|
|
@@ -595,31 +594,60 @@ | |
| free(replyPtr); | ||
| if (err) | ||
| free(err); | ||
| if (ret == 0 && props.cookies[i].sequence) | ||
| xcb_discard_reply(conn, props.cookies[i].sequence); | ||
|
sourcery-ai[bot] marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| 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<quint64, PerWindowProps>::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) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (complexity): Consider simplifying the async property infrastructure by reusing window-based keys, centralizing polling and timeouts, and encoding completion in cookies instead of extra state and helper functions.
You can keep the new robustness while simplifying a few key areas. The main opportunities are:
xcb_window_tas the primary key, and track multiple requests per window inside.pollTimers.donevector by encoding completion incookies.cleanupAsyncRequestindirection.Below are concrete, incremental changes that keep your current behavior (timeouts, discard of pending replies, etc.) but reduce moving parts.
1. Go back to
QMap<xcb_window_t, PerWindowProps>, allow multiple requests per window internallyInstead of
QMap<quint64, PerWindowProps>and a syntheticrequestId, you can keep the map keyed by window and add an inner per-request structure if you really need multiple concurrent sessions per window:Then
cancelAsyncProperties(windowId)stays a simplefind(windowId):This keeps multi-session support without global
requestIdbookkeeping or iterator invalidation issues across aQMap<quint64, …>.2. Replace per-request
pollTimerwith a single shared poll timerInstead of creating one
pollTimerper request, keep a single timer inWXWaylandPrivatethat drivesxcbPollReplies()as long as there are pending sessions:Start/stop it based on
asyncPropsemptiness:And in
xcbPollReplies():This removes all per-request
pollTimerallocation and the need to manage them incleanupAsyncRequest.3. Replace
QVector<bool> donewith “cookie cleared” as completion flagYou can drop the
donevector by treating a cleared cookie (sequence == 0) as “done”. This keeps the same semantics but avoids parallel arrays.When you successfully poll or decide a cookie is finished (including timeout/discard), clear its sequence:
Use in
xcbPollReplies():Completion check becomes:
Timeout handling uses the same mechanism and calls
xcb_discard_replybefore clearing:This preserves all your new behavior (including discarding unconsumed replies) without the additional
donevector and its synchronization cost.4. Simplify cleanup, avoid
cleanupAsyncRequestas a separate conceptIf you:
xcb_window_t,pollTimer,cookies,then
cleanupAsyncRequestcan be inlined into the point where you decide a session is done:Timeouts can be handled similarly via a single timer that checks deadlines per session instead of maintaining a
timeoutTimerper request. If you want to keep a per-request timeout, you can store the expiry time inAsyncPropSessioninstead of spawning anotherQTimer.These changes preserve the functional improvements you added (timeouts, discard of pending replies, more robust async handling) while:
requestIdindirection,QVector<bool> done,