WSLg 里粘贴 Windows 截图:Xwayland 不传图片,和一次剪贴板卡死

在 WSLg 的 Claude Desktop 中粘贴 Windows 截图:Xwayland 只桥接文字类型、RDP 剪贴板的单请求死锁,以及最终的 PowerShell + xclip 方案。

本文目录

一句话结论:不要指望 WSLg 的 Xwayland 把 Windows 图片转成 X11 的 image/png。X11 侧始终只看到文字类型;手动用 xclip 写入 PNG 可以成功,但任何通过 WSLg 的 RDP 剪贴板桥进行的并发读取,都可能把整条剪贴板通道卡死。最终我让 Windows 端的 PowerShell 直接读图,输出 base64,再由 Linux 端写入 X11,彻底绕开 Wayland 和 WSLg 剪贴板桥。

这篇是剪贴板图片的专题记录;输入法安装、候选窗残影和 kimpanel 面板见另一篇文章。

要解决的问题

Claude Desktop 是 X11 下的 Electron 应用,粘贴图片时读取的是 X11 剪贴板里的 image/png。在 Windows 里截图或复制图片后,WSLg 确实能让 Linux 看到“有图片”,但 Claude Desktop 始终贴不上。

问题可以分为三层:

  1. Wayland 一侧只有 image/bmp,没有 PNG;
  2. X11 的 TARGETS 里根本没有图片类型;
  3. 即使手动把 PNG 写进 X11,后台如果通过 WSLg 剪贴板桥反复读取,仍可能让通道永久卡死。

验证环境

项 值
系统 WSL2 Ubuntu 24.04.5 LTS
图形栈 WSLg / Weston 9.0.0,Xwayland :0
Claude Desktop 4.4212.0,Electron 44.9.3
剪贴板工具 wl-clipboard 2.2.1、xclip 0.13、ImageMagick 6.9.12
输入法 Fcitx5 5.1.7,已禁用 clipboard 插件

验证日期为 2026-11-02 至 2026-11-08。主体实验在 2026-11-02 完成。

第一层:X11 里永远没有图片类型

在 Windows 里复制一张图,然后在 WSL 里只看类型,不读内容:

timeout 3 wl-paste -l
# image/bmp

DISPLAY=:0 timeout 3 xclip -selection clipboard -t TARGETS -o
# TIMESTAMP
# TARGETS
# UTF8_STRING
# TEXT

Wayland 侧的类型列表里确实有 image/bmp;X11 侧却只有文字类型。即使 Claude 窗口有焦点、连续观察 3 分钟,image/png 和 image/bmp 都从未出现在 X11 的 TARGETS 里。

我又试了反向操作:在 Wayland 侧把转好的 PNG 放回去:

wl-copy -t image/png < screenshot.png
wl-paste -l
# image/png

Wayland 的类型列表变了,但 X11 的 TARGETS 完全没有变化,仍然只有文字。这说明 Xwayland 并没有把 Wayland 的图片类型转发给 X11。

结论是:WSLg 的 X11 选择桥对图片类型不可用。 更准确地说,Xwayland 对所有 Wayland selection 都只宣告文字类型;这与 Weston 的 xwm 只桥接 text/plain 的已知行为一致。这是根据多次观察得到的推断,没有读 Weston 源码核实。

这也解释了一个常见误区:在 WSL 里用 wl-paste 能读到 BMP,不代表 Claude Desktop 能读到;两个应用看的不是同一个剪贴板协议层。

手动桥:xclip 能成功,但不能这样长期跑

最直接的办法是绕开 Xwayland 的类型转换:把 Windows 图片读成 BMP,转成 PNG,再用 xclip 直接写进 X11 剪贴板。因为此时 selection owner 是 xclip,Chromium 会直接从 xclip 读数据,不再向 Xwayland/RDP 要数据。

#!/bin/bash
# clipimg:手动把 Windows 剪贴板图片写入 X11
export DISPLAY=${DISPLAY:-:0}
tmp=$(mktemp --suffix=.png)

if /usr/bin/wl-paste -l 2>/dev/null | grep -qx 'image/bmp'; then
  /usr/bin/wl-paste -t image/bmp | convert bmp:- png:- >"$tmp"
elif /usr/bin/wl-paste -l 2>/dev/null | grep -qx 'image/png'; then
  /usr/bin/wl-paste -t image/png >"$tmp"
else
  echo "剪贴板里没有图片" >&2
  rm -f "$tmp"
  exit 1
fi

[ -s "$tmp" ] || { echo "转换失败" >&2; rm -f "$tmp"; exit 1; }

xclip -selection clipboard -t image/png -i <"$tmp"
echo "已写入 X11 剪贴板: $(file -b "$tmp" | cut -d, -f1-2)"
rm -f "$tmp"

手动执行后,X11 的 TARGETS 会变成 TARGETS image/png,Claude Desktop 里 Ctrl+V 可以贴图,而且当时没有卡。

