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:
parent
b91e8d47d1
commit
96c94f030a
@ -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
188
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"))
|
||||
}
|
||||
|
||||
|
||||
@ -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: >k::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: >k::PasswordEntry,
|
||||
session_dropdown: >k::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()
|
||||
|
||||
11
src/i18n.rs
11
src/i18n.rs
@ -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.
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
30
src/main.rs
30
src/main.rs
@ -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: >k::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: >k::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: >k::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() {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
29
src/users.rs
29
src/users.rs
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user