use clap::{Parser, Subcommand};
use ddc_hi::{Ddc, Display};
const BOLD: &str = "\x1b[1m";
const RESET: &str = "\x1b[0m";
const CYAN: &str = "\x1b[36m";
const GREEN: &str = "\x1b[32m";
const RED: &str = "\x1b[31m";
const YELLOW: &str = "\x1b[33m";
const DIM: &str = "\x1b[2m";
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None, after_help="@readwithai 📖 https://readwithai.substack.com/ ⚡️ machine-aided reading ✒️")]
struct Cli {
#[command(subcommand)]
command: Command,
}
enum ValueChange {
Delta(i16),
Absolute(u16)
}
fn parse_change(s: &str) -> ValueChange {
let trimmed = s.trim();
if trimmed.starts_with('-') {
return ValueChange::Delta(trimmed.parse().unwrap())
} else if trimmed.starts_with('+') {
return ValueChange::Delta(trimmed[1..].parse().unwrap())
} else if trimmed.ends_with('-') {
let tmp:i16 = trimmed[..s.len() - 1].parse().unwrap();
return ValueChange::Delta(-tmp)
} else if trimmed.ends_with('+') {
return ValueChange::Delta(trimmed[..s.len() - 1].parse().unwrap())
} else {
return ValueChange::Absolute(trimmed.parse().unwrap())
}
}
#[derive(Subcommand, Debug)]
enum Command {
Get {
monitor: Option<usize>,
},
Set {
brightness: String,
monitor: Option<usize>,
},
}
fn main() {
let cli = Cli::parse();
match cli.command {
Command::Get { monitor } => handle_get(monitor),
Command::Set {
brightness,
monitor,
} => handle_set(parse_change(brightness.as_str()), monitor),
}
}
fn handle_get(monitor_index: Option<usize>) {
let monitors = find_monitors(monitor_index);
if monitors.is_empty() {
println!(
"{RED}No DDC/CI-enabled monitors found.{RESET}\n{YELLOW}Hint:{RESET} Integrated laptop displays often do not support DDC/CI.",
);
return;
}
println!("\n{}Monitor Brightness Status:{}\n", BOLD, RESET);
for (i, mut display) in monitors {
match display.handle.get_vcp_feature(0x10) {
Ok(value) => {
let bar = create_brightness_bar(value.value(), value.maximum());
println!(
"{CYAN}Monitor {i}{RESET}: {BOLD}{:>3}%{RESET} {DIM}[0..{:<3}]{RESET} {}",
value.value(),
value.maximum(),
bar
);
}
Err(e) => {
println!(
"{CYAN}Monitor {i}{RESET}: {RED}Error reading brightness: {}{RESET} {DIM}(likely does not support DDC/CI){RESET}",
e
);
}
}
}
println!();
}
fn handle_set(brightness: ValueChange, monitor_index: Option<usize>) {
let monitors = find_monitors(monitor_index);
if monitors.is_empty() {
println!("{RED}No DDC/CI-enabled monitors found to set brightness on.{RESET}",);
return;
}
for (i, mut display) in monitors {
let value:u16 = match brightness {
ValueChange::Delta(delta) => {
let current = {
match display.handle.get_vcp_feature(0x10) {
Ok(info) => info.value(),
Err(e) => {
println!(
"{RED}✖{RESET} Error getting current brightness for monitor {CYAN}{i}{RESET}: {} (skipping)", e);
continue;
}
}
};
(current as i16 + delta as i16).clamp(0, 100) as u16
}
ValueChange::Absolute(value) => value,
};
match display.handle.set_vcp_feature(0x10, value) {
Ok(_) => {
println!(
"{GREEN}✔{RESET} Set brightness to {BOLD}{value}%{RESET} for monitor {CYAN}{i}{RESET}"
);
}
Err(e) => {
println!(
"{RED}✖{RESET} Error setting brightness for monitor {CYAN}{i}{RESET}: {}",
e
);
}
}
}
}
fn find_monitors(monitor_index: Option<usize>) -> Vec<(usize, Display)> {
Display::enumerate()
.into_iter()
.enumerate()
.filter(|(i, _)| monitor_index.map_or(true, |index| *i == index))
.collect()
}
fn create_brightness_bar(current: u16, max: u16) -> String {
let bar_width = 20;
let percentage = if max > 0 { current as f32 / max as f32 } else { 0.0 };
let filled = (percentage * bar_width as f32).round() as usize;
let empty = bar_width - filled;
format!(
"[{}{}{}{}{}]",
GREEN,
"█".repeat(filled),
DIM,
"░".repeat(empty),
RESET
)
}