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 New {
231 name: String,
233 #[arg(long)]
235 shader: bool,
236 },
237 Watch {
239 dir: PathBuf,
241 },
242 Preview {
244 path: PathBuf,
246 #[arg(long)]
248 watch: bool,
249 #[arg(long, value_name = "PACKAGE|FILE")]
251 animation: Option<String>,
252 #[command(flatten)]
254 effect_args: EffectArgs,
255 },
256 Install {
258 package: String,
260 },
261 Publish,
263 Search {
265 query: String,
267 },
268 Ipc {
270 #[command(subcommand)]
271 subcommand: IpcCommands,
272 },
273 Quit,
275}
276
277#[derive(Subcommand, Debug)]
278pub enum ConfigCommands {
279 Get { key: String },
281 Set { key: String, value: String },
283 Path,
285}
286
287#[derive(Subcommand, Debug)]
288pub enum CacheCommands {
289 Clear,
291 Info,
293}
294
295#[derive(Subcommand, Debug)]
296pub enum MonitorCommands {
297 List,
299 Current,
301}
302
303#[derive(Subcommand, Debug)]
304pub enum IpcCommands {
305 Pause {
307 #[arg(long)]
309 monitor: Option<String>,
310 },
311 Resume {
313 #[arg(long)]
315 monitor: Option<String>,
316 },
317 Reload,
319 Preview,
321 Stop,
323 Status,
325 Info {
327 #[arg(long)]
329 monitor: Option<String>,
330 },
331 Seek {
333 timestamp: String,
335 #[arg(long)]
337 monitor: Option<String>,
338 },
339 Blank {
341 #[arg(long)]
343 monitor: Option<String>,
344 },
345 Restore {
347 #[arg(long)]
349 monitor: Option<String>,
350 },
351}