use crate::animation::{Effect, EffectOverrides, apply_effect_overrides, effect_from_name};
use crate::config::{ScalingMode, ThemeProvider};
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Args, Debug, Clone, Default)]
pub struct EffectArgs {
#[arg(long, value_name = "NAME", value_parser = parse_effect_name)]
pub effect: Option<String>,
#[arg(long, value_name = "DURATION")]
pub duration: Option<String>,
#[arg(long, value_name = "PRESET|X,Y")]
pub origin: Option<String>,
#[arg(long, value_name = "DEG")]
pub angle: Option<f32>,
#[arg(long, value_name = "X,Y")]
pub direction: Option<String>,
#[arg(long, value_enum)]
pub easing: Option<crate::animation::Easing>,
#[arg(long, value_name = "VALUE")]
pub from: Option<f32>,
#[arg(long, value_name = "VALUE")]
pub to: Option<f32>,
#[arg(long, value_name = "HZ")]
pub frequency: Option<f32>,
#[arg(long, value_name = "VALUE")]
pub amplitude: Option<f32>,
#[arg(long, value_name = "VALUE")]
pub speed: Option<f32>,
#[arg(long, value_name = "VALUE")]
pub softness: Option<f32>,
#[arg(long, value_name = "VALUE")]
pub scale: Option<f32>,
}
impl EffectArgs {
pub fn to_effect(&self, fallback: Effect) -> Effect {
let mut effect = self
.effect
.as_deref()
.and_then(effect_from_name)
.unwrap_or(fallback);
apply_effect_overrides(&mut effect, &self.to_overrides());
effect
}
pub fn to_overrides(&self) -> EffectOverrides {
let origin = self.origin.as_deref().and_then(parse_origin);
EffectOverrides {
origin,
origin_preset: if origin.is_none() {
self.origin.clone()
} else {
None
},
direction: self.direction.as_deref().and_then(parse_vec2),
angle: self.angle,
easing: self.easing,
from: self.from,
to: self.to,
frequency: self.frequency,
amplitude: self.amplitude,
speed: self.speed,
softness: self.softness,
scale: self.scale,
}
}
}
fn parse_effect_name(s: &str) -> Result<String, String> {
if crate::animation::effect_names().contains(&s) {
Ok(s.to_string())
} else {
Err(format!(
"unknown effect '{}' — expected one of: {}",
s,
crate::animation::effect_names().join(", ")
))
}
}
fn parse_origin(s: &str) -> Option<(f32, f32)> {
let mut parts = s.split(',');
let x = parts.next()?.trim().parse::<f32>().ok()?;
let y = parts.next()?.trim().parse::<f32>().ok()?;
Some((x.clamp(0.0, 1.0), y.clamp(0.0, 1.0)))
}
fn parse_vec2(s: &str) -> Option<[f32; 2]> {
let mut parts = s.split(',');
let x = parts.next()?.trim().parse::<f32>().ok()?;
let y = parts.next()?.trim().parse::<f32>().ok()?;
Some([x, y])
}
#[derive(Parser, Debug)]
#[command(
name = "wallr",
about = "Wayland wallpaper engine with animation and theme pipelines",
version
)]
pub struct WallrCli {
#[command(subcommand)]
pub command: Commands,
#[arg(global = true, long)]
pub config: Option<PathBuf>,
#[arg(global = true, short = 'v', long, action = clap::ArgAction::Count)]
pub verbose: u8,
#[arg(global = true, short = 'q', long)]
pub quiet: bool,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Img {
path: PathBuf,
#[arg(long)]
no_theme: bool,
#[arg(long, value_enum)]
theme: Option<ThemeProvider>,
#[arg(long)]
monitor: Option<String>,
#[arg(long)]
animation: Option<String>,
#[arg(long, value_enum)]
mode: Option<ScalingMode>,
#[command(flatten)]
effect_args: EffectArgs,
},
Set {
path: PathBuf,
#[arg(long)]
no_theme: bool,
#[arg(long, value_enum)]
theme: Option<ThemeProvider>,
#[arg(long)]
monitor: Option<String>,
#[arg(long)]
animation: Option<String>,
#[arg(long, value_enum)]
mode: Option<ScalingMode>,
#[command(flatten)]
effect_args: EffectArgs,
},
Doctor,
Validate {
path: PathBuf,
},
Config {
#[command(subcommand)]
subcommand: ConfigCommands,
},
Cache {
#[command(subcommand)]
subcommand: CacheCommands,
},
Reload,
Monitor {
#[command(subcommand)]
subcommand: MonitorCommands,
},
Daemon {
#[arg(long)]
max_fps: Option<u32>,
},
New {
name: String,
#[arg(long)]
shader: bool,
},
Watch {
dir: PathBuf,
},
Preview {
path: PathBuf,
#[arg(long)]
watch: bool,
#[arg(long, value_name = "PACKAGE|FILE")]
animation: Option<String>,
#[command(flatten)]
effect_args: EffectArgs,
},
Install {
package: String,
},
Publish,
Search {
query: String,
},
Ipc {
#[command(subcommand)]
subcommand: IpcCommands,
},
Quit,
}
#[derive(Subcommand, Debug)]
pub enum ConfigCommands {
Get { key: String },
Set { key: String, value: String },
Path,
}
#[derive(Subcommand, Debug)]
pub enum CacheCommands {
Clear,
Info,
}
#[derive(Subcommand, Debug)]
pub enum MonitorCommands {
List,
Current,
}
#[derive(Subcommand, Debug)]
pub enum IpcCommands {
Pause {
#[arg(long)]
monitor: Option<String>,
},
Resume {
#[arg(long)]
monitor: Option<String>,
},
Reload,
Preview,
Stop,
Status,
Info {
#[arg(long)]
monitor: Option<String>,
},
Seek {
timestamp: String,
#[arg(long)]
monitor: Option<String>,
},
Blank {
#[arg(long)]
monitor: Option<String>,
#[command(flatten)]
effect_args: EffectArgs,
},
Restore {
#[arg(long)]
monitor: Option<String>,
#[command(flatten)]
effect_args: EffectArgs,
},
}