wluma 4.4.0

Automatic brightness adjustment based on screen contents and ALS
use std::collections::HashMap;
use std::collections::HashSet;
use std::error::Error;
use std::fs;
mod app;
mod file;
pub use app::*;

pub fn load() -> Result<app::Config, Box<dyn Error>> {
    validate(parse()?)
}

fn parse() -> Result<app::Config, toml::de::Error> {
    let file_config = xdg::BaseDirectories::with_prefix("wluma")
        .ok()
        .and_then(|xdg| xdg.find_config_file("config.toml"))
        .and_then(|cfg_path| fs::read_to_string(cfg_path).ok())
        .unwrap_or_else(|| include_str!("../../config.toml").to_string());

    let parse_als_thresholds = |t: HashMap<String, String>| -> HashMap<u64, String> {
        t.into_iter()
            .map(|(k, v)| (k.parse().unwrap(), v))
            .collect()
    };

    toml::from_str(&file_config).map(|file_config: file::Config| app::Config {
        output: file_config
            .output
            .backlight
            .into_iter()
            .map(|o| {
                app::Output::Backlight(app::BacklightOutput {
                    name: o.name,
                    path: o.path,
                    min_brightness: 1,
                    capturer: match o.capturer {
                        file::Capturer::None => app::Capturer::None,
                        file::Capturer::Wlroots => app::Capturer::Wlroots,
                    },
                })
            })
            .chain(file_config.output.ddcutil.into_iter().map(|o| {
                app::Output::DdcUtil(app::DdcUtilOutput {
                    name: o.name,
                    min_brightness: 1,
                    capturer: match o.capturer {
                        file::Capturer::None => app::Capturer::None,
                        file::Capturer::Wlroots => app::Capturer::Wlroots,
                    },
                })
            }))
            .chain(file_config.keyboard.into_iter().map(|k| {
                app::Output::Backlight(app::BacklightOutput {
                    name: k.name,
                    path: k.path,
                    min_brightness: 0,
                    capturer: Capturer::None,
                })
            }))
            .collect(),

        als: match file_config.als {
            file::Als::Iio { path, thresholds } => app::Als::Iio {
                path,
                thresholds: parse_als_thresholds(thresholds),
            },
            file::Als::Webcam { video, thresholds } => app::Als::Webcam {
                video,
                thresholds: parse_als_thresholds(thresholds),
            },
            file::Als::Time { thresholds } => app::Als::Time {
                thresholds: parse_als_thresholds(thresholds),
            },
            file::Als::None => app::Als::None,
        },
    })
}

fn validate(config: app::Config) -> Result<app::Config, Box<dyn Error>> {
    let names = config
        .output
        .iter()
        .map(|output| match output {
            app::Output::Backlight(app::BacklightOutput { name, .. }) => name,
            app::Output::DdcUtil(DdcUtilOutput { name, .. }) => name,
        })
        .collect::<HashSet<_>>();

    match (names.len(), names.len() == config.output.len()) {
        (0, _) => Err("No output or keyboard configured".into()),
        (_, false) => Err("Names of all outputs and keyboards are not unique".into()),
        _ => Ok(config),
    }
}