1use crate::animation::{Effect, EffectOverrides, apply_effect_overrides, effect_from_name};
2use crate::config::{ScalingMode, ThemeProvider};
3use clap::{Args, Parser, Subcommand};
4use std::path::PathBuf;
5
6#[derive(Args, Debug, Clone, Default)]
8pub struct EffectArgs {
9 #[arg(long, value_name = "NAME", value_parser = parse_effect_name)]
13 pub effect: Option<String>,
14
15 #[arg(long, value_name = "DURATION")]
17 pub duration: Option<String>,
18
19 #[arg(long, value_name = "PRESET|X,Y")]
22 pub origin: Option<String>,
23
24 #[arg(long, value_name = "DEG")]
26 pub angle: Option<f32>,
27
28 #[arg(long, value_name = "X,Y")]
30 pub direction: Option<String>,
31
32 #[arg(long, value_enum)]
34 pub easing: Option<crate::animation::Easing>,
35
36 #[arg(long, value_name = "VALUE")]
38 pub from: Option<f32>,
39
40 #[arg(long, value_name = "VALUE")]
42 pub to: Option<f32>,
43
44 #[arg(long, value_name = "HZ")]
46 pub frequency: Option<f32>,
47
48 #[arg(long, value_name = "VALUE")]
50 pub amplitude: Option<f32>,
51
52 #[arg(long, value_name = "VALUE")]
54 pub speed: Option<f32>,
55
56 #[arg(long, value_name = "VALUE")]
58 pub softness: Option<f32>,
59
60 #[arg(long, value_name = "VALUE")]
62 pub scale: Option<f32>,
63}
64
65impl EffectArgs {
66 pub fn to_effect(&self, fallback: Effect) -> Effect {
69 let mut effect = self
70 .effect
71 .as_deref()
72 .and_then(effect_from_name)
73 .unwrap_or(fallback);
74 apply_effect_overrides(&mut effect, &self.to_overrides());
75 effect
76 }
77
78 pub fn to_overrides(&self) -> EffectOverrides {
79 let origin = self.origin.as_deref().and_then(parse_origin);
80 EffectOverrides {
81 origin,
82 origin_preset: if origin.is_none() {
83 self.origin.clone()
84 } else {
85 None
86 },
87 direction: self.direction.as_deref().and_then(parse_vec2),
88 angle: self.angle,
89 easing: self.easing,
90 from: self.from,
91 to: self.to,
92 frequency: self.frequency,
93 amplitude: self.amplitude,
94 speed: self.speed,
95 softness: self.softness,
96 scale: self.scale,
97 }
98 }
99}
100
101fn parse_effect_name(s: &str) -> Result<String, String> {
103 if crate::animation::effect_names().contains(&s) {
104 Ok(s.to_string())
105 } else {
106 Err(format!(
107 "unknown effect '{}' — expected one of: {}",
108 s,
109 crate::animation::effect_names().join(", ")
110 ))
111 }
112}
113
114fn parse_origin(s: &str) -> Option<(f32, f32)> {
116 let mut parts = s.split(',');
117 let x = parts.next()?.trim().parse::<f32>().ok()?;
118 let y = parts.next()?.trim().parse::<f32>().ok()?;
119 Some((x.clamp(0.0, 1.0), y.clamp(0.0, 1.0)))
120}
121
122fn parse_vec2(s: &str) -> Option<[f32; 2]> {
124 let mut parts = s.split(',');
125 let x = parts.next()?.trim().parse::<f32>().ok()?;
126 let y = parts.next()?.trim().parse::<f32>().ok()?;
127 Some([x, y])
128}
129
130#[derive(Parser, Debug)]
131#[command(
132 name = "wallr",
133 about = "Wayland wallpaper engine with animation and theme pipelines",
134 version
135)]
136pub struct WallrCli {
137 #[command(subcommand)]
138 pub command: Commands,
139
140 #[arg(global = true, long)]
142 pub config: Option<PathBuf>,
143
144 #[arg(global = true, short = 'v', long, action = clap::ArgAction::Count)]
146 pub verbose: u8,
147
148 #[arg(global = true, short = 'q', long)]
150 pub quiet: bool,
151}
152
153#[derive(Subcommand, Debug)]
154pub enum Commands {
155 Img {
157 path: PathBuf,
159 #[arg(long)]
161 no_theme: bool,
162 #[arg(long, value_enum)]
164 theme: Option<ThemeProvider>,
165 #[arg(long)]
167 monitor: Option<String>,
168 #[arg(long)]
170 animation: Option<String>,
171 #[arg(long, value_enum)]
173 mode: Option<ScalingMode>,
174 #[command(flatten)]
176 effect_args: EffectArgs,
177 },
178 Set {
180 path: PathBuf,
182 #[arg(long)]
184 no_theme: bool,
185 #[arg(long, value_enum)]
187 theme: Option<ThemeProvider>,
188 #[arg(long)]
190 monitor: Option<String>,
191 #[arg(long)]
193 animation: Option<String>,
194 #[arg(long, value_enum)]
196 mode: Option<ScalingMode>,
197 #[command(flatten)]
199 effect_args: EffectArgs,
200 },
201 Doctor,
203 Validate {
205 path: PathBuf,
207 },
208 Config {
210 #[command(subcommand)]
211 subcommand: ConfigCommands,
212 },
213 Cache {
215 #[command(subcommand)]
216 subcommand: CacheCommands,
217 },
218 Reload,
220 Monitor {
222 #[command(subcommand)]
223 subcommand: MonitorCommands,
224 },
225
226 Daemon {
229 #[arg(long)]
231 max_fps: Option<u32>,
232 },
233 New {
235 name: String,
237 #[arg(long)]
239 shader: bool,
240 },
241 Watch {
243 dir: PathBuf,
245 },
246 Preview {
248 path: PathBuf,
250 #[arg(long)]
252 watch: bool,
253 #[arg(long, value_name = "PACKAGE|FILE")]
255 animation: Option<String>,
256 #[command(flatten)]
258 effect_args: EffectArgs,
259 },
260 Install {
262 package: String,
264 },
265 Publish,
267 Search {
269 query: String,
271 },
272 Ipc {
274 #[command(subcommand)]
275 subcommand: IpcCommands,
276 },
277 Quit,
279}
280
281#[derive(Subcommand, Debug)]
282pub enum ConfigCommands {
283 Get { key: String },
285 Set { key: String, value: String },
287 Path,
289}
290
291#[derive(Subcommand, Debug)]
292pub enum CacheCommands {
293 Clear,
295 Info,
297}
298
299#[derive(Subcommand, Debug)]
300pub enum MonitorCommands {
301 List,
303 Current,
305}
306
307#[derive(Subcommand, Debug)]
308pub enum IpcCommands {
309 Pause {
311 #[arg(long)]
313 monitor: Option<String>,
314 },
315 Resume {
317 #[arg(long)]
319 monitor: Option<String>,
320 },
321 Reload,
323 Preview,
325 Stop,
327 Status,
329 Info {
331 #[arg(long)]
333 monitor: Option<String>,
334 },
335 Seek {
337 timestamp: String,
339 #[arg(long)]
341 monitor: Option<String>,
342 },
343 Blank {
345 #[arg(long)]
347 monitor: Option<String>,
348 #[command(flatten)]
349 effect_args: EffectArgs,
350 },
351 Restore {
353 #[arg(long)]
355 monitor: Option<String>,
356 #[command(flatten)]
357 effect_args: EffectArgs,
358 },
359}