feat: switch to systemd-journal-logger, add debug logging (v0.4.0)

Replace env_logger file-based logging with systemd-journal-logger for
consistency with moonlock and native journalctl integration. Add debug-level
logging at all decision points: config loading, user/session detection,
avatar resolution, locale detection, IPC messages, login flow, and
persistence. No credentials are ever logged.
This commit is contained in:
nevaforget 2026-03-28 01:23:18 +01:00
parent b91e8d47d1
commit 96c94f030a
11 changed files with 137 additions and 230 deletions

View File

@ -46,7 +46,7 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git-<version>-x86_64.pkg.tar.z
- `i18n.rs` — Locale-Erkennung (LANG / /etc/locale.conf) und String-Tabellen (DE/EN), alle UI- und Login-Fehlermeldungen
- `config.rs` — TOML-Config ([appearance] background, gtk-theme) + Wallpaper-Fallback
- `greeter.rs` — GTK4 UI (Overlay-Layout), Login-Flow via greetd IPC, Faillock-Warnung, Avatar-Cache, Last-User/Last-Session Persistence (0o600 Permissions)
- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor, Log-Datei (0o640)
- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor, systemd-journal-logger
- `resources/style.css` — Catppuccin-inspiriertes Theme
## Design Decisions
@ -59,5 +59,6 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git-<version>-x86_64.pkg.tar.z
- **Symmetrie mit moonset**: Gleiche Patterns (i18n, config, users, power, GResource)
- **Session-Validierung**: Relative Pfade erlaubt (greetd löst PATH auf), nur `..`/Null-Bytes werden abgelehnt
- **GTK-Theme-Validierung**: Nur alphanumerisch + `_-+.` erlaubt, verhindert Path-Traversal über Config
- **File Permissions**: Cache-Dateien 0o600, Log-Datei 0o640
- **Journal-Logging**: `systemd-journal-logger` statt File-Logging — `journalctl -t moongreet`
- **File Permissions**: Cache-Dateien 0o600
- **Testbare Persistence**: `save_*_to`/`load_*_from` Varianten mit konfigurierbarem Pfad für Unit-Tests

188
Cargo.lock generated
View File

