diff --git a/CHANGELOG.md b/CHANGELOG.md index f312309..d858a61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.2 +## New features +- Add a new `recursive` config option, enabled by default (fixes #112). + # 1.1.1 ## New features - Make jemalloc feature optional, enabled by default diff --git a/README.md b/README.md index 17bbf2a..1d647b5 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,8 @@ represents a different display and can contain the following keys: - `queue-size`, decide how big the queue should be when `path` is set a directory and `sorting` is set to `random`. (_Optional_, `10` by default) - `initial-transition`, enable the initial transition at wpaperd startup. (_Optional_, true by default) +- `recursive`, recursively iterate the directory `path` when looking for available wallpapers; + it is only valid when `path` points to a directory. (_Optional_, true by default) The section `default` will be used as base for the all the display configuration; the section `any` will be used for all the displays that are not explictly listed. This allows to have a diff --git a/daemon/src/config.rs b/daemon/src/config.rs index ad27239..058dec4 100644 --- a/daemon/src/config.rs +++ b/daemon/src/config.rs @@ -23,7 +23,7 @@ use smithay_client_toolkit::reexports::calloop::ping::Ping; use crate::{ image_picker::ImagePicker, render::Transition, - wallpaper_info::{BackgroundMode, Sorting, WallpaperInfo}, + wallpaper_info::{BackgroundMode, Recursive, Sorting, WallpaperInfo}, }; #[derive(Default, Deserialize, PartialEq, Debug, Clone)] @@ -59,6 +59,10 @@ pub struct SerializedWallpaperInfo { /// Assign these displays to a group that shows the same wallpaper pub group: Option, + + /// Recursively traverse the directory set as path + /// Set as true by default + pub recursive: Option, } impl SerializedWallpaperInfo { @@ -222,6 +226,13 @@ impl SerializedWallpaperInfo { (None, None) => None, }; + let recursive = match (&self.recursive, &default.recursive) { + (Some(recursive), _) | (None, Some(recursive)) => { + Some(std::convert::Into::::into(*recursive)) + } + (None, None) => None, + }; + Ok(WallpaperInfo { path, duration, @@ -233,6 +244,7 @@ impl SerializedWallpaperInfo { initial_transition, transition, offset, + recursive, }) } } @@ -370,11 +382,18 @@ impl Config { Ok(()) } - pub fn paths(&self) -> Vec { + pub fn paths(&self) -> Vec<(PathBuf, Recursive)> { let mut paths: Vec<_> = self .data .values() - .filter_map(|info| info.path.as_ref().map(|p| p.to_path_buf())) + .filter_map(|info| { + info.path.as_ref().map(|p| { + ( + p.to_path_buf(), + info.recursive.map(Recursive::from).unwrap_or_default(), + ) + }) + }) .collect(); paths.sort_unstable(); paths.dedup(); diff --git a/daemon/src/filelist_cache.rs b/daemon/src/filelist_cache.rs index 6919320..b1b09a2 100644 --- a/daemon/src/filelist_cache.rs +++ b/daemon/src/filelist_cache.rs @@ -12,28 +12,36 @@ use log::error; use smithay_client_toolkit::reexports::calloop::{self, ping::Ping, LoopHandle}; use walkdir::WalkDir; -use crate::wpaperd::Wpaperd; +use crate::{wallpaper_info::Recursive, wpaperd::Wpaperd}; #[derive(Debug)] struct Filelist { path: PathBuf, + recursive: Recursive, filelist: Arc>, outdated: Arc, } impl Filelist { - fn new(path: &Path) -> Self { + fn new(path: &Path, recursive: Recursive) -> Self { let mut res = Self { path: path.to_path_buf(), + recursive, filelist: Arc::new(Vec::new()), outdated: Arc::new(AtomicBool::new(true)), }; res.populate(); res } + fn populate(&mut self) { self.filelist = Arc::new( WalkDir::new(&self.path) + .max_depth(if self.recursive == Recursive::Off { + 1 + } else { + usize::MAX + }) .follow_links(true) .sort_by_file_name() .into_iter() @@ -58,7 +66,7 @@ pub struct FilelistCache { impl FilelistCache { pub fn new( - paths: Vec, + paths: Vec<(PathBuf, Recursive)>, hotwatch: &mut Hotwatch, event_loop_handle: LoopHandle, ) -> Result<(Ping, Self)> { @@ -76,11 +84,11 @@ impl FilelistCache { Ok((ping, filelist_cache)) } - pub fn get(&self, path: &Path) -> Arc> { + pub fn get(&self, path: &Path, recursive: Recursive) -> Arc> { debug_assert!(path.is_dir()); self.cache .iter() - .find(|filelist| filelist.path == path) + .find(|filelist| filelist.path == path && filelist.recursive == recursive) .expect("path passed to Filelist::get has been cached") .filelist .clone() @@ -89,13 +97,17 @@ impl FilelistCache { /// paths must be sorted pub fn update_paths( &mut self, - paths: Vec, + paths: Vec<(PathBuf, Recursive)>, hotwatch: &mut Hotwatch, event_loop_ping: Ping, ) { self.cache.retain(|filelist| { let path_exists = filelist.path.exists(); - if paths.contains(&filelist.path) && path_exists { + if paths + .iter() + .any(|(path, recursive)| &filelist.path == path && filelist.recursive == *recursive) + && path_exists + { true } else { // Stop watching paths that have been removed @@ -112,13 +124,13 @@ impl FilelistCache { } }); - for path in paths { + for (path, recursive) in paths { if !self.cache.iter().any(|filelist| filelist.path == path) { // Skip paths that don't exists and files if !path.exists() || !path.is_dir() { continue; } - let filelist = Filelist::new(&path); + let filelist = Filelist::new(&path, recursive); let outdated = filelist.outdated.clone(); self.cache.push(filelist); let ping_clone = event_loop_ping.clone(); diff --git a/daemon/src/image_picker.rs b/daemon/src/image_picker.rs index b678560..4e3e2b6 100644 --- a/daemon/src/image_picker.rs +++ b/daemon/src/image_picker.rs @@ -11,7 +11,7 @@ use smithay_client_toolkit::reexports::client::{protocol::wl_surface::WlSurface, use crate::{ filelist_cache::FilelistCache, wallpaper_groups::{WallpaperGroup, WallpaperGroups}, - wallpaper_info::{Sorting, WallpaperInfo}, + wallpaper_info::{Recursive, Sorting, WallpaperInfo}, wpaperd::Wpaperd, }; @@ -185,7 +185,10 @@ impl ImagePickerSorting { let files_len = filelist_cache .clone() .borrow() - .get(&wallpaper_info.path) + .get( + &wallpaper_info.path, + *wallpaper_info.recursive.as_ref().unwrap(), + ) .len(); Self::new_ascending(files_len) } @@ -339,9 +342,16 @@ impl ImagePicker { } } - pub fn get_image_from_path(&mut self, path: &Path) -> Option<(PathBuf, usize)> { + pub fn get_image_from_path( + &mut self, + path: &Path, + recursive: &Option, + ) -> Option<(PathBuf, usize)> { if path.is_dir() { - let files = self.filelist_cache.borrow().get(path); + let files = self + .filelist_cache + .borrow() + .get(path, recursive.unwrap_or_default()); // There are no images, forcefully break out of the loop if files.is_empty() { @@ -405,9 +415,9 @@ impl ImagePicker { } /// Update wallpaper by going up 1 index through the cached image paths - pub fn next_image(&mut self, path: &Path) { + pub fn next_image(&mut self, path: &Path, recursive: &Option) { self.action = Some(ImagePickerAction::Next); - self.get_image_from_path(path); + self.get_image_from_path(path, recursive); } pub fn current_image(&self) -> PathBuf { @@ -419,6 +429,7 @@ impl ImagePicker { &mut self, new_sorting: Option, path: &Path, + recursive: Option, path_changed: bool, wl_surface: &WlSurface, drawn_images_queue_size: usize, @@ -433,7 +444,10 @@ impl ImagePicker { if !path_changed => {} (_, Sorting::Ascending) if path_changed => { self.sorting = ImagePickerSorting::new_ascending( - self.filelist_cache.borrow().get(path).len(), + self.filelist_cache + .borrow() + .get(path, recursive.unwrap()) + .len(), ); } (_, Sorting::Descending) if path_changed => { diff --git a/daemon/src/ipc_server.rs b/daemon/src/ipc_server.rs index 99de2d0..d7cf09e 100644 --- a/daemon/src/ipc_server.rs +++ b/daemon/src/ipc_server.rs @@ -117,9 +117,10 @@ pub fn handle_message( IpcMessage::NextWallpaper { monitors } => check_monitors(wpaperd, &monitors).map(|_| { for surface in collect_surfaces(wpaperd, monitors) { - surface - .image_picker - .next_image(&surface.wallpaper_info.path); + surface.image_picker.next_image( + &surface.wallpaper_info.path, + &surface.wallpaper_info.recursive, + ); surface.queue_draw(&qh); } diff --git a/daemon/src/surface.rs b/daemon/src/surface.rs index ae186e0..272f3eb 100644 --- a/daemon/src/surface.rs +++ b/daemon/src/surface.rs @@ -207,10 +207,10 @@ impl Surface { pub fn load_wallpaper(&mut self, handle: Option<&LoopHandle>) -> Result { // If we were not already trying to load an image if self.loading_image.is_none() { - if let Some(item) = self - .image_picker - .get_image_from_path(&self.wallpaper_info.path) - { + if let Some(item) = self.image_picker.get_image_from_path( + &self.wallpaper_info.path, + &self.wallpaper_info.recursive.clone(), + ) { if self.image_picker.current_image() == item.0 && !self.image_picker.is_reloading() { return Ok(true); @@ -451,11 +451,14 @@ impl Surface { // if the two paths are different and the new path is a directory but doesn't contain the // old image let path_changed = self.wallpaper_info.path != wallpaper_info.path - && (!self.wallpaper_info.path.is_dir() - || !wallpaper_info.path.starts_with(&self.wallpaper_info.path)); + && self.wallpaper_info.path.is_dir() + && !wallpaper_info.path.starts_with(&self.wallpaper_info.path) + // and the recursive mode is different + && wallpaper_info.recursive.as_ref().zip(self.wallpaper_info.recursive.as_ref()).map(|(x, y)| x != y).unwrap_or(false); self.image_picker.update_sorting( self.wallpaper_info.sorting, &self.wallpaper_info.path, + self.wallpaper_info.recursive, path_changed, &self.wl_surface, wallpaper_info.drawn_images_queue_size, @@ -463,7 +466,8 @@ impl Surface { ); if path_changed { // ask the image_picker to pick a new a image - self.image_picker.next_image(&self.wallpaper_info.path); + self.image_picker + .next_image(&self.wallpaper_info.path, &self.wallpaper_info.recursive); } // Always queue draw to load changes (needed for GroupedRandom) self.queue_draw(qh); @@ -547,7 +551,10 @@ impl Surface { let saturating_sub = new_duration.saturating_sub(time_passed); if saturating_sub.is_zero() { // The image was on screen for the same time as the new duration - self.image_picker.next_image(&self.wallpaper_info.path); + self.image_picker.next_image( + &self.wallpaper_info.path, + &self.wallpaper_info.recursive, + ); if let Err(err) = self.load_wallpaper(None) { warn!("{err:?}"); } @@ -656,9 +663,10 @@ impl Surface { surface.renderer.transition_finished(); surface.renderer.force_transition_end(); } - surface - .image_picker - .next_image(&surface.wallpaper_info.path); + surface.image_picker.next_image( + &surface.wallpaper_info.path, + &surface.wallpaper_info.recursive, + ); surface.queue_draw(&qh); surface.wallpaper_info.duration.unwrap() }; diff --git a/daemon/src/wallpaper_info.rs b/daemon/src/wallpaper_info.rs index 940587b..5a8b7b0 100644 --- a/daemon/src/wallpaper_info.rs +++ b/daemon/src/wallpaper_info.rs @@ -4,6 +4,23 @@ use serde::Deserialize; use crate::{image_picker::ImagePicker, render::Transition}; +#[derive(Debug, PartialEq, Default, Ord, Eq, PartialOrd, Clone, Copy)] +pub enum Recursive { + #[default] + On, + Off, +} + +impl From for Recursive { + fn from(b: bool) -> Self { + if b { + Recursive::On + } else { + Recursive::Off + } + } +} + #[derive(PartialEq, Debug)] pub struct WallpaperInfo { pub path: PathBuf, @@ -23,6 +40,9 @@ pub struct WallpaperInfo { /// Determine the offset for the wallpaper to be drawn into the screen /// Must be from 0.0 to 1.0, by default is 0.0 in tile mode and 0.5 in all the others pub offset: Option, + + /// Recursively iterate the directory set as path + pub recursive: Option, } impl Default for WallpaperInfo { @@ -38,6 +58,7 @@ impl Default for WallpaperInfo { initial_transition: true, transition: Transition::Fade {}, offset: None, + recursive: None, } } }