From 6c5b4d5701334e7b1db103eb355c88ac71bdf9e3 Mon Sep 17 00:00:00 2001 From: dev-null-undefined Date: Mon, 12 Oct 2026 11:46:03 +0200 Subject: [PATCH] =?UTF-8?q?UI:=20find=20gsettings=20on=20PATH=20for=20Appe?= =?UTF-8?q?arance=20Mode=20=E2=96=B8=20Auto=20(NixOS=20has=20no=20/usr/bin?= =?UTF-8?q?/gsettings)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without the XDG settings portal, the start-up fallback asks gsettings for the GNOME colour scheme, but only looked in /usr/bin, /bin and /usr/local/bin. NixOS keeps it in /run/current-system/sw/bin or the per-user profile, so Auto never followed the desktop there. It now tries the absolute PATH entries first, then those three; relative entries are still skipped, so nothing runs from the working folder. Co-Authored-By: Claude --- apps/lightcraft/src/appearance.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/lightcraft/src/appearance.rs b/apps/lightcraft/src/appearance.rs index 74333a301..223dbc560 100644 --- a/apps/lightcraft/src/appearance.rs +++ b/apps/lightcraft/src/appearance.rs @@ -51,7 +51,7 @@ fn decode(code: u32) -> Option { /// absolute path, and is never repeated. #[cfg(target_os = "linux")] fn gtk_theme() -> Option { - let exe = ["/usr/bin/gsettings", "/bin/gsettings", "/usr/local/bin/gsettings"].into_iter().find(|p| std::path::Path::new(p).is_file())?; + let exe = gsettings_exe(std::env::var_os("PATH").as_deref())?; let output = std::process::Command::new(exe).args(["get", "org.gnome.desktop.interface", "color-scheme"]).output().ok()?; if !output.status.success() { return None; @@ -59,6 +59,15 @@ fn gtk_theme() -> Option { parse_gtk_scheme(std::str::from_utf8(&output.stdout).ok()?) } +/// `gsettings` by absolute path: the absolute `PATH` entries first (NixOS has no /usr/bin/gsettings), +/// then the usual places for a sparse `PATH`. Relative entries are skipped, so nothing runs from +/// the working folder. +#[cfg(target_os = "linux")] +fn gsettings_exe(path: Option<&std::ffi::OsStr>) -> Option { + let listed = path.map(std::env::split_paths).into_iter().flatten().filter(|dir| dir.is_absolute()); + listed.chain(["/usr/bin", "/bin", "/usr/local/bin"].map(std::path::PathBuf::from)).map(|dir| dir.join("gsettings")).find(|exe| exe.is_file()) +} + #[cfg(target_os = "linux")] fn parse_gtk_scheme(s: &str) -> Option { match s.trim().trim_matches('\'') { @@ -174,6 +183,24 @@ mod tests { assert_eq!(calls.load(Ordering::Relaxed), after_settle); } + /// NixOS keeps `gsettings` in /run/current-system/sw/bin or the per-user profile: it is found + /// on `PATH`, but never through a relative entry, even one that leads to a real file. + #[cfg(target_os = "linux")] + #[test] + fn gsettings_is_found_on_absolute_path_entries_only() { + let dir = std::env::temp_dir().join(format!("lightcraft-gsettings-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write(dir.join("gsettings"), "").unwrap(); + let path = std::env::join_paths([std::path::Path::new("relative/bin"), &dir]).unwrap(); + assert_eq!(super::gsettings_exe(Some(&path)), Some(dir.join("gsettings"))); + let cwd = std::env::current_dir().unwrap(); + let relative = std::path::PathBuf::from("../".repeat(cwd.components().count() - 1)).join(dir.strip_prefix("/").unwrap()); + assert!(relative.join("gsettings").is_file()); + let found = super::gsettings_exe(Some(relative.as_os_str())); + assert!(found.as_ref().is_none_or(|p| p.is_absolute()), "{found:?}"); + let _ = std::fs::remove_dir_all(&dir); + } + #[cfg(target_os = "linux")] #[test] fn portal_values_and_gsettings_output_decode() {