@ -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"
@ -118,35 +59,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[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"
@ -604,42 +516,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"
@ -687,9 +569,8 @@ dependencies = [
[[package]]
name = "moongreet"
version = "0.3.2"
version = "0.4.0"
dependencies = [
"env_logger",
"gdk-pixbuf",
"gdk4",
"gio",
@ -700,6 +581,7 @@ dependencies = [
"log",
"serde",
"serde_json",
"systemd-journal-logger",
"tempfile",
"toml 0.8.23",
]
@ -710,12 +592,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 = "pango"
version = "0.22.0"
@ -752,21 +628,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"
@ -810,35 +671,6 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[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"
@ -964,6 +796,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"
@ -1096,12 +938,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"

View File

@ -1,6 +1,6 @@
[package]
name = "moongreet"
version = "0.3.2"
version = "0.4.0"
edition = "2024"
description = "A greetd greeter for Wayland with GTK4 and Layer Shell"
license = "MIT"
@ -16,7 +16,7 @@ toml = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
log = "0.4"
env_logger = "0.11"
systemd-journal-logger = "2.2"
[dev-dependencies]
tempfile = "3"

View File

@ -40,27 +40,39 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
let mut merged = Config::default();
for path in paths {
if let Ok(content) = fs::read_to_string(path) {
if let Ok(parsed) = toml::from_str::<TomlConfig>(&content) {
if let Some(appearance) = parsed.appearance {
if let Some(bg) = appearance.background {
// Resolve relative paths against config file directory
let bg_path = PathBuf::from(&bg);
if bg_path.is_absolute() {
merged.background_path = Some(bg);
} else if let Some(parent) = path.parent() {
merged.background_path =
Some(parent.join(&bg).to_string_lossy().to_string());
match fs::read_to_string(path) {
Ok(content) => {
match toml::from_str::<TomlConfig>(&content) {
Ok(parsed) => {
log::debug!("Config loaded: {}", path.display());
if let Some(appearance) = parsed.appearance {
if let Some(bg) = appearance.background {
// Resolve relative paths against config file directory
let bg_path = PathBuf::from(&bg);
if bg_path.is_absolute() {
merged.background_path = Some(bg);
} else if let Some(parent) = path.parent() {
merged.background_path =
Some(parent.join(&bg).to_string_lossy().to_string());
}
}
if appearance.gtk_theme.is_some() {
merged.gtk_theme = appearance.gtk_theme;
}
}
}
if appearance.gtk_theme.is_some() {
merged.gtk_theme = appearance.gtk_theme;
Err(e) => {
log::warn!("Config parse error in {}: {e}", path.display());
}
}
}
Err(_) => {
log::debug!("Config not found: {}", path.display());
}
}
}
log::debug!("Config result: background={:?}, gtk_theme={:?}", merged.background_path, merged.gtk_theme);
merged
}
@ -77,16 +89,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: using config path {}", path.display());
return path;
}
log::debug!("Wallpaper: config path {} not found, trying fallbacks", path.display());
}
// Moonarch ecosystem default
if moonarch_wallpaper.is_file() {
log::debug!("Wallpaper: using moonarch default {}", moonarch_wallpaper.display());
return moonarch_wallpaper.to_path_buf();
}
// GResource fallback path (loaded from compiled resources at runtime)
log::debug!("Wallpaper: using GResource fallback");
PathBuf::from(format!("{GRESOURCE_PREFIX}/wallpaper.jpg"))
}

View File

@ -163,6 +163,10 @@ pub fn create_greeter_window(
let strings = load_strings(None);
let all_users = users::get_users(None);
let all_sessions = sessions::get_sessions(None, None);
log::debug!("Greeter window: {} user(s), {} session(s)", all_users.len(), all_sessions.len());
if let Some(ref theme) = config.gtk_theme {
log::debug!("GTK theme: {theme}");
}
let state = Rc::new(RefCell::new(GreeterState {
selected_user: None,
@ -489,6 +493,7 @@ fn select_initial_user(
.as_ref()
.and_then(|name| users.iter().find(|u| &u.username == name))
.unwrap_or(&users[0]);
log::debug!("Initial user: {} (last_user={:?})", target.username, last_username);
switch_to_user(
target,
@ -515,6 +520,7 @@ fn switch_to_user(
sessions: &[Session],
window: &gtk::ApplicationWindow,
) {
log::debug!("Switching to user: {}", user.username);
{
let mut s = state.borrow_mut();
s.selected_user = Some(user.clone());
@ -531,8 +537,10 @@ fn switch_to_user(
};
if let Some(texture) = cached {
log::debug!("Avatar cache hit for {}", user.username);
avatar_image.set_paintable(Some(&texture));
} else {
log::debug!("Avatar cache miss for {}", user.username);
let avatar_path = users::get_avatar_path(&user.username, &user.home);
if let Some(path) = avatar_path {
// get_avatar_path already checks existence — go straight to loading
@ -558,6 +566,7 @@ fn set_avatar_from_file(
// Reject oversized files
if let Ok(meta) = std::fs::metadata(path) {
if meta.len() > MAX_AVATAR_FILE_SIZE {
log::debug!("Avatar file too large ({} bytes): {}", meta.len(), path.display());
image.set_icon_name(Some("avatar-default-symbolic"));
return;
}
@ -574,7 +583,8 @@ fn set_avatar_from_file(
}
image.set_paintable(Some(&texture));
}
Err(_) => {
Err(e) => {
log::debug!("Failed to load avatar {}: {e}", path.display());
image.set_icon_name(Some("avatar-default-symbolic"));
}
}
@ -590,10 +600,12 @@ fn set_default_avatar(
{
let s = state.borrow();
if let Some(ref texture) = s.default_avatar_texture {
log::debug!("Default avatar: using cached texture");
image.set_paintable(Some(texture));
return;
}
}
log::debug!("Default avatar: tinting SVG from GResource");
let resource_path = users::get_default_avatar_path();
if let Ok(bytes) =
@ -698,6 +710,7 @@ fn show_greetd_error(
/// Cancel any in-progress greetd session.
fn cancel_pending_session(state: &Rc<RefCell<GreeterState>>) {
log::debug!("Cancelling pending greetd session");
let s = state.borrow();
s.login_cancelled
.store(true, std::sync::atomic::Ordering::SeqCst);
@ -731,6 +744,7 @@ fn attempt_login(
password_entry: &gtk::PasswordEntry,
session_dropdown: &gtk::DropDown,
) {
log::debug!("Login attempt for user: {}", user.username);
let sock_path = match std::env::var("GREETD_SOCK") {
Ok(p) if !p.is_empty() => p,
_ => {
@ -740,6 +754,7 @@ fn attempt_login(
};
// Validate socket path
log::debug!("GREETD_SOCK: {sock_path}");
let sock_pathbuf = PathBuf::from(&sock_path);
if !sock_pathbuf.is_absolute() {
show_error(
@ -886,9 +901,11 @@ fn login_worker(
strings: &Strings,
) -> Result<LoginResult, String> {
if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) {
log::debug!("Login cancelled before connect");
return Ok(LoginResult::Cancelled);
}
log::debug!("Connecting to greetd socket: {sock_path}");
let mut sock = UnixStream::connect(sock_path).map_err(|e| e.to_string())?;
if let Err(e) = sock.set_read_timeout(Some(std::time::Duration::from_secs(10))) {
log::warn!("Failed to set read timeout: {e}");
@ -902,6 +919,7 @@ fn login_worker(
}
// Step 1: Create session — if a stale session exists, cancel it and retry
log::debug!("Creating greetd session for {username}");
let mut response = ipc::create_session(&mut sock, username).map_err(|e| e.to_string())?;
if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) {
@ -909,6 +927,7 @@ fn login_worker(
}
if response.get("type").and_then(|v| v.as_str()) == Some("error") {
log::debug!("Stale session detected, cancelling and retrying");
let _ = ipc::cancel_session(&mut sock);
response = ipc::create_session(&mut sock, username).map_err(|e| e.to_string())?;
if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) {
@ -927,6 +946,7 @@ fn login_worker(
// Step 2: Send password if auth message received
if response.get("type").and_then(|v| v.as_str()) == Some("auth_message") {
log::debug!("Sending auth response for {username}");
response =
ipc::post_auth_response(&mut sock, Some(password)).map_err(|e| e.to_string())?;
@ -953,6 +973,7 @@ fn login_worker(
// Step 3: Start session
if response.get("type").and_then(|v| v.as_str()) == Some("success") {
log::debug!("Auth successful, starting session: {exec_cmd}");
let cmd = match split_shell_words(exec_cmd) {
Some(words) if !words.is_empty() => words,
_ => {
@ -981,6 +1002,7 @@ fn login_worker(
}
if response.get("type").and_then(|v| v.as_str()) == Some("success") {
log::info!("Login successful for {username}");
return Ok(LoginResult::Success {
username: username.to_string(),
});
@ -1039,8 +1061,10 @@ fn load_last_user_from(path: &Path) -> Option<String> {
let content = std::fs::read_to_string(path).ok()?;
let username = content.trim();
if is_valid_username(username) {
log::debug!("Loaded last user: {username}");
Some(username.to_string())
} else {
log::debug!("Invalid last user in {}", path.display());
None
}
}
@ -1050,6 +1074,7 @@ fn save_last_user(username: &str) {
}
fn save_last_user_to(path: &Path, username: &str) {
log::debug!("Saving last user: {username}");
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
@ -1072,8 +1097,10 @@ fn load_last_session_from(path: &Path) -> Option<String> {
let content = std::fs::read_to_string(path).ok()?;
let name = content.trim();
if is_valid_session_name(name) {
log::debug!("Loaded last session: {name}");
Some(name.to_string())
} else {
log::debug!("Invalid last session in {}", path.display());
None
}
}
@ -1101,6 +1128,7 @@ fn save_last_session(username: &str, session_name: &str) {
}
fn save_last_session_to(path: &Path, session_name: &str) {
log::debug!("Saving last session: {session_name}");
use std::os::unix::fs::OpenOptionsExt;
use std::io::Write;
let _ = std::fs::OpenOptions::new()

View File

@ -127,10 +127,15 @@ pub fn detect_locale() -> String {
.filter(|s| !s.is_empty())
.or_else(|| read_lang_from_conf(Path::new(DEFAULT_LOCALE_CONF)));
match lang {
Some(l) => parse_lang_prefix(&l),
let result = match lang {
Some(ref l) => parse_lang_prefix(l),
None => "en".to_string(),
}
};
log::debug!("Detected locale: {result} (source: {})", match lang {
Some(_) => "LANG env or locale.conf",
None => "default",
});
result
}
/// Return the string table for the given locale, defaulting to English.

View File

@ -84,6 +84,9 @@ pub fn send_message(
return Err(IpcError::PayloadTooLarge(payload.len()));
}
let msg_type = msg.get("type").and_then(|v| v.as_str()).unwrap_or("unknown");
log::debug!("IPC send: type={msg_type}, size={} bytes", payload.len());
let header = (payload.len() as u32).to_le_bytes();
stream.write_all(&header)?;
stream.write_all(&payload)?;
@ -101,6 +104,8 @@ pub fn recv_message(stream: &mut UnixStream) -> Result<serde_json::Value, IpcErr
let payload = recv_payload(stream, length)?;
let value: serde_json::Value = serde_json::from_slice(&payload)?;
let msg_type = value.get("type").and_then(|v| v.as_str()).unwrap_or("unknown");
log::debug!("IPC recv: type={msg_type}, size={length} bytes");
Ok(value)
}

View File

@ -13,8 +13,6 @@ use gdk4 as gdk;
use gtk4::prelude::*;
use gtk4::{self as gtk, gio};
use gtk4_layer_shell::LayerShell;
use std::path::PathBuf;
fn load_css(display: &gdk::Display) {
let css_provider = gtk::CssProvider::new();
css_provider.load_from_resource("/dev/moonarch/moongreet/style.css");
@ -48,13 +46,16 @@ fn activate(app: &gtk::Application) {
}
};
log::debug!("Display: {:?}", display);
load_css(&display);
// Load config and resolve wallpaper
let config = config::load_config(None);
let bg_path = config::resolve_background_path(&config);
log::debug!("Background path: {}", bg_path.display());
let use_layer_shell = std::env::var("MOONGREET_NO_LAYER_SHELL").is_err();
log::debug!("Layer shell: {use_layer_shell}");
// Main greeter window (login UI) — compositor picks focused monitor
let greeter_window = greeter::create_greeter_window(&bg_path, &config, app);
@ -66,6 +67,7 @@ fn activate(app: &gtk::Application) {
// Wallpaper-only windows on all monitors (only with layer shell)
if use_layer_shell {
let monitors = display.monitors();
log::debug!("Monitor count: {}", monitors.n_items());
for i in 0..monitors.n_items() {
if let Some(monitor) = monitors
.item(i)
@ -81,25 +83,11 @@ fn activate(app: &gtk::Application) {
}
fn setup_logging() {
let mut builder = env_logger::Builder::from_default_env();
builder.filter_level(log::LevelFilter::Info);
// Try file logging to /var/cache/moongreet/ — fall back to stderr
let log_dir = PathBuf::from("/var/cache/moongreet");
if log_dir.is_dir() {
let log_file = log_dir.join("moongreet.log");
use std::os::unix::fs::OpenOptionsExt;
if let Ok(file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.mode(0o640)
.open(&log_file)
{
builder.target(env_logger::Target::Pipe(Box::new(file)));
}
}
builder.init();
systemd_journal_logger::JournalLog::new()
.unwrap()
.install()
.unwrap();
log::set_max_level(log::LevelFilter::Debug);
}
fn main() {

View File

@ -23,6 +23,7 @@ impl std::error::Error for PowerError {}
/// Run a command 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 child = Command::new(program)
.args(args)
.spawn()
@ -38,6 +39,9 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(),
message: e.to_string(),
})?;
if output.status.success() {
log::debug!("Power action {action} completed successfully");
}
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(PowerError::CommandFailed {

View File

@ -46,8 +46,17 @@ fn parse_desktop_file(path: &Path, session_type: &str) -> Option<Session> {
}
}
let name = name.filter(|s| !s.is_empty())?;
let exec_cmd = exec_cmd.filter(|s| !s.is_empty())?;
let name = name.filter(|s| !s.is_empty());
let exec_cmd = exec_cmd.filter(|s| !s.is_empty());
if name.is_none() || exec_cmd.is_none() {
log::debug!("Skipping {}: missing Name={} Exec={}", path.display(),
name.is_some(), exec_cmd.is_some());
return None;
}
let name = name?;
let exec_cmd = exec_cmd?;
Some(Session {
name,
@ -74,7 +83,10 @@ pub fn get_sessions(
for (dirs, session_type) in [(wayland, "wayland"), (xsession, "x11")] {
for directory in dirs {
let entries = match fs::read_dir(directory) {
Ok(e) => e,
Ok(e) => {
log::debug!("Scanning session directory: {}", directory.display());
e
}
Err(_) => continue,
};
@ -93,6 +105,7 @@ pub fn get_sessions(
}
}
log::debug!("Found {} session(s)", sessions.len());
sessions
}

View File

@ -46,7 +46,10 @@ pub fn get_users(passwd_path: Option<&Path>) -> Vec<User> {
let content = match fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return Vec::new(),
Err(e) => {
log::warn!("Failed to read passwd file {}: {e}", path.display());
return Vec::new();
}
};
let mut users = Vec::new();
@ -88,6 +91,7 @@ pub fn get_users(passwd_path: Option<&Path>) -> Vec<User> {
});
}
log::debug!("Found {} login user(s)", users.len());
users
}
@ -106,21 +110,28 @@ pub fn get_avatar_path_with(
// AccountsService icon takes priority
if accountsservice_dir.exists() {
let icon = accountsservice_dir.join(username);
if let Ok(meta) = icon.symlink_metadata()
&& !meta.file_type().is_symlink()
{
return Some(icon);
if let Ok(meta) = icon.symlink_metadata() {
if meta.file_type().is_symlink() {
log::warn!("Rejecting symlink avatar for {username}: {}", icon.display());
} else {
log::debug!("Avatar for {username}: AccountsService {}", icon.display());
return Some(icon);
}
}
}
// ~/.face fallback
let face = home.join(".face");
if let Ok(meta) = face.symlink_metadata()
&& !meta.file_type().is_symlink()
{
return Some(face);
if let Ok(meta) = face.symlink_metadata() {
if meta.file_type().is_symlink() {
log::warn!("Rejecting symlink avatar for {username}: {}", face.display());
} else {
log::debug!("Avatar for {username}: ~/.face {}", face.display());
return Some(face);
}
}
log::debug!("No avatar found for {username}");
None
}