Skip to content

fix: create XWayland wrappers before initial xprop reads caused by #948#1031

Open
LFRon wants to merge 1 commit into
linuxdeepin:masterfrom
LFRon:fix-issue-with-948
Open

fix: create XWayland wrappers before initial xprop reads caused by #948#1031
LFRon wants to merge 1 commit into
linuxdeepin:masterfrom
LFRon:fix-issue-with-948

Conversation

@LFRon

@LFRon LFRon commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

该PR用于修复 #948 引起的:

  1. Xwayland运行的QQ主窗口打开是无动画
  2. Xwayland运行的QQ有概率无法响应鼠标点击事件
  3. Xwayland运行的QQ查看图片时查看图片的窗口无法显示
  4. Xwayland运行的全屏3A大作 (例如JDM:漂移大师) 不仅无法响应键盘点击, 连鼠标光标都无法显示的问题

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.

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: LFRon

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-ci-robot

Copy link
Copy Markdown

Hi @LFRon. Thanks for your PR.

I'm waiting for a linuxdeepin member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@sourcery-ai

sourcery-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors WXWayland async X11 property reading to be per-request and independent of PropertyNotify, adds robust cancellation and polling, and reorders ShellHandler so XWayland SurfaceWrapper instances are created before optional initial xprop reads—plus ensures late-created wrappers apply mapped state correctly.

Sequence diagram for per-request async X11 property reading in WXWayland

sequenceDiagram
    participant ShellHandler
    participant WXWayland
    participant WXWaylandPrivate as WXWaylandPrivate
    participant Xcb as XCB

    ShellHandler->>WXWayland: readAsyncProperties(windowId, requests, timeoutMs, callback)
    WXWayland->>WXWaylandPrivate: store PerWindowProps(windowId, requests)
    WXWaylandPrivate->>Xcb: xcb_get_property_unchecked(...)
    Xcb-->>WXWaylandPrivate: cookies
    WXWaylandPrivate->>WXWaylandPrivate: start timeoutTimer
    WXWaylandPrivate->>WXWaylandPrivate: start pollTimer

    loop poll
        WXWaylandPrivate->>WXWaylandPrivate: xcbPollReplies()
        WXWaylandPrivate->>Xcb: xcb_poll_for_reply64(cookie)
        Xcb-->>WXWaylandPrivate: reply
        WXWaylandPrivate->>WXWaylandPrivate: mark done[i], store results
        alt all requests done
            WXWaylandPrivate->>WXWaylandPrivate: cleanupAsyncRequest(it, false)
            WXWaylandPrivate-->>ShellHandler: callback(windowId, results)
        end
    end

    alt timeout fires
        WXWaylandPrivate->>WXWaylandPrivate: xcbAsyncTimeoutForRequest(requestId)
        WXWaylandPrivate->>Xcb: xcb_poll_for_reply64 / xcb_discard_reply
        WXWaylandPrivate->>WXWaylandPrivate: cleanupAsyncRequest(it, false)
        WXWaylandPrivate-->>ShellHandler: callback(windowId, results)
    end

    opt cancel from windowId
        ShellHandler->>WXWayland: cancelAsyncProperties(windowId)
        WXWayland->>WXWaylandPrivate: cleanupAsyncRequest(it, true)
        WXWaylandPrivate->>Xcb: xcb_discard_reply(sequence)
    end
Loading

Sequence diagram for XWayland SurfaceWrapper creation and initial property handling

sequenceDiagram
    participant WXWaylandSurface as WXWaylandSurface
    participant ShellHandler
    participant RootContainer as RootSurfaceContainer
    participant Wrapper as SurfaceWrapper
    participant IMMgr as IMCandidatePanelManager

    WXWaylandSurface-->>ShellHandler: onXWaylandSurfaceAdded(surface)
    ShellHandler->>ShellHandler: ensureXwaylandWrapper(surface, appId)
    ShellHandler->>RootContainer: getSurface(surface)
    RootContainer-->>ShellHandler: wrapper
    ShellHandler->>ShellHandler: fetchInitialProperties(surface)
    ShellHandler->>WXWaylandSurface: xwayland()
    ShellHandler->>WXWaylandSurface: handle()->handle()->window_id
    ShellHandler->>IMMgr: imCandidatePanelAtom()
    ShellHandler->>WXWaylandSurface: xwayland()->readAsyncProperties(..., callback)

    WXWaylandSurface-->>ShellHandler: onInitialPropertiesReady(surface, result)
    ShellHandler->>IMMgr: isIMCandidatePanel(result, imCandidatePanelAtom)
    ShellHandler->>WXWaylandSurface: setProperty(imCandidatePanel)
    ShellHandler->>RootContainer: getSurface(surface)
    RootContainer-->>ShellHandler: wrapper
    ShellHandler->>IMMgr: checkAndApplyIMCandidatePanel(wrapper, surface)

    note over Wrapper: Late-wrapper mapped handling
    Wrapper->>Wrapper: setHasInitializeContainer(true)
    Wrapper->>Wrapper: onMappedChanged()
