rustdesk-wayland-setup
RustDesk 在 Wayland(niri / sway)上的配置与排障总结
机器:Ubuntu 24.04.5 + nix home-manager,合成器 niri 26.04 / sway 1.11
一、背景与现象
RustDesk 1.4.5 在这台机器上抓屏完全不可用。客户端连接后立刻报:
ERROR [src/server/wayland.rs:76] Failed scrap Failed to obtain screen capture. |
上一行是关键的 portal 响应:
WARN [libs/scrap/src/wayland/pipewire.rs:488] DBus response: Unknown error, code: 2. |
两个特征值得注意:
response: 2—— portal 明确拒绝了请求- 从请求到失败只有 ~160ms —— 快到不可能弹出过任何授权对话框
二、根因
两个互相独立的坑,缺一不可。
坑 1:运行模式决定用哪个 portal 接口(决定性)
RustDesk 的 Wayland 抓屏在 on_create_session_response 中按 is_server_running() 分叉:
| 运行方式 | 使用的 portal 接口 | 输入方式 | 结果 |
|---|---|---|---|
| 用户态(GUI 直接跑) | org.freedesktop.portal.RemoteDesktop.CreateSession |
RemoteDesktop portal | ❌ response: 2 |
| 服务态(root 服务在跑) | org.freedesktop.portal.ScreenCast.CreateSession |
uinput(内核级) | ✅ response: 0 |
而 RemoteDesktop 这个接口在 niri 和 sway 上没有任何后端能提供:
- niri 只导出
org.gnome.Mutter.ScreenCast/DisplayConfig/ServiceChannel/Shell.Introspect/Shell.Screenshot,没有org.gnome.Mutter.RemoteDesktop xdg-desktop-portal-wlr只实现ScreenCast+Screenshotxdg-desktop-portal-gnome只在 mutter 提供 RemoteDesktop 时才注册该接口,所以在 niri 上它也没有
决定性证据在 portal 前端的日志里:
A backend call failed: No such interface "org.freedesktop.impl.portal.RemoteDesktop" |
结论:用户态在 niri/sway 上是死路,改任何配置都无效。
坑 2:wayland-restore-token 在显示器布局变化后变毒
RustDesk 把 portal 返回的 restore token 存进 ~/.config/rustdesk/RustDesk_local.toml,下次连接时带 persist_mode 直接恢复上次会话、跳过授权框。
但恢复出来的是保存那一刻的显示器坐标。 一旦布局变化(拔插显示器、改分辨率、改位置),坐标就对不上:
portal 返回的流: position: [3840, 0] ← 旧布局的坐标 |
规律:流的 position 是 (0,0) 时成功,是 (3840,0) 时失败。
三、解决方案
3.1 安装 RustDesk 特权服务
注意:rustdesk-server 是错的包。 它提供 hbbs / hbbr / rustdesk-utils,是自建中继/信令服务器,与客户端服务无关。
需要的是客户端自带的特权服务,单元文件在 ~/rustdesk.service:
[Unit] |
安装:
sudo cp ~/rustdesk.service /etc/systemd/system/rustdesk.service |
两个容易踩的坑:
- 单元文件必须手写。
rustdesk --install-service只会调systemctl enable/start,它不生成单元文件(deb/rpm 能用是因为发行版包里自带了)。直接跑会得到Unit file rustdesk.service does not exist。 ExecStart必须指向 nix wrapper,不能指向 store 里的裸二进制。wrapper 会设置GST_PLUGIN_SYSTEM_PATH_1_0(GStreamer/PipeWire 抓屏管线必需)、LD_LIBRARY_PATH、XDG_DATA_DIRS等。
服务做三件事:
- 以 root 接收连接 —— 无人值守的前提(GUI 没开也能连)
- 访问
/dev/uinput—— 创建虚拟键鼠做输入注入 - 降权到用户跑
--server——sudo -E -u papillon .../rustdesk --server,实际会话由它处理
3.2 修 portal 后端选择
~/.config/xdg-desktop-portal/niri-portals.conf:
[preferred] |
~/.config/xdg-desktop-portal/sway-portals.conf(新建):
[preferred] |
为什么必须显式指定:
- niri 那份原本指向
hyprland后端 —— compositor 对不上(/run/user/1000/hypr/不存在),必然失败 - sway 上
xdg-desktop-portal-hyprland的UseIn包含sway,且按字母序排在wlr前面,会劫持 sway 会话;必须用显式配置压过去 - gnome 后端在 niri 上虽然接口存在,但其 ScreenCast 授权对话框可能依赖 gnome-shell,从未跑通过实际会话。用 wlr
3.3 修复 portal 的 dbus 激活
nix 装的 portal 在会话总线上不可激活,导致任何需要 portal 的程序都会失败。
根因:会话总线是 dbus-broker,它在启动时就把服务目录列表固定了,而它启动得比 niri 的 ~/scripts/systemctl-env.sh 早。虽然那个脚本后来用 import-environment 把含 nix profile 的 XDG_DATA_DIRS 导入了 user manager,但总线已经不会重扫。
易误判点:
systemctl --user show-environment里XDG_DATA_DIRS是含 nix profile 的,看着一切正常。必须去看/proc/<dbus-broker-launch pid>/environ才能发现差异。
修法(绕开 XDG_DATA_DIRS,走总线一定会扫的 XDG_DATA_HOME):
mkdir -p ~/.local/share/dbus-1/services |
软链指向 ~/.nix-profile/...(稳定符号链接),nix 更新后自动跟随,无需维护。ReloadConfig 立即生效,不需要重新登录。
3.4 免点击(跳过屏幕选择器)
wlr portal 的默认选择器是 slurp:
slurp -f 'Monitor: %o' -or |
slurp 是图形化区域选择器,必须本地用户用鼠标点一下显示器。这就是「被连接方需要用鼠标点一下屏幕」的来源。
新建 ~/.config/xdg-desktop-portal-wlr/config:
[screencast] |
chooser_type 可选 dmenu / simple / none,none 会无条件授权,不再弹任何东西。
⚠️ 安全代价:任何能通过 RustDesk 连到本机的人都能直接拿到屏幕,本地不再有确认这道防线。需要保留确认就改回
simple。
四、验证方法
正常连接时,服务日志应出现:
response: 0, results: {"streams": [{position:[0,0], size:[3840,2160]}]} |
日志位置(关键):
| 组件 | 路径 |
|---|---|
| GUI | ~/.local/share/logs/RustDesk/rustdesk_rCURRENT.log |
| 服务端(连接事件真正写在这里) | ~/.local/share/logs/RustDesk/server/rustdesk_rCURRENT.log |
⚠️ 服务态下连接事件不会写进 GUI 日志。 不知道这点会误判成「连接根本没到达」,白白排查网络。
五、日常维护
每次改过显示器布局后(必做)
sed -i '/wayland-restore-token/d' ~/.config/rustdesk/RustDesk_local.toml |
之后首次连接会重新弹一次授权框(或按 3.4 的配置直接静默授权),成功后会写入一个匹配新布局的新 token,后续就正常了。
切换合成器(niri ↔ sway)后
需要把环境切过去,并让 --server 重新发现 display:
# 以 niri 为例 |
RustDesk 的
--server子进程不直接继承 session 环境,它会去读其他 rustdesk / portal 进程的environ来「发现」WAYLAND_DISPLAY和DISPLAY。所以必须先有一个该会话下的 rustdesk GUI 进程在跑,它才找得对;否则可能拿到另一个会话的值。
六、关键配置文件清单
| 路径 | 作用 |
|---|---|
/etc/systemd/system/rustdesk.service |
RustDesk 特权服务(源本 ~/rustdesk.service) |
~/.config/xdg-desktop-portal/niri-portals.conf |
niri 的 portal 后端选择(ScreenCast→wlr) |
~/.config/xdg-desktop-portal/sway-portals.conf |
sway 的 portal 后端选择 |
~/.config/xdg-desktop-portal-wlr/config |
跳过 chooser(chooser_type=none) |
~/.local/share/dbus-1/services/*.service |
7 个软链,让 nix portal 可被 dbus 激活 |
~/.config/rustdesk/RustDesk_local.toml |
含 wayland-restore-token(布局变化时要清) |
备份文件:
~/.config/xdg-desktop-portal/niri-portals.conf.bak~/.config/rustdesk/RustDesk_local.toml.bak
七、已排除的方案(别重复走)
| 方案 | 为什么不行 |
|---|---|
RUSTDESK_FORCED_DISPLAY_SERVER=x11 |
niri/sway 都是 rootless XWayland,实测 ffmpeg -f x11grab 抓到的是全黑(没有合成后的根窗口) |
| 换 gnome portal 后端 | 它的 ScreenCast 授权对话框可能依赖 gnome-shell;且没解决 RemoteDesktop 缺失的问题 |
| 只关掉一个显示器 | 无效。真凶是 restore token 里的过期坐标,不是显示器数量 |
装 rustdesk-server |
那是自建中继服务器(hbbs/hbbr),与客户端抓屏毫无关系 |
八、已知遗留问题
- 音频捕获未接上:服务日志每秒刷一次
ERROR [src/server/service.rs:308] Error of audio service: Connection refused (os error 111)
视频不受影响,但噪音较大,且远程听不到声音。 rustdesk-flutter(1.4.6)未构建成功:已写进home.nix,但 store 里只有.drv没有构建产物,profile 仍是 1.4.5。NixOS Wiki 记录过某些情况下rustdesk-flutter能工作而rustdesk不行 —— 如再遇到抓屏问题可以试试它。- 多显示器场景未完整验证:目前只在单屏下验证通过。双屏时要留意(先清 token,若失败则在授权框里改选
x=0的那块显示器)。
九、一句话总结
装 root 服务让 RustDesk 从 RemoteDesktop 分支切到 ScreenCast + uinput 分支,从而绕开了 niri/sway 都缺失的 RemoteDesktop 接口;再修好 portal 的后端选择和 dbus 激活,最后用 chooser_type=none 免掉本地点击。
