setup-desktop: configure the headless display
x11vnc mirrors :0, but with no monitor attached the connector has no EDID and no CRTC, so GNOME renders nothing and the remote desktop is black. Reproduced the hard way on this box: the desktop only worked because the session had started while a screen was plugged in, and survived exactly until the next restart. The step captures a connected monitor's EDID, installs it as drm.edid_firmware with video=<connector>:1920x1080e so the connector reports permanently attached, and adds a login-time hook to raise the resolution — the replayed EDID's *preferred* mode is the captured panel's native one, which can be tiny, and GNOME picks preferred. monitors.xml is the documented override but its monitor matching did not take. The EDID can only be captured from a screen that is plugged in, so a headless run skips with instructions rather than pretending to succeed. Re-running is safe: GRUB is left alone once the argument is present, and the mode setter no-ops when the mode is already right. officer-set-display.sh discovers the output and the largest mode within a cap rather than hardcoding either, since setup runs before X exists and cannot know them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f0a0166ecd
commit
592cc72f85
Executable
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Pick the largest sane mode on whichever output is connected, at session start.
|
||||||
|
#
|
||||||
|
# With no monitor attached the connector's EDID comes from drm.edid_firmware (see the kernel command
|
||||||
|
# line). That EDID advertises plenty of modes but its *preferred* one is the captured screen's native
|
||||||
|
# resolution, which may be tiny — GNOME picks preferred, so an 800x480 panel yields an 800x480
|
||||||
|
# desktop. monitors.xml is the documented override but its monitor matching proved unreliable here,
|
||||||
|
# so set the mode directly. No-ops when the mode is already right, so it is safe to run repeatedly.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
MAX_WIDTH="${OFFICER_MAX_WIDTH:-1920}"
|
||||||
|
|
||||||
|
# The session's X server may not be up yet when autostart fires.
|
||||||
|
for _ in $(seq 1 20); do
|
||||||
|
xrandr --query >/dev/null 2>&1 && break
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
|
xrandr --query >/dev/null 2>&1 || exit 0
|
||||||
|
|
||||||
|
output=$(xrandr --query | awk '/ connected/ { print $1; exit }')
|
||||||
|
[ -n "$output" ] || exit 0
|
||||||
|
|
||||||
|
# Modes are listed under the output but are not reliably sorted, so pick the widest that fits.
|
||||||
|
mode=$(
|
||||||
|
xrandr --query \
|
||||||
|
| sed -n "/^${output} connected/,/^[^ ]/p" \
|
||||||
|
| awk '/^[[:space:]]+[0-9]+x[0-9]+/ { print $1 }' \
|
||||||
|
| awk -F x -v max="$MAX_WIDTH" '
|
||||||
|
{ w = $1 + 0; h = $2 + 0 }
|
||||||
|
w <= max && (w > bw || (w == bw && h > bh)) { bw = w; bh = h; best = $0 }
|
||||||
|
END { if (best) print best }'
|
||||||
|
)
|
||||||
|
[ -n "$mode" ] || exit 0
|
||||||
|
|
||||||
|
current=$(xrandr --query | awk -v o="$output" '$1 == o { print $3 }' | cut -d+ -f1)
|
||||||
|
[ "$current" = "$mode" ] && exit 0
|
||||||
|
|
||||||
|
xrandr --output "$output" --mode "$mode" || true
|
||||||
@@ -15,7 +15,7 @@ echo ""
|
|||||||
DESKTOP_USER="$(whoami)"
|
DESKTOP_USER="$(whoami)"
|
||||||
|
|
||||||
# --- Step 1: Install GNOME desktop + GDM + x11vnc ---
|
# --- Step 1: Install GNOME desktop + GDM + x11vnc ---
|
||||||
echo "[1/5] Installing ubuntu-desktop, GDM, x11vnc..."
|
echo "[1/6] Installing ubuntu-desktop, GDM, x11vnc..."
|
||||||
sudo apt update -qq
|
sudo apt update -qq
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt install -y -qq \
|
sudo DEBIAN_FRONTEND=noninteractive apt install -y -qq \
|
||||||
ubuntu-desktop \
|
ubuntu-desktop \
|
||||||
@@ -29,7 +29,7 @@ echo " Done."
|
|||||||
# created and the desktop could not authenticate.
|
# created and the desktop could not authenticate.
|
||||||
|
|
||||||
# --- Step 2: Force GDM onto Xorg + enable auto-login (x11vnc cannot mirror Wayland) ---
|
# --- Step 2: Force GDM onto Xorg + enable auto-login (x11vnc cannot mirror Wayland) ---
|
||||||
echo "[2/5] Forcing Xorg session and auto-login in GDM..."
|
echo "[2/6] Forcing Xorg session and auto-login in GDM..."
|
||||||
GDM_CONF=/etc/gdm3/custom.conf
|
GDM_CONF=/etc/gdm3/custom.conf
|
||||||
sudo mkdir -p /etc/gdm3
|
sudo mkdir -p /etc/gdm3
|
||||||
[ -f "$GDM_CONF" ] || echo "[daemon]" | sudo tee "$GDM_CONF" > /dev/null
|
[ -f "$GDM_CONF" ] || echo "[daemon]" | sudo tee "$GDM_CONF" > /dev/null
|
||||||
@@ -52,7 +52,7 @@ set_gdm_key AutomaticLogin "$DESKTOP_USER"
|
|||||||
echo " Xorg forced (WaylandEnable=false), auto-login as $DESKTOP_USER."
|
echo " Xorg forced (WaylandEnable=false), auto-login as $DESKTOP_USER."
|
||||||
|
|
||||||
# --- Step 3: Make GDM the default display manager ---
|
# --- Step 3: Make GDM the default display manager ---
|
||||||
echo "[3/5] Setting GDM as the default display manager..."
|
echo "[3/6] Setting GDM as the default display manager..."
|
||||||
echo "/usr/sbin/gdm3" | sudo tee /etc/X11/default-display-manager > /dev/null
|
echo "/usr/sbin/gdm3" | sudo tee /etc/X11/default-display-manager > /dev/null
|
||||||
sudo systemctl enable gdm3 >/dev/null 2>&1 || sudo systemctl enable gdm >/dev/null 2>&1 || true
|
sudo systemctl enable gdm3 >/dev/null 2>&1 || sudo systemctl enable gdm >/dev/null 2>&1 || true
|
||||||
sudo systemctl set-default graphical.target >/dev/null 2>&1 || true
|
sudo systemctl set-default graphical.target >/dev/null 2>&1 || true
|
||||||
@@ -61,7 +61,7 @@ sudo systemctl disable lightdm >/dev/null 2>&1 || true
|
|||||||
echo " Done (takes effect on next reboot)."
|
echo " Done (takes effect on next reboot)."
|
||||||
|
|
||||||
# --- Step 4: Install Brave browser (native .deb, not snap) ---
|
# --- Step 4: Install Brave browser (native .deb, not snap) ---
|
||||||
echo "[4/5] Installing Brave browser..."
|
echo "[4/6] Installing Brave browser..."
|
||||||
if ! command -v brave-browser-stable > /dev/null 2>&1; then
|
if ! command -v brave-browser-stable > /dev/null 2>&1; then
|
||||||
sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg \
|
sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg \
|
||||||
https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
|
https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
|
||||||
@@ -81,7 +81,7 @@ echo '--password-store=basic' | sudo tee /etc/brave/brave-flags.conf > /dev/null
|
|||||||
echo " Done."
|
echo " Done."
|
||||||
|
|
||||||
# --- Step 5: Remove GNOME Keyring + set default browser (prevents password prompts on login) ---
|
# --- Step 5: Remove GNOME Keyring + set default browser (prevents password prompts on login) ---
|
||||||
echo "[5/5] Removing GNOME Keyring and setting default browser..."
|
echo "[5/6] Removing GNOME Keyring and setting default browser..."
|
||||||
sudo apt remove -y --purge gnome-keyring > /dev/null 2>&1 || true
|
sudo apt remove -y --purge gnome-keyring > /dev/null 2>&1 || true
|
||||||
rm -rf ~/.local/share/keyrings
|
rm -rf ~/.local/share/keyrings
|
||||||
if command -v brave-browser-stable > /dev/null 2>&1; then
|
if command -v brave-browser-stable > /dev/null 2>&1; then
|
||||||
@@ -91,6 +91,71 @@ else
|
|||||||
echo " No supported browser found, skipping default."
|
echo " No supported browser found, skipping default."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- Step 6: Headless display (keep a desktop when no monitor is attached) ---
|
||||||
|
echo "[6/6] Configuring the headless display..."
|
||||||
|
|
||||||
|
# x11vnc mirrors :0, but with nothing plugged in the connector has no EDID and no CRTC, so GNOME
|
||||||
|
# renders nothing and the mirror is black. Replaying a real monitor's EDID makes the connector look
|
||||||
|
# permanently attached. The EDID has to be captured from a screen that is plugged in *now* — there is
|
||||||
|
# nothing to copy otherwise — so this step is skipped on a headless run and can be re-run later.
|
||||||
|
EDID_DIR=/lib/firmware/edid
|
||||||
|
EDID_FILE="$EDID_DIR/officer-screen.bin"
|
||||||
|
CONNECTED_SYSFS=""
|
||||||
|
for c in /sys/class/drm/card*-*/status; do
|
||||||
|
[ "$(cat "$c" 2>/dev/null)" = "connected" ] || continue
|
||||||
|
CONNECTED_SYSFS="$(basename "$(dirname "$c")")"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$CONNECTED_SYSFS" ] && [ ! -f "$EDID_FILE" ]; then
|
||||||
|
echo " ! No display connected and no EDID saved — skipping."
|
||||||
|
echo " Plug a monitor in and re-run this script to capture one, otherwise the remote"
|
||||||
|
echo " desktop will be black whenever nothing is attached."
|
||||||
|
else
|
||||||
|
# cardN-HDMI-A-1 -> HDMI-A-1, which is the name the kernel parameters use (card numbering can
|
||||||
|
# change between boots; the connector name does not).
|
||||||
|
if [ -n "$CONNECTED_SYSFS" ]; then
|
||||||
|
CONNECTOR="${CONNECTED_SYSFS#*-}"
|
||||||
|
sudo mkdir -p "$EDID_DIR"
|
||||||
|
sudo cp "/sys/class/drm/$CONNECTED_SYSFS/edid" "$EDID_FILE"
|
||||||
|
echo " Captured EDID from $CONNECTOR ($(stat -c%s "$EDID_FILE") bytes)."
|
||||||
|
echo "$CONNECTOR" | sudo tee "$EDID_DIR/officer-connector" > /dev/null
|
||||||
|
else
|
||||||
|
CONNECTOR="$(cat "$EDID_DIR/officer-connector" 2>/dev/null || echo HDMI-A-1)"
|
||||||
|
echo " Reusing the saved EDID for $CONNECTOR."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# drm.edid_firmware makes the connector report that EDID; the trailing "e" on video= forces it
|
||||||
|
# enabled with nothing attached. amdgpu is not in the initramfs, so /lib/firmware is readable by
|
||||||
|
# the time the driver loads and no initramfs rebuild is needed.
|
||||||
|
KERNEL_ARGS="drm.edid_firmware=$CONNECTOR:edid/officer-screen.bin video=$CONNECTOR:1920x1080e"
|
||||||
|
if grep -q "drm.edid_firmware=" /etc/default/grub; then
|
||||||
|
echo " GRUB already carries a forced EDID — leaving it alone."
|
||||||
|
else
|
||||||
|
sudo cp /etc/default/grub "/etc/default/grub.bak-$(date +%Y%m%d%H%M%S)"
|
||||||
|
CURRENT=$(grep "^GRUB_CMDLINE_LINUX_DEFAULT=" /etc/default/grub | sed 's/^[^"]*"//; s/"$//')
|
||||||
|
sudo sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=.*|GRUB_CMDLINE_LINUX_DEFAULT=\"${CURRENT:+$CURRENT }$KERNEL_ARGS\"|" /etc/default/grub
|
||||||
|
sudo update-grub > /dev/null 2>&1
|
||||||
|
echo " Added the forced EDID to the kernel command line (takes effect on reboot)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The EDID's preferred mode is whatever the captured panel was, which may be small. GNOME picks
|
||||||
|
# preferred, so raise it at session start.
|
||||||
|
install -d "$HOME/.local/bin" "$HOME/.config/autostart"
|
||||||
|
install -m 755 "$(dirname "$0")/officer-set-display.sh" "$HOME/.local/bin/officer-set-display.sh"
|
||||||
|
cat > "$HOME/.config/autostart/officer-set-display.desktop" <<AUTOSTART
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Officer display mode
|
||||||
|
Comment=Raise the headless display to its largest usable mode at login
|
||||||
|
Exec=$HOME/.local/bin/officer-set-display.sh
|
||||||
|
X-GNOME-Autostart-enabled=true
|
||||||
|
NoDisplay=true
|
||||||
|
AUTOSTART
|
||||||
|
echo " Installed the login-time mode setter."
|
||||||
|
fi
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
# --- Cleanup old systemd service if it exists ---
|
# --- Cleanup old systemd service if it exists ---
|
||||||
if systemctl list-unit-files officer-vnc.service &>/dev/null; then
|
if systemctl list-unit-files officer-vnc.service &>/dev/null; then
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
Reference in New Issue
Block a user