# shellcheck shell=bash #------------------------------------------ # lib/session.sh — display manager and graphical session wiring # # DM policy: # * A DE module leaves its native DM alone (GNOME→gdm, Plasma→sddm, XFCE→sddm). # * A TWM module enforces SDDM, because SDDM is the only DM that presents both # Wayland compositors and X11 sessions in one menu. One greeter, no surprises. #------------------------------------------ if [[ -n "${_SB_SESSION_LOADED:-}" ]]; then return 0 fi _SB_SESSION_LOADED=1 # Name of the active display manager ("sddm", "gdm", "lightdm"), or "" if none. detect_display_manager() { local link=/etc/systemd/system/display-manager.service if [[ -L "$link" ]]; then basename "$(readlink "$link")" | sed 's/\.service$//' else echo "" fi } enable_graphical_target() { sudo systemctl set-default graphical.target } # Make $1 the display manager, disabling whatever is active now. switch_display_manager() { local target="$1" local current current=$(detect_display_manager) if [[ "$current" == "$target" ]]; then ok "Display manager already $target." else if [[ -n "$current" ]]; then info "Disabling current display manager: $current" # disable may fail if the unit vanished; that is not fatal sudo systemctl disable "$current" 2>/dev/null || true fi info "Enabling display manager: $target" sudo systemctl enable "$target" fi enable_graphical_target } # Register a Wayland session so the DM can list it. # $1 = identifier, $2 = display name, $3 = command write_wayland_session() { local id="$1" name="$2" exec_cmd="$3" local file="/usr/share/wayland-sessions/${id}.desktop" if [[ -f "$file" ]]; then ok "Wayland session already registered: $id" return 0 fi info "Registering Wayland session: $id" sudo mkdir -p "$(dirname "$file")" printf '[Desktop Entry]\nName=%s\nComment=%s\nExec=%s\nType=Application\nDesktopNames=%s\n' \ "$name" "$name session" "$exec_cmd" "$id" | sudo tee "$file" >/dev/null sudo chmod 644 "$file" } # Register an X11 session so the DM can list it. write_x11_session() { local id="$1" name="$2" exec_cmd="$3" local file="/usr/share/xsessions/${id}.desktop" if [[ -f "$file" ]]; then ok "X11 session already registered: $id" return 0 fi info "Registering X11 session: $id" # A bare Fedora has no X session yet, so the directory may not exist. sudo mkdir -p "$(dirname "$file")" printf '[Desktop Entry]\nName=%s\nComment=%s\nExec=%s\nType=Application\n' \ "$name" "$name session" "$exec_cmd" | sudo tee "$file" >/dev/null sudo chmod 644 "$file" } # Common login-time plumbing every Wayland compositor wants on a bare install. install_session_prereqs() { # polkit-kde ships the agent (polkit-gnome is orphaned, EPEL-only). # BUT Fedora's autostart .desktop carries X-systemd-skip=true (and a # OnlyShowIn on some versions): niri starts apps via systemd's # xdg-desktop-autostart.target, which honors those flags -> agent never # starts in a niri session (VM-proven twice). Fighting .desktop semantics # is fragile, so we ship our own user unit bound to # graphical-session.target, which niri/Hyprland/MangoWC all bring up. install_packages \ xdg-desktop-portal \ polkit \ polkit-kde \ pipewire \ pipewire-pulseaudio \ wireplumber local unit_dir="/etc/systemd/user" if [[ ! -f "$unit_dir/polkit-agent.service" ]]; then info "Installing polkit-agent user unit (compositor-safe)..." sudo mkdir -p "$unit_dir" sudo tee "$unit_dir/polkit-agent.service" >/dev/null <<'UNIT' [Unit] Description=PolicyKit Authentication Agent (for bare compositors) PartOf=graphical-session.target [Service] # polkit-kde's binary on Fedora (kf6 path). Restart in case the agent dies # before the session settles. ExecStart=/usr/libexec/kf6/polkit-kde-authentication-agent-1 Restart=on-failure RestartSec=2 [Install] WantedBy=graphical-session.target UNIT fi # Enable per-user: preset-all would also do, but be explicit. This is a # global unit dir; each user links it at first login via the preset. if [[ ! -f /etc/systemd/user/graphical-session.target.wants/polkit-agent.service ]]; then sudo mkdir -p /etc/systemd/user/graphical-session.target.wants sudo ln -sf ../polkit-agent.service \ /etc/systemd/user/graphical-session.target.wants/polkit-agent.service fi # libvirt group: members manage qemu:///system without a polkit prompt. # Without this, virt-manager dies on "access denied by policy" even with # a working agent (VM-proven). Idempotent; applies after re-login. # # Gate on the DAEMON, not the 'libvirt' metapkg name: virt-manager only # requires libvirt-glib client-side, so on a fresh VM libvirt can be # functionally present via libvirt-daemon* without rpm -q libvirt passing # (VM-proven: user stuck in yuri+wheel with virt-manager installed). if pkg_installed_any libvirt libvirt-daemon libvirt-daemon-driver-qemu virt-manager \ || [[ -x /usr/sbin/libvirtd ]]; then local real_user="${SUDO_USER:-$USER}" if ! id -nG "$real_user" 2>/dev/null | grep -qw libvirt; then info "Adding $real_user to the libvirt group (re-login to apply)..." sudo usermod -aG libvirt "$real_user" fi fi }