moonlock/src/config.rs
nevaforget 2a9cc52223
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 2s
fix: audit fixes — peek icon, blur limit, GResource compression, sync markers (v0.6.8)
- Enable peek icon on password entry (consistent with moongreet)
- Raise blur limit from 100 to 200 (consistent with moongreet/moonset)
- Add compressed="true" to GResource CSS/SVG entries
- Add SYNC comments to duplicated blur/background functions
2026-03-31 11:08:36 +02:00

152 lines
5.9 KiB
Rust

// ABOUTME: Configuration loading for the lockscreen.
// ABOUTME: Reads moonlock.toml for wallpaper and fingerprint settings with fallback hierarchy.
use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
const MOONARCH_WALLPAPER: &str = "/usr/share/moonarch/wallpaper.jpg";
fn default_config_paths() -> Vec<PathBuf> {
let mut paths = vec![PathBuf::from("/etc/moonlock/moonlock.toml")];
if let Some(home) = std::env::var_os("HOME") {
paths.push(PathBuf::from(home).join(".config/moonlock/moonlock.toml"));
}
paths
}
/// Raw deserialization struct — fingerprint_enabled is optional so that
/// an empty user config does not override the system config's value.
#[derive(Debug, Clone, Default, Deserialize)]
struct RawConfig {
pub background_path: Option<String>,
pub background_blur: Option<f32>,
pub fingerprint_enabled: Option<bool>,
}
/// Resolved configuration with concrete values.
#[derive(Debug, Clone)]
pub struct Config {
pub background_path: Option<String>,
pub background_blur: Option<f32>,
pub fingerprint_enabled: bool,
}
impl Default for Config {
fn default() -> Self {
Config {
background_path: None,
background_blur: None,
fingerprint_enabled: true,
}
}
}
pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
let default_paths = default_config_paths();
let paths = config_paths.unwrap_or(&default_paths);
let mut merged = Config::default();
for path in paths {
if let Ok(content) = fs::read_to_string(path) {
match toml::from_str::<RawConfig>(&content) {
Ok(parsed) => {
if parsed.background_path.is_some() { merged.background_path = parsed.background_path; }
if let Some(blur) = parsed.background_blur {
merged.background_blur = Some(blur.clamp(0.0, 200.0));
}
if let Some(fp) = parsed.fingerprint_enabled { merged.fingerprint_enabled = fp; }
}
Err(e) => {
log::warn!("Failed to parse {}: {e}", path.display());
}
}
}
}
merged
}
pub fn resolve_background_path(config: &Config) -> Option<PathBuf> {
resolve_background_path_with(config, Path::new(MOONARCH_WALLPAPER))
}
pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path) -> Option<PathBuf> {
if let Some(ref bg) = config.background_path {
let path = PathBuf::from(bg);
if let Ok(meta) = path.symlink_metadata() {
if meta.is_file() && !meta.file_type().is_symlink() { return Some(path); }
}
}
if moonarch_wallpaper.is_file() { return Some(moonarch_wallpaper.to_path_buf()); }
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test] fn default_config() { let c = Config::default(); assert!(c.background_path.is_none()); assert!(c.background_blur.is_none()); assert!(c.fingerprint_enabled); }
#[test] fn load_default_fingerprint_true() {
let dir = tempfile::tempdir().unwrap();
let conf = dir.path().join("moonlock.toml");
fs::write(&conf, "").unwrap();
let c = load_config(Some(&[conf]));
assert!(c.fingerprint_enabled);
}
#[test] fn load_background() {
let dir = tempfile::tempdir().unwrap();
let conf = dir.path().join("moonlock.toml");
fs::write(&conf, "background_path = \"/custom/bg.jpg\"\nbackground_blur = 15.0\nfingerprint_enabled = false\n").unwrap();
let c = load_config(Some(&[conf]));
assert_eq!(c.background_path.as_deref(), Some("/custom/bg.jpg"));
assert_eq!(c.background_blur, Some(15.0));
assert!(!c.fingerprint_enabled);
}
#[test] fn load_blur_optional() {
let dir = tempfile::tempdir().unwrap();
let conf = dir.path().join("moonlock.toml");
fs::write(&conf, "background_path = \"/bg.jpg\"\n").unwrap();
let c = load_config(Some(&[conf]));
assert!(c.background_blur.is_none());
}
#[test] fn resolve_config_path() {
let dir = tempfile::tempdir().unwrap();
let wp = dir.path().join("bg.jpg"); fs::write(&wp, "fake").unwrap();
let c = Config { background_path: Some(wp.to_str().unwrap().to_string()), ..Config::default() };
assert_eq!(resolve_background_path_with(&c, Path::new("/nonexistent")), Some(wp));
}
#[test] fn empty_user_config_preserves_system_fingerprint() {
let dir = tempfile::tempdir().unwrap();
let sys_conf = dir.path().join("system.toml");
let usr_conf = dir.path().join("user.toml");
fs::write(&sys_conf, "fingerprint_enabled = false\n").unwrap();
fs::write(&usr_conf, "").unwrap();
let c = load_config(Some(&[sys_conf, usr_conf]));
assert!(!c.fingerprint_enabled);
}
#[test] fn resolve_no_wallpaper_returns_none() {
let c = Config::default();
let r = resolve_background_path_with(&c, Path::new("/nonexistent"));
assert!(r.is_none());
}
#[test] fn toml_parse_error_returns_default() {
let dir = tempfile::tempdir().unwrap();
let conf = dir.path().join("moonlock.toml");
fs::write(&conf, "this is not valid toml {{{{").unwrap();
let c = load_config(Some(&[conf]));
assert!(c.fingerprint_enabled);
assert!(c.background_path.is_none());
}
#[cfg(unix)]
#[test] fn symlink_rejected_for_background() {
let dir = tempfile::tempdir().unwrap();
let real = dir.path().join("bg.jpg");
let link = dir.path().join("link.jpg");
fs::write(&real, "fake").unwrap();
std::os::unix::fs::symlink(&real, &link).unwrap();
let c = Config { background_path: Some(link.to_str().unwrap().to_string()), ..Config::default() };
// Symlink should be rejected — falls through to None
let r = resolve_background_path_with(&c, Path::new("/nonexistent"));
assert!(r.is_none());
}
}