但这不是一个可以常驻的方案。 它仍然要通过 wl-paste 读 Wayland 剪贴板;而 wl-paste 在 Weston 9.0.0 的 WSLg 里会抢焦点,并且会参与 WSLg 剪贴板的并发读取。

两次卡死:RDP 剪贴板桥只能有一个未完成请求

第一次:Claude 在 Ctrl+V 时挂起

当时后台有两个东西在轮询 X11:一个每 0.7 秒查询 TARGETS 的图片桥,以及一个每 0.5 秒记录 TARGETS 和活动窗口的 watch.sh。我在 Claude Desktop 里按下 Ctrl+V 后,应用直接卡住,Claude Code 终端打出:

FATAL:content/child/child_thread_impl.cc:380 Crashing because hung

紧接着,Windows 里复制的文字也进不来了。Wayland 的类型列表还在,但读取内容报:

Clipboard content is not available as requested type

X11 读文字也是空。/mnt/wslg/weston.log 里反复出现:

RDP clipboard_data_source_send new (...:failed:fd 99)
  vs prev (...:cancel pending:fd 102): outstanding RDP data request (client to server)

在 Windows 里重新复制一段文字、等 10 秒,都无效。最后由我执行 wsl --shutdown 才恢复。

第二次:复制文字又卡死

第二次发生在复制网页文字之后。此时后台的图片桥仍会通过 wl-paste 读取类型,ps 里能看到一个挂起的 /usr/bin/wl-paste -t image/bmp;杀掉它并不能解开通道。又一次 wsl --shutdown 后恢复。

60 条日志给出的读取者分布

我解析了 weston.log,找到每条 outstanding RDP data request 之前最近的 selection request,按“目标类型 + 请求 property”统计:

次数 target property(请求者) 谁
37 UTF8_STRING FCITX_X11_SEL_CLIPBOARD Fcitx5 的 clipboard 插件
14 UTF8_STRING CHROME_SELECTION Claude Desktop / Chromium
4 TARGETS CHROME_SELECTION Claude Desktop / Chromium
3 UTF8_STRING XCLIP_OUT 我自己的 xclip 轮询
1 TIMESTAMP XCLIP_OUT 我自己的 xclip
1 Wayland 客户端请求 — wl-paste

Fcitx5 的剪贴板插件占了 37/60,是最主要的并发读取者;Chromium 占 18/60,我自己的 xclip 轮询占 4/60。

机制结论(推断,与现象和日志吻合):WSLg 的 RDP 剪贴板桥在同一时刻只能处理一个“向 Windows 要数据”的请求。第二个请求到来时会与前一个冲突;前一个请求处于 cancel pending,拿不到 Windows 回复,之后所有读取都失败。类型列表仍然在,所以看起来“剪贴板有内容”,但数据请求已经死了。

只有 wsl --shutdown 能可靠恢复。这不是普通进程重启能解决的问题,而且会终止整个发行版,所以我没有把它自动化,恢复动作交给使用者决定。

为什么 wl-paste 会抢焦点

WSLg 的 Weston 9.0.0 不提供 wlr-data-control 协议:

Watch mode requires a compositor that supports the wlroots data-control protocol

普通 Wayland 客户端只有拥有键盘焦点的表面才能收到剪贴板 selection 事件。于是 wl-paste 要读剪贴板,就必须临时创建一个表面去拿焦点。我能明显感觉到“复制后打字被打断”。

有意思的是,在 X11 侧用 _NET_ACTIVE_WINDOW 连续采样,焦点和窗口数都没有变化。焦点切换发生在 Weston/Windows 一侧,X11 测不出来。 这也是为什么不能靠“X11 没变化”来判断 Wayland 读取是无害的。

另一个死路是用 X11 的 TARGETS 预判“现在是不是图片”:Xwayland 对文字和图片都只宣告文字类型,TARGETS 完全一样。只要还在 Wayland 路径上,“自动读取”和“不抢焦点”就不可兼得。

最终方案:Windows 端读图,Linux 端写 X11

既然 WSLg 的剪贴板桥既不能识别图片,又可能因并发读取卡死,最后的方案是完全不走 Wayland:

Windows 剪贴板
      │ PowerShell(Windows 进程,经 WSL interop 启动)
      │ GetClipboardSequenceNumber 轮询
      │ 仅在“有图片、没有文字”时输出一行 base64 PNG
      ▼
clip-img-bridge(systemd 用户服务)
      │ base64 -d
      ▼
xclip -selection clipboard -t image/png -i
      │
      ▼
X11 CLIPBOARD → Claude Desktop Ctrl+V

Windows 侧:clipwatch.ps1

# 在 Windows 侧运行;每次检测到“仅图片”剪贴板时输出一行 base64 PNG。
Add-Type -AssemblyName System.Windows.Forms,System.Drawing
Add-Type -TypeDefinition @'
using System.Runtime.InteropServices;
public class CB {
  [DllImport("user32.dll")]
  public static extern uint GetClipboardSequenceNumber();
}
'@

$last = [uint32]0
$beat = 0

