mod awww;
mod backends;
mod config;
mod dependencies;
mod desktop;
mod dimension;
mod environment;
mod error;
mod fileinfo;
mod list;
mod monitors;
mod orientation;
mod pids;
mod state;
mod traits;
mod walkdir;
pub use self::{
awww::*, backends::*, config::*, dependencies::*, desktop::*, dimension::*, environment::*,
error::*, fileinfo::*, list::*, monitors::*, orientation::*, pids::*, state::*, traits::*,
walkdir::*,
};
cfg_select! {
feature = "args_v2" => {
mod args_v2;
pub use args_v2::*;
}
_ => {
mod args_v1;
pub use args_v1::*;
}
}
use std::{
env,
hash::{BuildHasher, Hasher, RandomState},
thread,
};
pub fn show_initial_msgs(config: &Config) -> WallSwitchResult<()> {
let pkg_name = ENVIRON.get_pkg_name();
let pkg_desc = env!("CARGO_PKG_DESCRIPTION");
let pkg_version = env!("CARGO_PKG_VERSION");
let interval = config.interval;
let info = format!("Interval between each wallpaper: {interval} seconds.");
let author = "Claudio Fernandes de Souza Rodrigues (claudiofsrodrigues@gmail.com)";
println!("{pkg_name} {pkg_desc}\n{info}\n{author}");
println!("version: {pkg_version}\n");
let depend1 = "imagemagick (image viewing/manipulation program)";
let depend2 = "feh (fast and light image viewer)";
let dependencies = [depend1, depend2];
println!("Dependencies:");
dependencies.print_with_spaces(" ");
println!();
config.print()?;
Ok(())
}
pub fn get_images(config: &Config, state: &mut State) -> WallSwitchResult<Vec<FileInfo>> {
let images: Vec<FileInfo> = gather_files(config, state)?;
if images.is_empty() {
let directories = config.directories.clone();
let error = WallSwitchError::NoImages { paths: directories };
eprintln!("{error}");
std::process::exit(1);
}
let mut pool: Vec<FileInfo> = images
.iter()
.filter(|img| !state.history.contains(&img.path))
.cloned()
.collect();
let nimages: usize = pool.len();
if nimages < config.monitors.len() {
if config.verbose {
println!("Image pool exhausted. Resetting history cycle.");
}
state.history.clear();
pool = images.clone();
}
pool.update_number();
if !config.sort {
shuffle(&mut pool);
}
Ok(pool)
}
pub fn gather_files(config: &Config, state: &mut State) -> WallSwitchResult<Vec<FileInfo>> {
state.garbage_collect();
let mut raw_files = Vec::new();
for dir in &config.directories {
raw_files.extend(get_files_from_directory(dir, config)?);
}
let mut needs_hash = Vec::new();
let mut cached_files = Vec::new();
for mut file in raw_files {
if let Some(cache) = state.hashes.get(&file.path) {
if cache.size == file.size && cache.mtime == file.mtime {
file.hash = cache.hash.clone();
cached_files.push(file);
continue;
}
}
needs_hash.push(file);
}
if !needs_hash.is_empty() {
if config.verbose {
println!(
"Calculating deep BLAKE3 hashes for {} new/modified files...",
needs_hash.len()
);
}
needs_hash.update_hash()?;
}
for file in &needs_hash {
state.hashes.insert(
file.path.clone(),
CacheEntry {
size: file.size,
mtime: file.mtime,
hash: file.hash.clone(),
},
);
}
let all_files = cached_files.into_iter().chain(needs_hash);
let mut files = Vec::new();
let mut seen_hashes = std::collections::HashSet::new();
for file in all_files {
if seen_hashes.insert(file.hash.clone()) {
files.push(file);
} else if config.verbose {
println!("Visual duplicate ignored: {:?}", file.path);
}
}
Ok(files)
}
pub fn shuffle<T>(vec: &mut [T]) {
let n: usize = vec.len();
for i in 0..(n - 1) {
let j = (rand() as usize) % (n - i) + i;
vec.swap(i, j);
}
}
pub fn get_random_integer(min: u64, max: u64) -> u64 {
min + rand() % (max - min + 1)
}
pub fn get_random_integer_v2(min: u64, max: u64) -> WallSwitchResult<u64> {
if min > max {
Err(WallSwitchError::MinMax { min, max })
} else {
Ok(min + rand() % (max - min + 1))
}
}
pub fn rand() -> u64 {
RandomState::new().build_hasher().finish()
}
pub fn display_files(files: &[FileInfo], config: &Config) {
let nfiles = files.len();
if nfiles == 0 {
return;
}
let ndigits = nfiles.to_string().len();
if config.sort {
println!(
"\n{} images were found (sorted):",
nfiles.to_string().green().bold()
);
} else {
println!(
"\n{} images were found (shuffled):",
nfiles.to_string().green().bold()
);
}
for file in files {
println!(
"images[{n:0ndigits$}/{t}]: {p:?}",
n = file.number,
p = file.path,
t = file.total,
);
}
println!();
}
pub fn update_images(files: &[FileInfo], config: &Config) -> Vec<FileInfo> {
let mut owned_files: Vec<FileInfo> = files.to_vec();
thread::scope(|scope| {
for file in &mut owned_files {
scope.spawn(move || -> WallSwitchResult<()> {
file.update_info(config)
});
}
});
owned_files
}
#[cfg(test)]
mod test_lib {
use crate::*;
#[test]
fn vec_shuffle() {
let mut vec: Vec<u32> = (1..=100).collect();
shuffle(&mut vec);
println!("vec: {vec:?}");
assert_eq!(vec.len(), 100);
}
#[test]
fn random_integers_v1() {
let value: u64 = get_random_integer(1, 20);
println!("integer: {value:?}");
let integers: Vec<u64> = (0..100).map(|_| get_random_integer(1, 20)).collect();
println!("integers: {integers:?}");
let condition_a = integers.iter().min() >= Some(&1);
let condition_b = integers.iter().max() <= Some(&20);
assert!(condition_a);
assert!(condition_b);
assert_eq!(integers.len(), 100);
}
#[test]
fn random_integers_v2() -> WallSwitchResult<()> {
let value: u64 = get_random_integer_v2(1, 20)?;
println!("integer: {value:?}");
let integers: Vec<u64> = (0..100)
.map(|_| get_random_integer_v2(1, 20))
.collect::<Result<Vec<u64>, _>>()?;
println!("integers: {integers:?}");
let condition_a = integers.iter().min() >= Some(&1);
let condition_b = integers.iter().max() <= Some(&20);
assert!(condition_a);
assert!(condition_b);
assert_eq!(integers.len(), 100);
Ok(())
}
#[test]
fn random_integers_v3() -> WallSwitchResult<()> {
let result = get_random_integer_v2(21, 20).map_err(|err| {
eprintln!("{err}");
err
});
assert!(result.is_err());
let error = result.unwrap_err();
eprintln!("error: {error:?}");
Ok(())
}
}