Ubuntu 无显示器远程桌面完美方案
适用环境:Ubuntu 20.04 + Intel 集成显卡 + GNOME 桌面 + ToDesk / RustDesk 等远程工具
问题描述
Linux 主机不接物理显示器时,远程桌面工具(ToDesk、RustDesk、向日葵等)提示 "No display" 或画面分辨率极低(如 1024x768),无法正常远程操作。
根本原因
Intel 集成显卡在没有物理显示器连接时,所有视频输出口(DP、HDMI)均为 disconnected 状态。X11/GNOME 检测不到任何可用的显示输出,远程桌面工具自然无法捕获屏幕画面。
解决思路
整个方案分为两步:
- 让虚拟显示器"存在" --- 通过内核启动参数强制启用一个 HDMI 输出
- 让虚拟显示器"好用" --- 通过后台服务自动将分辨率设为 1920x1080
第一步:内核参数 --- 强制启用虚拟 HDMI 输出
1.1 确认你的 HDMI 接口名称
bash
ls /sys/class/drm/
输出类似:card0-DP-1 card0-DP-2 card0-HDMI-A-1 card0-HDMI-A-2 ...
选一个你不常用 的 HDMI 口,比如 HDMI-A-1。
1.2 修改 GRUB 配置
bash
sudo cp /etc/default/grub /etc/default/grub.bak
sudo nano /etc/default/grub
找到 GRUB_CMDLINE_LINUX_DEFAULT 行,添加 video=HDMI-A-1:1920x1080e:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=HDMI-A-1:1920x1080e"
参数说明 :
HDMI-A-1是内核中的接口名,1920x1080是虚拟分辨率,末尾的e表示 force enable(强制启用,即使没有接显示器)。
1.3 更新 GRUB 并重启
bash
sudo update-grub
sudo reboot
1.4 验证
重启后执行:
bash
cat /proc/cmdline | grep video
xrandr | grep HDMI-1
应该能看到 HDMI-1 connected,即使没接物理显示器。
第二步:后台服务 --- 自动设置 1920x1080 分辨率
虽然第一步让 HDMI-1 显示为"已连接",但由于没有真实显示器提供 EDID 信息,GNOME 只会给它分配一个极低的默认分辨率(通常 1024x768)。需要一个后台服务来自动修正。
2.1 创建监听脚本
bash
sudo nano /usr/local/bin/display-monitor.sh
写入以下内容(纯 bash,无需 Python):
bash
#!/bin/bash
export DISPLAY=:0
export XAUTHORITY=/run/user/$(id -u)/gdm/Xauthority
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
LAST_HANDLE_TIME=0
# 获取 GNOME 显示配置的完整状态
get_display_state() {
gdbus call --session \
--dest org.gnome.Mutter.DisplayConfig \
--object-path /org/gnome/Mutter/DisplayConfig \
--method org.gnome.Mutter.DisplayConfig.GetCurrentState 2>/dev/null
}
# 通过 GNOME D-Bus 接口应用显示配置(method=1 临时模式,无需确认)
apply_config() {
local serial="$1"
local monitors="$2"
gdbus call --session \
--dest org.gnome.Mutter.DisplayConfig \
--object-path /org/gnome/Mutter/DisplayConfig \
--method org.gnome.Mutter.DisplayConfig.ApplyMonitorsConfig \
"$serial" 1 "$monitors" "{}" >/dev/null 2>&1
}
handle_display_change() {
# 防抖:5 秒内不重复处理(一次插拔可能触发多个事件)
local now=$(date +%s)
if (( now - LAST_HANDLE_TIME < 5 )); then
return
fi
LAST_HANDLE_TIME=$now
sleep 2
# 为虚拟 HDMI-1 添加 1920x1080 模式(已存在则忽略)
xrandr --newmode "1920x1080_60.00" 173.00 1920 2048 2248 2576 \
1080 1083 1088 1120 -hsync +vsync 2>/dev/null
xrandr --addmode HDMI-1 "1920x1080_60.00" 2>/dev/null
# 检查是否有物理显示器连接(排除虚拟的 HDMI-1)
PHYSICAL=$(xrandr 2>/dev/null \
| grep -E "^(DP-|HDMI-[^1])" \
| grep " connected " \
| head -1 | awk '{print $1}')
# 一次 D-Bus 调用获取全部状态,避免重复请求
STATE=$(get_display_state)
SERIAL=$(echo "$STATE" | grep -oP '\(uint32 \K\d+')
[ -z "$SERIAL" ] && return
if [ -n "$PHYSICAL" ]; then
# ---- 有物理显示器:物理屏做唯一主屏,关闭虚拟屏 ----
MODE=$(echo "$STATE" \
| grep -oP "'${PHYSICAL}'.*?'is-preferred': <true>" \
| grep -oP "'\d+x\d+@[\d.]+'" | head -1 | tr -d "'")
[ -z "$MODE" ] && return
apply_config "$SERIAL" \
"[(0, 0, 1.0, 0, true, [('${PHYSICAL}', '${MODE}', {})])]"
else
# ---- 无物理显示器:虚拟屏做唯一主屏 ----
CURRENT_RES=$(xrandr 2>/dev/null \
| grep "HDMI-1 connected" | grep -oP "\d+x\d+")
if [ -n "$CURRENT_RES" ] && [ "$CURRENT_RES" != "1920x1080" ]; then
apply_config "$SERIAL" \
"[(0, 0, 1.0, 0, true, [('HDMI-1', '1920x1080@59.962844848632812', {})])]"
fi
fi
}
# 启动时先执行一次,处理开机就没接物理显示器的情况
handle_display_change
# 然后持续监听热插拔事件
udevadm monitor --kernel --subsystem-match=drm 2>/dev/null | while read line; do
if echo "$line" | grep -q "change"; then
handle_display_change
fi
done
bash
sudo chmod +x /usr/local/bin/display-monitor.sh
2.2 创建 systemd 服务
bash
sudo nano /etc/systemd/system/fix-virtual-display.service
写入:
ini
[Unit]
Description=Auto fix virtual display on monitor hotplug
After=gdm.service graphical.target
[Service]
Type=simple
User=你的用户名
ExecStart=/usr/local/bin/display-monitor.sh
Restart=always
RestartSec=5
[Install]
WantedBy=graphical.target
将
User=你的用户名替换为你的实际用户名。
2.3 启用服务
bash
sudo systemctl daemon-reload
sudo systemctl enable fix-virtual-display.service
sudo systemctl start fix-virtual-display.service
验证运行状态:
bash
sudo systemctl status fix-virtual-display.service
最终效果
| 场景 | 行为 |
|---|---|
| 接了物理显示器 | 纯物理屏,无虚拟屏干扰 |
| 拔掉物理显示器 | 约 2 秒后自动切换为 1920x1080 虚拟屏 |
| 换了一个物理接口 | 自动识别新接口并设为主屏 |
| 重启(无物理显示器) | 开机后自动启用 1920x1080 虚拟屏 |
全程无需手动操作。
技术要点
为什么不能只用 xrandr?
GNOME 的窗口管理器 Mutter 会覆盖 xrandr 的设置。必须通过 Mutter 的 D-Bus 接口 org.gnome.Mutter.DisplayConfig.ApplyMonitorsConfig 来配置,GNOME 才会认可。
为什么用 method=1(临时模式)?
D-Bus 的 ApplyMonitorsConfig 有两种模式:
method=1(临时):立即生效,无需确认method=2(持久):弹出确认对话框,20 秒不点击会自动回退
必须用临时模式,原因有二:
- 持久模式会弹确认框。切换发生在无物理显示器时(人不在电脑前),没人能点确认,20 秒后就会回退。
- 临时模式不影响可靠性。虽然叫"临时",只是说不写入 GNOME 的持久配置文件,但我们的后台服务常驻运行,每次开机、每次热插拔都会自动重新设置,效果等同于持久。
为什么内核参数不可省略?
没有 video=HDMI-A-1:1920x1080e,HDMI-1 在无显示器时状态为 disconnected,GNOME 根本不会把它视为一个可用的显示输出,后续所有操作都无从谈起。
故障排除
虚拟屏分辨率不对:
bash
# 手动触发修复
xrandr --newmode "1920x1080_60.00" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync
xrandr --addmode HDMI-1 "1920x1080_60.00"
# 然后通过 D-Bus 应用(参考脚本中的 gdbus 部分)
服务未运行:
bash
sudo systemctl restart fix-virtual-display.service
journalctl -u fix-virtual-display.service -f
想恢复原状:
bash
sudo systemctl disable --now fix-virtual-display.service
sudo rm /usr/local/bin/display-monitor.sh
sudo rm /etc/systemd/system/fix-virtual-display.service
sudo cp /etc/default/grub.bak /etc/default/grub
sudo update-grub
sudo reboot