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,