fix: wallpaper discovery

Allows settings to show installed wallpapers.

- Modified `load_each_from_path` to recursively
discover wallpapers, limited depth to 3 for
performance.
This commit is contained in:
Eduardo Flores 2025-06-13 16:23:28 -07:00 committed by Michael Murphy
parent 1b2aba0107
commit 8ca65bafe4
4 changed files with 34 additions and 49 deletions

1
Cargo.lock generated
View file

@ -1844,6 +1844,7 @@ dependencies = [
"jxl-oxide", "jxl-oxide",
"tokio", "tokio",
"tracing", "tracing",
"walkdir",
] ]
[[package]] [[package]]

View file

@ -1178,11 +1178,6 @@ pub async fn change_folder(current_folder: PathBuf) -> Context {
let mut update = Context::default(); let mut update = Context::default();
let mut streams = Vec::with_capacity(2); let mut streams = Vec::with_capacity(2);
// Include the cosmic background folder when loading the system wallpapers.
if current_folder == Config::default_folder() {
streams.push(wallpaper::load_each_from_path(Config::default_folder().join("cosmic")).await);
}
streams.push(wallpaper::load_each_from_path(current_folder).await); streams.push(wallpaper::load_each_from_path(current_folder).await);
for mut wallpapers in streams { for mut wallpapers in streams {

View file

@ -23,3 +23,4 @@ infer = "0.16.0"
jxl-oxide = "0.11.3" jxl-oxide = "0.11.3"
tokio = { version = "1.44.1", features = ["sync"] } tokio = { version = "1.44.1", features = ["sync"] }
tracing = "0.1.41" tracing = "0.1.41"
walkdir = "=2.5.0"

View file

@ -2,18 +2,20 @@ pub use cosmic_bg_config::{Color, Config, Entry, Gradient, ScalingMode, Source};
use eyre::{eyre, OptionExt}; use eyre::{eyre, OptionExt};
use fast_image_resize::SrcCropping; use fast_image_resize::SrcCropping;
use futures_lite::Stream; use futures_lite::Stream;
use futures_util::StreamExt;
use image::imageops::FilterType; use image::imageops::FilterType;
use image::{DynamicImage, ImageBuffer, Rgba, RgbaImage}; use image::{DynamicImage, ImageBuffer, Rgba, RgbaImage};
use jxl_oxide::{EnumColourEncoding, JxlImage, PixelFormat}; use jxl_oxide::{EnumColourEncoding, JxlImage, PixelFormat};
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{hash_map::DefaultHasher, BTreeSet, HashMap}, collections::{hash_map::DefaultHasher, HashMap},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
io::Read, io::Read,
path::{Path, PathBuf}, path::{Path, PathBuf},
pin::Pin, pin::Pin,
}; };
use walkdir::WalkDir;
pub const DEFAULT_COLORS: &[Color] = &[ pub const DEFAULT_COLORS: &[Color] = &[
Color::Single([0.580, 0.922, 0.922]), Color::Single([0.580, 0.922, 0.922]),
@ -99,54 +101,40 @@ pub fn cache_dir() -> Option<PathBuf> {
pub async fn load_each_from_path( pub async fn load_each_from_path(
path: PathBuf, path: PathBuf,
) -> Pin<Box<dyn Send + Stream<Item = (PathBuf, RgbaImage, RgbaImage)>>> { ) -> Pin<Box<dyn Send + Stream<Item = (PathBuf, RgbaImage, RgbaImage)>>> {
let wallpapers = tokio::task::spawn_blocking(move || { let candidate_paths: Vec<_> = WalkDir::new(path)
// Discovered image files that will be loaded as wallpapers. .max_depth(3)
let mut wallpapers = BTreeSet::new(); .into_iter()
.filter_map(Result::ok)
.filter(|entry| entry.file_type().is_file())
.map(|entry| entry.path().to_path_buf())
.collect();
if let Ok(dir) = path.read_dir() { let future = futures_util::stream::iter(candidate_paths)
for entry in dir.filter_map(Result::ok) { .map(|path| {
let Ok(file_type) = entry.file_type() else { tokio::task::spawn_blocking(move || {
continue; let is_jxl = path.extension().map(|ext| ext == "jxl").unwrap_or_default();
let is_image = if !is_jxl {
if let Ok(Some(kind)) = infer::get_from_path(&path) {
infer::MatcherType::Image == kind.matcher_type()
} else {
false
}
} else {
true
}; };
let path = entry.path(); if is_jxl || is_image {
load_image_with_thumbnail(path)
if file_type.is_file() { } else {
let path = if path.extension().map(|ext| ext == "jxl").unwrap_or_default() { None
path
} else if let Ok(Some(kind)) = infer::get_from_path(&path) {
if infer::MatcherType::Image == kind.matcher_type() {
path
} else {
continue;
}
} else {
continue;
};
wallpapers.insert(path);
if wallpapers.len() > 99 {
break;
}
} }
} })
} })
.buffered(4)
.filter_map(|value| async { value.ok().flatten() })
.take(100);
wallpapers Box::pin(future)
});
if let Ok(wallpapers) = wallpapers.await {
use futures_util::StreamExt;
let future = futures_util::stream::iter(wallpapers)
.map(|path| tokio::task::spawn_blocking(|| load_image_with_thumbnail(path)))
.buffered(4)
.filter_map(|value| async { value.ok()? });
Box::pin(future)
} else {
Box::pin(futures_lite::stream::empty())
}
} }
#[must_use] #[must_use]