diff --git a/docs/FOLLOWUPS.md b/docs/FOLLOWUPS.md index 08ed278..84f9cb7 100644 --- a/docs/FOLLOWUPS.md +++ b/docs/FOLLOWUPS.md @@ -249,9 +249,12 @@ TROUBLESHOOTING.md §13. **Done so far**: the *desktop* piece of the x11 backend is now reproducible. `install.sh` detects `capture = x11` (via `capture_backend_is_x11`) and installs -+ enables `files/headless-desktop.service` (Openbox on `:0`) so the headless -Xorg has a window manager to render — without it, capture is a black screen. -`status.sh` gained a matching check (FAIL if no WM on `:0`). Still outstanding: ++ enables `headless-desktop.service` so the headless Xorg has a desktop to +render — without it, capture is a black screen. Desktop is selectable via +`HEADLESS_DESKTOP`: `gnome` (default — full Ubuntu GNOME session, X11 path) or +`openbox` (lightweight bare WM); each ships as its own unit template +(`files/headless-desktop-{gnome,openbox}.service`). `status.sh` gained a matching +check (FAIL if no desktop on `:0`, and reports which one). Still outstanding: the `--backend x11|wlr` flag/auto-detect, the `config.sh` x11 conf variant, and shipping the `xorg-headless.service` + `xorg-headless.conf` templates. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index c56b80f..a3bccff 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -490,12 +490,19 @@ DISPLAY=:0 xlsclients # only 'sunshine' = no WM/des DISPLAY=:0 xprop -root _NET_SUPPORTING_WM_CHECK # 'not found' = no window manager ``` -Fix: run a window manager on `:0`. The installer ships a `headless-desktop.service` -(Openbox) for exactly this and enables it whenever it detects `capture = x11`: +Fix: run a desktop on `:0`. The installer ships a `headless-desktop.service` +for exactly this and enables it whenever it detects `capture = x11`. It defaults +to a full **GNOME** session (`gnome-session --session=ubuntu`, forced to the X11 +path); set `HEADLESS_DESKTOP=openbox` for a lightweight bare WM instead (lower +overhead, but no panel/launcher — right-click menu only). The unit forces +`XDG_SESSION_TYPE=x11` and does **not** wrap GNOME in `dbus-run-session` — GNOME +must share the systemd *user* bus the service already inherits. ```bash systemctl --user enable --now headless-desktop.service DISPLAY=:0 xprop -root _NET_SUPPORTING_WM_CHECK # now reports a 'window id' +# read the running desktop's name off that window: +DISPLAY=:0 xprop -id _NET_WM_NAME # e.g. "GNOME Shell" ``` `status.sh` checks for this directly: with `capture = x11` it now FAILs if no diff --git a/files/headless-desktop-gnome.service b/files/headless-desktop-gnome.service new file mode 100644 index 0000000..a2b29d3 --- /dev/null +++ b/files/headless-desktop-gnome.service @@ -0,0 +1,30 @@ +[Unit] +Description=GNOME session on the headless Xorg (gives Sunshine something to capture) +# Without a desktop running on :0, Sunshine's x11 capture grabs an empty black +# X root window — pairing, NVENC, and input all work but the client sees only +# black. This runs a full GNOME session on :0 so the stream shows a real +# desktop. Only relevant to the X11/NVENC capture backend (capture = x11); the +# wlr backend's compositor renders for itself. +Requires=xorg-headless.service +After=xorg-headless.service +PartOf=graphical-session.target + +[Service] +Type=simple +# Force the X11 session path (not Wayland) onto the existing headless Xorg. +# No dbus-run-session wrapper: GNOME integrates with the systemd *user* bus, +# which this service already inherits — a fresh bus would break that. +Environment=DISPLAY=:0 +Environment=XDG_SESSION_TYPE=x11 +Environment=XDG_CURRENT_DESKTOP=ubuntu:GNOME +Environment=GNOME_SHELL_SESSION_MODE=ubuntu +# Wait for the X server to accept connections before launching the session. +ExecStartPre=/bin/sh -c 'for i in $(seq 1 20); do xset -display :0 -q >/dev/null 2>&1 && exit 0; sleep 0.5; done; exit 1' +ExecStart=/usr/bin/gnome-session --session=ubuntu +Restart=on-failure +RestartSec=2s + +[Install] +# Lingering user manager reaches default.target without a graphical login, +# matching the headless-boot drop-in pattern used for Sunshine. +WantedBy=default.target diff --git a/files/headless-desktop.service b/files/headless-desktop-openbox.service similarity index 100% rename from files/headless-desktop.service rename to files/headless-desktop-openbox.service diff --git a/install.sh b/install.sh index 0fdde47..9d86432 100755 --- a/install.sh +++ b/install.sh @@ -59,6 +59,9 @@ Environment overrides: HEADLESS_HOSTS Comma-separated hostnames that default to headless mode. Unset by default; anything not listed defaults to mirror. Override per-invocation with --headless or --mirror. + HEADLESS_DESKTOP Desktop to render on the headless Xorg :0 for the X11/NVENC + capture path (capture = x11): 'gnome' (default) or 'openbox' + (lightweight, for minimal hosts). Ignored on wlr/kms. EOF } @@ -194,7 +197,7 @@ main() { # (this backend is hand-configured; see FOLLOWUPS.md P3). Harmless no-op on # the wlr/kms backends, whose capture source renders for itself. if capture_backend_is_x11; then - step "Installing headless desktop (Openbox on :0 for the X11 capture path)" + step "Installing headless desktop (${HEADLESS_DESKTOP:-gnome} on :0 for the X11 capture path)" install_headless_desktop fi # NOTE: the headless prestart drop-in needs the sunshine unit to already diff --git a/lib/headless.sh b/lib/headless.sh index 0f4e8bc..da37d93 100644 --- a/lib/headless.sh +++ b/lib/headless.sh @@ -48,30 +48,52 @@ capture_backend_is_x11() { HEADLESS_DESKTOP_UNIT="headless-desktop.service" -# Pull the lightweight WM (Openbox) we drive on :0, plus xsetroot for the -# solid-background touch. Idempotent — pkg_install skips what's present. +# Which desktop to render on the headless Xorg :0. Default is a full GNOME +# session (the familiar Ubuntu desktop). Set HEADLESS_DESKTOP=openbox for a +# lightweight bare WM instead — lower overhead, better for a truly minimal or +# low-power host, but no panel/launcher out of the box (right-click menu only). +: "${HEADLESS_DESKTOP:=gnome}" + +# Install the packages the chosen desktop needs. Idempotent — pkg_install +# skips what's already present. ensure_headless_desktop_packages() { - command -v openbox-session >/dev/null 2>&1 || pkg_install openbox - if ! command -v xsetroot >/dev/null 2>&1; then - case "$DISTRO" in - debian) pkg_install x11-xserver-utils ;; - arch) pkg_install xorg-xsetroot ;; - esac - fi + case "$HEADLESS_DESKTOP" in + gnome) + case "$DISTRO" in + debian) pkg_install gnome-session gnome-shell ubuntu-session ;; + arch) pkg_install gnome-shell gnome-session ;; + esac + ;; + openbox) + command -v openbox-session >/dev/null 2>&1 || pkg_install openbox + # xsetroot paints a solid root so the bare desktop is visibly non-black. + if ! command -v xsetroot >/dev/null 2>&1; then + case "$DISTRO" in + debian) pkg_install x11-xserver-utils ;; + arch) pkg_install xorg-xsetroot ;; + esac + fi + ;; + *) + err "Unknown HEADLESS_DESKTOP='$HEADLESS_DESKTOP' (expected: gnome | openbox)" + return 1 + ;; + esac } -# Install + enable the Openbox session that renders onto the headless Xorg -# display (:0), so the X11/NVENC capture path has something to show instead of -# a black root window. Idempotent: safe to re-run. Bound to xorg-headless.service +# Install + enable a desktop session that renders onto the headless Xorg +# display (:0), so the X11/NVENC capture path shows a real desktop instead of a +# black root window. Picks the GNOME or Openbox unit template per +# $HEADLESS_DESKTOP. Idempotent: safe to re-run. Bound to xorg-headless.service # and to default.target (the lingering-user manager reaches it without a login). install_headless_desktop() { - local src="$SCRIPT_DIR/files/headless-desktop.service" + local src="$SCRIPT_DIR/files/headless-desktop-${HEADLESS_DESKTOP}.service" if [[ ! -f "$src" ]]; then - err "Desktop unit source missing: $src" + err "Desktop unit source missing: $src (HEADLESS_DESKTOP=$HEADLESS_DESKTOP)" return 1 fi - ensure_headless_desktop_packages + ensure_headless_desktop_packages || return 1 local unit_dir="$HOME/.config/systemd/user" mkdir -p "$unit_dir" @@ -82,7 +104,7 @@ install_headless_desktop() { # headless box with no session yet, fall back to enable-only so it comes up # on next boot via default.target. if systemctl --user enable --now "$HEADLESS_DESKTOP_UNIT" >/dev/null 2>&1; then - ok "Enabled $HEADLESS_DESKTOP_UNIT (Openbox session on :0)" + ok "Enabled $HEADLESS_DESKTOP_UNIT ($HEADLESS_DESKTOP session on :0)" else systemctl --user enable "$HEADLESS_DESKTOP_UNIT" >/dev/null 2>&1 || true warn "Installed $HEADLESS_DESKTOP_UNIT but couldn't start it now — it will start on next login/boot." diff --git a/status.sh b/status.sh index d65fbb7..dc43879 100755 --- a/status.sh +++ b/status.sh @@ -186,15 +186,19 @@ case "${CAP:-}" in # yet have no WM/desktop drawing anything — Sunshine then captures an empty # black root window (pairing/NVENC/input all work; client sees only black). if command -v xprop >/dev/null 2>&1; then - wm_check=$( (if [[ $EUID -eq 0 && "$SUSER" != "$(id -un)" ]]; then sudo -u "$SUSER" DISPLAY=:0 xprop -root _NET_SUPPORTING_WM_CHECK; else DISPLAY=:0 xprop -root _NET_SUPPORTING_WM_CHECK; fi) 2>/dev/null) - if grep -q 'window id' <<<"$wm_check"; then - pass "a window manager is running on :0 (something to capture)" + _xp(){ if [[ $EUID -eq 0 && "$SUSER" != "$(id -un)" ]]; then sudo -u "$SUSER" DISPLAY=:0 xprop "$@"; else DISPLAY=:0 xprop "$@"; fi; } + wm_win=$(_xp -root _NET_SUPPORTING_WM_CHECK 2>/dev/null | grep -o '0x[0-9a-f]*' | head -1) + if [[ -n $wm_win ]]; then + # Read the WM's advertised name (e.g. "GNOME Shell", "Openbox") so the + # report says which desktop is actually rendering, not just that one is. + wm_name=$(_xp -id "$wm_win" _NET_WM_NAME 2>/dev/null | sed -n 's/.*= "\(.*\)"/\1/p') + pass "a desktop is running on :0${wm_name:+ (${wm_name})} — something to capture" else fail "no window manager on :0 — capture will be a black screen" \ - "Start a desktop on the headless Xorg: 'systemctl --user enable --now headless-desktop.service' (install.sh installs it for the x11 backend). See TROUBLESHOOTING.md §13." + "Start a desktop on the headless Xorg: 'systemctl --user enable --now headless-desktop.service' (install.sh installs it for the x11 backend; HEADLESS_DESKTOP=gnome|openbox). See TROUBLESHOOTING.md §13." fi else - note "xprop not installed — skipping window-manager-on-:0 check (install x11-utils to enable it)" + note "xprop not installed — skipping desktop-on-:0 check (install x11-utils to enable it)" fi # wlr env leaking into an x11 unit (stale sway drop-in — TROUBLESHOOTING §13) if [[ -n $UNIT ]]; then