无头显示、欺骗器与 RustDesk 排障总结

这篇文章记录一次比较完整的无头显示排障过程。目标很简单:在 Linux 主机没有实体显示器的情况下,依然保持图形桌面可用,并让 RustDesk 能稳定连接。

最后的结论并不复杂,但中间确实绕了不少路:

  1. 问题核心不在 RustDesk 本身,而在显示链路。
  2. 在“独显 + DP 转 HDMI + HDMI 欺骗器”的方案下,Linux 的 DRM 层和 Xorg/RandR 层对输出状态的判断并不一致。
  3. RustDesk 关心的是图形桌面里有没有活跃显示器,不是 /sys/class/drm 里某个 connector 看起来是不是 connected
  4. 改成核显输出后,问题直接简单了很多。

初始问题

最开始的现象是:

  • 某 HDMI 欺骗器在 Windows 下工作正常;
  • 在 Linux 下表现异常;
  • 目标是让 Linux 在无实体显示器时仍能保持桌面可用,并供 RustDesk 远程连接。

一开始直觉上会怀疑很多地方:

  1. EDID 读取失败;
  2. 输出口没有被内核识别为 connected
  3. 图形会话没有真正绑定到这条输出链路。

这个方向后来证明基本是对的,只是问题并不在同一层。

早期诊断:先看 DRM 和 EDID

最开始用 get-edid | parse-edid 看过一次,结果并不好:

  • 多个 i2c bus 都读不到 EDID;
  • VBE 回退接口也失败。

这说明当时 Linux 没有从这条“显示器/欺骗器”链路上拿到稳定可用的 EDID。遇到这种情况,继续围着 get-edid 打转通常意义不大,应该直接去看 DRM 层和内核日志。

当时主要用到的命令如下:

1
2
3
4
5
6
7
8
echo "$XDG_SESSION_TYPE"
ls -1 /sys/class/drm
for f in /sys/class/drm/card*-*/status; do echo "$f: $(cat "$f")"; done
for f in /sys/class/drm/card*-*/enabled; do echo "$f: $(cat "$f" 2>/dev/null)"; done
for f in /sys/class/drm/card*-*/modes; do echo "== $f =="; cat "$f" 2>/dev/null; done
for f in /sys/class/drm/card*-*/edid; do echo "== $f =="; sudo hexdump -C "$f" | head; done
sudo dmesg | grep -iE 'drm|edid|hdmi|displayport|dp'
lspci | grep -E 'VGA|3D|Display'

排查过程中出现过几种很关键的现象:

  • 某些 HDMI/DP connector 在 /sys/class/drm/.../status 中全部显示 disconnected
  • 某个 DP 输出在 DRM 层显示 connected
  • 但同一时期,Xorg/RandR 层不一定能看到对应的可用输出。

这其实已经提前暴露了核心问题:不同图形层面对“有没有显示器”的判断并不总是一致。

问题链路:DP 转 HDMI 再接欺骗器

后来逐渐确认,实际使用的不是一条纯 HDMI 路径,而是:

DP -> HDMI 转接器 -> HDMI 欺骗器

这点非常重要。因为一旦中间多了转接器,事情就不再只是“插了个假显示器”那么简单,而是会引入:

  • 转接器本身的兼容性;
  • 显卡驱动对该链路的识别;
  • DRM/KMS 层与 Xorg 层之间的状态传递问题。

曾经一度确认到某个 cardX-DP-Y

  • status: connected
  • modes 里能看到 2560x14401920x1080 等分辨率;
  • edid 也是非空的。

这说明什么?

说明内核的 KMS/DRM 层并不是完全看不见这条链路。问题在于,内核能认到,不代表当前图形桌面一定会把它当成有效显示器。

中途试过 VKMS

中间还走过一段 VKMS 路线,也就是虚拟 KMS 显示器。

当时检查 VKMS 的方式大概是:

1
2
3
4
lsmod | grep vkms
ls /sys/class/drm | grep Virtual
cat /sys/class/drm/card*-Virtual-1/status
cat /sys/class/drm/card*-Virtual-1/modes

那时候已经能看到:

  • vkms 模块加载成功;
  • 存在 cardX-Virtual-1
  • status: connected
  • modes 里也有多种分辨率,甚至包括 2560x1440。

但最后没有把 VKMS 作为主方案,原因也很现实:

  • 目标并不是“有个软件虚拟屏就行”;
  • 还希望它尽量接近真实显示器;
  • RustDesk 和 GNOME/Xorg 的配合要尽量稳定;
  • 后来尝试在 GDM/Xorg 层面做全局固定配置时,还一度把图形会话弄得不太正常。

所以 VKMS 更像一条技术上可行、但不够符合实际使用目标的路线。

真实 EDID 固定方案:只解决了一半

为了尽量模拟真实显示器,后面又尝试过把真实显示器的 EDID 固定给指定输出口。

思路是:

  1. 从真实显示器抓原始 EDID;
  2. 放到 /lib/firmware/edid/
  3. 通过 GRUB 内核参数绑定到目标输出。

示意命令如下:

1
2
3
sudo mkdir -p /lib/firmware/edid
sudo cp /sys/class/drm/cardX-DP-Y/edid /lib/firmware/edid/real-monitor.bin
sudo hexdump -C /lib/firmware/edid/real-monitor.bin | head

GRUB 参数类似这样:

1
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=DP-5:e drm.edid_firmware=DP-5:edid/real-monitor.bin"

更新启动环境:

1
2
sudo update-initramfs -u
sudo update-grub

这一阶段的结论很关键:

  • 真实显示器接上时,GNOME/Xorg 工作正常;
  • 换成欺骗器后,DRM 层有时仍能看到 connected
  • 但 Xorg 中所有 RandR 输出依然可能全部显示 disconnected

