diff --git a/modules/desktop_display.sh b/modules/desktop_display.sh index dde9269..d2e6f7d 100644 --- a/modules/desktop_display.sh +++ b/modules/desktop_display.sh @@ -299,6 +299,29 @@ configure_gdm3() { done } +# Ordered session candidates for SDDM autologin: +# Wayland first, then X11, per release. Existence +# decides (no codename gates). The LXQt sessions +# stay last for when the module starts offering LXQt. +_sddm_session_candidates() { + local wayland_dir="${SESSION_DIR_WAYLAND:-/usr/share/wayland-sessions}" + local x11_dir="${SESSION_DIR_X11:-/usr/share/xsessions}" + local candidate + for candidate in \ + "$wayland_dir/plasmawayland.desktop" \ + "$wayland_dir/plasma.desktop" \ + "$x11_dir/plasma.desktop" \ + "$x11_dir/plasmax11.desktop" \ + "$wayland_dir/lxqt-wayland.desktop" \ + "$x11_dir/lxqt.desktop"; do + if [ -f "$candidate" ]; then + basename "$candidate" .desktop + return 0 + fi + done + return 0 +} + configure_sddm() { while true; do local choice @@ -317,19 +340,11 @@ configure_sddm() { ;; 2) local sddm_session="" + sddm_session=$(_sddm_session_candidates) local sddm_user="${SUDO_USER:-}" if [ -z "$sddm_user" ] || [ "$sddm_user" = "root" ]; then sddm_user=$(awk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd | head -1) fi - if [ -f /usr/share/wayland-sessions/plasmawayland.desktop ]; then - sddm_session="plasmawayland" - elif [ -f /usr/share/wayland-sessions/lxqt-wayland.desktop ]; then - sddm_session="lxqt-wayland" - elif [ -f /usr/share/xsessions/plasma.desktop ]; then - sddm_session="plasma" - elif [ -f /usr/share/xsessions/lxqt.desktop ]; then - sddm_session="lxqt" - fi sudo mkdir -p /etc/sddm.conf.d cat </dev/null [Autologin] diff --git a/modules/desktops/desktop_kde.sh b/modules/desktops/desktop_kde.sh index 49807a7..7ef7d61 100644 --- a/modules/desktops/desktop_kde.sh +++ b/modules/desktops/desktop_kde.sh @@ -1,10 +1,32 @@ #!/usr/bin/env bash # desktop_kde.sh — KDE Plasma desktop installer (SP2) # Full: kde-standard + sddm | Minimal: kde-plasma-desktop + sddm -# Bookworm ships Plasma 5.27.5; Wayland is the default -# session since Debian 12. +# bookworm: Plasma 5.27.5 / KF5 / Qt5; SDDM defaults to +# X11 (plasma.desktop in xsessions); Wayland is optional +# via plasma-workspace-wayland (plasmawayland.desktop). +# trixie: Plasma 6.3.6 / KF6 / Qt6; Wayland is the +# primary session (plasma.desktop in wayland-sessions), +# X11 the alternative (plasmax11.desktop). sddm is only +# Recommends in the meta-kde chain, so the explicit sddm +# in both routes is what guarantees the DM (and +# xserver-xorg/xauth, hard deps of sddm in both releases). # License GPL v3 +# plasma-workspace-wayland exists only on bookworm: +# bookworm's plasma-workspace ships the X11 session only, +# and the Wayland session (plasmawayland.desktop) lives +# in this separate package (a Recommends of plasma-desktop, +# listed explicitly for the same determinism as the +# explicit sddm). trixie integrates Wayland into +# plasma-workspace itself and the package does not exist +# there (packages.debian.org/trixie/plasma-workspace-wayland), +# so naming it off-bookworm would fail the apt transaction. +_kde_wayland_pkg() { + case "${DEBIAN_CODENAME:-}" in + bookworm) printf '%s\n' "plasma-workspace-wayland" ;; + *) printf '%s\n' "" ;; + esac +} kde_menu() { while true; do local -a kd_items=() @@ -27,8 +49,15 @@ kde_menu() { _install_kde_full() { # Pre-apply summary: confirm the selection before any - # debconf or apt transaction starts. - _confirm_install_list "KDE Plasma Full" kde-standard sddm || return + # debconf or apt transaction starts. The Wayland + # session package is resolved once (bookworm only; + # the package does not exist on trixie) so the + # summary lists exactly what apt will install. + local wayland_pkg + wayland_pkg=$(_kde_wayland_pkg) + local -a pkgs=(kde-standard sddm) + [ -n "$wayland_pkg" ] && pkgs+=("$wayland_pkg") + _confirm_install_list "KDE Plasma Full" "${pkgs[@]}" || return echo -e "${GREEN}Installing KDE Plasma (Full)...${NC}" @@ -42,7 +71,7 @@ _install_kde_full() { fi echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections _ensure_apt_updated - _run_cmd "KDE Full" "sudo apt install -y kde-standard sddm" \ + _run_cmd "KDE Full" "sudo apt install -y ${pkgs[*]}" \ "Installing KDE Plasma (Full Meta Package) + SDDM..." if [ "$target_dm" = "sddm" ]; then sudo systemctl enable sddm @@ -56,8 +85,15 @@ _install_kde_full() { _install_kde_minimal() { # Pre-apply summary: confirm the selection before any - # debconf or apt transaction starts. - _confirm_install_list "KDE Plasma Minimal" kde-plasma-desktop sddm || return + # debconf or apt transaction starts. The Wayland + # session package is resolved once (bookworm only; + # the package does not exist on trixie) so the + # summary lists exactly what apt will install. + local wayland_pkg + wayland_pkg=$(_kde_wayland_pkg) + local -a pkgs=(kde-plasma-desktop sddm) + [ -n "$wayland_pkg" ] && pkgs+=("$wayland_pkg") + _confirm_install_list "KDE Plasma Minimal" "${pkgs[@]}" || return echo -e "${GREEN}Installing KDE Plasma Desktop (Minimal)...${NC}" @@ -71,7 +107,7 @@ _install_kde_minimal() { fi echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections _ensure_apt_updated - _run_cmd "KDE Minimal" "sudo apt install -y kde-plasma-desktop sddm" \ + _run_cmd "KDE Minimal" "sudo apt install -y ${pkgs[*]}" \ "Installing KDE Plasma Desktop (Minimal) + SDDM..." if [ "$target_dm" = "sddm" ]; then sudo systemctl enable sddm diff --git a/tests/desktops.bats b/tests/desktops.bats index f92479c..d5d7e1d 100644 --- a/tests/desktops.bats +++ b/tests/desktops.bats @@ -60,6 +60,10 @@ teardown() { if [ -n "${MOCK_APT_ROOT:-}" ]; then rm -rf "$MOCK_APT_ROOT" fi + if [ -n "${SESSION_FIXTURE:-}" ]; then + rm -rf "$SESSION_FIXTURE" + fi + unset SESSION_DIR_WAYLAND SESSION_DIR_X11 } # ── _existing_display_manager ──────────────────────── @@ -254,3 +258,172 @@ _gnome_capture_apt_cmd() { [[ "$output" == *"apt install -y gnome gdm3 gnome-terminal"* ]] [[ "$output" != *ptyxis* ]] } + +# ── KDE Plasma: bookworm Wayland gate ───────────── +# plasma-workspace-wayland exists only on bookworm: +# bookworm's plasma-workspace ships the X11 session +# only, so the explicit listing keeps the Wayland +# session in the transaction deterministically; off +# bookworm the package does not exist and nothing is +# named in the apt command. + +# _kde_capture_apt_cmd runs one KDE installer with +# every external effect stubbed (same shape as the +# GNOME capture helper): the apt command is captured +# instead of executed, and the PATH sudo mock stays +# in dry-run mode so debconf and systemctl invocations +# are logged, not executed. +_kde_capture_apt_cmd() { + local installer="$1" + _load_module_with_deps_marked kde + KDE_APT_CMD="" + _confirm_install_list() { return 0; } + _ensure_apt_updated() { :; } + _run_cmd() { KDE_APT_CMD="$2"; } + "$installer" + printf '%s\n' "$KDE_APT_CMD" +} + +# _kde_capture_confirm runs one KDE installer with the +# pre-apply summary captured instead of shown: _confirm +# records the list _confirm_install_list builds, so the +# summary content (never an empty package line) can be +# asserted directly. The pipewire follow-up offer is +# stubbed so the recorded list is the package summary. +_kde_capture_confirm() { + local installer="$1" + _load_module_with_deps_marked kde + KDE_CONFIRM_LIST="" + _confirm() { KDE_CONFIRM_LIST="$2"; return 0; } + _ensure_apt_updated() { :; } + _run_cmd() { :; } + _offer_pipewire_after_desktop() { :; } + "$installer" + printf '%s\n' "$KDE_CONFIRM_LIST" +} + +@test "_kde_wayland_pkg prints plasma-workspace-wayland on bookworm" { + export DEBIAN_CODENAME="bookworm" + _load_module_with_deps_marked kde + run _kde_wayland_pkg + [ "$status" -eq 0 ] + [ "$output" = "plasma-workspace-wayland" ] +} + +@test "_kde_wayland_pkg prints nothing off bookworm" { + export DEBIAN_CODENAME="trixie" + _load_module_with_deps_marked kde + run _kde_wayland_pkg + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "KDE Full installs the Wayland session package on bookworm" { + export DEBIAN_CODENAME="bookworm" + export MOCK_SUDO_DRY_RUN=1 + run _kde_capture_apt_cmd _install_kde_full + [ "$status" -eq 0 ] + [[ "$output" == *"apt install -y kde-standard sddm plasma-workspace-wayland"* ]] +} + +@test "KDE Full omits the Wayland session package off bookworm" { + export DEBIAN_CODENAME="trixie" + export MOCK_SUDO_DRY_RUN=1 + run _kde_capture_apt_cmd _install_kde_full + [ "$status" -eq 0 ] + [[ "$output" == *"apt install -y kde-standard sddm"* ]] + [[ "$output" != *"plasma-workspace-wayland"* ]] +} + +@test "KDE Minimal installs the Wayland session package on bookworm" { + export DEBIAN_CODENAME="bookworm" + export MOCK_SUDO_DRY_RUN=1 + run _kde_capture_apt_cmd _install_kde_minimal + [ "$status" -eq 0 ] + [[ "$output" == *"apt install -y kde-plasma-desktop sddm plasma-workspace-wayland"* ]] +} + +@test "KDE Minimal omits the Wayland session package off bookworm" { + export DEBIAN_CODENAME="trixie" + export MOCK_SUDO_DRY_RUN=1 + run _kde_capture_apt_cmd _install_kde_minimal + [ "$status" -eq 0 ] + [[ "$output" == *"apt install -y kde-plasma-desktop sddm"* ]] + [[ "$output" != *"plasma-workspace-wayland"* ]] +} + +@test "KDE Minimal summary lists the Wayland session package on bookworm" { + export DEBIAN_CODENAME="bookworm" + export MOCK_SUDO_DRY_RUN=1 + run _kde_capture_confirm _install_kde_minimal + [ "$status" -eq 0 ] + [[ "$output" == *"plasma-workspace-wayland"* ]] +} + +@test "KDE Minimal summary shows no empty package line off bookworm" { + export DEBIAN_CODENAME="trixie" + export MOCK_SUDO_DRY_RUN=1 + run _kde_capture_confirm _install_kde_minimal + [ "$status" -eq 0 ] + [[ "$output" == *"kde-plasma-desktop"* ]] + [[ "$output" == *"sddm"* ]] + [[ "$output" != *"plasma-workspace-wayland"* ]] + [[ "$output" != *"- "$'\n'* ]] +} + +# ── _sddm_session_candidates: ordered session probe ── +# Existence decides (no codename gates) and Wayland is +# preferred. The roots are redirected to a per-test +# fixture tree through SESSION_DIR_WAYLAND / +# SESSION_DIR_X11 (same spirit as the DM_FILE fixture), +# so the probe never touches the real /usr/share tree. + +_sddm_session_fixture() { + SESSION_FIXTURE="$(mktemp -d)" + mkdir -p "$SESSION_FIXTURE/wayland" "$SESSION_FIXTURE/x11" + SESSION_DIR_WAYLAND="$SESSION_FIXTURE/wayland" + SESSION_DIR_X11="$SESSION_FIXTURE/x11" + export SESSION_DIR_WAYLAND SESSION_DIR_X11 +} + +@test "SDDM session probe prefers bookworm Wayland (plasmawayland)" { + _sddm_session_fixture + : >"$SESSION_DIR_WAYLAND/plasmawayland.desktop" + : >"$SESSION_DIR_X11/plasma.desktop" + run _sddm_session_candidates + [ "$status" -eq 0 ] + [ "$output" = "plasmawayland" ] +} + +@test "SDDM session probe picks trixie Wayland (plasma.desktop)" { + _sddm_session_fixture + : >"$SESSION_DIR_WAYLAND/plasma.desktop" + : >"$SESSION_DIR_X11/plasmax11.desktop" + run _sddm_session_candidates + [ "$status" -eq 0 ] + [ "$output" = "plasma" ] +} + +@test "SDDM session probe falls back to trixie X11 (plasmax11)" { + _sddm_session_fixture + : >"$SESSION_DIR_X11/plasmax11.desktop" + run _sddm_session_candidates + [ "$status" -eq 0 ] + [ "$output" = "plasmax11" ] +} + +@test "SDDM session probe keeps LXQt last when no KDE session exists" { + _sddm_session_fixture + : >"$SESSION_DIR_WAYLAND/lxqt-wayland.desktop" + : >"$SESSION_DIR_X11/lxqt.desktop" + run _sddm_session_candidates + [ "$status" -eq 0 ] + [ "$output" = "lxqt-wayland" ] +} + +@test "SDDM session probe returns empty when no session file exists" { + _sddm_session_fixture + run _sddm_session_candidates + [ "$status" -eq 0 ] + [ -z "$output" ] +}