amberyctl 0.1.0

A CLI controller for Ambery
use std::time::Duration;

use amberylib::{AmberyClient, Easing};
use clap::{Parser, Subcommand};
use owo_colors::OwoColorize;

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Get current config
    Get,
    /// Set Config using RGB colors
    Rgb {
        /// Red value (0-255)
        r: u8,
        /// Green value (0-255)
        g: u8,
        /// Blue value (0-255)
        b: u8,

        /// Optional duration (in milliseconds)
        #[arg(short, long)]
        duration: Option<u64>,

        /// Optional easing
        #[arg(short, long)]
        easing: Option<Easing>,
    },
    /// Set Config Temperature in Kelvin
    Temp {
        /// Temperature in kelvin
        temperature: u32,

        /// Optional duration (in milliseconds)
        #[arg(short, long)]
        duration: Option<u64>,

        /// Optional easing
        #[arg(short, long)]
        easing: Option<Easing>,
    },
}

fn main() {
    let cli = Cli::parse();

    // Initialize
    let mut client = match AmberyClient::connect() {
        Ok(client) => client,
        Err(e) => {
            eprintln!("{} {}", "Failed to connect to Ambery:".red(), e);
            eprintln!(
                "{}",
                "The daemon man not be running. Please try restarting it.".red()
            );
            std::process::exit(1);
        }
    };

    match &cli.command {
        Some(Commands::Get) => {
            let rgb = match client.get() {
                Ok(rgb) => rgb,
                Err(e) => {
                    eprintln!("{} {}", "Failed to get current config:".red(), e);
                    std::process::exit(1);
                }
            };

            println!("Red:   {}", rgb.r);
            println!("Blue:  {}", rgb.b);
            println!("Green: {}", rgb.g);
        }
        Some(Commands::Rgb {
            r,
            g,
            b,
            duration,
            easing,
        }) => {
            let r = *r as f64 / 255.0;
            let g = *g as f64 / 255.0;
            let b = *b as f64 / 255.0;
            let rgb = match client.animate(
                Some(r),
                Some(g),
                Some(b),
                Duration::from_millis(duration.unwrap_or(700)),
                easing.unwrap_or(Easing::EaseInOutQuad),
            ) {
                Ok(rgb) => rgb,
                Err(e) => {
                    eprintln!("{} {}", "Failed to set config:".red(), e);
                    std::process::exit(1);
                }
            };

            println!("Red:   {}", rgb.r);
            println!("Blue:  {}", rgb.b);
            println!("Green: {}", rgb.g);
        }
        Some(Commands::Temp {
            temperature,
            duration,
            easing,
        }) => {
            let (r, g, b) = temperature_to_rgb(*temperature);
            let rgb = match client.animate(
                Some(r),
                Some(g),
                Some(b),
                Duration::from_millis(duration.unwrap_or(700)),
                easing.unwrap_or(Easing::EaseInOutQuad),
            ) {
                Ok(rgb) => rgb,
                Err(e) => {
                    eprintln!("{} {}", "Failed to set config:".red(), e);
                    std::process::exit(1);
                }
            };

            println!("Red:   {}", rgb.r);
            println!("Blue:  {}", rgb.b);
            println!("Green: {}", rgb.g);
        }
        None => {}
    }
}

/// Approximate blackbody color temperature as normalized RGB channel gains.
///
/// This is the Tanner Helland approximation used by redshift-style tools such
/// as sunsetr and hyprsunset.
fn temperature_to_rgb(kelvin: u32) -> (f64, f64, f64) {
    let temp = kelvin as f64 / 100.0;

    let red = if temp <= 66.0 {
        1.0
    } else {
        (1.292_936_2 * (temp - 60.0).powf(-0.133_204_76)).clamp(0.0, 1.0)
    };

    let green = if temp <= 66.0 {
        (0.390_081_58 * temp.ln() - 0.631_841_4).clamp(0.0, 1.0)
    } else {
        (1.129_890_86 * (temp - 60.0).powf(-0.075_514_846)).clamp(0.0, 1.0)
    };

    let blue = if temp >= 66.0 {
        1.0
    } else if temp <= 19.0 {
        0.0
    } else {
        (0.543_206_79 * (temp - 10.0).ln() - 1.196_254_1).clamp(0.0, 1.0)
    };

    (red, green, blue)
}