windows-troll 0.1.0

Modular Windows prank library
//! CLI for the [`window_hider`](windows_troll::modules::window_hider) module.

use std::thread;
use std::time::Duration;

use clap::{arg, ArgMatches, Command};
use rand::{rng, RngExt};
use windows::Win32::Foundation::HWND;

use windows_troll::modules::window_hider;

use crate::cli::CliModule;

/// CLI registration for the window-hider module.
pub struct WindowHiderCli;

impl CliModule for WindowHiderCli {
    fn name(&self) -> &'static str {
        "window-hider"
    }

    fn about(&self) -> &'static str {
        "Minimize (hide) and restore windows"
    }

    fn command(&self) -> Command {
        Command::new(self.name())
            .about(self.about())
            .subcommand_required(true)
            .arg_required_else_help(true)
            .subcommand(
                Command::new("list").about("List visible windows with their titles and handles"),
            )
            .subcommand(
                Command::new("hide")
                    .about("Minimize a window")
                    .arg(arg!(-t --title <TITLE> "Minimize the first window with this exact title")),
            )
            .subcommand(
                Command::new("restore")
                    .about("Restore a minimized window by handle")
                    .arg(arg!(<HWND> "Window handle (decimal, as shown by `list`)").value_parser(clap::value_parser!(isize))),
            )
            .subcommand(
                Command::new("loop")
                    .about("Continuously minimize a random window")
                    .arg(
                        arg!(-m --min <SECONDS> "Minimum wait time in seconds")
                            .default_value("10")
                            .value_parser(clap::value_parser!(u64)),
                    )
                    .arg(
                        arg!(-M --max <SECONDS> "Maximum wait time in seconds")
                            .default_value("60")
                            .value_parser(clap::value_parser!(u64)),
                    ),
            )
    }

    fn run(&self, matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
        match matches.subcommand() {
            Some(("list", _)) => run_list(),
            Some(("hide", m)) => run_hide(m),
            Some(("restore", m)) => run_restore(m),
            Some(("loop", m)) => run_loop(m),
            _ => unreachable!("exhausted subcommands"),
        }
    }
}

fn run_list() -> Result<(), Box<dyn std::error::Error>> {
    let windows = window_hider::list_windows();
    if windows.is_empty() {
        println!("No visible windows with a title found");
        return Ok(());
    }
    println!("{:>4}  {:>12}  title", "idx", "handle");
    for (i, w) in windows.iter().enumerate() {
        println!("{:>4}  {:>12}  {}", i, w.handle().0 as isize, w.title());
    }
    Ok(())
}

fn run_hide(m: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
    match m.get_one::<String>("title") {
        Some(title) => match window_hider::hide_by_title(title) {
            Some(w) => println!("Minimized: \"{}\"", w.title()),
            None => println!("No window with title \"{}\" found", title),
        },
        None => match window_hider::hide_random_window() {
            Some(w) => println!("Minimized: \"{}\"", w.title()),
            None => println!("No eligible windows found"),
        },
    }
    Ok(())
}

fn run_restore(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_hider::restore_window(hwnd) {
        println!("Restored window handle {raw}");
    } else {
        println!("Window handle {raw}: nothing to restore (already visible)");
    }
    Ok(())
}

fn run_loop(m: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
    let mut min = *m.get_one::<u64>("min").expect("has default");
    let mut max = *m.get_one::<u64>("max").expect("has default");
    if min > max {
        std::mem::swap(&mut min, &mut max);
    }

    println!("window-hider loop started. Press Ctrl+C to exit.");
    println!("wait range: {min}..={max} seconds");

    let mut rng = rng();
    loop {
        let wait = rng.random_range(min..=max);
        println!("waiting {wait}s...");
        thread::sleep(Duration::from_secs(wait));

        match window_hider::hide_random_window() {
            Some(w) => println!("minimized: \"{}\"", w.title()),
            None => println!("no eligible windows found"),
        }
    }
}