概述
GNOME Wayland + 125% 分数缩放的机器上,所有非原生应用(Steam、QQ、微信、飞书)窗口里的鼠标光标和桌面原生光标大小、样式不一致,而且每个应用偏的方向还不一样:QQ 偏小、微信偏大、Steam 特别小、Discord 反而正常。这篇文章记录完整的排查过程和最终修复。
原理:为什么只有 XWayland 应用出问题
- 原生 Wayland 应用:光标由合成器统一绘制,自动跟随显示器缩放,永远正确
- XWayland 应用:X11 应用自己提供光标位图,经 libXcursor 加载,尺寸读
XCURSOR_SIZE、主题读XCURSOR_THEME;mutter 不会对 XWayland 的光标表面做分数缩放放大,位图按物理像素直接呈现——所以 125% 缩放下天然偏小
由此得出第一个补丁:XCURSOR_SIZE = 逻辑大小 × 缩放系数(如 30 × 1.25 = 38),通过 ~/.config/environment.d/ 下发,用 systemd user service 在每次登录时根据当前缩放自动计算。
但这只对"老实读环境变量的 XWayland 应用"有效。Electron/CEF/Qt 应用各有各的脾气:
| 应用 | 框架 | 行为 |
|---|---|---|
| Electron | XCURSOR_SIZE 生效但仍偏小 | |
| 飞书 | Electron | 同上 |
| 微信 | 静态链接 Qt(自定义 UI 框架) | 光标反而偏大 |
| Steam 客户端 | CEF,且跑在 pressure-vessel 容器里 | 光标特别小,完全不理会环境变量 |
| Discord | Electron | 正常(它的进程环境里恰好没有这些变量) |
同一个全局变量无法同时满足"要更大的"和"要更小的"——继续调数字是死路,正确方向是让这些应用直接跑原生 Wayland,光标交还合成器统一绘制。
诊断方法论
没有截图对比的情况下,纯命令行定位:
# 1. 环境变量到底传到哪一层了:逐进程检查 /proc/PID/environ
tr '\0' '\n' < /proc/$PID/environ | grep XCURSOR
# 沿 gnome-shell → Xwayland → 应用 链路逐个验证,断在哪一层一目了然
# 2. 变量是谁注入的:沿进程树向上追
ps -o ppid= -p $PID # 一层层往上,找到第一个带该变量的进程
# 3. 应用是什么框架:决定它能听懂什么参数
ldd /opt/wechat/wechat # 无 libQt/libX11 → 静态链接
strings -a /opt/wechat/wechat | grep -i wayland # QWaylandIntegrationPlugin → 内置 Qt Wayland 插件
pgrep -a qq | grep zygote # zygote → Electron逐个修复
QQ(Electron)
--ozone-platform-hint=auto 在新版 QQ 上静默失效,必须用 --ozone-platform=wayland 强制。覆盖 ~/.local/share/applications/qq.desktop:
Exec=linuxqq --ozone-platform=wayland %U飞书(Electron)
官方就支持用户参数文件 ~/.config/feishu-flags.conf(/usr/bin/feishu 包装脚本会读取):
--ozone-platform=wayland微信(静态 Qt + portable 沙箱)
坑最多的一个。微信 Linux 是静态链接的 Qt 应用,同时内置 xcb 和 wayland 两个 QPA 插件,理论上 QT_QPA_PLATFORM=wayland 即可。但 CachyOS 的 portable 沙箱会清洗环境变量——env QT_QPA_PLATFORM=wayland ... /usr/bin/portable -- 启动后,微信进程里这个变量消失了(WAYLAND_DISPLAY 都被换成沙箱自己的路径)。
绕过方法:portable 的 -- 之后的参数会透传给应用,而 Qt 自己会解析 -platform 命令行参数:
Exec=env _portableConfig=com.tencent.wechat PORTABLE_CONF=com.tencent.wechat /usr/bin/portable -- -platform wayland %u验证方式:ls -l /proc/$PID/fd | grep wayland,原生 Wayland 进程会持有 wayland socket。
切到 Wayland 后微信光标仍偏大一档:它的 Qt 加载光标主题时用整个会话的 XCURSOR_SIZE=38 且不做缩放修正。portable 沙箱恰好不清洗 XCURSOR_* 变量,于是最终方案是在 desktop 文件里单独给微信喂逻辑大小:
Exec=env XCURSOR_SIZE=30 _portableConfig=com.tencent.wechat PORTABLE_CONF=com.tencent.wechat /usr/bin/portable -- -platform wayland %uSteam 客户端(CEF + pressure-vessel)
光标:客户端 UI 在 pressure-vessel 容器里用 CEF 自绘光标,不跟随缩放(与 electron#19810 同源)。确认过 XCURSOR_SIZE/XCURSOR_THEME 已完整传入容器、路径正确,依然无效;设置里的 “Scale text and icons to match monitor settings” 也只缩放 UI 不缩放光标。-forcedesktopscaling=1.25 会把整个界面连光标一起放大导致双重缩放,不适用。目前无干净解,可关注客户端 Beta 通道的修复。
卡顿(排查光标时的意外收获):Steam 主进程环境里发现 __NV_PRIME_RENDER_OFFLOAD=1——从手动 export 过这些变量的终端启动的,CEF 在 XWayland 上做双 GPU 离屏混合渲染。客户端从应用图标正常启动、留在核显;需要独显的游戏在单个游戏的启动选项里写 prime-run %command%。
结论
分数缩放时代,XWayland 是各种显示怪问题的共同根源。能切原生 Wayland 的应用(Electron 系用 --ozone-platform=wayland,Qt 系用 -platform wayland 或 QT_QPA_PLATFORM)就切;切不了的,XCURSOR_SIZE = 逻辑大小 × 缩放系数 是通用兜底。诊断时不要猜,用 /proc/PID/environ 把环境变量的传播链路逐层验明,用进程树定位注入点。