diff --git a/Cargo.lock b/Cargo.lock index 271abc6..df6bcd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -640,7 +640,7 @@ dependencies = [ [[package]] name = "moongreet" -version = "0.4.1" +version = "0.4.0" dependencies = [ "gdk-pixbuf", "gdk4", diff --git a/src/greeter.rs b/src/greeter.rs index 90a7f09..9729275 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -9,10 +9,14 @@ use gtk4::{self as gtk, gio}; use image::imageops; use std::cell::RefCell; use std::collections::HashMap; +use std::fs; +use std::io::Write; +use std::os::unix::fs::OpenOptionsExt; use std::os::unix::net::UnixStream; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::{Arc, Mutex}; +use std::time::SystemTime; use crate::config::Config; use crate::i18n::{faillock_warning, load_strings, Strings}; @@ -137,11 +141,117 @@ pub fn load_background_texture(bg_path: &Path, blur_radius: Option) -> Opti }?; match blur_radius { - Some(sigma) if sigma > 0.0 => Some(apply_blur(&texture, sigma)), + Some(sigma) if sigma > 0.0 => Some(load_blurred_with_cache(bg_path, &texture, sigma)), _ => Some(texture), } } +// -- Blur cache ---------------------------------------------------------------- + +const BLUR_CACHE_PNG: &str = "blur-cache.png"; +const BLUR_CACHE_META: &str = "blur-cache.meta"; + +fn blur_cache_dir() -> Option { + Some(PathBuf::from("/var/cache/moongreet")) +} + +/// Build the cache key string for the current wallpaper + sigma. +fn build_cache_meta(bg_path: &Path, sigma: f32) -> Option { + if bg_path.starts_with("/dev/moonarch/") { + let binary = std::env::current_exe().ok()?; + let binary_mtime = fs::metadata(&binary) + .ok()? + .modified() + .ok()? + .duration_since(SystemTime::UNIX_EPOCH) + .ok()? + .as_secs(); + Some(format!( + "path={}\nbinary_mtime={}\nsigma={}\n", + bg_path.display(), binary_mtime, sigma, + )) + } else { + let meta = fs::metadata(bg_path).ok()?; + let mtime = meta + .modified() + .ok()? + .duration_since(SystemTime::UNIX_EPOCH) + .ok()? + .as_secs(); + Some(format!( + "path={}\nsize={}\nmtime={}\nsigma={}\n", + bg_path.display(), meta.len(), mtime, sigma, + )) + } +} + +/// Try to load a cached blurred texture if the cache key matches. +fn load_cached_blur(cache_dir: &Path, expected_meta: &str) -> Option { + let stored_meta = fs::read_to_string(cache_dir.join(BLUR_CACHE_META)).ok()?; + if stored_meta != expected_meta { + log::debug!("Blur cache meta mismatch, will re-blur"); + return None; + } + let file = gio::File::for_path(cache_dir.join(BLUR_CACHE_PNG)); + match gdk::Texture::from_file(&file) { + Ok(texture) => { + log::debug!("Loaded blurred wallpaper from cache"); + Some(texture) + } + Err(e) => { + log::debug!("Failed to load cached blur PNG: {e}"); + None + } + } +} + +/// Save a blurred texture to the cache directory. +fn save_blur_cache(cache_dir: &Path, texture: &gdk::Texture, meta: &str) { + if let Err(e) = save_blur_cache_inner(cache_dir, texture, meta) { + log::debug!("Failed to save blur cache: {e}"); + } +} + +fn save_blur_cache_inner( + cache_dir: &Path, + texture: &gdk::Texture, + meta: &str, +) -> Result<(), Box> { + fs::create_dir_all(cache_dir)?; + + let png_bytes = texture.save_to_png_bytes(); + + let mut f = fs::OpenOptions::new() + .create(true).write(true).truncate(true).mode(0o600) + .open(cache_dir.join(BLUR_CACHE_PNG))?; + f.write_all(&png_bytes)?; + + let mut f = fs::OpenOptions::new() + .create(true).write(true).truncate(true).mode(0o600) + .open(cache_dir.join(BLUR_CACHE_META))?; + f.write_all(meta.as_bytes())?; + + log::debug!("Saved blur cache to {}", cache_dir.display()); + Ok(()) +} + +/// Load blurred texture, using disk cache when available. +fn load_blurred_with_cache(bg_path: &Path, texture: &gdk::Texture, sigma: f32) -> gdk::Texture { + if let Some(cache_dir) = blur_cache_dir() { + if let Some(meta) = build_cache_meta(bg_path, sigma) { + if let Some(cached) = load_cached_blur(&cache_dir, &meta) { + return cached; + } + let blurred = apply_blur(texture, sigma); + save_blur_cache(&cache_dir, &blurred, &meta); + return blurred; + } + } + apply_blur(texture, sigma) +} + +// -- Blur implementation ------------------------------------------------------- + /// Apply Gaussian blur to a texture and return a blurred texture. fn apply_blur(texture: &gdk::Texture, sigma: f32) -> gdk::Texture { let width = texture.width() as u32; @@ -1683,4 +1793,60 @@ mod tests { let result = load_background_texture(path, None); assert!(result.is_none()); } + + // -- Blur cache tests -- + + #[test] + fn build_cache_meta_for_file() { + let dir = tempfile::tempdir().unwrap(); + let file = dir.path().join("wallpaper.jpg"); + std::fs::write(&file, b"fake image").unwrap(); + let meta = build_cache_meta(&file, 20.0); + assert!(meta.is_some()); + let meta = meta.unwrap(); + assert!(meta.contains("path=")); + assert!(meta.contains("size=10")); + assert!(meta.contains("sigma=20")); + } + + #[test] + fn build_cache_meta_for_gresource() { + let path = Path::new("/dev/moonarch/moongreet/wallpaper.jpg"); + let meta = build_cache_meta(path, 15.0); + assert!(meta.is_some()); + let meta = meta.unwrap(); + assert!(meta.contains("binary_mtime=")); + assert!(meta.contains("sigma=15")); + assert!(!meta.contains("size=")); + } + + #[test] + fn build_cache_meta_missing_file() { + let meta = build_cache_meta(Path::new("/nonexistent/wallpaper.jpg"), 20.0); + assert!(meta.is_none()); + } + + #[test] + fn cache_meta_mismatch_returns_none() { + let dir = tempfile::tempdir().unwrap(); + std::fs::write( + dir.path().join(BLUR_CACHE_META), + "path=/old.jpg\nsize=100\nmtime=1\nsigma=20\n", + ).unwrap(); + let result = load_cached_blur( + dir.path(), + "path=/new.jpg\nsize=200\nmtime=2\nsigma=20\n", + ); + assert!(result.is_none()); + } + + #[test] + fn cache_missing_meta_returns_none() { + let dir = tempfile::tempdir().unwrap(); + let result = load_cached_blur( + dir.path(), + "path=/any.jpg\nsize=1\nmtime=1\nsigma=20\n", + ); + assert!(result.is_none()); + } }