Loading

File-Level Changes

Change Details Files
Make async X11 property reads per-request, decoupled from PropertyNotify, with explicit polling, timeouts, and cancellation by X window.
  • Track async property reads in WXWaylandPrivate using a request id key, storing window id, request list, cookies, per-request completion flags, and callback.
  • Replace single per-window timer with separate timeout and short-interval poll timers to drive xcbPollReplies and enforce timeouts.
  • Have xcbPollReplies iterate over all pending requests, mark individual cookies as done, accumulate results, and complete callbacks as soon as all replies for a request are received.
  • Implement xcbAsyncTimeoutForRequest to drain or discard remaining replies on timeout, then invoke the callback with whatever results were obtained.
  • Add cleanupAsyncRequest helper to stop/delete timers and optionally discard pending XCB replies, and use it from normal completion, timeout, and cancelAsyncProperties (iterating by window id).
waylib/src/server/protocols/wxwayland.cpp
Ensure XWayland SurfaceWrapper is created/matched before optional initial X11 property fetches and simplify property flow for IM candidate panels.
  • Change onXWaylandSurfaceAdded so that both async and sync appId resolution paths call ensureXwaylandWrapper before issuing initial async property reads.
  • Refactor fetchInitialProperties to no longer take appId, early-out if the wrapper is missing or already an IM candidate panel, and only read the IM candidate panel atom when available.
  • Update onInitialPropertiesReady to drop appId, set the imCandidatePanel property on the surface, and immediately apply IM candidate panel state to the associated wrapper if present.
src/core/shellhandler.cpp
src/core/shellhandler.h
Handle late container initialization for already-mapped SurfaceWrapper by applying mapped state once the container is initialized.
  • In SurfaceWrapper::setHasInitializeContainer, when marking container as initialized for non-prelaunch-splash surfaces that are already mapped but not yet marked as MappedOrSplash, invoke onMappedChanged to apply mapped state and related behavior.
src/surface/surfacewrapper.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The new per-request pollTimer running every 1ms for each async property read could be quite heavy under load; consider using a shared timer or integrating xcbPollReplies() into an existing event loop tick to avoid a proliferation of high-frequency timers.
  • cleanupAsyncRequest uses deleteLater() for timers, which assumes an active event loop and object lifetime; if this can be called during teardown (e.g., in destructors), consider using direct deletion or guarding against late timer invocations to avoid leaks or use-after-free issues.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new per-request `pollTimer` running every 1ms for each async property read could be quite heavy under load; consider using a shared timer or integrating `xcbPollReplies()` into an existing event loop tick to avoid a proliferation of high-frequency timers.
- `cleanupAsyncRequest` uses `deleteLater()` for timers, which assumes an active event loop and object lifetime; if this can be called during teardown (e.g., in destructors), consider using direct deletion or guarding against late timer invocations to avoid leaks or use-after-free issues.

## Individual Comments

### Comment 1
<location path="waylib/src/server/protocols/wxwayland.cpp" line_range="598-604" />
<code_context>
+    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))
</code_context>
<issue_to_address>
**issue (bug_risk):** `windowDone` never becomes false when replies are not ready, causing premature completion.

In the new `xcbPollReplies` loop, `windowDone` stays `true` even when `xcb_poll_for_reply64` returns 0 or an error, and `props.done[i]` is set unconditionally. This means a failed or pending poll can still mark the whole window as complete. Only set `props.done[i]` and keep `windowDone` as `true` when `ret == 1`, and set `windowDone = false` when `ret == 0` or on error so incomplete properties don’t prematurely complete the request.
</issue_to_address>

