feat: add optional background blur, align to shared texture pattern

Gaussian blur applied at texture load time when `background_blur` is
set in moonlock.toml. Refactored wallpaper loading from per-window
Picture::for_filename() to shared gdk::Texture pattern (matching
moonset/moongreet), avoiding redundant JPEG decoding on multi-monitor.
This commit is contained in:
2026-03-28 14:53:27 +01:00
parent 09e0d47a38
commit de9a3e9e6a
6 changed files with 220 additions and 18 deletions
+48 -7
View File
@@ -6,6 +6,7 @@ use gdk_pixbuf::Pixbuf;
use glib::clone;
use gtk4::prelude::*;
use gtk4::{self as gtk, gio};
use image::imageops;
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
@@ -41,7 +42,7 @@ struct LockscreenState {
/// Create a lockscreen window for a single monitor.
/// Fingerprint is not initialized here — use `wire_fingerprint()` after async init.
pub fn create_lockscreen_window(
bg_path: &Path,
bg_texture: &gdk::Texture,
_config: &Config,
app: &gtk::Application,
unlock_callback: Rc<dyn Fn()>,
@@ -82,7 +83,7 @@ pub fn create_lockscreen_window(
window.set_child(Some(&overlay));
// Background wallpaper
let background = create_background_picture(bg_path);
let background = create_background_picture(bg_texture);
overlay.set_child(Some(&background));
// Centered vertical box
@@ -413,13 +414,53 @@ pub fn start_fingerprint(
});
}
/// Create a Picture widget for the wallpaper background.
fn create_background_picture(bg_path: &Path) -> gtk::Picture {
let background = if bg_path.starts_with("/dev/moonarch/moonlock") {
gtk::Picture::for_resource(bg_path.to_str().unwrap_or(""))
/// Load the wallpaper as a texture once, for sharing across all windows.
/// When `blur_radius` is `Some(sigma)` with sigma > 0, a Gaussian blur is applied.
pub fn load_background_texture(bg_path: &Path, blur_radius: Option<f32>) -> gdk::Texture {
let fallback = "/dev/moonarch/moonlock/wallpaper.jpg";
let texture = if bg_path.starts_with("/dev/moonarch/moonlock") {
let resource_path = bg_path.to_str().unwrap_or(fallback);
gdk::Texture::from_resource(resource_path)
} else {
gtk::Picture::for_filename(bg_path.to_str().unwrap_or(""))
let file = gio::File::for_path(bg_path);
gdk::Texture::from_file(&file).unwrap_or_else(|_| {
gdk::Texture::from_resource(fallback)
})
};
match blur_radius {
Some(sigma) if sigma > 0.0 => apply_blur(&texture, sigma),
_ => texture,
}
}
/// 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;
let height = texture.height() as u32;
let stride = width as usize * 4;
let mut pixel_data = vec![0u8; stride * height as usize];
texture.download(&mut pixel_data, stride);
let img = image::RgbaImage::from_raw(width, height, pixel_data)
.expect("pixel buffer size matches texture dimensions");
let blurred = imageops::blur(&image::DynamicImage::ImageRgba8(img), sigma);
let bytes = glib::Bytes::from(blurred.as_raw());
let mem_texture = gdk::MemoryTexture::new(
width as i32,
height as i32,
gdk::MemoryFormat::B8g8r8a8Premultiplied,
&bytes,
stride,
);
mem_texture.upcast()
}
/// Create a Picture widget for the wallpaper background from a shared texture.
fn create_background_picture(texture: &gdk::Texture) -> gtk::Picture {
let background = gtk::Picture::for_paintable(texture);
background.set_content_fit(gtk::ContentFit::Cover);
background.set_hexpand(true);
background.set_vexpand(true);