wallr_core/wallpaper/
mod.rs1use crate::config::{ThemeProvider, WallrConfig};
2use crate::theme;
3use anyhow::Result;
4use std::path::Path;
5use tracing::info;
6
7#[derive(Debug, Clone)]
8pub struct SetOptions {
9 pub no_theme: bool,
10 pub theme_provider: Option<ThemeProvider>,
12 pub monitor: Option<String>,
13}
14
15#[derive(Debug, Clone)]
16pub enum DiagnosticStatus {
17 Pass,
18 Warn,
19 Fail,
20}
21
22#[derive(Debug, Clone)]
23pub struct DiagnosticCheck {
24 pub name: String,
25 pub status: DiagnosticStatus,
26 pub message: String,
27}
28
29#[derive(Debug, Clone)]
30pub struct DiagnosticReport {
31 pub checks: Vec<DiagnosticCheck>,
32}
33
34#[derive(Debug, thiserror::Error)]
35pub enum WallpaperError {
36 #[error("Theme error: {0}")]
37 Theme(#[from] crate::theme::ThemeError),
38 #[error("Custom error: {0}")]
39 Custom(String),
40}
41
42pub struct WallpaperEngine {
43 pub config: WallrConfig,
44}
45
46impl WallpaperEngine {
47 pub fn new(config: WallrConfig) -> Result<Self, WallpaperError> {
48 Ok(Self { config })
49 }
50
51 pub fn doctor(&self) -> DiagnosticReport {
52 let mut checks = Vec::new();
53
54 if std::env::var("WAYLAND_DISPLAY").is_ok() {
55 checks.push(DiagnosticCheck {
56 name: "Wayland Session".to_string(),
57 status: DiagnosticStatus::Pass,
58 message: "Wayland display socket detected.".to_string(),
59 });
60 checks.push(DiagnosticCheck {
61 name: "wlr-layer-shell".to_string(),
62 status: DiagnosticStatus::Warn,
63 message: "Protocol probing occurs when the daemon binds layer-shell; run wallr daemon for a definitive check.".to_string(),
64 });
65 } else {
66 checks.push(DiagnosticCheck {
67 name: "Wayland Session".to_string(),
68 status: DiagnosticStatus::Fail,
69 message: "$WAYLAND_DISPLAY is not set. wallr requires a Wayland environment."
70 .to_string(),
71 });
72 }
73
74 let provider_binary = match self.config.theme.provider {
75 ThemeProvider::Matugen => Some("matugen"),
76 ThemeProvider::Wallust => Some("wallust"),
77 ThemeProvider::Pywal => Some("wal"),
78 ThemeProvider::None => None,
79 };
80
81 if let Some(bin) = provider_binary {
82 let is_available = theme::check_provider_available(&self.config.theme.provider);
83 if is_available {
84 checks.push(DiagnosticCheck {
85 name: format!("Theme Provider ({})", bin),
86 status: DiagnosticStatus::Pass,
87 message: format!("Binary '{}' is available on PATH.", bin),
88 });
89 } else {
90 checks.push(DiagnosticCheck {
91 name: format!("Theme Provider ({})", bin),
92 status: DiagnosticStatus::Fail,
93 message: format!("Binary '{}' was not found on PATH.", bin),
94 });
95 }
96 }
97
98 if let Some(warning) = theme::detect_matugen_loop_risk() {
99 checks.push(DiagnosticCheck {
100 name: "Infinite Loop Risk".to_string(),
101 status: DiagnosticStatus::Warn,
102 message: warning,
103 });
104 } else {
105 checks.push(DiagnosticCheck {
106 name: "Infinite Loop Risk".to_string(),
107 status: DiagnosticStatus::Pass,
108 message: "No immediate parent loop risks detected.".to_string(),
109 });
110 }
111
112 DiagnosticReport { checks }
113 }
114
115 pub async fn set_wallpaper(
116 &mut self,
117 path: &Path,
118 options: &SetOptions,
119 ) -> Result<(), WallpaperError> {
120 self.set_wallpaper_blocking(path, options)
121 }
122
123 pub fn set_wallpaper_blocking(
128 &mut self,
129 path: &Path,
130 options: &SetOptions,
131 ) -> Result<(), WallpaperError> {
132 info!("Setting wallpaper to: {:?}", path);
133
134 let result = (|| {
135 theme::run_hooks(&self.config.hooks.before)?;
136 self.apply_wallpaper(path)?;
137 if !options.no_theme {
138 let provider = options
139 .theme_provider
140 .as_ref()
141 .unwrap_or(&self.config.theme.provider);
142 if *provider != ThemeProvider::None {
143 theme::dispatch_theme(provider, path, &self.config.matugen)?;
144 }
145 }
146 theme::run_reload_list(&self.config.reload)?;
147 theme::run_hooks(&self.config.hooks.after)?;
148 Ok::<(), WallpaperError>(())
149 })();
150 if result.is_err() {
151 let _ = theme::run_hooks(&self.config.hooks.error);
152 }
153 result
154 }
155
156 fn apply_wallpaper(&self, path: &Path) -> Result<(), WallpaperError> {
157 if !path.is_file() {
158 return Err(WallpaperError::Custom(format!(
159 "wallpaper file not found: {}",
160 path.display()
161 )));
162 }
163 info!(
164 "Applying wallpaper natively via Wayland layer-shell surface: {:?}",
165 path
166 );
167 Ok(())
168 }
169
170 pub fn reload(&self) -> Result<(), WallpaperError> {
171 self.reload_blocking()
172 }
173
174 pub fn reload_blocking(&self) -> Result<(), WallpaperError> {
177 info!("Running reload commands...");
178 theme::run_reload_list(&self.config.reload)?;
179 Ok(())
180 }
181}