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 始终贴不上。
问题可以分为三层:
- Wayland 一侧只有
image/bmp,没有 PNG; - X11 的
TARGETS里根本没有图片类型; - 即使手动把 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适合临时验证;常驻服务适合长期使用。
复现清单
-
安装
xclip,确认/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe可执行。 -
把
clipwatch.ps1放到~/.local/share/clip-img-bridge/,把clip-img-bridge放到~/.local/bin/并加执行权限。 -
启用
clip-img-bridge.service,确认服务状态是active。 -
在 Windows 里截图,然后检查 X11 类型:
DISPLAY=:0 xclip -selection clipboard -t TARGETS -o # 应包含 image/png -
在 Claude Desktop 里按
Ctrl+V,确认图片贴入且没有卡顿。 -
如果失败,先看
journalctl --user -u clip-img-bridge -n 100,再检查weston.log里的outstanding RDP data request计数;不要直接反复复制、反复粘贴。