Compare commits

..

No commits in common. "64470f99c32fa72a97a2fc16b057f31107faf5f1" and "14d6476e5a50d832337cba1b385d8ecaff04a915" have entirely different histories.

6 changed files with 11 additions and 198 deletions

131
Cargo.lock generated
View File

@ -2,12 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "anyhow"
version = "1.0.102"
@ -26,18 +20,6 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "bytemuck"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cairo-rs"
version = "0.22.0"
@ -77,15 +59,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@ -108,15 +81,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "field-offset"
version = "0.3.6"
@ -127,16 +91,6 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "foldhash"
version = "0.1.5"
@ -550,21 +504,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"moxcms",
"num-traits",
"png",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "indexmap"
version = "2.13.0"
@ -628,16 +567,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "moongreet"
version = "0.4.1"
@ -649,7 +578,6 @@ dependencies = [
"glib-build-tools",
"gtk4",
"gtk4-layer-shell",
"image",
"log",
"serde",
"serde_json",
@ -658,25 +586,6 @@ dependencies = [
"toml 0.8.23",
]
[[package]]
name = "moxcms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [
"num-traits",
"pxfm",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@ -719,19 +628,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "png"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
@ -760,12 +656,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pxfm"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
[[package]]
name = "quote"
version = "1.0.45"
@ -870,12 +760,6 @@ dependencies = [
"serde_core",
]
[[package]]
name = "simd-adler32"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "slab"
version = "0.4.12"
@ -1244,18 +1128,3 @@ name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zune-core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
[[package]]
name = "zune-jpeg"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]

View File

@ -1,6 +1,6 @@
[package]
name = "moongreet"
version = "0.4.0"
version = "0.4.1"
edition = "2024"
description = "A greetd greeter for Wayland with GTK4 and Layer Shell"
license = "MIT"
@ -15,7 +15,6 @@ gio = "0.22"
toml = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
log = "0.4"
systemd-journal-logger = "2.2"

View File

@ -1,12 +1,5 @@
# Decisions
## 2026-03-28 Optional background blur via `image` crate
- **Who**: Selene, Dom
- **Why**: Blurred wallpaper as greeter background is a common UX pattern for login screens
- **Tradeoffs**: Adds `image` crate dependency (~15 transitive crates); CPU-side Gaussian blur at load time adds startup latency proportional to image size and sigma. Acceptable because blur runs once and the texture is shared across monitors.
- **How**: `load_background_texture(bg_path, blur_radius)` loads texture, optionally applies `imageops::blur()`, returns blurred `gdk::Texture`. Config option `background-blur: Option<f32>` in `[appearance]` TOML section.
## 2026-03-28 Audit fixes for shared wallpaper texture (v0.4.1)
- **Who**: Selene, Dominik
- **Why**: Quality, performance, and security audits flagged issues in `load_background_texture()`, debug logging, and greetd error handling

View File

