From e05aa31ce365664ca9cb67cb72ad53177eddeee9 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 30 Sep 2026 14:56:43 +0100 Subject: [PATCH] Decode portal wallpapers by content and simplify status bar Detect wallpaper image formats from their contents so extensionless portal copies render on the desktop and lock screen. Preserve decode limits and cover extensionless files with regression tests. Remove the Bluetooth icon from the status bar. --- Cargo.lock | 1 + compositor/Cargo.toml | 3 +++ compositor/src/wallpaper.rs | 35 +++++++++++++++++++++++++++-- shell/ferese-lock/src/appearance.rs | 25 +++++++++++++++++++-- shell/ferese-shell/src/status_ui.rs | 3 +-- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0f8026..c5d764e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1663,6 +1663,7 @@ dependencies = [ "serde", "serde_json", "smithay", + "tempfile", "tracing", "tracing-subscriber", "xcursor", diff --git a/compositor/Cargo.toml b/compositor/Cargo.toml index 026a488..4c559f9 100644 --- a/compositor/Cargo.toml +++ b/compositor/Cargo.toml @@ -26,3 +26,6 @@ serde_json.workspace = true tracing.workspace = true tracing-subscriber.workspace = true xcursor.workspace = true + +[dev-dependencies] +tempfile.workspace = true diff --git a/compositor/src/wallpaper.rs b/compositor/src/wallpaper.rs index df56f0e..b572623 100644 --- a/compositor/src/wallpaper.rs +++ b/compositor/src/wallpaper.rs @@ -83,11 +83,20 @@ impl WallpaperState { let wakeup = wakeup.clone(); std::thread::spawn(move || { let load = || -> Result { - let (width, height) = image::image_dimensions(&path).map_err(|e| e.to_string())?; + use image::ImageDecoder; + let mut reader = image::ImageReader::open(&path) + .map_err(|e| e.to_string())? + .with_guessed_format() + .map_err(|e| e.to_string())?; + let mut limits = image::Limits::default(); + limits.max_alloc = Some(256 * 1024 * 1024); + reader.limits(limits); + let decoder = reader.into_decoder().map_err(|e| e.to_string())?; + let (width, height) = decoder.dimensions(); if u64::from(width) * u64::from(height) * 4 > 256 * 1024 * 1024 { return Err("wallpaper exceeds the 256 MiB decode limit".into()); } - image::open(path) + image::DynamicImage::from_decoder(decoder) .map(|image| image.into_rgba8()) .map_err(|e| e.to_string()) }; @@ -329,6 +338,28 @@ fn image_geometry( mod tests { use super::*; + #[test] + fn portal_wallpaper_decodes_without_a_filename_extension() { + let directory = tempfile::tempdir().unwrap(); + let expected = image::RgbaImage::from_pixel(3, 2, image::Rgba([32, 80, 160, 255])); + for name in ["wallpaper-portal", "wallpaper.jpg"] { + let path = directory.path().join(name); + expected.save_with_format(&path, image::ImageFormat::Png).unwrap(); + let mut state = WallpaperState::new(WallpaperConfig { + path: Some(path), + mode: WallpaperMode::Fill, + }); + let decoded = state + .receiver + .take() + .unwrap() + .recv_timeout(Duration::from_secs(5)) + .unwrap() + .unwrap(); + assert_eq!(decoded, expected); + } + } + #[test] fn failed_upload_retry_wakes_idle_loop_and_does_not_spin_when_output_is_inactive() { use smithay::backend::renderer::ContextId; diff --git a/shell/ferese-lock/src/appearance.rs b/shell/ferese-lock/src/appearance.rs index fa57ead..e009253 100644 --- a/shell/ferese-lock/src/appearance.rs +++ b/shell/ferese-lock/src/appearance.rs @@ -169,6 +169,16 @@ fn account_picture(user: &str) -> Option { #[cfg(test)] mod tests { use super::*; + #[test] + fn portal_wallpaper_loads_without_a_filename_extension() { + let file = tempfile::NamedTempFile::new().unwrap(); + ::image::RgbaImage::from_pixel(3, 2, ::image::Rgba([32, 80, 160, 255])) + .save_with_format(file.path(), ::image::ImageFormat::Png) + .unwrap(); + assert!(load_wallpaper(file.path(), 0.).is_ok()); + assert!(load_wallpaper(file.path(), 2.).is_ok()); + } + #[test] fn lock_options_share_shell_style_without_changing_security() { let directory = tempfile::tempdir().unwrap(); @@ -211,11 +221,22 @@ mod tests { } pub fn load_wallpaper(path: &std::path::Path, blur: f32) -> Result { - let (width, height) = ::image::image_dimensions(path).map_err(|e| e.to_string())?; + use ::image::ImageDecoder; + let mut reader = ::image::ImageReader::open(path) + .map_err(|e| e.to_string())? + .with_guessed_format() + .map_err(|e| e.to_string())?; + let mut limits = ::image::Limits::default(); + limits.max_alloc = Some(256 * 1024 * 1024); + reader.limits(limits); + let decoder = reader.into_decoder().map_err(|e| e.to_string())?; + let (width, height) = decoder.dimensions(); if u64::from(width) * u64::from(height) * 4 > 256 * 1024 * 1024 { return Err("Lock wallpaper exceeds the decode limit".into()); } - let pixels = ::image::open(path).map_err(|e| e.to_string())?.into_rgba8(); + let pixels = ::image::DynamicImage::from_decoder(decoder) + .map_err(|e| e.to_string())? + .into_rgba8(); let pixels = if blur > 0. { ::image::imageops::blur(&::image::imageops::thumbnail(&pixels, 640, 640), blur) } else { diff --git a/shell/ferese-shell/src/status_ui.rs b/shell/ferese-shell/src/status_ui.rs index ca0c866..6820c79 100644 --- a/shell/ferese-shell/src/status_ui.rs +++ b/shell/ferese-shell/src/status_ui.rs @@ -252,11 +252,10 @@ impl FereseShell { pub fn view_status_bar(&self) -> Element<'_, cosmic::Action> { let theme = self.config.theme.for_bar(); let metrics = BarMetrics::from(theme); - let mut controls = row::with_capacity(7).spacing(1).align_y(Alignment::Center); + let mut controls = row::with_capacity(6).spacing(1).align_y(Alignment::Center); for kind in [ Menu::System, Menu::Network, - Menu::Bluetooth, Menu::Audio, Menu::Recording, Menu::Notifications,