### Comment 2
<location path="waylib/src/server/protocols/wxwayland.cpp" line_range="677-678" />
<code_context>
                     free(replyPtr);
                 if (err)
                     free(err);
+                if (ret == 0 && props.cookies[i].sequence)
+                    xcb_discard_reply(conn, props.cookies[i].sequence);
             }
         }
</code_context>
<issue_to_address>
**issue (bug_risk):** The `xcb_discard_reply` call is currently unreachable due to being nested inside `if (ret == 1 && replyPtr)`.

Because this logic is inside the `if (ret == 1 && replyPtr)` block in `xcbAsyncTimeoutForRequest`, the `if (ret == 0 && props.cookies[i].sequence)` branch can never run. If the goal is to discard unretrieved replies when `ret == 0`, this check should be moved outside the `ret == 1` block and run whenever `ret == 0` with a valid sequence; otherwise we neither keep polling nor discard, leaving replies in the XCB queue.
</issue_to_address>

### Comment 3
<location path="waylib/src/server/protocols/wxwayland.cpp" line_range="147" />
<code_context>
+        QTimer *pollTimer = nullptr;
     };

-    QMap<xcb_window_t, PerWindowProps> asyncProps;
+    QMap<quint64, PerWindowProps> asyncProps;
+    quint64 nextAsyncPropRequestId = 1;
</code_context>
<issue_to_address>
**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:

1. **Use `xcb_window_t` as the primary key, and track multiple requests per window inside.**
2. **Use a single shared poll timer instead of per-request `pollTimer`s.**
3. **Drop the parallel `done` vector by encoding completion in `cookies`.**
4. **Simplify cleanup by removing `cleanupAsyncRequest` indirection.**

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 internally

Instead of `QMap<quint64, PerWindowProps>` and a synthetic `requestId`, you can keep the map keyed by window and add an inner per-request structure if you really need multiple concurrent sessions per window:

```cpp
struct AsyncPropSession
{
    QVector<WXWayland::AsyncPropRequest> requests;
    QMap<xcb_atom_t, QByteArray> results;
    QVector<xcb_get_property_cookie_t> cookies;
    std::function<void(xcb_window_t, const QMap<xcb_atom_t, QByteArray> &)> callback;
    QElapsedTimer deadline; // or qint64 deadlineMs
};

struct PerWindowProps
{
    QVector<AsyncPropSession> sessions;
};

QMap<xcb_window_t, PerWindowProps> asyncProps;
```

Then `cancelAsyncProperties(windowId)` stays a simple `find(windowId)`:

```cpp
void WXWayland::cancelAsyncProperties(xcb_window_t windowId)
{
    W_D(WXWayland);
    auto it = d->asyncProps.find(windowId);
    if (it == d->asyncProps.end())
        return;

    auto *conn = xcbConnection();
    if (conn) {
        for (auto &session : it->sessions) {
            for (const auto &cookie : session.cookies) {
                if (cookie.sequence)
                    xcb_discard_reply(conn, cookie.sequence);
            }
        }
    }
    d->asyncProps.erase(it);
}
```

This keeps multi-session support without global `requestId` bookkeeping or iterator invalidation issues across a `QMap<quint64, …>`.

---

### 2. Replace per-request `pollTimer` with a single shared poll timer

Instead of creating one `pollTimer` per request, keep a single timer in `WXWaylandPrivate` that drives `xcbPollReplies()` as long as there are pending sessions:

```cpp
// In WXWaylandPrivate
QTimer *pollTimer = nullptr;

// init once
WXWaylandPrivate::WXWaylandPrivate()
{
    pollTimer = new QTimer(q);
    connect(pollTimer, &QTimer::timeout, q, [this]() { xcbPollReplies(); });
    pollTimer->setInterval(1); // or a slightly larger value if acceptable
    pollTimer->setSingleShot(false);
}
```

Start/stop it based on `asyncProps` emptiness:

```cpp
void WXWayland::readWindowPropertiesAsync(...)
{
    W_D(WXWayland);
    // ... set up session ...
    d->asyncProps[windowId].sessions.append(std::move(session));

    if (!d->pollTimer->isActive())
        d->pollTimer->start();
}
```

And in `xcbPollReplies()`:

