Ubuntu 无显示器远程桌面完美方案

Ubuntu 无显示器远程桌面完美方案

适用环境:Ubuntu 20.04 + Intel 集成显卡 + GNOME 桌面 + ToDesk / RustDesk 等远程工具

问题描述

Linux 主机不接物理显示器时,远程桌面工具(ToDesk、RustDesk、向日葵等)提示 "No display" 或画面分辨率极低(如 1024x768),无法正常远程操作。

根本原因

Intel 集成显卡在没有物理显示器连接时,所有视频输出口(DP、HDMI)均为 disconnected 状态。X11/GNOME 检测不到任何可用的显示输出,远程桌面工具自然无法捕获屏幕画面。

解决思路

整个方案分为两步:

  1. 让虚拟显示器"存在" --- 通过内核启动参数强制启用一个 HDMI 输出
  2. 让虚拟显示器"好用" --- 通过后台服务自动将分辨率设为 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 秒不点击会自动回退

必须用临时模式,原因有二:

  1. 持久模式会弹确认框。切换发生在无物理显示器时(人不在电脑前),没人能点确认,20 秒后就会回退。
  2. 临时模式不影响可靠性。虽然叫"临时",只是说不写入 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
相关推荐
Vect__8 小时前
深刻理解进程、线程、程序
linux
末日汐9 小时前
传输层协议UDP
linux·网络·udp
zzzsde11 小时前
【Linux】库的制作和使用(3)ELF&&动态链接
linux·运维·服务器
CQU_JIAKE11 小时前
4.3【A]
linux·运维·服务器
qing2222222211 小时前
Linux中修改mysql数据表
linux·运维·mysql
Alvin千里无风11 小时前
在 Ubuntu 上从源码安装 Nanobot:轻量级 AI 助手完整指南
linux·人工智能·ubuntu
杨云龙UP12 小时前
Oracle 中 NOMOUNT、MOUNT、OPEN 怎么理解? 在不同场景下如何操作?_20260402
linux·运维·数据库·oracle
Amctwd12 小时前
【Linux】OpenCode 安装教程
linux·运维·服务器
wwj888wwj13 小时前
Docker基础(复习)
java·linux·运维·docker
paldier13 小时前
rootfs挂载失败(error -5)的一个可能
linux