From 95f907198707dd0171c803cf13f4ad930b862c8a Mon Sep 17 00:00:00 2001 From: nevaforget Date: Fri, 7 Aug 2026 13:24:38 +0200 Subject: [PATCH] Resolve helper programs and session dirs without FHS paths systemctl was invoked as /usr/bin/systemctl, and session directories were fixed to /usr/share. Both assume an FHS layout and fail on NixOS. Programs now resolve via PATH; session directories walk XDG_DATA_DIRS with /usr/share as fallback. Behaviour on Arch is unchanged. --- src/power.rs | 4 +-- src/sessions.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/power.rs b/src/power.rs index 4cf4516..b8fd1df 100644 --- a/src/power.rs +++ b/src/power.rs @@ -105,7 +105,7 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), /// agent — the greeter session has none, so without it a denied authorization /// would hang instead of failing fast. pub fn reboot() -> Result<(), PowerError> { - run_command("reboot", "/usr/bin/systemctl", &["--no-ask-password", "reboot"]) + run_command("reboot", "systemctl", &["--no-ask-password", "reboot"]) } /// Shut down the system via systemctl. @@ -113,7 +113,7 @@ pub fn reboot() -> Result<(), PowerError> { /// `--no-ask-password` for the same reason as [`reboot`] — the agent-less /// greeter session has nothing to answer an authorization challenge. pub fn shutdown() -> Result<(), PowerError> { - run_command("shutdown", "/usr/bin/systemctl", &["--no-ask-password", "poweroff"]) + run_command("shutdown", "systemctl", &["--no-ask-password", "poweroff"]) } #[cfg(test)] diff --git a/src/sessions.rs b/src/sessions.rs index 09828bf..36a985d 100644 --- a/src/sessions.rs +++ b/src/sessions.rs @@ -4,8 +4,34 @@ use std::fs; use std::path::{Path, PathBuf}; -const DEFAULT_WAYLAND_DIRS: &[&str] = &["/usr/share/wayland-sessions"]; -const DEFAULT_XSESSION_DIRS: &[&str] = &["/usr/share/xsessions"]; +/// Fallback data directory used when XDG_DATA_DIRS is unset or does not +/// contain the session directory. +const FALLBACK_DATA_DIR: &str = "/usr/share"; + +/// Build the search path for a session directory. +/// +/// Walks XDG_DATA_DIRS as specified by the XDG Base Directory spec and +/// appends `/usr/share` as a fallback, so setups that place session files +/// outside the FHS layout are found as well. +fn session_search_dirs(subdir: &str) -> Vec { + session_search_dirs_from(&std::env::var("XDG_DATA_DIRS").unwrap_or_default(), subdir) +} + +/// Build the search path from an explicit XDG_DATA_DIRS value. +fn session_search_dirs_from(data_dirs: &str, subdir: &str) -> Vec { + let mut dirs: Vec = data_dirs + .split(':') + .filter(|s| !s.is_empty()) + .map(|d| PathBuf::from(d).join(subdir)) + .collect(); + + let fallback = PathBuf::from(FALLBACK_DATA_DIR).join(subdir); + if !dirs.contains(&fallback) { + dirs.push(fallback); + } + + dirs +} /// Represents an available login session. #[derive(Debug, Clone)] @@ -82,10 +108,8 @@ pub fn get_sessions( wayland_dirs: Option<&[PathBuf]>, xsession_dirs: Option<&[PathBuf]>, ) -> Vec { - let default_wayland: Vec = - DEFAULT_WAYLAND_DIRS.iter().map(PathBuf::from).collect(); - let default_xsession: Vec = - DEFAULT_XSESSION_DIRS.iter().map(PathBuf::from).collect(); + let default_wayland = session_search_dirs("wayland-sessions"); + let default_xsession = session_search_dirs("xsessions"); let wayland = wayland_dirs.unwrap_or(&default_wayland); let xsession = xsession_dirs.unwrap_or(&default_xsession); @@ -129,6 +153,52 @@ mod tests { fs::write(dir.join(name), content).unwrap(); } + #[test] + fn search_dirs_walk_xdg_data_dirs() { + let dirs = session_search_dirs_from( + "/run/current-system/sw/share:/usr/local/share", + "wayland-sessions", + ); + assert_eq!( + dirs, + vec![ + PathBuf::from("/run/current-system/sw/share/wayland-sessions"), + PathBuf::from("/usr/local/share/wayland-sessions"), + PathBuf::from("/usr/share/wayland-sessions"), + ] + ); + } + + #[test] + fn search_dirs_fall_back_when_unset() { + let dirs = session_search_dirs_from("", "xsessions"); + assert_eq!(dirs, vec![PathBuf::from("/usr/share/xsessions")]); + } + + #[test] + fn search_dirs_do_not_duplicate_fallback() { + let dirs = session_search_dirs_from("/usr/share:/opt/share", "wayland-sessions"); + assert_eq!( + dirs, + vec![ + PathBuf::from("/usr/share/wayland-sessions"), + PathBuf::from("/opt/share/wayland-sessions"), + ] + ); + } + + #[test] + fn search_dirs_skip_empty_segments() { + let dirs = session_search_dirs_from("::/opt/share:", "wayland-sessions"); + assert_eq!( + dirs, + vec![ + PathBuf::from("/opt/share/wayland-sessions"), + PathBuf::from("/usr/share/wayland-sessions"), + ] + ); + } + #[test] fn parse_valid_desktop_file() { let dir = tempfile::tempdir().unwrap();