From 71670eb263efa6890bd8c40de58dd97fffec0396 Mon Sep 17 00:00:00 2001 From: nevaforget Date: Sat, 28 Mar 2026 22:58:25 +0100 Subject: [PATCH] feat: switch to systemd-journal-logger, add debug logging (v0.6.0) Replace env_logger with systemd-journal-logger for consistent logging across moonset/moonlock/moongreet. Add MOONSET_DEBUG env var and debug statements across all modules. Also includes shared blur cache for multi-monitor and detached moonlock spawn for lock action. --- CHANGELOG.md | 13 ++++ CLAUDE.md | 3 +- Cargo.lock | 186 +++----------------------------------------------- Cargo.toml | 4 +- DECISIONS.md | 7 ++ src/config.rs | 6 +- src/i18n.rs | 17 +++-- src/main.rs | 33 +++++++-- src/panel.rs | 40 +++++++++-- src/power.rs | 19 +++++- src/users.rs | 2 + 11 files changed, 131 insertions(+), 199 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a22b75..a8201d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. Format based on [Keep a Changelog](https://keepachangelog.com/). +## [0.6.0] - 2026-03-28 + +### Added + +- Systemd journal logging (`journalctl -t moonset`) replacing env_logger stderr output +- `MOONSET_DEBUG` env var to enable debug-level journal output +- Debug logging across all modules (config resolution, wallpaper source, avatar loading, power actions, locale detection, blur cache) +- Shared blur cache for multi-monitor — GPU blur computed once, reused by all windows + +### Changed + +- Lock action spawns moonlock as detached process instead of blocking via run_command — moonset can quit immediately while moonlock runs independently + ## [0.4.1] - 2026-03-28 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index dfd9c1c..52a8070 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,7 @@ LD_PRELOAD=/usr/lib/libgtk4-layer-shell.so ./target/release/moonset ## Architektur -- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor, zentrale `GRESOURCE_PREFIX`-Konstante +- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor, systemd-Journal-Logging, Debug-Level per `MOONSET_DEBUG` Env-Var, zentrale `GRESOURCE_PREFIX`-Konstante - `power.rs` — 5 Power-Action-Wrapper mit absoluten Pfaden und 30s Timeout (lock, logout, hibernate, reboot, shutdown) - `i18n.rs` — Locale-Erkennung und String-Tabellen (DE/EN) - `config.rs` — TOML-Config + Wallpaper-Fallback @@ -56,3 +56,4 @@ Kurzfassung der wichtigsten Entscheidungen: - **Absolute Pfade für Binaries**: `/usr/bin/systemctl` etc. statt relativer Pfade (Security) - **GResource-Bundle**: CSS, Wallpaper (komprimiert) und Default-Avatar sind in die Binary kompiliert - **Async Power Actions**: `glib::spawn_future_local` + `gio::spawn_blocking` mit 30s Timeout +- **Journal-Logging**: `systemd-journal-logger` statt File-Logging — `journalctl -t moonset`, Debug-Level per `MOONSET_DEBUG` Env-Var diff --git a/Cargo.lock b/Cargo.lock index 48a89a5..9e1573d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,65 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - [[package]] name = "anyhow" version = "1.0.102" @@ -124,12 +65,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - [[package]] name = "dirs" version = "6.0.0" @@ -151,29 +86,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "env_filter" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -642,42 +554,12 @@ dependencies = [ "serde_core", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jiff" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-static" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "khronos_api" version = "3.1.0" @@ -737,7 +619,6 @@ name = "moonset" version = "0.5.0" dependencies = [ "dirs", - "env_logger", "gdk-pixbuf", "gdk4", "glib", @@ -748,6 +629,7 @@ dependencies = [ "log", "nix", "serde", + "systemd-journal-logger", "tempfile", "toml 0.8.23", ] @@ -770,12 +652,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "option-ext" version = "0.2.0" @@ -818,21 +694,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "portable-atomic-util" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" -dependencies = [ - "portable-atomic", -] - [[package]] name = "prettyplease" version = "0.2.37" @@ -887,35 +748,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - [[package]] name = "rustc_version" version = "0.4.1" @@ -1041,6 +873,16 @@ dependencies = [ "version-compare", ] +[[package]] +name = "systemd-journal-logger" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7266304d24ca5a4b230545fc558c80e18bd3e1d2eb1be149b6bcd04398d3e79c" +dependencies = [ + "log", + "rustix", +] + [[package]] name = "target-lexicon" version = "0.13.3" @@ -1193,12 +1035,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "version-compare" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 0696f95..e2893ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "moonset" -version = "0.5.0" +version = "0.6.0" edition = "2024" description = "Wayland session power menu with GTK4 and Layer Shell" license = "MIT" @@ -17,7 +17,7 @@ serde = { version = "1", features = ["derive"] } nix = { version = "0.29", features = ["user"] } graphene-rs = { version = "0.22", package = "graphene-rs" } log = "0.4" -env_logger = "0.11" +systemd-journal-logger = "2.2" [dev-dependencies] tempfile = "3" diff --git a/DECISIONS.md b/DECISIONS.md index 10decc3..d65ae81 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -2,6 +2,13 @@ Architectural and design decisions for Moonset, in reverse chronological order. +## 2026-03-28 – Switch from env_logger to systemd-journal-logger + +- **Who**: Ragnar, Dom +- **Why**: moonlock and moongreet already use systemd-journal-logger. moonset used env_logger which writes to stderr — not useful for a GUI app launched via keybind. Journal integration enables `journalctl -t moonset` and consistent troubleshooting across all three Moon projects. +- **Tradeoffs**: Requires systemd at runtime. Graceful fallback to eprintln if journal logger fails. Acceptable since Moonarch targets systemd-based Arch Linux. +- **How**: Replace `env_logger` dep with `systemd-journal-logger`, add `setup_logging()` with `MOONSET_DEBUG` env var for debug-level output. Same pattern as moonlock/moongreet. + ## 2026-03-28 – Replace action name dispatch with `quit_after` field - **Who**: Hekate, Dom diff --git a/src/config.rs b/src/config.rs index bacc87c..c8685f0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,6 +33,7 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config { if let Ok(content) = fs::read_to_string(path) { match toml::from_str::(&content) { Ok(parsed) => { + log::debug!("Config loaded: {}", path.display()); if parsed.background_path.is_some() { merged.background_path = parsed.background_path; } @@ -41,7 +42,7 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config { } } Err(e) => { - eprintln!("Warning: failed to parse {}: {e}", path.display()); + log::warn!("Failed to parse {}: {e}", path.display()); } } } @@ -63,17 +64,20 @@ pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path) if let Some(ref bg) = config.background_path { let path = PathBuf::from(bg); if path.is_file() { + log::debug!("Wallpaper source: config ({})", path.display()); return path; } } // Moonarch ecosystem default if moonarch_wallpaper.is_file() { + log::debug!("Wallpaper source: moonarch default ({})", moonarch_wallpaper.display()); return moonarch_wallpaper.to_path_buf(); } // GResource fallback path (loaded from compiled resources at runtime) let prefix = crate::GRESOURCE_PREFIX; + log::debug!("Wallpaper source: GResource fallback"); PathBuf::from(format!("{prefix}/wallpaper.jpg")) } diff --git a/src/i18n.rs b/src/i18n.rs index fc77647..14c5461 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -112,15 +112,20 @@ fn read_lang_from_conf(path: &Path) -> Option { /// Determine the system language from LANG env var or /etc/locale.conf. pub fn detect_locale() -> String { - let lang = env::var("LANG") - .ok() - .filter(|s| !s.is_empty()) - .or_else(|| read_lang_from_conf(Path::new(DEFAULT_LOCALE_CONF))); + let (raw, source) = if let Some(val) = env::var("LANG").ok().filter(|s| !s.is_empty()) { + (Some(val), "LANG env") + } else if let Some(val) = read_lang_from_conf(Path::new(DEFAULT_LOCALE_CONF)) { + (Some(val), "locale.conf") + } else { + (None, "default") + }; - match lang { + let result = match raw { Some(l) => parse_lang_prefix(&l), None => "en".to_string(), - } + }; + log::debug!("Detected locale: {result} (source: {source})"); + result } /// Return the string table for the given locale, defaulting to English. diff --git a/src/main.rs b/src/main.rs index 641bf5c..dc9cdbb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,14 +53,16 @@ fn activate(app: >k::Application) { load_css(&display); - // Resolve wallpaper once, decode texture once, share across all windows - // Blur is applied on the GPU via GskBlurNode at widget realization time. + // Resolve wallpaper once, decode texture once, share across all windows. + // Blur is applied on the GPU via GskBlurNode at first widget realization, + // then cached and reused by all subsequent windows. let config = config::load_config(None); let bg_path = config::resolve_background_path(&config); let texture = panel::load_background_texture(&bg_path); + let blur_cache = panel::new_blur_cache(); // Panel on focused output (no set_monitor → compositor picks focused) - let panel = panel::create_panel_window(&texture, config.background_blur, app); + let panel = panel::create_panel_window(&texture, config.background_blur, &blur_cache, app); setup_layer_shell(&panel, true, gtk4_layer_shell::Layer::Overlay); panel.present(); @@ -68,7 +70,7 @@ fn activate(app: >k::Application) { let monitors = display.monitors(); for i in 0..monitors.n_items() { if let Some(monitor) = monitors.item(i).and_then(|obj| obj.downcast::().ok()) { - let wallpaper = panel::create_wallpaper_window(&texture, config.background_blur, app); + let wallpaper = panel::create_wallpaper_window(&texture, config.background_blur, &blur_cache, app); setup_layer_shell(&wallpaper, false, gtk4_layer_shell::Layer::Top); wallpaper.set_monitor(Some(&monitor)); wallpaper.present(); @@ -76,10 +78,27 @@ fn activate(app: >k::Application) { } } +fn setup_logging() { + match systemd_journal_logger::JournalLog::new() { + Ok(logger) => { + if let Err(e) = logger.install() { + eprintln!("Failed to install journal logger: {e}"); + } + } + Err(e) => { + eprintln!("Failed to create journal logger: {e}"); + } + } + let level = if std::env::var("MOONSET_DEBUG").is_ok() { + log::LevelFilter::Debug + } else { + log::LevelFilter::Info + }; + log::set_max_level(level); +} + fn main() { - env_logger::Builder::from_default_env() - .filter_level(log::LevelFilter::Info) - .init(); + setup_logging(); log::info!("Moonset starting"); // Register compiled GResources diff --git a/src/panel.rs b/src/panel.rs index 24bf10d..db06962 100644 --- a/src/panel.rs +++ b/src/panel.rs @@ -91,6 +91,7 @@ pub fn action_definitions() -> Vec { pub fn load_background_texture(bg_path: &Path) -> gdk::Texture { let fallback = format!("{}/wallpaper.jpg", crate::GRESOURCE_PREFIX); + log::debug!("Background: {}", bg_path.display()); if bg_path.starts_with(crate::GRESOURCE_PREFIX) { let resource_path = bg_path.to_str().unwrap_or(&fallback); gdk::Texture::from_resource(resource_path) @@ -134,14 +135,19 @@ fn fade_out_and_quit(app: >k::Application) { }); } +/// Create a new shared blur cache for GPU-blurred wallpaper textures. +pub fn new_blur_cache() -> BlurCache { + Rc::new(RefCell::new(None)) +} + /// Create a wallpaper-only window for secondary monitors. -pub fn create_wallpaper_window(texture: &gdk::Texture, blur_radius: Option, app: >k::Application) -> gtk::ApplicationWindow { +pub fn create_wallpaper_window(texture: &gdk::Texture, blur_radius: Option, blur_cache: &BlurCache, app: >k::Application) -> gtk::ApplicationWindow { let window = gtk::ApplicationWindow::builder() .application(app) .build(); window.add_css_class("wallpaper"); - let background = create_background_picture(texture, blur_radius); + let background = create_background_picture(texture, blur_radius, blur_cache); window.set_child(Some(&background)); // Fade-in on map @@ -159,7 +165,7 @@ pub fn create_wallpaper_window(texture: &gdk::Texture, blur_radius: Option, } /// Create the main panel window with action buttons and confirm flow. -pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option, app: >k::Application) -> gtk::ApplicationWindow { +pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option, blur_cache: &BlurCache, app: >k::Application) -> gtk::ApplicationWindow { let window = gtk::ApplicationWindow::builder() .application(app) .build(); @@ -172,6 +178,7 @@ pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option, app home: dirs::home_dir().unwrap_or_default(), uid: u32::MAX, }); + log::debug!("User: {} ({})", user.display_name, user.username); // State for confirm box let confirm_box: Rc>> = Rc::new(RefCell::new(None)); @@ -181,7 +188,7 @@ pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option, app window.set_child(Some(&overlay)); // Background wallpaper - let background = create_background_picture(texture, blur_radius); + let background = create_background_picture(texture, blur_radius, blur_cache); overlay.set_child(Some(&background)); // Click on background dismisses the menu @@ -285,8 +292,17 @@ pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option, app window } +/// Shared cache for the GPU-blurred wallpaper texture. +/// Computed once on first window realize, reused by all subsequent windows. +type BlurCache = Rc>>; + /// Create a Picture widget for the wallpaper background, optionally with GPU blur. -fn create_background_picture(texture: &gdk::Texture, blur_radius: Option) -> gtk::Picture { +/// When a blur_cache is provided, the blurred texture is computed once and shared. +fn create_background_picture( + texture: &gdk::Texture, + blur_radius: Option, + blur_cache: &BlurCache, +) -> gtk::Picture { let background = gtk::Picture::for_paintable(texture); background.set_content_fit(gtk::ContentFit::Cover); background.set_hexpand(true); @@ -294,9 +310,18 @@ fn create_background_picture(texture: &gdk::Texture, blur_radius: Option) - if let Some(sigma) = blur_radius.filter(|s| *s > 0.0) { let texture = texture.clone(); + let cache = blur_cache.clone(); background.connect_realize(move |picture| { + // Use cached blur if available, otherwise compute and cache + if let Some(ref cached) = *cache.borrow() { + log::debug!("Blur cache hit"); + picture.set_paintable(Some(cached)); + return; + } + log::debug!("Blur cache miss, rendering GPU blur"); if let Some(blurred) = render_blurred_texture(picture, &texture, sigma) { picture.set_paintable(Some(&blurred)); + *cache.borrow_mut() = Some(blurred); } }); } @@ -470,6 +495,7 @@ fn execute_action( error_label: >k::Label, ) { dismiss_confirm(confirm_area, confirm_box); + log::debug!("Executing power action: {}", action_def.name); let action_fn = action_def.action_fn; let action_name = action_def.name; @@ -514,6 +540,7 @@ fn load_avatar_async(image: >k::Image, window: >k::ApplicationWindow, user: match avatar_path { Some(path) => { + log::debug!("Avatar source: file {}", path.display()); // File-based avatar: load and scale in background thread glib::spawn_future_local(clone!( #[weak] @@ -521,7 +548,7 @@ fn load_avatar_async(image: >k::Image, window: >k::ApplicationWindow, user: async move { let result = gio::spawn_blocking(move || { Pixbuf::from_file_at_scale( - path.to_str().unwrap_or(""), + &path, AVATAR_SIZE, AVATAR_SIZE, true, @@ -539,6 +566,7 @@ fn load_avatar_async(image: >k::Image, window: >k::ApplicationWindow, user: )); } None => { + log::debug!("Avatar source: default SVG"); // Default SVG avatar: needs widget color, keep synchronous set_default_avatar(image, window); } diff --git a/src/power.rs b/src/power.rs index a50bff6..1a31cb6 100644 --- a/src/power.rs +++ b/src/power.rs @@ -31,6 +31,7 @@ impl std::error::Error for PowerError {} /// Run a command with timeout and return a PowerError on failure. fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), PowerError> { + log::debug!("Power action: {action} ({program} {args:?})"); let mut child = Command::new(program) .args(args) .stdout(Stdio::piped()) @@ -45,6 +46,9 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), loop { match child.try_wait() { Ok(Some(status)) => { + if status.success() { + log::debug!("Power action {action} completed"); + } if !status.success() { let mut stderr_buf = String::new(); if let Some(mut stderr) = child.stderr.take() { @@ -76,8 +80,21 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), } /// Lock the current session by launching moonlock. +/// Spawns moonlock as a detached process and returns immediately — +/// moonlock runs independently until the user unlocks. pub fn lock() -> Result<(), PowerError> { - run_command("lock", "/usr/bin/moonlock", &[]) + log::debug!("Power action: lock (spawning moonlock)"); + Command::new("/usr/bin/moonlock") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|e| PowerError::CommandFailed { + action: "lock", + message: e.to_string(), + })?; + // Child handle is dropped here — moonlock continues running independently. + Ok(()) } /// Quit the Niri compositor (logout). diff --git a/src/users.rs b/src/users.rs index f853ced..f474939 100644 --- a/src/users.rs +++ b/src/users.rs @@ -55,6 +55,7 @@ pub fn get_avatar_path_with( // ~/.face takes priority let face = home.join(".face"); if face.exists() { + log::debug!("Avatar: using ~/.face"); return Some(face); } @@ -63,6 +64,7 @@ pub fn get_avatar_path_with( if accountsservice_dir.exists() { let icon = accountsservice_dir.join(name); if icon.exists() { + log::debug!("Avatar: using AccountsService icon"); return Some(icon); } }