```cpp
void WXWaylandPrivate::xcbPollReplies()
{
    if (asyncProps.isEmpty()) {
        pollTimer->stop();
        return;
    }

    // ... iterate windows/sessions, read replies ...

    if (asyncProps.isEmpty())
        pollTimer->stop();
}
```

This removes all per-request `pollTimer` allocation and the need to manage them in `cleanupAsyncRequest`.

---

### 3. Replace `QVector<bool> done` with “cookie cleared” as completion flag

You can drop the `done` vector 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:

```cpp
// helper
inline bool isCookiePending(const xcb_get_property_cookie_t &cookie)
{
    return cookie.sequence != 0;
}

inline void markCookieDone(xcb_get_property_cookie_t &cookie)
{
    cookie.sequence = 0;
}
```

Use in `xcbPollReplies()`:

```cpp
for (int i = 0; i < session.cookies.size(); ++i) {
    auto &cookie = session.cookies[i];
    if (!isCookiePending(cookie))
        continue;

    void *replyPtr = nullptr;
    xcb_generic_error_t *err = nullptr;
    int ret = xcb_poll_for_reply64(conn, cookie.sequence, &replyPtr, &err);

    if (ret == 0) {
        windowDone = false;
        continue;
    }

    markCookieDone(cookie);

    if (ret == 1 && replyPtr) {
        auto *reply = static_cast<xcb_get_property_reply_t *>(replyPtr);
        if (reply->type != 0 && reply->value_len > 0) {
            QByteArray data(static_cast<const char *>(xcb_get_property_value(reply)),
                            xcb_get_property_value_length(reply));
            session.results.insert(session.requests[i].atom, data);
        }
        free(reply);
    } else if (err) {
        free(err);
    }
}
```

Completion check becomes:

```cpp
bool windowDone = std::all_of(session.cookies.begin(), session.cookies.end(),
                              [](const auto &c) { return !isCookiePending(c); });
```

Timeout handling uses the same mechanism and calls `xcb_discard_reply` before clearing:

```cpp
for (int i = 0; i < session.cookies.size(); ++i) {
    auto &cookie = session.cookies[i];
    if (!isCookiePending(cookie))
        continue;

    void *replyPtr = nullptr;
    xcb_generic_error_t *err = nullptr;
    int ret = xcb_poll_for_reply64(conn, cookie.sequence, &replyPtr, &err);
    if (ret == 0 && cookie.sequence)
        xcb_discard_reply(conn, cookie.sequence);

    markCookieDone(cookie);
    // ... same result insertion / free logic ...
}
```

This preserves all your new behavior (including discarding unconsumed replies) without the additional `done` vector and its synchronization cost.

---

### 4. Simplify cleanup, avoid `cleanupAsyncRequest` as a separate concept

If you:

- key by `xcb_window_t`,
- use a single shared `pollTimer`,
- encode completion in `cookies`,

then `cleanupAsyncRequest` can be inlined into the point where you decide a session is done:

```cpp
void WXWaylandPrivate::xcbPollReplies()
{
    W_Q(WXWayland);
    xcb_connection_t *conn = q->xcbConnection();
    if (!conn)
        return;

    for (auto winIt = asyncProps.begin(); winIt != asyncProps.end();) {
        auto &perWindow = winIt.value();

        for (int s = 0; s < perWindow.sessions.size();) {
            auto &session = perWindow.sessions[s];
            // ... poll replies, update cookies/results ...

            bool sessionDone = std::all_of(session.cookies.begin(), session.cookies.end(),
                                           [](const auto &c) { return !isCookiePending(c); });

            if (sessionDone) {
                auto callback = std::move(session.callback);
                auto windowId = winIt.key();
                auto results = session.results;
                perWindow.sessions.remove(s);
                callback(windowId, results);
            } else {
                ++s;
            }
        }

        if (perWindow.sessions.isEmpty())
            winIt = asyncProps.erase(winIt);
        else
            ++winIt;
    }
}
```

Timeouts can be handled similarly via a single timer that checks deadlines per session instead of maintaining a `timeoutTimer` per request. If you want to keep a per-request timeout, you can store the expiry time in `AsyncPropSession` instead of spawning another `QTimer`.

---

These changes preserve the functional improvements you added (timeouts, discard of pending replies, more robust async handling) while:

- removing artificial `requestId` indirection,
- centralizing polling into one timer,
- eliminating `QVector<bool> done`,
- and simplifying lifecycle / cleanup logic.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 598 to 604
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): windowDone never becomes false when replies are not ready, causing premature completion.

