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 info!("Setting wallpaper to: {:?}", path);
121
122 let result = (|| {
123 theme::run_hooks(&self.config.hooks.before)?;
124 self.apply_wallpaper(path)?;
125 if !options.no_theme {
126 let provider = options
127 .theme_provider
128 .as_ref()
129 .unwrap_or(&self.config.theme.provider);
130 if *provider != ThemeProvider::None {
131 theme::dispatch_theme(provider, path, &self.config.matugen)?;
132 }
133 }
134 theme::run_reload_list(&self.config.reload)?;
135 theme::run_hooks(&self.config.hooks.after)?;
136 Ok::<(), WallpaperError>(())
137 })();
138 if result.is_err() {
139 let _ = theme::run_hooks(&self.config.hooks.error);
140 }
141 result
142 }
143
144 fn apply_wallpaper(&self, path: &Path) -> Result<(), WallpaperError> {
145 if !path.is_file() {
146 return Err(WallpaperError::Custom(format!(
147 "wallpaper file not found: {}",
148 path.display()
149 )));
150 }
151 info!(
152 "Applying wallpaper natively via Wayland layer-shell surface: {:?}",
153 path
154 );
155 Ok(())
156 }
157
158 pub fn reload(&self) -> Result<(), WallpaperError> {
159 info!("Running reload commands...");
160 theme::run_reload_list(&self.config.reload)?;
161 Ok(())
162 }
163}