perf: cache blurred wallpaper to disk to avoid re-blur on startup

First launch with blur blurs and saves to /var/cache/moongreet/.
Subsequent starts load the cached PNG directly. Cache invalidates
when wallpaper path, size, mtime, or sigma changes.
This commit is contained in:
nevaforget 2026-03-28 21:23:36 +01:00
parent 64470f99c3
commit 3c39467508
2 changed files with 168 additions and 2 deletions

2
Cargo.lock generated
View File

@ -640,7 +640,7 @@ dependencies = [
[[package]]
name = "moongreet"
version = "0.4.1"
version = "0.4.0"
dependencies = [
"gdk-pixbuf",
"gdk4",

View File

@ -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<f32>) -> 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<PathBuf> {
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<String> {
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<gdk::Texture> {
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<dyn std::error::Error>> {
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());
}
}