Skip to main content

wallr_core/cli/
mod.rs

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/// Transition + customization flags shared by `img` / `set` / `preview`.
7#[derive(Args, Debug, Clone, Default)]
8pub struct EffectArgs {
9    /// Transition effect: simple, fade, blur, wipe, slide, left, right, top,
10    /// bottom, zoom, pixelate, ripple, dissolve, wave, grow, center, outer,
11    /// any, random
12    #[arg(long, value_name = "NAME", value_parser = parse_effect_name)]
13    pub effect: Option<String>,
14
15    /// Transition duration, for example 800ms, 1s, or 1.2s
16    #[arg(long, value_name = "DURATION")]
17    pub duration: Option<String>,
18
19    /// Effect origin: a preset (top_left, top, top_right, left, center, right,
20    /// bottom_left, bottom, bottom_right) or a normalized "x,y" (0..1)
21    #[arg(long, value_name = "PRESET|X,Y")]
22    pub origin: Option<String>,
23
24    /// Wipe/wave travel angle in degrees (0 = right, 90 = up)
25    #[arg(long, value_name = "DEG")]
26    pub angle: Option<f32>,
27
28    /// Wipe/slide direction vector "x,y" (e.g. "1,0" for left-to-right)
29    #[arg(long, value_name = "X,Y")]
30    pub direction: Option<String>,
31
32    /// Easing curve: linear, ease_in, ease_out, ease_in_out
33    #[arg(long, value_enum)]
34    pub easing: Option<crate::animation::Easing>,
35
36    /// Start value (fade opacity, blur radius, zoom scale, pixelate size)
37    #[arg(long, value_name = "VALUE")]
38    pub from: Option<f32>,
39
40    /// End value (fade opacity, blur radius, zoom scale, pixelate size)
41    #[arg(long, value_name = "VALUE")]
42    pub to: Option<f32>,
43
44    /// Wave/ripple frequency
45    #[arg(long, value_name = "HZ")]
46    pub frequency: Option<f32>,
47
48    /// Wave/ripple amplitude
49    #[arg(long, value_name = "VALUE")]
50    pub amplitude: Option<f32>,
51
52    /// Ripple expansion speed
53    #[arg(long, value_name = "VALUE")]
54    pub speed: Option<f32>,
55
56    /// Wipe feather / dissolve edge softness
57    #[arg(long, value_name = "VALUE")]
58    pub softness: Option<f32>,
59
60    /// Dissolve noise scale
61    #[arg(long, value_name = "VALUE")]
62    pub scale: Option<f32>,
63}
64
65impl EffectArgs {
66    /// Build an `Effect` from `--effect <name>` + all override flags.
67    /// Falls back to `fallback` when no effect name is given.
68    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    /// Convert the override flags into `EffectOverrides`.
79    pub fn to_overrides(&self) -> EffectOverrides {
80        let origin = self.origin.as_deref().and_then(parse_origin);
81        EffectOverrides {
82            origin,
83            origin_preset: if origin.is_none() {
84                self.origin.clone()
85            } else {
86                None
87            },
88            direction: self.direction.as_deref().and_then(parse_vec2),
89            angle: self.angle,
90            easing: self.easing,
91            from: self.from,
92            to: self.to,
93            frequency: self.frequency,
94            amplitude: self.amplitude,
95            speed: self.speed,
96            softness: self.softness,
97            scale: self.scale,
98        }
99    }
100}
101
102/// Validate `--effect` against the known effect names.
103fn parse_effect_name(s: &str) -> Result<String, String> {
104    if crate::animation::effect_names().contains(&s) {
105        Ok(s.to_string())
106    } else {
107        Err(format!(
108            "unknown effect '{}' — expected one of: {}",
109            s,
110            crate::animation::effect_names().join(", ")
111        ))
112    }
113}
114
115/// Parse "x,y" into a normalized origin, or None if it's not numeric.
116fn parse_origin(s: &str) -> Option<(f32, f32)> {
117    let mut parts = s.split(',');
118    let x = parts.next()?.trim().parse::<f32>().ok()?;
119    let y = parts.next()?.trim().parse::<f32>().ok()?;
120    Some((x.clamp(0.0, 1.0), y.clamp(0.0, 1.0)))
121}
122
123/// Parse "x,y" into a direction vector (not normalized here — shader normalizes).
124fn parse_vec2(s: &str) -> Option<[f32; 2]> {
125    let mut parts = s.split(',');
126    let x = parts.next()?.trim().parse::<f32>().ok()?;
127    let y = parts.next()?.trim().parse::<f32>().ok()?;
128    Some([x, y])
129}
130
131/// Wallr CLI configuration
132#[derive(Parser, Debug)]
133#[command(
134    name = "wallr",
135    about = "Wayland wallpaper engine with animation and theme pipelines",
136    version
137)]
138pub struct WallrCli {
139    #[command(subcommand)]
140    pub command: Commands,
141
142    /// Custom config file path
143    #[arg(global = true, long)]
144    pub config: Option<PathBuf>,
145
146    /// Increase log verbosity
147    #[arg(global = true, short = 'v', long, action = clap::ArgAction::Count)]
148    pub verbose: u8,
149
150    /// Suppress non-error output
151    #[arg(global = true, short = 'q', long)]
152    pub quiet: bool,
153}
154
155#[derive(Subcommand, Debug)]
156pub enum Commands {
157    /// Set wallpaper with animation + theme pipeline
158    Img {
159        /// Path to the wallpaper image
160        path: PathBuf,
161        /// Disable theme generation
162        #[arg(long)]
163        no_theme: bool,
164        /// Theme provider override for this call (matugen, wallust, pywal)
165        #[arg(long, value_enum)]
166        theme: Option<ThemeProvider>,
167        /// Target monitor
168        #[arg(long)]
169        monitor: Option<String>,
170        /// Animation package to use
171        #[arg(long)]
172        animation: Option<String>,
173        /// Scaling mode
174        #[arg(long, value_enum)]
175        mode: Option<ScalingMode>,
176        /// Transition effect + customization flags
177        #[command(flatten)]
178        effect_args: EffectArgs,
179    },
180    /// Alias/superset of img
181    Set {
182        /// Path to the wallpaper image
183        path: PathBuf,
184        /// Disable theme generation
185        #[arg(long)]
186        no_theme: bool,
187        /// Theme provider override for this call (matugen, wallust, pywal)
188        #[arg(long, value_enum)]
189        theme: Option<ThemeProvider>,
190        /// Target monitor
191        #[arg(long)]
192        monitor: Option<String>,
193        /// Animation package to use
194        #[arg(long)]
195        animation: Option<String>,
196        /// Scaling mode
197        #[arg(long, value_enum)]
198        mode: Option<ScalingMode>,
199        /// Transition effect + customization flags
200        #[command(flatten)]
201        effect_args: EffectArgs,
202    },
203    /// Environment diagnostics
204    Doctor,
205    /// Lint animation YAML
206    Validate {
207        /// Path to animation YAML file
208        path: PathBuf,
209    },
210    /// Configuration management
211    Config {
212        #[command(subcommand)]
213        subcommand: ConfigCommands,
214    },
215    /// Cache management
216    Cache {
217        #[command(subcommand)]
218        subcommand: CacheCommands,
219    },
220    /// Re-run reload list without changing wallpaper
221    Reload,
222    /// Monitor management
223    Monitor {
224        #[command(subcommand)]
225        subcommand: MonitorCommands,
226    },
227
228    // Core Daemon & Package commands
229    /// Start the background wallpaper daemon
230    Daemon,
231    /// Create a working animation package starter.
232    New {
233        /// Package name or output directory.
234        name: String,
235        /// Include a raw WGSL shader starter.
236        #[arg(long)]
237        shader: bool,
238    },
239    /// Watch a directory for new images and automatically set them
240    Watch {
241        /// Directory to watch
242        dir: PathBuf,
243    },
244    /// Preview an animation or wallpaper image
245    Preview {
246        /// Path to preview
247        path: PathBuf,
248        /// Watch for changes
249        #[arg(long)]
250        watch: bool,
251        /// Animation package or YAML file to preview
252        #[arg(long, value_name = "PACKAGE|FILE")]
253        animation: Option<String>,
254        /// Transition effect + customization flags
255        #[command(flatten)]
256        effect_args: EffectArgs,
257    },
258    /// Install an animation package from registry or repository
259    Install {
260        /// Package to install
261        package: String,
262    },
263    /// Publish an animation package to the registry
264    Publish,
265    /// Search for packages in the registry
266    Search {
267        /// Query to search for
268        query: String,
269    },
270    /// Control the running daemon via IPC commands
271    Ipc {
272        #[command(subcommand)]
273        subcommand: IpcCommands,
274    },
275}
276
277#[derive(Subcommand, Debug)]
278pub enum ConfigCommands {
279    /// Get a configuration value
280    Get { key: String },
281    /// Set a configuration value
282    Set { key: String, value: String },
283    /// Print the config path
284    Path,
285}
286
287#[derive(Subcommand, Debug)]
288pub enum CacheCommands {
289    /// Clear the cache
290    Clear,
291    /// Show cache info
292    Info,
293}
294
295#[derive(Subcommand, Debug)]
296pub enum MonitorCommands {
297    /// List monitors
298    List,
299    /// Show current monitor
300    Current,
301}
302
303#[derive(Subcommand, Debug)]
304pub enum IpcCommands {
305    /// Pause animations
306    Pause,
307    /// Resume animations
308    Resume,
309    /// Reload wallpaper
310    Reload,
311    /// Preview animation
312    Preview,
313    /// Stop daemon
314    Stop,
315    /// Get daemon status
316    Status,
317}