diff --git a/.gitignore b/.gitignore index 2c1f958..de25842 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ target/ **/*.rs.bk vendor/ -# Fork; becomes a submodule per spec repo topology +# Forks; become submodules per spec repo topology cosmic-comp/ +cosmic-session/ diff --git a/tools/nested-session.sh b/tools/nested-session.sh new file mode 100755 index 0000000..c2c05d0 --- /dev/null +++ b/tools/nested-session.sh @@ -0,0 +1,102 @@ +#!/usr/bin/bash +# +# Run the forked cosmic-session nested inside the live desktop, safely. +# +# WHY THIS SCRIPT EXISTS +# ---------------------- +# On 2026-08-10 an ad-hoc version of this test logged the developer out of their +# own desktop. Two independent mistakes did it, and both are easy to repeat by +# hand, so the test lives in a script instead: +# +# 1. `pkill -x cosmic-session` matched the *real* session leader. The fork and +# the system COSMIC ship binaries with the same name, so no name-based +# match can distinguish them. This script therefore never uses pkill or +# pgrep; it kills the process group it created, by ID. +# +# 2. A nested cosmic-session on the shared session bus takes the well-known +# D-Bus name `com.system76.CosmicSession` away from the running session +# (journal: "Connection `:1.3` lost name `com.system76.CosmicSession`"). +# That destabilises the outer desktop before anything is even killed. This +# script always runs under `dbus-run-session`, so the nested session gets a +# private bus and cannot touch the real one's names. +# +# Nesting cosmic-comp alone is safe and does not need any of this; the hazard is +# specific to running a second cosmic-session. +# +# Usage: tools/nested-session.sh [seconds] [-- extra env assignments] +# e.g. tools/nested-session.sh 12 -- HYPRCOSMIC_PROFILE=hyprcosmic + +set -uo pipefail + +REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +SESSION_BIN="$REPO/cosmic-session/target/debug/cosmic-session" +COMP_BIN="$REPO/cosmic-comp/target/debug/cosmic-comp" + +DURATION="${1:-12}" +shift || true +[[ "${1:-}" == "--" ]] && shift + +die() { printf 'nested-session: %s\n' "$*" >&2; exit 1; } + +# Refuse to run outside a Wayland session. Without a host compositor the winit +# backend would fall back to DRM and try to take over the real display. +[[ -n "${WAYLAND_DISPLAY:-}" ]] || die "no WAYLAND_DISPLAY; refusing to run (would grab the DRM device)" +[[ -x "$SESSION_BIN" ]] || die "not built: $SESSION_BIN" +[[ -x "$COMP_BIN" ]] || die "not built: $COMP_BIN" +command -v dbus-run-session >/dev/null || die "dbus-run-session is required for bus isolation" + +# Record the live session's leader purely so the exit check can prove we did not +# disturb it. Asked of logind rather than matched by process name: a name-based +# lookup is what destroyed the developer's session twice, once in the very test +# written to prove name matching was unsafe. There is no `ps -C cosmic-...` +# anywhere in this file, deliberately. +OUTER_LEADER="$(loginctl show-session "${XDG_SESSION_ID:-}" -p Leader --value 2>/dev/null)" + +LOG="$(mktemp -t nested-session.XXXXXX.log)" +echo "nested-session: logging to $LOG" +echo "nested-session: live session leader=$OUTER_LEADER (must survive)" + +# setsid puts the whole tree in a fresh process group whose ID equals the child +# PID, so one negative kill reaps the session, the compositor and every +# component it spawned -- with no pattern matching anywhere. +setsid env \ + COSMIC_BACKEND=winit \ + RUST_LOG="${RUST_LOG:-info}" \ + "$@" \ + dbus-run-session -- "$SESSION_BIN" "$COMP_BIN" >"$LOG" 2>&1 & +PGID=$! + +cleanup() { + # Negative PID = process group. Never a name. + kill -TERM -"$PGID" 2>/dev/null + for _ in $(seq 20); do + kill -0 -"$PGID" 2>/dev/null || break + sleep 0.25 + done + kill -KILL -"$PGID" 2>/dev/null + + # An abruptly-killed compositor leaves its IPC directory behind, so drop any + # whose owning PID is gone. Matching is on the PID embedded in the name. + for dir in "${XDG_RUNTIME_DIR:?}"/hypr/cosmic_*; do + [[ -d "$dir" ]] || continue + pid="${dir##*/cosmic_}"; pid="${pid%%_*}" + kill -0 "$pid" 2>/dev/null || rm -rf "$dir" + done +} +trap cleanup EXIT INT TERM + +sleep "$DURATION" +cleanup +trap - EXIT INT TERM + +# The whole point: confirm the developer still has a desktop. +status=0 +if [[ -n "$OUTER_LEADER" ]] && ! kill -0 "$OUTER_LEADER" 2>/dev/null; then + echo "nested-session: FAIL - live session leader $OUTER_LEADER died during the test" >&2 + status=1 +else + echo "nested-session: live session survived" +fi + +echo "--- log: $LOG ---" +exit $status