也就是说,固定 EDID 可以让 DRM 层更像“真的插了显示器”,却不能保证 Xorg 一定接受这条独显输出链。

核心定位:RustDesk 看的是 Xorg 层

真正把问题打透的,是在 Xorg 会话环境里直接跑 xrandr --query

大致命令如下:

1
2
3
sudo -u <用户名> env DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority \
XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
xrandr --query

对比非常明显。

接真实显示器时:

  • xrandr --query 里能看到类似 DP-N connected primary 2560x1440+0+0 的输出。

换成独显链路上的欺骗器时:

  • xrandr --query 中所有输出都可能是 disconnected
  • 桌面尺寸虽然可能还保留着之前的数值,但实际上没有活跃输出。

这就是 RustDesk 报“没有显示器”的直接原因。

RustDesk 关心的是当前图形桌面里有没有活跃显示器,而不是 /sys/class/drm 里某个 connector 是否被底层驱动勉强识别成 connected

进一步用 xrandr --listproviders 看过之后,又确认当前 GNOME/Xorg 桌面跑在独显 provider 上,而核显是另一套输出路径。于是问题就更清楚了:

  • DRM 层识别到某条链路,不等于当前桌面真的在使用它;
  • 如果桌面、输出和欺骗器不在同一条 provider 路径上,RustDesk 还是会认为“没有显示器”。

关键转折:改走核显输出

后面的转折其实很简单:

  • 不再继续折腾独显输出;
  • 把 DP 适配器/欺骗器改插到核显的 DP 输出口。

结果是几乎立刻见效:

  • 不需要额外复杂配置;
  • 图形桌面可以正常工作;
  • RustDesk 也能正常识别显示器。

这一步实际上把很多原本叠加在一起的问题一起绕开了:

  1. 独显输出链路和转接器的兼容性;
  2. Xorg provider 与当前桌面绑定关系;
  3. DRM 层和 Xorg 层状态不一致带来的额外复杂度。

所以这次排障最重要的判断,不是某个参数怎么配,而是及时放弃错误路线。

如果长期目标是“无头 + GNOME + RustDesk + 尽量接近真实显示器”,那么核显输出通常比继续死磕独显路径更合理。

当前剩下的问题:分辨率和刷新率偏低

切到核显路径后,系统已经能稳定工作了,剩下的问题只剩显示模式不够理想。

新的 DP 适配器默认能力偏保守:

  • 2K 只有 30Hz;
  • 4K 只有 17Hz。

看到这种情况,判断重点是:到底是 EDID 太保守,还是链路带宽本身就不够。

如果只是 EDID 保守,那么还有调整空间:

  • 先用 xrandr 手工添加更高模式;
  • 如果稳定,再考虑做自动化脚本或者自定义 EDID 固化。

如果链路带宽本来就不够,那么即使强行加模式,也大概率会出现:

  • 模式设置失败;
  • 黑屏;
  • 闪回低刷新率;
  • 根本点不亮。

换句话说,所谓“强制调”只能释放硬件本来就有的能力,不能突破物理上限。

用 xrandr 手工加一个 2K60 模式

当前最值得做的一步,是先手工测试一次 2560x1440@60

先确认当前输出名:

1
2
sudo -u <用户名> env DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority \
xrandr --query

生成 2560x1440@60 的时序:

1
cvt -r 2560 1440 60

一般会得到类似下面这样的 modeline:

1
Modeline "2560x1440R" 241.50 2560 2608 2640 2720 1440 1443 1448 1481 +hsync -vsync

然后注意一个很容易踩的坑:xrandr --newmode 后面不要把 Modeline 这个词原样带上。

正确写法:

1
2
3
4
5
6
sudo -u <用户名> env DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority \
xrandr --newmode "2560x1440R" 241.50 2560 2608 2640 2720 1440 1443 1448 1481 +hsync -vsync
sudo -u <用户名> env DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority \
xrandr --addmode DP-1 "2560x1440R"
sudo -u <用户名> env DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority \
xrandr --output DP-1 --mode "2560x1440R"

完整模板就是:

1
2
3
4
5
6
7
cvt -r 2560 1440 60
sudo -u <用户名> env DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority \
xrandr --newmode "2560x1440R" 241.50 2560 2608 2640 2720 1440 1443 1448 1481 +hsync -vsync
sudo -u <用户名> env DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority \
xrandr --addmode <输出名> "2560x1440R"
sudo -u <用户名> env DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority \
xrandr --output <输出名> --mode "2560x1440R"

错误示例则是:

1
xrandr --newmode Modeline "2560x1440R" ...

这种写法会把 Modeline 当成参数的一部分,直接导致解析错误。

这次排障的核心经验

整个过程里,我觉得最值得记下来的不是具体命令,而是判断顺序:

  1. 先分清楚自己在看的是 DRM/KMS 层,还是 Xorg/RandR 层;
  2. 不要把 /sys/class/drm 里的 connected 直接等同于“桌面里真的有显示器”;
  3. RustDesk 的报错往往是在反映图形会话状态,而不是在反映底层内核状态;
  4. 遇到“独显 + 转接器 + 欺骗器 + 多 provider”这种链路时,要尽早怀疑兼容性问题;
  5. 如果换一条更简单、更一致的输出路径就能解决,通常比继续堆配置更值得。

到这里,其实问题已经从“系统级图形链路不通”,收缩成了“怎么把默认模式调到更理想的分辨率/刷新率”。

这类问题至少已经不是架构性故障,而只是参数和硬件能力边界的问题了。


无头显示、欺骗器与 RustDesk 排障总结
http://example.com/2026/03/27/headless-display-rustdesk/
作者
Ding Yi
发布于
2026年3月27日
许可协议