In the new xcbPollReplies loop, windowDone stays true even when xcb_poll_for_reply64 returns 0 or an error, and props.done[i] is set unconditionally. This means a failed or pending poll can still mark the whole window as complete. Only set props.done[i] and keep windowDone as true when ret == 1, and set windowDone = false when ret == 0 or on error so incomplete properties don’t prematurely complete the request.

Comment thread waylib/src/server/protocols/wxwayland.cpp
QTimer *pollTimer = nullptr;
};

QMap<xcb_window_t, PerWindowProps> asyncProps;

Copy link
Copy Markdown

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:

  1. Use xcb_window_t as the primary key, and track multiple requests per window inside.
  2. Use a single shared poll timer instead of per-request pollTimers.
  3. Drop the parallel done vector by encoding completion in cookies.
  4. Simplify cleanup by removing cleanupAsyncRequest indirection.

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 internally

Instead of QMap<quint64, PerWindowProps> and a synthetic requestId, you can keep the map keyed by window and add an inner per-request structure if you really need multiple concurrent sessions per window:

struct AsyncPropSession
{
    QVector<WXWayland::AsyncPropRequest> requests;
    QMap<xcb_atom_t, QByteArray> results;
    QVector<xcb_get_property_cookie_t> cookies;
    std::function<void(xcb_window_t, const QMap<xcb_atom_t, QByteArray> &)> callback;
    QElapsedTimer deadline; // or qint64 deadlineMs
};

struct PerWindowProps
{
    QVector<AsyncPropSession> sessions;
};

QMap<xcb_window_t, PerWindowProps> asyncProps;

Then cancelAsyncProperties(windowId) stays a simple find(windowId):

void WXWayland::cancelAsyncProperties(xcb_window_t windowId)
{
    W_D(WXWayland);
    auto it = d->asyncProps.find(windowId);
    if (it == d->asyncProps.end())
        return;

    auto *conn = xcbConnection();
    if (conn) {
        for (auto &session : it->sessions) {
            for (const auto &cookie : session.cookies) {
                if (cookie.sequence)
                    xcb_discard_reply(conn, cookie.sequence);
            }
        }
    }
    d->asyncProps.erase(it);
}

This keeps multi-session support without global requestId bookkeeping or iterator invalidation issues across a QMap<quint64, …>.


2. Replace per-request pollTimer with a single shared poll timer

Instead of creating one pollTimer per request, keep a single timer in WXWaylandPrivate that drives xcbPollReplies() as long as there are pending sessions:

// In WXWaylandPrivate
QTimer *pollTimer = nullptr;

// init once
WXWaylandPrivate::WXWaylandPrivate()
{
    pollTimer = new QTimer(q);
    connect(pollTimer, &QTimer::timeout, q, [this]() { xcbPollReplies(); });
    pollTimer->setInterval(1); // or a slightly larger value if acceptable
    pollTimer->setSingleShot(false);
}

Start/stop it based on asyncProps emptiness:

void WXWayland::readWindowPropertiesAsync(...)
{
    W_D(WXWayland);
    // ... set up session ...
    d->asyncProps[windowId].sessions.append(std::move(session));

    if (!d->pollTimer->isActive())
        d->pollTimer->start();
}

And in xcbPollReplies():

void WXWaylandPrivate::xcbPollReplies()
{
    if (asyncProps.isEmpty()) {
        pollTimer->stop();
        return;
    }

    // ... iterate windows/sessions, read replies ...

    if (asyncProps.isEmpty())
        pollTimer->stop();
}

This removes all per-request pollTimer allocation and the need to manage them in cleanupAsyncRequest.


3. Replace QVector<bool> done with “cookie cleared” as completion flag

You can drop the done vector 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:

// helper
inline bool isCookiePending(const xcb_get_property_cookie_t &cookie)
{
    return cookie.sequence != 0;
}

inline void markCookieDone(xcb_get_property_cookie_t &cookie)
{
    cookie.sequence = 0;
}

Use in xcbPollReplies():

