1#![allow(clippy::tabs_in_doc_comments)]
2#![warn(unreachable_pub)]
3#![cfg_attr(all(doc, nightly), feature(doc_auto_cfg))]
4
5#![doc = doc_WallpaperBuilder_example!()]
54macro_rules! doc_WallpaperBuilder_example {
62 () => {
63 r#"``` no_run
64# fn main() -> Result<(), Box<dyn std::error::Error>> {
65use more_wallpapers::{Mode, WallpaperBuilder};
66
67let fallback_images = vec!["/usr/share/wallpapers/1.jpg", "/usr/share/wallpapers/2.jpg"];
68let mut i = 0;
69WallpaperBuilder::new()?.set_wallpapers(|screen| {
70 i += 1;
71 if i == 1 {
72 return ("first.jpg".to_owned(), Mode::default());
73 }
74 if screen.name == "HDMI1" {
75 return ("/usr/share/wallpapers/hdmi.jpg".to_owned(), Mode::Fit);
76 }
77 (
78 fallback_images[i % fallback_images.len()].to_owned(),
79 Mode::Tile,
80 )
81})?;
82# Ok(())}
83```
84"#
85 };
86}
87
88macro_rules! doc_set_wallpapers_from_vec {
89 (fn) => {
90 concat!(doc_set_wallpapers_from_vec!(@private head), "set_wallpapers_from_vec", doc_set_wallpapers_from_vec!(@private body),
91 doc_set_wallpapers_from_vec!(@private tail))
92 };
93 (builder) => {
94 concat!(doc_set_wallpapers_from_vec!(@private head), "WallpaperBuilder", doc_set_wallpapers_from_vec!(@private body), "WallpaperBuilder::new()?.",
95 doc_set_wallpapers_from_vec!(@private tail))
96 };
97 (@private head) => {
98 r#"
99Set the background of all screens to the wallpapers of `wallpapers`.
100The wallpaper of `screen[i]` will be set to `wallpapers[i mod wallpapers.len()]`.
101The `default_wallpaper` param is used if the given `wallpapers` vec is empty and as wallpaper for [inactive screens](Screen::active).
102Return a vec, with dose inlcude the path of the Wallpapers,
103witch was set as background.
104If the same wallpaper was set multiple times to different screens,
105the return value does also include the wallpaper multiple times.
106```no_run
107# fn main() -> Result<(), Box<dyn std::error::Error>> {
108use more_wallpapers::{"#
109 };
110 (@private body) => {
111 r#", Mode};
112
113let images = vec!["1.jpg", "/usr/share/wallpapers/2.jpg"];
114let used_wallpapers = "#
115 };
116 (@private tail) => {
117 r#"set_wallpapers_from_vec(images, "default.png", Mode::Crop)?;
118println!("background was set to the following wallpapers {used_wallpapers:?}");
119# Ok(())}
120```"#
121 };
122 }
123
124mod error;
125
126use camino::{Utf8Path, Utf8PathBuf};
127#[cfg(target_os = "linux")]
128use error::load_env_var;
129#[cfg(target_os = "linux")]
130pub use error::CommandError;
131use error::Context;
132pub use error::WallpaperError;
133use std::io;
134use strum_macros::{Display, EnumString};
135
136#[cfg(feature = "rand")]
137use rand::{prelude::IteratorRandom, seq::SliceRandom};
138
139#[cfg(target_os = "linux")]
140mod linux;
141#[cfg(target_os = "linux")]
142use crate::linux::*;
143
144#[cfg(all(target_os = "windows", not(feature = "fallback")))]
145std::compile_error!("Windows does need the \"wallpaper\" feature");
146#[cfg(target_os = "windows")]
147mod windows;
148#[cfg(target_os = "windows")]
149use crate::windows::*;
150
151#[cfg(all(target_os = "macos", not(feature = "fallback")))]
152std::compile_error!("MacOS does need the \"wallpaper\" feature");
153#[cfg(target_os = "macos")]
154mod macos;
155#[cfg(target_os = "macos")]
156use crate::macos::*;
157
158#[derive(Debug, Clone, Copy, Default, EnumString, Display, PartialEq, Eq)]
160#[strum(serialize_all = "lowercase")]
161pub enum Mode {
162 Center,
164 #[default]
166 Crop,
167 Fit,
169 Stretch,
171 Tile,
173}
174
175#[cfg(feature = "fallback")]
176impl From<Mode> for fallback::Mode {
177 fn from(mode: Mode) -> Self {
178 match mode {
179 Mode::Center => fallback::Mode::Center,
180 Mode::Crop => fallback::Mode::Crop,
181 Mode::Fit => fallback::Mode::Fit,
182 Mode::Stretch => fallback::Mode::Stretch,
183 Mode::Tile => fallback::Mode::Tile,
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, Display, PartialEq, Eq)]
191#[strum(serialize_all = "lowercase")]
192#[non_exhaustive]
193pub enum Environment {
194 #[cfg(target_os = "linux")]
195 Cinnamon,
196 #[cfg(target_os = "linux")]
197 Kde,
198 #[cfg(target_os = "linux")]
199 Sway,
200 #[cfg(all(target_os = "linux", feature = "fallback"))]
201 LinuxFallback,
202 #[cfg(all(target_os = "macos", feature = "fallback"))]
203 MacOS,
204 #[cfg(all(target_os = "windows", feature = "fallback"))]
205 Windows,
206 #[cfg(target_os = "linux")]
207 X11,
208 #[cfg(target_os = "linux")]
209 Xfce,
210}
211impl Environment {
212 pub fn support_various_wallpaper(&self) -> bool {
214 match self {
215 #[cfg(target_os = "linux")]
216 Self::Cinnamon => true,
217 #[cfg(target_os = "linux")]
218 Self::Kde => true,
219 #[cfg(target_os = "linux")]
220 Self::Sway => true,
221 #[cfg(all(target_os = "linux", feature = "fallback"))]
222 Self::LinuxFallback => false,
223 #[cfg(all(target_os = "macos", feature = "fallback"))]
224 Self::MacOS => false,
225 #[cfg(all(target_os = "windows", feature = "fallback"))]
226 Self::Windows => false,
227 #[cfg(target_os = "linux")]
228 Self::X11 => true,
229 #[cfg(target_os = "linux")]
230 Self::Xfce => true,
231 }
232 }
233}
234
235#[derive(Clone, Debug)]
237pub struct Screen {
238 pub name: String,
239 pub wallpaper: Option<Utf8PathBuf>,
241 pub mode: Option<Mode>,
243 pub active: bool,
246}
247
248#[derive(Debug)]
251pub struct WallpaperBuilder {
252 screens: Vec<Screen>,
253 environment: Environment,
254}
255
256impl WallpaperBuilder {
257 pub fn new() -> Result<Self, WallpaperError> {
258 get_builder()
259 }
260
261 pub fn screen_count(&self) -> usize {
263 self.screens.len()
264 }
265
266 pub fn active_screen_count(&self) -> usize {
268 self.screens.iter().filter(|screen| screen.active).count()
269 }
270
271 pub fn environment(&self) -> Environment {
273 self.environment
274 }
275
276 pub fn screens(&self) -> &Vec<Screen> {
278 &self.screens
279 }
280
281 #[doc = doc_WallpaperBuilder_example!()]
284 pub fn set_wallpapers<F, P>(mut self, mut f: F) -> Result<(), WallpaperError>
285 where
286 P: AsRef<Utf8Path>,
287 F: FnMut(&Screen) -> (P, Mode),
288 {
289 for screen in self.screens.iter_mut() {
290 let tuple = f(screen);
291 let path = tuple.0.as_ref();
292 let path = path.canonicalize_utf8().context(path)?;
293 if !path.exists() {
294 return Err(io::Error::from(io::ErrorKind::NotFound)).context(path);
295 }
296 screen.wallpaper = Some(path);
297 screen.mode = Some(tuple.1)
298 }
299 set_screens_from_builder(self)
300 }
301
302 #[doc = doc_set_wallpapers_from_vec!(builder)]
303 pub fn set_wallpapers_from_vec<P>(
304 self,
305 wallpapers: Vec<P>,
306 default_wallpaper: P,
307 mode: Mode,
308 ) -> Result<Vec<Utf8PathBuf>, WallpaperError>
309 where
310 P: AsRef<Utf8Path>,
311 {
312 let mut used_wallpapers = Vec::new();
313 let mut i = 0;
314 self.set_wallpapers(|screen| {
315 if !screen.active {
316 return (default_wallpaper.as_ref(), mode);
317 }
318 let wallpaper = if wallpapers.is_empty() {
319 default_wallpaper.as_ref()
320 } else {
321 wallpapers[i % wallpapers.len()].as_ref()
322 };
323 i += 1;
324 used_wallpapers.push(wallpaper.to_owned());
325 (wallpaper, mode)
326 })?;
327 Ok(used_wallpapers)
328 }
329
330 #[cfg(feature = "rand")]
334 pub fn set_random_wallpapers_from_vec<P>(
335 self,
336 wallpapers: Vec<P>,
337 default_wallpaper: P,
338 mode: Mode,
339 ) -> Result<Vec<Utf8PathBuf>, WallpaperError>
340 where
341 P: AsRef<Utf8Path>,
342 P: Clone,
343 {
344 if wallpapers.is_empty() {
345 return self.set_wallpapers_from_vec(wallpapers, default_wallpaper, mode);
347 }
348 let mut rng = rand::thread_rng();
349 let wallpapers = if wallpapers.len() < self.screen_count() {
350 let mut new_wallpapers = Vec::new();
352 while new_wallpapers.len() < self.active_screen_count() {
353 let count = (self.screen_count() - new_wallpapers.len()).min(wallpapers.len());
354 let mut add = wallpapers.clone().into_iter().choose_multiple(&mut rng, count);
355 new_wallpapers.append(&mut add);
356 }
357 new_wallpapers
358 } else {
359 wallpapers
360 };
361 let mut choose_wallpapers = wallpapers.into_iter().choose_multiple(&mut rng, self.screen_count());
362 choose_wallpapers.shuffle(&mut rng);
363 self.set_wallpapers_from_vec(choose_wallpapers, default_wallpaper, mode)
364 }
365}
366
367#[doc = doc_set_wallpapers_from_vec!(fn)]
368pub fn set_wallpapers_from_vec<P>(
369 wallpapers: Vec<P>,
370 default_wallpaper: P,
371 mode: Mode,
372) -> Result<Vec<Utf8PathBuf>, WallpaperError>
373where
374 P: AsRef<Utf8Path>,
375{
376 let builder = WallpaperBuilder::new()?;
377 builder.set_wallpapers_from_vec(wallpapers, default_wallpaper, mode)
378}
379
380#[cfg(feature = "rand")]
384pub fn set_random_wallpapers_from_vec<P>(
385 wallpapers: Vec<P>,
386 default_wallpaper: P,
387 mode: Mode,
388) -> Result<Vec<Utf8PathBuf>, WallpaperError>
389where
390 P: AsRef<Utf8Path>,
391 P: Clone,
392{
393 let builder = WallpaperBuilder::new()?;
394 builder.set_random_wallpapers_from_vec(wallpapers, default_wallpaper, mode)
395}