while ($true) {
  $s = [CB]::GetClipboardSequenceNumber()
  if ($s -ne $last) {
    $last = $s
    try {
      if ([Windows.Forms.Clipboard]::ContainsImage() -and
          -not [Windows.Forms.Clipboard]::ContainsText()) {
        $img = [Windows.Forms.Clipboard]::GetImage()
        $ms  = New-Object IO.MemoryStream
        $img.Save($ms, [Drawing.Imaging.ImageFormat]::Png)
        [Console]::Out.WriteLine([Convert]::ToBase64String($ms.ToArray()))
        $img.Dispose()
        $ms.Dispose()
      }
    } catch { }
  }

  Start-Sleep -Milliseconds 400
  $beat++
  if ($beat -ge 25) {
    $beat = 0
    # 心跳:WSL 侧管道关闭时写入会抛异常,PowerShell 自己退出,不留孤儿进程。
    try { [Console]::Out.WriteLine("") } catch { exit }
  }
}

关键点是 GetClipboardSequenceNumber:它只查序号,不打开剪贴板,轮询开销很小。序号变化时才检查内容,并且只接受“有图片、没有文字”的剪贴板。网页或 Excel 里同时带 text/ 和图片的复制会被跳过,这是故意的取舍:保留 Linux 侧的文字,不让图片桥把文字覆盖掉。

Linux 侧:clip-img-bridge

#!/bin/bash
# ~/.local/bin/clip-img-bridge
export DISPLAY=${DISPLAY:-:0}

enc=$(iconv -t UTF-16LE ~/.local/share/clip-img-bridge/clipwatch.ps1 | base64 -w0)

/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe \
  -NoLogo -NoProfile -STA -NonInteractive -EncodedCommand "$enc" 2>/dev/null |
while IFS= read -r line; do
  line=${line%$'\r'}
  [ -n "$line" ] || continue
  base64 -d <<<"$line" | xclip -selection clipboard -t image/png -i
done

服务单元:

# ~/.config/systemd/user/clip-img-bridge.service
[Unit]
Description=Publish Windows clipboard images as PNG to X11

[Service]
Environment=DISPLAY=:0
ExecStart=%h/.local/bin/clip-img-bridge
Restart=always
RestartSec=3
KillMode=control-group

[Install]
WantedBy=default.target

启用:

chmod +x ~/.local/bin/clip-img-bridge
systemctl --user daemon-reload
systemctl --user enable --now clip-img-bridge.service

几个实际踩过的坑

  • PowerShell 输出行尾带 \r:base64 -d 会报 invalid input,必须用 ${line%$'\r'} 去掉。
  • systemd 用户服务的 PATH 不含 /mnt/c/...:直接写 powershell.exe 会找不到,必须用 /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe。
  • KillMode=control-group 不能省:默认的 process 会留下 xclip 或 PowerShell 子进程。
  • -STA 不能省:Clipboard API 需要 STA 线程。
  • 不要在服务里再调用 wl-paste:一旦回到 Wayland 路径,焦点和 RDP 单请求问题都会回来。

为什么这次不会再触发剪贴板卡死

这条路径中,Windows 端只读自己的剪贴板,不经过 WSLg 的 RDP 剪贴板桥;Linux 端只写 X11,也不向 Windows 要数据。图片数据通过 stdout 管道传输,不涉及 Xwayland 的 selection 转发。

这并不代表“以后永远不会卡”。如果其他程序仍然通过 WSLg 剪贴板桥并发读取,问题依然可能发生;最终桥只是绕开了已知的触发源。

取舍和边界

  • 混合内容会被跳过:网页图片经常同时带 text/html,Excel 复制也可能同时有文字。当前策略只处理“仅有图片”的剪贴板,避免文字被覆盖。
  • 透明度会丢:Clipboard.GetImage() 走 DIB,PNG 的 alpha 不保留。
  • 大图未测:几十 MB 的图片经 base64 管道要多久,没有压测。
  • 通道仍可能被别的程序卡死:Fcitx5 的 clipboard 插件已禁用,但不能保证所有应用都不并发读取。
  • 恢复手段很重:如果 WSLg 剪贴板桥已经卡死,只能手动执行 wsl --shutdown;这会终止整个发行版,不要自动化。
  • 手动命令仍有价值:clipimg 适合临时验证;常驻服务适合长期使用。

复现清单

  1. 安装 xclip,确认 /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe 可执行。

  2. 把 clipwatch.ps1 放到 ~/.local/share/clip-img-bridge/,把 clip-img-bridge 放到 ~/.local/bin/ 并加执行权限。

  3. 启用 clip-img-bridge.service,确认服务状态是 active。

  4. 在 Windows 里截图,然后检查 X11 类型:

    DISPLAY=:0 xclip -selection clipboard -t TARGETS -o
    # 应包含 image/png
  5. 在 Claude Desktop 里按 Ctrl+V,确认图片贴入且没有卡顿。

  6. 如果失败,先看 journalctl --user -u clip-img-bridge -n 100,再检查 weston.log 里的 outstanding RDP data request 计数;不要直接反复复制、反复粘贴。

相关阅读