#!/bin/bash
# Usage: linuxdoors-gnome51-vnc [WxH] [vnc-password]
# Starts (or reuses, if already running) a real headless GNOME Shell 51
# session and exposes it over VNC on 127.0.0.1:5900 via
# gnome-remote-desktop. Run this AS the target user (not root) with a
# real XDG_RUNTIME_DIR/DBUS_SESSION_BUS_ADDRESS already set -- i.e.
# inside a real logind session for that user (a plain `su -l <user>`
# after `loginctl enable-linger <user>` is enough; a full graphical
# login is not required).
set -e
GEOM="${1:-1920x1080}"
# Real fix (2026-09-19): this used to default to the literal password
# "linuxdoors" whenever the 2nd arg was omitted, and unconditionally
# reset the VNC password to whatever PASS ended up being on every single
# call -- including a bare resolution-only call. That's fine for
# x2goclient's own two call sites (both always pass the session's real
# password explicitly, to keep it consistent across a resize-triggered
# restart), but it means ANY other caller that only wants to change the
# resolution -- e.g. linuxdoors-gnome51-resolution below, invoked from
# INSIDE the running session itself, which has no reason to know or care
# what password an already-connected x2goclient is using -- would
# silently clobber that password and drop the existing connection's
# auth out from under it. Now: no 2nd arg at all means "leave the
# password alone", full stop.
PASS="${2:-}"
export PREFIX=/opt/gnome51
export PATH="$PREFIX/bin:$PATH"
export LD_LIBRARY_PATH="$PREFIX/lib"
export XDG_DATA_DIRS="$PREFIX/share:/usr/share"
export GI_TYPELIB_PATH="$PREFIX/lib/girepository-1.0"

