cosmic-settings/pages/desktop/src/wallpaper.rs
Michael Aaron Murphy a4eee2186c
feat: partial implementation of wallpaper settings
Will take some time to refactor the rest
2023-05-31 02:50:18 +02:00

130 lines
4 KiB
Rust

pub use cosmic_bg_config::{Config, Entry, Output};
use image::RgbaImage;
use std::{
collections::hash_map::DefaultHasher,
fs::DirEntry,
hash::{Hash, Hasher},
path::{Path, PathBuf},
sync::Arc,
};
use tokio::sync::mpsc::{self, Receiver};
pub fn config() -> Config {
let helper = Config::helper().expect("failed to get helper for cosmic bg config");
match Config::load(&helper) {
Ok(conf) => conf,
Err(why) => {
tracing::warn!(?why, "Config file error, falling back to defaults");
Config::default()
}
}
}
pub fn set(config: &mut Config, entry: Entry) {
if let Ok(context) = Config::helper() {
tracing::info!(
"setting wallpaper for {} to {}",
entry.output.to_string(),
entry.source.display()
);
if let Err(why) = config.set_entry(&context, entry) {
tracing::error!(?why, "failed to set background");
}
}
}
/// Path to directory where wallpaper thumbnails are stored.
#[must_use]
pub fn cache_dir() -> Option<PathBuf> {
dirs::cache_dir().map(|path| {
let cache = path.join("cosmic-settings/wallpapers");
let _res = std::fs::create_dir_all(&cache);
cache
})
}
/// Loads wallpapers in parallel by spawning tasks with a rayon thread pool.
#[must_use]
pub fn load_each_from_path(path: PathBuf) -> Receiver<(PathBuf, RgbaImage)> {
let cache_dir = Arc::new(cache_dir());
let (tx, rx) = mpsc::channel(1);
tokio::task::spawn_blocking(move || {
let mut paths = vec![path];
while let Some(path) = paths.pop() {
if let Ok(dir) = path.read_dir() {
for entry in dir.filter_map(Result::ok) {
let Ok(file_type) = entry.file_type() else {
continue
};
let path = entry.path();
if file_type.is_dir() {
paths.push(path);
} else if file_type.is_file() {
let tx = tx.clone();
let cache_dir = cache_dir.clone();
rayon::spawn_fifo(move || {
let thumbnail =
load_thumbnail(cache_dir.as_deref(), &path, &entry, 300, 169);
if let Some(image) = thumbnail {
let _res = tx.blocking_send((path, image));
}
});
}
}
}
}
});
rx
}
/// Generates and caches the thumbnail of a wallpaper.
///
///
/// Caching reduces time required to load a wallpaper by 99%.
#[must_use]
pub fn load_thumbnail(
cache_dir: Option<&Path>,
path: &Path,
entry: &DirEntry,
width: u32,
height: u32,
) -> Option<RgbaImage> {
if let Some(cache_dir) = cache_dir {
if let Ok(ctime) = entry.metadata().and_then(|meta| meta.created()) {
// Search for thumbnail by a unique hash string.
let mut hasher = DefaultHasher::new();
path.hash(&mut hasher);
ctime.hash(&mut hasher);
let hash = hasher.finish();
let thumbnail_path = cache_dir.join(format!("{hash:x}.png"));
// Load image from thumbnail if it exists and can be opened.
if thumbnail_path.exists() {
if let Ok(image) = image::open(&thumbnail_path) {
return Some(image.into_rgba8());
}
}
// Create new thumbnail and save it if not.
return image::open(path).ok().map(|mut image| {
image = image.thumbnail_exact(width, height);
let _res = image.save(&thumbnail_path);
image.into_rgba8()
});
}
}
// Generate thumbnail from wallpaper without saving it
image::open(path).ok().map(|mut image| {
image = image.thumbnail_exact(width, height);
image.into_rgba8()
})
}