nighterrors 0.1.0

Ultra-light Rust Wayland blue-light filter daemon for Hyprland/Niri and wlroots compositors
mod backends;
mod cli;
mod color;
mod daemon;
mod ipc;
mod output;
mod protocols;
mod service;

use cli::{Command, ControlRequest};

fn main() {
    if let Err(err) = real_main() {
        if err.starts_with("error:") {
            eprintln!("{err}");
        } else {
            eprintln!("error: {err}");
        }
        std::process::exit(1);
    }
}

fn real_main() -> Result<(), String> {
    let parsed = cli::parse_args(std::env::args_os())?;

    match parsed.command {
        Command::Help(topic) => {
            println!("{}", cli::usage_for(topic));
            Ok(())
        }
        Command::Version => {
            println!("nighterrors {}", env!("CARGO_PKG_VERSION"));
            Ok(())
        }
        Command::Run(options) => daemon::run(options, parsed.socket),
        Command::Control(request) => {
            run_control_command(parsed.socket, parsed.output_mode, request)
        }
        Command::Service(command) => service::run(command),
    }
}

fn run_control_command(
    socket_override: Option<std::path::PathBuf>,
    output_mode: cli::OutputMode,
    request: ControlRequest,
) -> Result<(), String> {
    let socket_path = socket_override.unwrap_or_else(ipc::default_socket_path);
    let response = ipc::send_request(&socket_path, &request.to_wire())
        .map_err(|err| add_connect_hint(err, &socket_path))?;
    let rendered = output::render_response(&request, &response, output_mode, stdout_is_tty());
    println!("{rendered}");
    Ok(())
}

fn stdout_is_tty() -> bool {
    unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 }
}

fn add_connect_hint(err: String, socket_path: &std::path::Path) -> String {
    if err.starts_with("failed to connect to") {
        format!(
            "{err}\nhint: start daemon with: nighterrors run (socket: {})",
            socket_path.display()
        )
    } else {
        err
    }
}