for (int i = 0; i < session.cookies.size(); ++i) {
    auto &cookie = session.cookies[i];
    if (!isCookiePending(cookie))
        continue;

    void *replyPtr = nullptr;
    xcb_generic_error_t *err = nullptr;
    int ret = xcb_poll_for_reply64(conn, cookie.sequence, &replyPtr, &err);

    if (ret == 0) {
        windowDone = false;
        continue;
    }

    markCookieDone(cookie);

    if (ret == 1 && replyPtr) {
        auto *reply = static_cast<xcb_get_property_reply_t *>(replyPtr);
        if (reply->type != 0 && reply->value_len > 0) {
            QByteArray data(static_cast<const char *>(xcb_get_property_value(reply)),
                            xcb_get_property_value_length(reply));
            session.results.insert(session.requests[i].atom, data);
        }
        free(reply);
    } else if (err) {
        free(err);
    }
}

Completion check becomes:

bool windowDone = std::all_of(session.cookies.begin(), session.cookies.end(),
                              [](const auto &c) { return !isCookiePending(c); });

Timeout handling uses the same mechanism and calls xcb_discard_reply before clearing:

for (int i = 0; i < session.cookies.size(); ++i) {
    auto &cookie = session.cookies[i];
    if (!isCookiePending(cookie))
        continue;

    void *replyPtr = nullptr;
    xcb_generic_error_t *err = nullptr;
    int ret = xcb_poll_for_reply64(conn, cookie.sequence, &replyPtr, &err);
    if (ret == 0 && cookie.sequence)
        xcb_discard_reply(conn, cookie.sequence);

    markCookieDone(cookie);
    // ... same result insertion / free logic ...
}

This preserves all your new behavior (including discarding unconsumed replies) without the additional done vector and its synchronization cost.


4. Simplify cleanup, avoid cleanupAsyncRequest as a separate concept

If you:

  • key by xcb_window_t,
  • use a single shared pollTimer,
  • encode completion in cookies,

then cleanupAsyncRequest can be inlined into the point where you decide a session is done:

void WXWaylandPrivate::xcbPollReplies()
{
    W_Q(WXWayland);
    xcb_connection_t *conn = q->xcbConnection();
    if (!conn)
        return;

    for (auto winIt = asyncProps.begin(); winIt != asyncProps.end();) {
        auto &perWindow = winIt.value();

        for (int s = 0; s < perWindow.sessions.size();) {
            auto &session = perWindow.sessions[s];
            // ... poll replies, update cookies/results ...

            bool sessionDone = std::all_of(session.cookies.begin(), session.cookies.end(),
                                           [](const auto &c) { return !isCookiePending(c); });

            if (sessionDone) {
                auto callback = std::move(session.callback);
                auto windowId = winIt.key();
                auto results = session.results;
                perWindow.sessions.remove(s);
                callback(windowId, results);
            } else {
                ++s;
            }
        }

        if (perWindow.sessions.isEmpty())
            winIt = asyncProps.erase(winIt);
        else
            ++winIt;
    }
}

Timeouts can be handled similarly via a single timer that checks deadlines per session instead of maintaining a timeoutTimer per request. If you want to keep a per-request timeout, you can store the expiry time in AsyncPropSession instead of spawning another QTimer.


These changes preserve the functional improvements you added (timeouts, discard of pending replies, more robust async handling) while:

  • removing artificial requestId indirection,
  • centralizing polling into one timer,
  • eliminating QVector<bool> done,
  • and simplifying lifecycle / cleanup logic.

@BLumia BLumia requested a review from zccrs June 22, 2026 02:15
@deepin-bot

deepin-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 0.8.12
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1036

@LFRon LFRon force-pushed the fix-issue-with-948 branch 2 times, most recently from 7365cb5 to 7a05539 Compare June 24, 2026 12:18
@deepin-bot

deepin-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 0.8.13
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1063

@LFRon LFRon force-pushed the fix-issue-with-948 branch from 7a05539 to 65e1853 Compare June 26, 2026 07:32
@LFRon

LFRon commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

该问题看着已修复, 关闭这个PR

@LFRon LFRon closed this Jun 26, 2026
@LFRon

LFRon commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

最新提交里又出现了一样的问题,故重新开启该PR

@LFRon LFRon reopened this Jun 29, 2026
…nuxdeepin#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.
@LFRon LFRon force-pushed the fix-issue-with-948 branch from 65e1853 to 3e7aa7a Compare June 29, 2026 15:08
@zccrs zccrs requested a review from glyvut July 1, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants