Skip to content

fix: avoid truncating large clipboard payloads on non-blocking pipes - #283

Open
LFRon wants to merge 1 commit into
linuxdeepin:masterfrom
LFRon:fix/screenshot-copy
Open

LFRon wants to merge 1 commit into
linuxdeepin:masterfrom
LFRon:fix/screenshot-copy

Conversation

@LFRon

@LFRon LFRon commented Sep 4, 2026

Copy link
Copy Markdown

该PR修复了: 在treeland环境下, 走DDE内置的截图软件/xdg-desktop-portal-dde -> treeland截图时, 如果截图的图像体积太大, 复制到Xwayland跑的QQ/微信等软件上会被截成一块一块的而且最后报错发送失败

修复向非阻塞选区管道回传大数据被截断的问题。

合成器为选区传输下发的管道两端为 O_NONBLOCK,并依靠暂停读取对大传输(X11
INCR)节流;原先一次朴素的 QFile::write 在首次 EAGAIN 时即停止,仅送出约
64KiB。截图等大 payload 到达 XWayland 客户端时被截断,表现为图片只有部分
解码内容并出现花屏条带、发送失败;使用阻塞 offer 管道的 Wayland 客户端
不受影响。

  • 新增 writeDataToPipe():短写后继续写,EAGAIN 时用 poll() 等待可写,每次 write 周围屏蔽 SIGPIPE,并以 30 秒超时兜底,读取方卡死不会永久占用写 线程池。
  • 在所属线程求值请求数据,将隐式共享的 QByteArray 交给工作线程;检查写入 结果并记录日志;对空的 m_mimeData 判空并关闭 fd,避免空指针解引用。
  • 精确匹配的图片 MIME 请求优先返回已存储的原始字节,不再经 QImageWriter 重编码(避免 payload 尺寸变化和每请求一次完整解码+编码的开销);无存储 字节的条目(历史回放)保留原有重编码回退。

Log: avoid truncating large clipboard payloads on non-blocking pipes

Summary by Sourcery

Ensure reliable delivery of large clipboard payloads through non-blocking pipes while preserving original image data when possible.

Bug Fixes:

  • Prevent large clipboard and screenshot payloads from being truncated when delivered through non-blocking Wayland selection pipes.
  • Handle aborted, stalled, empty, or unavailable clipboard transfers without crashes or indefinitely blocked worker threads.

Enhancements:

  • Preserve original bytes for exact image MIME requests when available, avoiding unnecessary image re-encoding and payload changes.
  • Evaluate clipboard data on the owning thread before asynchronous delivery and report transfer failures through logging.

@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. 😃

@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 Sep 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fixes large clipboard transfers to XWayland clients by fully writing payloads to non-blocking selection pipes, with bounded retry/error handling and safer asynchronous data preparation; exact stored image MIME payloads are now returned without unnecessary re-encoding.

Sequence diagram for reliable large clipboard transfer

sequenceDiagram
    participant XWayland as XWayland client
    participant Compositor as Compositor pipe
    participant Clipboard as Clipboard daemon
    participant Worker as Write worker

    XWayland->>Compositor: Request clipboard MIME data
    Compositor->>Clipboard: onSourceSend(mimeType, fd)
    Clipboard->>Clipboard: getByteArray(mMimeData, mimeType)
    Clipboard->>Worker: QtConcurrent::run(data, fd)
    loop Until all bytes are written
        Worker->>Compositor: write(fd, remaining data)
        alt Pipe accepts bytes
            Compositor-->>Worker: Short or complete write
        else EAGAIN
            Worker->>Compositor: poll(fd, POLLOUT)
            Compositor-->>Worker: Pipe writable
        end
    end
    Worker-->>XWayland: Complete clipboard payload
Loading

Flow diagram for exact image payload selection

flowchart TD
    A[Request image MIME type] --> B{Stored exact MIME payload available?}
    B -->|Yes| C[Return original stored bytes]
    B -->|No| D[Read imageData from QMimeData]
    D --> E[Encode with QImageWriter]
    C --> F[Write payload to clipboard pipe]
    E --> F
Loading

File-Level Changes

Change Details Files
Reliably deliver complete clipboard payloads through non-blocking pipes.
  • Added a write loop that handles short writes and waits for EAGAIN with poll().
  • Suppressed SIGPIPE per worker-thread write and added a 30-second overall timeout.
  • Detected broken peers and other write failures with diagnostic logging.
dde-clipboard-daemon/wlrintegration/wlrdatacontrolclipboardinterface.cpp
Prepare clipboard data safely before asynchronous pipe transmission.
  • Evaluate QMimeData on the owning thread and pass the resulting QByteArray to the worker.
  • Guard against missing MIME data and close the supplied file descriptor.
  • Validate empty payloads and report failed transfers.
dde-clipboard-daemon/wlrintegration/wlrdatacontrolclipboardinterface.cpp
Preserve original bytes for exact image MIME requests when available.
  • Return stored source payloads without decode/re-encode for matching image MIME types.
  • Retain QImageWriter re-encoding as a fallback for unavailable or historical image payloads.
dde-clipboard-daemon/wlrintegration/wlrdatacontrolclipboardinterface.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 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="dde-clipboard-daemon/wlrintegration/wlrdatacontrolclipboardinterface.cpp" line_range="364-370" />
<code_context>
+        // Linux has no MSG_NOSIGNAL for pipes; block SIGPIPE around the
+        // write instead. pthread_sigmask is used because this runs on a
+        // worker thread of a thread pool.
+        sigset_t blocked, previous;
+        sigemptyset(&blocked);
+        sigaddset(&blocked, SIGPIPE);
+        pthread_sigmask(SIG_BLOCK, &blocked, &previous);
+        const ssize_t written = ::write(fd, pos, count);
+        const int savedErrno = errno;
+        pthread_sigmask(SIG_SETMASK, &previous, nullptr);
+        errno = savedErrno;
+        return written;
</code_context>
<issue_to_address>
**issue (bug_risk):** Blocking SIGPIPE during `write()` does not consume the signal generated when the peer has closed the pipe. Restoring the previous unblocked mask delivers the pending SIGPIPE immediately, so the daemon still terminates instead of treating a requestor-aborted transfer as a normal write failure.

