#![allow(clippy::tabs_in_doc_comments)]
#![warn(unreachable_pub)]
#![cfg_attr(all(doc, nightly), feature(doc_auto_cfg))]
#![doc = doc_WallpaperBuilder_example!()]
macro_rules! doc_WallpaperBuilder_example {
() => {
r#"``` no_run
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use more_wallpapers::{Mode, WallpaperBuilder};
let fallback_images = vec!["/usr/share/wallpapers/1.jpg", "/usr/share/wallpapers/2.jpg"];
let mut i = 0;
WallpaperBuilder::new()?.set_wallpapers(|screen| {
i += 1;
if i == 1 {
return ("first.jpg".to_owned(), Mode::default());
}
if screen.name == "HDMI1" {
return ("/usr/share/wallpapers/hdmi.jpg".to_owned(), Mode::Fit);
}
(
fallback_images[i % fallback_images.len()].to_owned(),
Mode::Tile,
)
})?;
# Ok(())}
```
"#
};
}
macro_rules! doc_set_wallpapers_from_vec {
(fn) => {
concat!(doc_set_wallpapers_from_vec!(@private head), "set_wallpapers_from_vec", doc_set_wallpapers_from_vec!(@private body),
doc_set_wallpapers_from_vec!(@private tail))
};
(builder) => {
concat!(doc_set_wallpapers_from_vec!(@private head), "WallpaperBuilder", doc_set_wallpapers_from_vec!(@private body), "WallpaperBuilder::new()?.",
doc_set_wallpapers_from_vec!(@private tail))
};
(@private head) => {
r#"
Set the background of all screens to the wallpapers of `wallpapers`.
The wallpaper of `screen[i]` will be set to `wallpapers[i mod wallpapers.len()]`.
The `default_wallpaper` param is used if the given `wallpapers` vec is empty and as wallpaper for [inactive screens](Screen::active).
Return a vec, with dose inlcude the path of the Wallpapers,
witch was set as background.
If the same wallpaper was set multiple times to different screens,
the return value does also include the wallpaper multiple times.
```no_run
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use more_wallpapers::{"#
};
(@private body) => {
r#", Mode};
let images = vec!["1.jpg", "/usr/share/wallpapers/2.jpg"];
let used_wallpapers = "#
};
(@private tail) => {
r#"set_wallpapers_from_vec(images, "default.png", Mode::Crop)?;
println!("background was set to the following wallpapers {used_wallpapers:?}");
# Ok(())}
```"#
};
}
mod error;
use camino::{Utf8Path, Utf8PathBuf};
#[cfg(target_os = "linux")]
use error::load_env_var;
#[cfg(target_os = "linux")]
pub use error::CommandError;
use error::Context;
pub use error::WallpaperError;
use std::io;
use strum_macros::{Display, EnumString};
#[cfg(feature = "rand")]
use rand::{prelude::IteratorRandom, seq::SliceRandom};
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "linux")]
use crate::linux::*;
#[cfg(all(target_os = "windows", not(feature = "fallback")))]
std::compile_error!("Windows does need the \"wallpaper\" feature");
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "windows")]
use crate::windows::*;
#[cfg(all(target_os = "macos", not(feature = "fallback")))]
std::compile_error!("MacOS does need the \"wallpaper\" feature");
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
use crate::macos::*;
#[derive(Debug, Clone, Copy, Default, EnumString, Display, PartialEq, Eq)]
#[strum(serialize_all = "lowercase")]
pub enum Mode {
Center,
#[default]
Crop,
Fit,
Stretch,
Tile,
}
#[cfg(feature = "fallback")]
impl From<Mode> for fallback::Mode {
fn from(mode: Mode) -> Self {
match mode {
Mode::Center => fallback::Mode::Center,
Mode::Crop => fallback::Mode::Crop,
Mode::Fit => fallback::Mode::Fit,
Mode::Stretch => fallback::Mode::Stretch,
Mode::Tile => fallback::Mode::Tile,
}
}
}
#[derive(Debug, Clone, Copy, Display, PartialEq, Eq)]
#[strum(serialize_all = "lowercase")]
#[non_exhaustive]
pub enum Environment {
#[cfg(target_os = "linux")]
Cinnamon,
#[cfg(target_os = "linux")]
Kde,
#[cfg(target_os = "linux")]
Sway,
#[cfg(all(target_os = "linux", feature = "fallback"))]
LinuxFallback,
#[cfg(all(target_os = "macos", feature = "fallback"))]
MacOS,
#[cfg(all(target_os = "windows", feature = "fallback"))]
Windows,
#[cfg(target_os = "linux")]
X11,
#[cfg(target_os = "linux")]
Xfce,
}
impl Environment {
pub fn support_various_wallpaper(&self) -> bool {
match self {
#[cfg(target_os = "linux")]
Self::Cinnamon => true,
#[cfg(target_os = "linux")]
Self::Kde => true,
#[cfg(target_os = "linux")]
Self::Sway => true,
#[cfg(all(target_os = "linux", feature = "fallback"))]
Self::LinuxFallback => false,
#[cfg(all(target_os = "macos", feature = "fallback"))]
Self::MacOS => false,
#[cfg(all(target_os = "windows", feature = "fallback"))]
Self::Windows => false,
#[cfg(target_os = "linux")]
Self::X11 => true,
#[cfg(target_os = "linux")]
Self::Xfce => true,
}
}
}
#[derive(Clone, Debug)]
pub struct Screen {
pub name: String,
pub wallpaper: Option<Utf8PathBuf>,
pub mode: Option<Mode>,
pub active: bool,
}
#[derive(Debug)]
pub struct WallpaperBuilder {
screens: Vec<Screen>,
environment: Environment,
}
impl WallpaperBuilder {
pub fn new() -> Result<Self, WallpaperError> {
get_builder()
}
pub fn screen_count(&self) -> usize {
self.screens.len()
}
pub fn active_screen_count(&self) -> usize {
self.screens.iter().filter(|screen| screen.active).count()
}
pub fn environment(&self) -> Environment {
self.environment
}
pub fn screens(&self) -> &Vec<Screen> {
&self.screens
}
#[doc = doc_WallpaperBuilder_example!()]
pub fn set_wallpapers<F, P>(mut self, mut f: F) -> Result<(), WallpaperError>
where
P: AsRef<Utf8Path>,
F: FnMut(&Screen) -> (P, Mode),
{
for screen in self.screens.iter_mut() {
let tuple = f(screen);
let path = tuple.0.as_ref();
let path = path.canonicalize_utf8().context(path)?;
if !path.exists() {
return Err(io::Error::from(io::ErrorKind::NotFound)).context(path);
}
screen.wallpaper = Some(path);
screen.mode = Some(tuple.1)
}
set_screens_from_builder(self)
}
#[doc = doc_set_wallpapers_from_vec!(builder)]
pub fn set_wallpapers_from_vec<P>(
self,
wallpapers: Vec<P>,
default_wallpaper: P,
mode: Mode,
) -> Result<Vec<Utf8PathBuf>, WallpaperError>
where
P: AsRef<Utf8Path>,
{
let mut used_wallpapers = Vec::new();
let mut i = 0;
self.set_wallpapers(|screen| {
if !screen.active {
return (default_wallpaper.as_ref(), mode);
}
let wallpaper = if wallpapers.is_empty() {
default_wallpaper.as_ref()
} else {
wallpapers[i % wallpapers.len()].as_ref()
};
i += 1;
used_wallpapers.push(wallpaper.to_owned());
(wallpaper, mode)
})?;
Ok(used_wallpapers)
}
#[cfg(feature = "rand")]
pub fn set_random_wallpapers_from_vec<P>(
self,
wallpapers: Vec<P>,
default_wallpaper: P,
mode: Mode,
) -> Result<Vec<Utf8PathBuf>, WallpaperError>
where
P: AsRef<Utf8Path>,
P: Clone,
{
if wallpapers.is_empty() {
return self.set_wallpapers_from_vec(wallpapers, default_wallpaper, mode);
}
let mut rng = rand::thread_rng();
let wallpapers = if wallpapers.len() < self.screen_count() {
let mut new_wallpapers = Vec::new();
while new_wallpapers.len() < self.active_screen_count() {
let count = (self.screen_count() - new_wallpapers.len()).min(wallpapers.len());
let mut add = wallpapers.clone().into_iter().choose_multiple(&mut rng, count);
new_wallpapers.append(&mut add);
}
new_wallpapers
} else {
wallpapers
};
let mut choose_wallpapers = wallpapers.into_iter().choose_multiple(&mut rng, self.screen_count());
choose_wallpapers.shuffle(&mut rng);
self.set_wallpapers_from_vec(choose_wallpapers, default_wallpaper, mode)
}
}
#[doc = doc_set_wallpapers_from_vec!(fn)]
pub fn set_wallpapers_from_vec<P>(
wallpapers: Vec<P>,
default_wallpaper: P,
mode: Mode,
) -> Result<Vec<Utf8PathBuf>, WallpaperError>
where
P: AsRef<Utf8Path>,
{
let builder = WallpaperBuilder::new()?;
builder.set_wallpapers_from_vec(wallpapers, default_wallpaper, mode)
}
#[cfg(feature = "rand")]
pub fn set_random_wallpapers_from_vec<P>(
wallpapers: Vec<P>,
default_wallpaper: P,
mode: Mode,
) -> Result<Vec<Utf8PathBuf>, WallpaperError>
where
P: AsRef<Utf8Path>,
P: Clone,
{
let builder = WallpaperBuilder::new()?;
builder.set_random_wallpapers_from_vec(wallpapers, default_wallpaper, mode)
}