brightwheel 0.1.1

Windows tray utility for DDC/CI monitor brightness and HDR control
#![cfg_attr(not(windows), allow(dead_code, unused_imports))]

#[cfg(not(windows))]
compile_error!("bright only supports Windows");

#[cfg(windows)]
use std::{env, process};

#[cfg(windows)]
fn main() {
    if let Err(error) = run() {
        eprintln!("bright: {error}");
        process::exit(1);
    }
}

#[cfg(windows)]
fn run() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = env::args().skip(1);
    let command = args.next().unwrap_or_else(|| "help".to_owned());

    match command.as_str() {
        "list" => {
            ensure_no_extra(args)?;
            for monitor in brightwheel::list()? {
                match monitor.brightness {
                    Ok(value) => println!(
                        "{}\t{}\t{}\t{}/{}\t{}%",
                        monitor.index,
                        if monitor.primary {
                            "primary"
                        } else {
                            "secondary"
                        },
                        monitor.description,
                        value.current,
                        value.maximum,
                        value.percent()
                    ),
                    Err(error) => println!(
                        "{}\t{}\t{}\tunsupported\t{}",
                        monitor.index,
                        if monitor.primary {
                            "primary"
                        } else {
                            "secondary"
                        },
                        monitor.description,
                        error
                    ),
                }
            }
        }
        "get" => {
            let index = optional_index(&mut args)?;
            ensure_no_extra(args)?;
            println!("{}", brightwheel::get(index)?.percent());
        }
        "set" => {
            let percent = required_u32(&mut args, "PERCENT")?;
            let index = optional_index(&mut args)?;
            ensure_no_extra(args)?;
            println!("{}", brightwheel::set(index, percent)?.percent());
        }
        "change" => {
            let delta = required_i32(&mut args, "DELTA")?;
            let index = optional_index(&mut args)?;
            ensure_no_extra(args)?;
            println!("{}", brightwheel::change(index, delta)?.percent());
        }
        "capabilities" | "caps" => {
            let index = optional_index(&mut args)?;
            ensure_no_extra(args)?;
            println!("{}", brightwheel::capabilities(index)?);
        }
        "hdr" => {
            ensure_no_extra(args)?;
            let state = brightwheel::hdr::state()?;
            println!("{}", if state.enabled { "on" } else { "off" });
        }
        "hdr-toggle" => {
            ensure_no_extra(args)?;
            let state = brightwheel::hdr::toggle()?;
            println!("{}", if state.enabled { "on" } else { "off" });
        }
        "help" | "--help" | "-h" => print_usage(),
        "version" | "--version" | "-V" => println!("bright {}", env!("CARGO_PKG_VERSION")),
        _ => {
            return Err(format!("unknown command '{command}'\n\n{}", usage()).into());
        }
    }

    Ok(())
}

#[cfg(windows)]
fn optional_index(args: &mut impl Iterator<Item = String>) -> Result<usize, String> {
    match args.next() {
        Some(value) => value
            .parse()
            .map_err(|_| format!("invalid monitor index '{value}'")),
        None => Ok(0),
    }
}

#[cfg(windows)]
fn required_u32(args: &mut impl Iterator<Item = String>, name: &str) -> Result<u32, String> {
    let value = args.next().ok_or_else(|| format!("missing {name}"))?;
    value
        .parse()
        .map_err(|_| format!("invalid {name} '{value}'"))
}

#[cfg(windows)]
fn required_i32(args: &mut impl Iterator<Item = String>, name: &str) -> Result<i32, String> {
    let value = args.next().ok_or_else(|| format!("missing {name}"))?;
    value
        .parse()
        .map_err(|_| format!("invalid {name} '{value}'"))
}

#[cfg(windows)]
fn ensure_no_extra(mut args: impl Iterator<Item = String>) -> Result<(), String> {
    match args.next() {
        Some(value) => Err(format!("unexpected argument '{value}'")),
        None => Ok(()),
    }
}

#[cfg(windows)]
fn print_usage() {
    println!("{}", usage());
}

#[cfg(windows)]
fn usage() -> &'static str {
    "Usage:
  bright list
  bright get [MONITOR]
  bright set PERCENT [MONITOR]
  bright change DELTA [MONITOR]
  bright capabilities [MONITOR]
  bright hdr
  bright hdr-toggle

PERCENT is 0..100. MONITOR defaults to 0."
}