lamp 0.3.1

A Linux backlight utility inspired by acpibacklight
// This file is part of the "lamp" program.
//     Copyright (C) 2022  crapStone
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{self, prelude::*};
use std::path::{Path, PathBuf};
use std::process::exit;

const SYS_PATHS: [&str; 2] = ["/sys/class/backlight", "/sys/class/leds"];

pub trait Controller {
    fn get_brightness(&self) -> u32;
    fn get_max_brightness(&self) -> u32;
    fn set_brightness(&self, value: u32);

    fn check_brightness_value(&self, value: u32) {
        let max = self.get_max_brightness();
        if value > max {
            eprintln!("brightness value too high: {value} > {max}",);
            exit(exitcode::DATAERR);
        }
    }
}

pub struct RawController {
    path: PathBuf,
}

impl RawController {
    pub fn new(path: PathBuf) -> Self {
        Self { path }
    }
}

impl Controller for RawController {
    fn get_brightness(&self) -> u32 {
        read_file_to_int(self.path.join("brightness"))
    }

    fn get_max_brightness(&self) -> u32 {
        read_file_to_int(self.path.join("max_brightness"))
    }

    fn set_brightness(&self, value: u32) {
        self.check_brightness_value(value);

        let path = self.path.join("brightness");

        let mut file = match OpenOptions::new().write(true).read(true).open(&path) {
            Err(why) => {
                eprintln!("couldn't open '{}': {:?}", &path.display(), why.kind());
                exit(exitcode::OSFILE);
            }
            Ok(file) => file,
        };

        match write!(file, "{}", value) {
            Ok(_) => {}
            Err(err) => {
                eprintln!(
                    "could not write '{value}' to file '{}': {:?}",
                    &path.display(),
                    err.kind()
                );
                exit(exitcode::OSFILE);
            }
        };
    }
}

pub struct LinController {
    parent_controller: RawController,
}

impl LinController {
    pub fn new(path: PathBuf) -> Self {
        Self {
            parent_controller: RawController::new(path),
        }
    }
}

impl Controller for LinController {
    fn get_brightness(&self) -> u32 {
        ((self.parent_controller.get_brightness() as f64
            / self.parent_controller.get_max_brightness() as f64)
            * self.get_max_brightness() as f64) as u32
    }

    fn get_max_brightness(&self) -> u32 {
        100
    }

    fn set_brightness(&self, value: u32) {
        self.check_brightness_value(value);

        if value > self.get_max_brightness() {
            eprintln!(
                "brightness value too high! {value} > {}",
                self.get_max_brightness()
            );
            exit(exitcode::DATAERR);
        }

        self.parent_controller.set_brightness(
            (value * self.parent_controller.get_max_brightness()) / self.get_max_brightness(),
        )
    }
}

pub struct LogController {
    parent_controller: RawController,
}

impl LogController {
    pub fn new(path: PathBuf) -> Self {
        Self {
            parent_controller: RawController::new(path),
        }
    }
}

impl Controller for LogController {
    fn get_brightness(&self) -> u32 {
        ((self.parent_controller.get_brightness() as f64).log10()
            / (self.parent_controller.get_max_brightness() as f64).log10()
            * self.get_max_brightness() as f64) as u32
    }

    fn get_max_brightness(&self) -> u32 {
        100
    }

    fn set_brightness(&self, value: u32) {
        self.check_brightness_value(value);

        if value > self.get_max_brightness() {
            eprintln!(
                "brightness value too high! {value} > {}",
                self.get_max_brightness()
            );
            exit(exitcode::DATAERR);
        }

        self.parent_controller.set_brightness(10f64.powf(
            (value as f64 / self.get_max_brightness() as f64)
                * (self.parent_controller.get_max_brightness() as f64).log10(),
        ) as u32)
    }
}

fn read_file_to_int(path: PathBuf) -> u32 {
    let mut file = match File::open(&path) {
        Err(why) => {
            eprintln!("couldn't open {}: {:?}", path.display(), why.kind());
            exit(exitcode::OSFILE);
        }
        Ok(file) => file,
    };

    let mut s = String::new();
    match file.read_to_string(&mut s) {
        Err(why) => {
            eprintln!("couldn't read {}: {:?}", path.display(), why.kind());
            exit(exitcode::OSFILE);
        }
        Ok(_) => return s.trim().parse().unwrap(),
    }
}

/// Searches through all paths in `SYS_PATHS` and creates a `HashMap` with the name and absolute path.
///
/// It returns a `Tuple` of the default backlight name and the `HashMap`.
pub fn get_controllers() -> Result<(String, HashMap<String, PathBuf>), io::Error> {
    let mut controllers: HashMap<String, PathBuf> = HashMap::new();

    let mut default = None;

    for path in SYS_PATHS {
        if Path::new(path).exists() {
            for name in Path::new(path).read_dir()? {
                let name = name?.path();
                let key = String::from(name.file_name().unwrap().to_str().unwrap());

                if default.is_none() {
                    default = Some(key.clone());
                }

                controllers.insert(key, name);
            }
        }
    }

    Ok((
        default.unwrap_or_else(|| {
            eprintln!("no devices found");
            exit(exitcode::OSFILE)
        }),
        controllers,
    ))
}