if [ -z "$XDG_RUNTIME_DIR" ] || [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
  echo "linuxdoors-gnome51-vnc: run this inside a real user session" \
       "(XDG_RUNTIME_DIR/DBUS_SESSION_BUS_ADDRESS must already be set)" >&2
  exit 1
fi

systemctl --user start pipewire.socket pipewire.service >/dev/null 2>&1 || true
# Real race found and fixed here (2026-09-19, during the x2goclient
# GNOME 51 (VNC) integration): "systemctl --user start" for a socket
# unit returns as soon as the socket exists, not once the actual
# pipewire daemon behind it is ready to negotiate a client connection --
# gnome-remote-desktop connecting immediately after can lose the race
# and fail with "Failed to start screen cast stream: ... Couldn't
# connect pipewire context". A short, fixed sleep is simpler and just as
# effective as polling for real readiness here.
sleep 2

# Real, much bigger root cause found and fixed here too (2026-09-19,
# same debugging pass, AFTER the sleep above): LinuxDoors' PipeWire
# stack has never included a session manager (wireplumber) at all --
# confirmed live via "systemctl --user status wireplumber" reporting
# "Unit wireplumber.service could not be found." Without a session
# manager, nothing ever links a Stream/Output/Video node (gnome-shell's
# own screencast producer) to a Stream/Input/Video node (grd's
# consumer) -- `pw-cli list-objects` showed both nodes present but ZERO
# Link objects between them. The VNC connection itself worked
# perfectly (real handshake, real window, even a real, separate
# Xwayland test client rendered fine on its own display) -- the stream
# was just never wired up, so grd faithfully encoded and served a
# permanently blank/never-updated frame forever. Installed via the
# Debian bridge (`deb install wireplumber`) on .101 and enabled with
# `systemctl --user enable wireplumber.service` (creates a
# `pipewire.service.wants/` symlink, so it now starts automatically
# alongside pipewire with no script changes needed) -- confirmed fixed:
# a real Link object appears the moment a VNC client connects, and the
# actual GNOME 51 overview (top bar, clock, search, wallpaper, the
# first-login Welcome tour) renders correctly over VNC. This line is
# added defensively for any future from-scratch account/session that
# hasn't had the one-time `enable` applied yet.
systemctl --user start wireplumber.service >/dev/null 2>&1 || true

# linuxdoors-gnome51-sessionmanager (2026-09-20, real user report: "the
# gnome 51 logoff not working") -- a minimal, real org.gnome.SessionManager
# D-Bus service, since this bare headless setup never runs a real
# gnome-session at all and GNOME Shell's own Log Out action has nothing
# to actually talk to otherwise (see the script's own header comment for
# the full story). Started early, right alongside pipewire/wireplumber,
# so it is already claiming the bus name well before gnome-shell itself
# is even up -- unlike gsd-media-keys-manual below (which specifically
# has to start AFTER gnome-shell so its accelerator grab targets the
# right instance), this service has no such dependency: it is a plain
# GDBus name owner with no display connection and nothing to grab, so
# there is no benefit to delaying it, and every reason to have it ready
# as early as possible. Idempotent -- systemd-run --unit= is stopped and
# reset first, same pattern as every other transient unit in this
# script, so a resize-triggered rerun doesn't pile up duplicate owners
# of the same bus name.
systemctl --user stop gnome51-sessionmanager >/dev/null 2>&1 || true
systemctl --user reset-failed gnome51-sessionmanager >/dev/null 2>&1 || true
systemd-run --user --unit=gnome51-sessionmanager \
  --property=Restart=on-failure \
  "$PREFIX/bin/linuxdoors-gnome51-sessionmanager" >/dev/null 2>&1 || true

# Real, deep bug found and fixed here (2026-09-19), found only once the
# user could actually click things in a working session: gnome-control-
# center (Settings) refused to open from a real icon click, even though
# it ran fine by hand -- traced via journalctl to dbus-daemon resolving
# the standard, globally-shared D-Bus name "org.gnome.Settings" to this
# project's separate, pre-existing GNOME 48 Settings build instead
# (/usr/share/dbus-1/services/org.gnome.Settings.service), since
# dbus-daemon started long before this /opt/gnome51 environment existed
# and never indexed /opt/gnome51/share/dbus-1/services/ at all.
# `dbus-update-activation-environment` only changes what env vars get
# injected into future activated processes, NOT which .service file a
# name resolves to -- that's fixed at the daemon's own startup
# XDG_DATA_DIRS. The one directory dbus-daemon's session bus always
# scans regardless of XDG_DATA_DIRS is the user's own $XDG_DATA_HOME
# override (~/.local/share/dbus-1/services/, plain XDG base-dir spec,
# nothing GNOME-51-specific) -- writing a real override there, plus a
# genuine `org.freedesktop.DBus.ReloadConfig` call, is what actually
# makes dbus-daemon pick this up. Idempotent (just overwrites the same
# file + reloads every time) so it's safe to run on every connection,
# not just the first.
mkdir -p "$HOME/.local/share/dbus-1/services"
cat > "$HOME/.local/share/dbus-1/services/org.gnome.Settings.service" << 'DBUSEOF'
[D-BUS Service]
Name=org.gnome.Settings
Exec=/opt/gnome51/bin/gnome-control-center --gapplication-service
DBUSEOF
dbus-update-activation-environment --systemd \
  XDG_DATA_DIRS="$XDG_DATA_DIRS" \
  GI_TYPELIB_PATH="$GI_TYPELIB_PATH" >/dev/null 2>&1 || true
gdbus call --session --dest org.freedesktop.DBus \
  --object-path /org/freedesktop/DBus \
  --method org.freedesktop.DBus.ReloadConfig >/dev/null 2>&1 || true

if ! secret-tool search --all dummy dummy >/dev/null 2>&1; then
  : # secret-tool isn't built here; unlock the keyring directly instead
fi
echo "" | gnome-keyring-daemon --unlock --replace --daemonize --components=secrets >/dev/null 2>&1 || true
# Same class of race as the pipewire one above: --replace tears down and
# restarts the keyring daemon's own D-Bus service, and the very next
# grdctl call (which stores the VNC password via libsecret, hitting this
# same daemon) can lose that race and silently fail against the dying
# old instance -- confirmed live, fixed the same way.
sleep 1

# Real bug found and fixed here (2026-09-19): this used to only check
# whether a session was already running at all, never whether it was
# running at the RESOLUTION actually being asked for now -- so once a
# session existed, changing the x2go session's own Geometry field and
# reconnecting silently kept using whatever resolution the very first
# connection happened to start at. A small state file records the
# geometry the CURRENTLY running instance was actually started with;
# a mismatch now restarts gnome-shell (and therefore the desktop
# session inside it) at the newly requested size.
GEOM_STATE="$XDG_RUNTIME_DIR/linuxdoors-gnome51-vnc-geometry"
RUNNING_GEOM=""
if systemctl --user is-active --quiet gnome-shell51-vnc.service 2>/dev/null; then
  RUNNING_GEOM="$(cat "$GEOM_STATE" 2>/dev/null || true)"
fi
if [ "$RUNNING_GEOM" != "$GEOM" ]; then
  systemctl --user stop gnome-shell51-vnc.service >/dev/null 2>&1 || true
  sleep 1
  systemctl --user reset-failed gnome-shell51-vnc.service >/dev/null 2>&1 || true
  systemd-run --user --unit=gnome-shell51-vnc \
    --setenv=PATH="$PATH" \
    --setenv=LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \
    --setenv=XDG_DATA_DIRS="$XDG_DATA_DIRS" \
    --setenv=GI_TYPELIB_PATH="$GI_TYPELIB_PATH" \
    "$PREFIX/bin/gnome-shell" --wayland --headless --virtual-monitor="$GEOM"
  echo "$GEOM" > "$GEOM_STATE"
  sleep 3
fi

# gnome-settings-daemon's media-keys plugin (2026-09-20, real user
# request: a keyboard shortcut to open linuxdoors-gnome51-resolution
# from inside the session itself, "in addition to" the outer
# x2goclient Session-menu action). Real, confirmed blocker: its own
# installed systemd unit, org.gnome.SettingsDaemon.MediaKeys.service,
# has RefuseManualStart=true and Requisite=gnome-session-initialized.target
# on its own .target -- neither of which this bare, hand-built headless
# session (no real gnome-session running at all) ever satisfies, so a
# plain `systemctl --user start` of either unit fails outright. Same
# workaround already used for gnome-shell itself just above in this
# script: a transient systemd-run --user unit, which sidesteps that
# whole gate entirely while still giving the daemon a real, supervised,
# properly cgroup'd systemd --user unit of its own (Restart=on-failure
# included, matching the real unit's own intent).
#
# Real bug found and fixed here (2026-09-20), reported live by a user
# ("super+r no longer working") right after a real resize: this block
# used to run BEFORE the geometry-check/gnome-shell-restart block
# above, not after. gsd-media-keys's own accelerator grab is registered
# against WHATEVER gnome-shell instance happens to be running at grab
# time, by calling gnome-shell's own /org/gnome/Shell D-Bus object --
# so starting/restarting gsd-media-keys *before* a resize restarts
# gnome-shell grabs against the OLD instance, which is torn down a
# moment later. gsd itself was never restarted again afterward (only
# gnome-shell was), so its grab stayed pointed at a dead shell and
# every subsequent keypress silently did nothing -- confirmed directly
# in gsd-media-keys's own journal: "Failed to grab accelerators:
# ... Object does not exist at path \"/org/gnome/Shell\"" appearing
# shortly after a resize. Moving this whole block to AFTER the
# geometry/restart logic above means it always (re)grabs against
# whichever gnome-shell instance is actually running right now,
# whether that's a freshly restarted one or the same one that was
# already up.
GSD_SCHEMAS="$PREFIX/share/glib-2.0/schemas"
systemctl --user stop gsd-media-keys-manual >/dev/null 2>&1 || true
systemctl --user reset-failed gsd-media-keys-manual >/dev/null 2>&1 || true
systemd-run --user --unit=gsd-media-keys-manual \
  --setenv=GSETTINGS_SCHEMA_DIR="$GSD_SCHEMAS" \
  --setenv=LD_LIBRARY_PATH="$PREFIX/lib" \
  --property=Restart=on-failure \
  "$PREFIX/libexec/gsd-media-keys" >/dev/null 2>&1 || true
sleep 1

# The actual custom keybinding: Super+R opens
# linuxdoors-gnome51-resolution. Verified for real (2026-09-20): gsd's
# own log for this exact setup shows "Setting up custom keybinding
# .../custom0/" followed by "Grab call completed!" -- confirming
# gnome-shell/Mutter genuinely accepted the accelerator grab, not just
# that gsettings holds the right values. <Super>r was checked against
# gnome-shell's own default keybindings (Activities/app-grid/window-
# switching all live on other Super combinations) before picking it,
# to avoid a silent collision. Idempotent -- safe to re-run every
# connection, same as everything else in this script.
gsettings --schemadir "$GSD_SCHEMAS" set \
  org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ \
  name 'GNOME 51 Resolution'
gsettings --schemadir "$GSD_SCHEMAS" set \
  org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ \
  command '/usr/local/bin/linuxdoors-gnome51-resolution'
gsettings --schemadir "$GSD_SCHEMAS" set \
  org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ \
  binding '<Super>r'
gsettings --schemadir "$GSD_SCHEMAS" set \
  org.gnome.settings-daemon.plugins.media-keys custom-keybindings \
  "['/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/']"

if [ -n "$PASS" ]; then
  grdctl vnc set-password "$PASS" || echo "linuxdoors-gnome51-vnc: warning: could not set VNC password (keyring not unlocked?)" >&2
fi
gsettings --schemadir "$PREFIX/share/glib-2.0/schemas" set org.gnome.desktop.remote-desktop.vnc enable true
gsettings --schemadir "$PREFIX/share/glib-2.0/schemas" set org.gnome.desktop.remote-desktop.vnc auth-method 'password'
# Real bug found and fixed here (2026-09-19), reported live by a user
# once the desktop was actually rendering: grd's VNC backend defaults
# to view-only (screen-sharing, no remote control at all) -- clicking
# inside the connected desktop did nothing. grdctl's real subcommand
# for this is confusingly named "disable-view-only" (confirmed via
# `strings` on the actual grdctl binary, since --help prints nothing
# useful) -- it's the one that actually turns ON mouse/keyboard input.
gsettings --schemadir "$PREFIX/share/glib-2.0/schemas" set org.gnome.desktop.remote-desktop.vnc view-only false
systemctl --user reset-failed gnome-remote-desktop.service >/dev/null 2>&1 || true
systemctl --user restart gnome-remote-desktop.service

sleep 1
if [ -n "$PASS" ]; then
  echo "GNOME 51 is now running headless and reachable over VNC on 127.0.0.1:5900 (password set)."
else
  echo "GNOME 51 is now running headless and reachable over VNC on 127.0.0.1:5900 (password left unchanged)."
fi
systemctl --user is-active gnome-shell51-vnc.service gnome-remote-desktop.service
