mod app;
mod cli;
mod config;
mod ddc;
mod ffi;
mod ui;
use anyhow::Result;
use app::{App, Focus, Mode};
use clap::Parser;
use cli::Cli;
use config::Favorites;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use std::time::Duration;
fn main() -> Result<()> {
let cli = Cli::parse();
ddc::init()?;
let config_dir = match &cli.config_dir {
Some(dir) => dir.clone(),
None => config::config_dir()?,
};
let favorites = Favorites::load(&config_dir)?;
let monitors = ddc::detect_monitors()?;
if monitors.is_empty() {
println!("No DDC/CI capable monitors detected.");
return Ok(());
}
let mut app = App::new(monitors, favorites)?;
let mut terminal = ratatui::try_init()?;
let result = run_event_loop(&mut terminal, &mut app);
ratatui::try_restore()?;
result
}
fn run_event_loop(terminal: &mut ratatui::DefaultTerminal, app: &mut App) -> Result<()> {
while !app.should_quit {
terminal.draw(|frame| ui::draw(frame, app))?;
if app.has_pending_value_fetch() {
if event::poll(Duration::from_millis(0))? {
dispatch_event(app, event::read()?);
} else {
app.step_value_fetch();
}
} else {
dispatch_event(app, event::read()?);
}
}
Ok(())
}
fn dispatch_event(app: &mut App, event: Event) {
let Event::Key(key) = event else {
return;
};
if key.kind != KeyEventKind::Press {
return;
}
match &app.mode {
Mode::Normal => handle_normal_key(app, key.code),
Mode::EditContinuous { .. } | Mode::EditChoice { .. } => handle_edit_key(app, key.code),
}
}
fn handle_normal_key(app: &mut App, code: KeyCode) {
app.status = None;
match code {
KeyCode::Char('q') | KeyCode::Esc => app.should_quit = true,
KeyCode::Tab => app.toggle_focus(),
KeyCode::Left => app.focus = Focus::Monitors,
KeyCode::Right => app.focus = Focus::Capabilities,
KeyCode::Char('j') | KeyCode::Down => match app.focus {
Focus::Monitors => app.move_monitor(1),
Focus::Capabilities => app.move_capability(1),
},
KeyCode::Char('k') | KeyCode::Up => match app.focus {
Focus::Monitors => app.move_monitor(-1),
Focus::Capabilities => app.move_capability(-1),
},
KeyCode::Char('f') => {
if app.focus == Focus::Capabilities {
app.toggle_favorite();
}
}
KeyCode::Char('r') => app.refresh_current_values(),
KeyCode::Enter => {
if app.focus == Focus::Capabilities {
app.start_edit();
}
}
_ => {}
}
}
fn handle_edit_key(app: &mut App, code: KeyCode) {
match code {
KeyCode::Esc => app.cancel_edit(),
KeyCode::Enter => app.confirm_edit(),
_ => {
if let Some(delta) = edit_delta(&app.mode, code) {
app.adjust_edit(delta);
}
}
}
}
fn edit_delta(mode: &Mode, code: KeyCode) -> Option<i32> {
match mode {
Mode::EditContinuous { .. } => match code {
KeyCode::Left | KeyCode::Char('h') | KeyCode::Down | KeyCode::Char('j') => Some(-1),
KeyCode::Right | KeyCode::Char('l') | KeyCode::Up | KeyCode::Char('k') => Some(1),
_ => None,
},
Mode::EditChoice { .. } => match code {
KeyCode::Up | KeyCode::Char('k') | KeyCode::Left | KeyCode::Char('h') => Some(-1),
KeyCode::Down | KeyCode::Char('j') | KeyCode::Right | KeyCode::Char('l') => Some(1),
_ => None,
},
Mode::Normal => None,
}
}