@ -22,8 +22,6 @@ struct TomlConfig {
#[derive(Debug, Clone, Default, Deserialize)]
struct Appearance {
background: Option<String>,
#[serde(rename = "background-blur")]
background_blur: Option<f32>,
#[serde(rename = "gtk-theme")]
gtk_theme: Option<String>,
}
@ -32,7 +30,6 @@ struct Appearance {
#[derive(Debug, Clone, Default)]
pub struct Config {
pub background_path: Option<String>,
pub background_blur: Option<f32>,
pub gtk_theme: Option<String>,
}
@ -59,9 +56,6 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
Some(parent.join(&bg).to_string_lossy().to_string());
}
}
if appearance.background_blur.is_some() {
merged.background_blur = appearance.background_blur;
}
if appearance.gtk_theme.is_some() {
merged.gtk_theme = appearance.gtk_theme;
}
@ -78,7 +72,7 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
}
}
log::debug!("Config result: background={:?}, blur={:?}, gtk_theme={:?}", merged.background_path, merged.background_blur, merged.gtk_theme);
log::debug!("Config result: background={:?}, gtk_theme={:?}", merged.background_path, merged.gtk_theme);
merged
}
@ -120,7 +114,6 @@ mod tests {
fn default_config_has_none_fields() {
let config = Config::default();
assert!(config.background_path.is_none());
assert!(config.background_blur.is_none());
assert!(config.gtk_theme.is_none());
}
@ -138,7 +131,7 @@ mod tests {
let conf = dir.path().join("moongreet.toml");
fs::write(
&conf,
"[appearance]\nbackground = \"/custom/wallpaper.jpg\"\nbackground-blur = 20.0\ngtk-theme = \"catppuccin\"\n",
"[appearance]\nbackground = \"/custom/wallpaper.jpg\"\ngtk-theme = \"catppuccin\"\n",
)
.unwrap();
let paths = vec![conf];
@ -147,20 +140,9 @@ mod tests {
config.background_path.as_deref(),
Some("/custom/wallpaper.jpg")
);
assert_eq!(config.background_blur, Some(20.0));
assert_eq!(config.gtk_theme.as_deref(), Some("catppuccin"));
}
#[test]
fn load_config_blur_optional() {
let dir = tempfile::tempdir().unwrap();
let conf = dir.path().join("moongreet.toml");
fs::write(&conf, "[appearance]\nbackground = \"/bg.jpg\"\n").unwrap();
let paths = vec![conf];
let config = load_config(Some(&paths));
assert!(config.background_blur.is_none());
}
#[test]
fn load_config_resolves_relative_background() {
let dir = tempfile::tempdir().unwrap();
@ -214,7 +196,7 @@ mod tests {
fs::write(&wallpaper, "fake").unwrap();
let config = Config {
background_path: Some(wallpaper.to_str().unwrap().to_string()),
..Config::default()
gtk_theme: None,
};
assert_eq!(
resolve_background_path_with(&config, Path::new("/nonexistent")),
@ -226,7 +208,7 @@ mod tests {
fn resolve_ignores_config_path_when_file_missing() {
let config = Config {
background_path: Some("/nonexistent/wallpaper.jpg".to_string()),
..Config::default()
gtk_theme: None,
};
let result = resolve_background_path_with(&config, Path::new("/nonexistent"));
assert!(result.to_str().unwrap().contains("moongreet"));

View File

@ -6,7 +6,6 @@ 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::collections::HashMap;
use std::os::unix::net::UnixStream;
@ -97,10 +96,9 @@ fn is_valid_username(name: &str) -> bool {
}
/// Load the background image as a shared texture (decode once, reuse everywhere).
/// 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>) -> Option<gdk::Texture> {
pub fn load_background_texture(bg_path: &Path) -> Option<gdk::Texture> {
let path_str = bg_path.to_str()?;
let texture = if bg_path.starts_with("/dev/moonarch/moongreet") {
if bg_path.starts_with("/dev/moonarch/moongreet") {
match gio::resources_lookup_data(path_str, gio::ResourceLookupFlags::NONE) {
Ok(bytes) => match gdk::Texture::from_bytes(&bytes) {
Ok(texture) => Some(texture),
@ -134,37 +132,9 @@ pub fn load_background_texture(bg_path: &Path, blur_radius: Option<f32>) -> Opti
None
}
}
}?;
match blur_radius {
Some(sigma) if sigma > 0.0 => Some(apply_blur(&texture, sigma)),
_ => Some(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 wallpaper-only window for secondary monitors.
pub fn create_wallpaper_window(
texture: &gdk::Texture,
@ -1658,7 +1628,7 @@ mod tests {
#[test]
fn load_background_texture_missing_file_returns_none() {
let result = load_background_texture(Path::new("/nonexistent/wallpaper.jpg"), None);
let result = load_background_texture(Path::new("/nonexistent/wallpaper.jpg"));
assert!(result.is_none());
}
@ -1669,7 +1639,7 @@ mod tests {
// Create a sparse file that exceeds MAX_WALLPAPER_FILE_SIZE
let f = std::fs::File::create(&path).unwrap();
f.set_len(MAX_WALLPAPER_FILE_SIZE + 1).unwrap();
let result = load_background_texture(&path, None);
let result = load_background_texture(&path);
assert!(result.is_none());
}
@ -1680,7 +1650,7 @@ mod tests {
// 0xFF is not valid UTF-8
let non_utf8 = OsStr::from_bytes(&[0xff, 0xfe, 0xfd]);
let path = Path::new(non_utf8);
let result = load_background_texture(path, None);
let result = load_background_texture(path);
assert!(result.is_none());
}
}

View File

@ -55,7 +55,7 @@ fn activate(app: &gtk::Application) {
log::debug!("Background path: {}", bg_path.display());
// Load background texture once — shared across all windows
let bg_texture = greeter::load_background_texture(&bg_path, config.background_blur);
let bg_texture = greeter::load_background_texture(&bg_path);
if bg_texture.is_none() {
log::error!("Failed to load background texture — greeter will start without wallpaper");
}