use std::time::Duration;
use clap::{arg, Arg, ArgMatches, Command};
use windows::Win32::Foundation::HWND;
use windows_troll::modules::window_wobbler::{self, WobbleDirection, WobbleSettings};
use crate::cli::CliModule;
pub struct WindowWobblerCli;
impl CliModule for WindowWobblerCli {
fn name(&self) -> &'static str {
"window-wobbler"
}
fn about(&self) -> &'static str {
"Wobble (shake) windows in place"
}
fn command(&self) -> Command {
Command::new(self.name())
.about(self.about())
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(
Command::new("wobble")
.about("Wobble a window by handle")
.arg(arg!(<HWND> "Window handle (decimal, as shown by `window-hider list`)").value_parser(clap::value_parser!(isize)))
.args(wobble_args()),
)
.subcommand(
Command::new("random")
.about("Wobble a random window")
.args(wobble_args()),
)
.subcommand(
Command::new("title")
.about("Wobble the first window with a matching title")
.arg(arg!(<TITLE> "Exact window title"))
.args(wobble_args()),
)
.subcommand(
Command::new("foreground")
.about("Wobble the currently focused window")
.args(wobble_args()),
)
}
fn run(&self, matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
match matches.subcommand() {
Some(("wobble", m)) => run_wobble(m),
Some(("random", m)) => run_random(m),
Some(("title", m)) => run_title(m),
Some(("foreground", m)) => run_foreground(m),
_ => unreachable!("exhausted subcommands"),
}
}
}
fn wobble_args() -> Vec<Arg> {
vec![
arg!(-d --duration <SECONDS> "How long to wobble, in seconds")
.default_value("2")
.value_parser(clap::value_parser!(u64)),
arg!(--direction <DIRECTION> "Wobble axis: horizontal, vertical, or both")
.default_value("horizontal")
.value_parser(parse_direction),
arg!(--min <PIXELS> "Minimum wobble amplitude, in pixels")
.default_value("4")
.value_parser(clap::value_parser!(i32)),
arg!(--max <PIXELS> "Maximum wobble amplitude, in pixels")
.default_value("8")
.value_parser(clap::value_parser!(i32)),
]
}
fn parse_direction(s: &str) -> Result<WobbleDirection, String> {
match s.to_ascii_lowercase().as_str() {
"horizontal" | "h" => Ok(WobbleDirection::Horizontal),
"vertical" | "v" => Ok(WobbleDirection::Vertical),
"both" | "b" | "circular" => Ok(WobbleDirection::Both),
other => Err(format!(
"unknown direction '{other}' (expected horizontal, vertical, or both)"
)),
}
}
fn build_settings(m: &ArgMatches) -> WobbleSettings {
WobbleSettings {
duration: Duration::from_secs(*m.get_one::<u64>("duration").expect("has default")),
direction: *m.get_one::<WobbleDirection>("direction").expect("has default"),
min_amplitude: *m.get_one::<i32>("min").expect("has default"),
max_amplitude: *m.get_one::<i32>("max").expect("has default"),
}
}
fn run_wobble(m: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
let raw = *m.get_one::<isize>("HWND").expect("required argument");
let hwnd = HWND(raw as *mut _);
if window_wobbler::wobble_window(hwnd, build_settings(m)) {
println!("Wobbled window handle {raw}");
} else {
println!("Failed to wobble window handle {raw}");
}
Ok(())
}
fn run_random(m: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
match window_wobbler::wobble_random_window(build_settings(m)) {
Some(w) => println!("Wobbled window: \"{}\"", w.title()),
None => println!("No eligible windows found"),
}
Ok(())
}
fn run_title(m: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
let title = m.get_one::<String>("TITLE").expect("required argument");
match window_wobbler::wobble_by_title(title, build_settings(m)) {
Some(w) => println!("Wobbled window: \"{}\"", w.title()),
None => println!("No window with title \"{}\" found", title),
}
Ok(())
}
fn run_foreground(m: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
if window_wobbler::wobble_foreground_window(build_settings(m)) {
println!("Wobbled foreground window");
} else {
println!("No foreground window");
}
Ok(())
}