**Triggers:** When the compositor or clipboard requestor closes the read end while a worker is writing.

**Suggested fix:** Use `SIG_IGN` with restoration, consume a pending SIGPIPE before unblocking it, or use an equivalent pipe-safe mechanism such as handling SIGPIPE explicitly and checking `EPIPE`.

```suggestion
        sigset_t blocked, previous;
        sigemptyset(&blocked);
        sigaddset(&blocked, SIGPIPE);
        pthread_sigmask(SIG_BLOCK, &blocked, &previous);
        const ssize_t written = ::write(fd, pos, count);
        const int savedErrno = errno;
        if (written < 0 && savedErrno == EPIPE) {
            sigset_t pending;
            if (sigpending(&pending) == 0
                && sigismember(&pending, SIGPIPE) == 1) {
                int signalNumber;
                sigwait(&blocked, &signalNumber);
            }
        }
        pthread_sigmask(SIG_SETMASK, &previous, nullptr);
```
</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 +364 to +370
sigset_t blocked, previous;
sigemptyset(&blocked);
sigaddset(&blocked, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &blocked, &previous);
const ssize_t written = ::write(fd, pos, count);
const int savedErrno = errno;
pthread_sigmask(SIG_SETMASK, &previous, 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): Blocking SIGPIPE during write() does not consume the signal generated when the peer has closed the pipe. Restoring the previous unblocked mask delivers the pending SIGPIPE immediately, so the daemon still terminates instead of treating a requestor-aborted transfer as a normal write failure.

Triggers: When the compositor or clipboard requestor closes the read end while a worker is writing.

Suggested fix: Use SIG_IGN with restoration, consume a pending SIGPIPE before unblocking it, or use an equivalent pipe-safe mechanism such as handling SIGPIPE explicitly and checking EPIPE.

Suggested change
sigset_t blocked, previous;
sigemptyset(&blocked);
sigaddset(&blocked, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &blocked, &previous);
const ssize_t written = ::write(fd, pos, count);
const int savedErrno = errno;
pthread_sigmask(SIG_SETMASK, &previous, nullptr);
sigset_t blocked, previous;
sigemptyset(&blocked);
sigaddset(&blocked, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &blocked, &previous);
const ssize_t written = ::write(fd, pos, count);
const int savedErrno = errno;
if (written < 0 && savedErrno == EPIPE) {
sigset_t pending;
if (sigpending(&pending) == 0
&& sigismember(&pending, SIGPIPE) == 1) {
int signalNumber;
sigwait(&blocked, &signalNumber);
}
}
pthread_sigmask(SIG_SETMASK, &previous, nullptr);

@LFRon
LFRon force-pushed the fix/screenshot-copy branch from 06d546e to c071101 Compare September 9, 2026 04:33
@deepin-bot

deepin-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

TAG Bot

New tag: 6.1.35
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #284

The compositor hands out O_NONBLOCK pipe ends for selection transfers and
paces large transfers (X11 INCR) by pausing reads, so a naive QFile::write()
stopped at the first EAGAIN after only ~64 KiB. Large payloads such as
screenshots reached XWayland clients truncated: images showed partially
decoded content with garbled bands and failed to send, while Wayland
clients using blocking offer pipes were unaffected.

- Add writeDataToPipe() which resumes after short writes, waits for
  writability on EAGAIN via poll(), suppresses SIGPIPE around each write
  and bounds the whole write with a 30s deadline so a stalled reader can
  never wedge the write thread pool.
- Evaluate the requested payload on the owning thread and pass the
  implicitly-shared QByteArray to the worker; check write results, log
  failures and guard against a null m_mimeData by closing the fd.
- Serve stored raw bytes for exact image MIME requests instead of
  re-encoding through QImageWriter, which changed the payload size and
  cost a full decode+encode round trip per request; keep the re-encoding
  fallback for entries without stored bytes (history replay).

修复向非阻塞选区管道回传大数据被截断的问题。

合成器为选区传输下发的管道两端为 O_NONBLOCK,并依靠暂停读取对大传输(X11
INCR)节流;原先一次朴素的 QFile::write 在首次 EAGAIN 时即停止,仅送出约
64KiB。截图等大 payload 到达 XWayland 客户端时被截断,表现为图片只有部分
解码内容并出现花屏条带、发送失败;使用阻塞 offer 管道的 Wayland 客户端
不受影响。

- 新增 writeDataToPipe():短写后继续写,EAGAIN 时用 poll() 等待可写,每次
  write 周围屏蔽 SIGPIPE,并以 30 秒超时兜底,读取方卡死不会永久占用写
  线程池。
- 在所属线程求值请求数据,将隐式共享的 QByteArray 交给工作线程;检查写入
  结果并记录日志;对空的 m_mimeData 判空并关闭 fd,避免空指针解引用。
- 精确匹配的图片 MIME 请求优先返回已存储的原始字节,不再经 QImageWriter
  重编码(避免 payload 尺寸变化和每请求一次完整解码+编码的开销);无存储
  字节的条目(历史回放)保留原有重编码回退。

Log: avoid truncating large clipboard payloads on non-blocking pipes
@LFRon
LFRon force-pushed the fix/screenshot-copy branch from c071101 to c97bf03 Compare September 10, 2026 13:57
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