initial commit

This commit is contained in:
crapStone 2020-07-22 18:47:26 +02:00
commit 363c6a18c4
11 changed files with 589 additions and 0 deletions

82
src/cli.rs Normal file
View file

@ -0,0 +1,82 @@
use clap::{App, Arg, ArgGroup, ArgMatches};
pub fn build_cli() -> App<'static, 'static> {
App::new("RS Light")
.version("1.0")
.author("crapStone <wewr.mc@gmail.com>")
.about("Utility to interact with backlight")
.arg(
Arg::with_name("set")
.short("s")
.long("set")
.value_name("VALUE")
.help("Sets brightness to given value")
.takes_value(true),
)
.arg(
Arg::with_name("inc")
.short("i")
.long("increase")
.value_name("PERCENT")
.help("Increases brightness")
.takes_value(true),
)
.arg(
Arg::with_name("dec")
.short("d")
.long("decrease")
.value_name("PERCENT")
.help("Decreases brightness")
.takes_value(true),
)
.arg(
Arg::with_name("get")
.short("g")
.long("get")
.help("Prints current brightness value"),
)
.arg(
Arg::with_name("zer")
.short("z")
.long("zero")
.help("Sets brightness to lowest value"),
)
.arg(
Arg::with_name("ful")
.short("f")
.long("full")
.help("Sets brightness to highest value"),
)
.group(ArgGroup::with_name("brightness_control").args(&["set", "inc", "dec", "get", "zer", "ful"]))
.arg(
Arg::with_name("list")
.short("l")
.long("list")
.help("Lists all available brightness and led controllers")
.conflicts_with_all(&["brightness_control"]),
)
.arg(
Arg::with_name("ctrl_type")
.short("t")
.long("type")
.value_name("controller_type")
.takes_value(true)
.possible_values(&["raw", "lin", "log"])
.default_value("lin")
.help("choose controller type")
.long_help(
r#"You can choose between these controller types:
raw: uses the raw values found in the device files
lin: uses percentage values (0.0 - 1.0) with a linear curve for the actual brightness
log: uses percentage values (0.0 - 1.0) with a logarithmic curve for the actual brightness
the perceived brightness for the human eyes should be linear with this controller
"#,
),
)
}
/// Creates a argument parser with [clap](../clap/index.html) and returns a `Box` with the
/// [matches](../clap/struct.ArgMatches.html).
pub fn parse_args<'a>() -> Box<ArgMatches<'a>> {
Box::new(build_cli().get_matches())
}

204
src/controllers.rs Normal file
View file

@ -0,0 +1,204 @@
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::exit;
use exitcode;
const SYS_PATHS: [&str; 2] = ["/sys/class/backlight", "/sys/class/leds"];
pub trait Controller {
fn get_brightness(&self) -> i32;
fn get_max_brightness(&self) -> i32;
fn set_brightness(&self, value: i32);
fn check_brightness_value(&self, value: i32) {
if value > self.get_max_brightness() {
eprintln!(
"brightness value too high: {} > {}",
value,
self.get_max_brightness()
);
exit(exitcode::DATAERR);
} else if value < 0 {
eprintln!("brightness value too low: {}", value);
exit(exitcode::DATAERR);
}
}
}
pub struct RawController {
path: Box<PathBuf>,
}
impl RawController {
pub fn new(path: Box<PathBuf>) -> Self {
Self { path: path }
}
}
impl Controller for RawController {
fn get_brightness(&self) -> i32 {
read_file_to_int(self.path.join("brightness"))
}
fn get_max_brightness(&self) -> i32 {
read_file_to_int(self.path.join("max_brightness"))
}
fn set_brightness(&self, value: i32) {
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 '{}' to file '{}': {:?}",
value,
&path.display(),
err.kind()
);
exit(exitcode::OSFILE);
}
};
}
}
pub struct LinController {
parent_controller: RawController,
}
impl LinController {
pub fn new(path: Box<PathBuf>) -> Self {
Self {
parent_controller: RawController::new(path),
}
}
}
impl Controller for LinController {
fn get_brightness(&self) -> i32 {
((self.parent_controller.get_brightness() as f64
/ self.parent_controller.get_max_brightness() as f64)
* self.get_max_brightness() as f64) as i32
}
fn get_max_brightness(&self) -> i32 {
100
}
fn set_brightness(&self, value: i32) {
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: Box<PathBuf>) -> Self {
Self {
parent_controller: RawController::new(path),
}
}
}
impl Controller for LogController {
fn get_brightness(&self) -> i32 {
((self.parent_controller.get_brightness() as f64).log10()
/ (self.parent_controller.get_max_brightness() as f64).log10()
* self.get_max_brightness() as f64) as i32
}
fn get_max_brightness(&self) -> i32 {
100
}
fn set_brightness(&self, value: i32) {
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 i32)
}
}
fn read_file_to_int(path: PathBuf) -> i32 {
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() -> (String, HashMap<String, Box<PathBuf>>) {
let mut controllers: HashMap<String, Box<PathBuf>> = HashMap::new();
let mut default = None;
for path in SYS_PATHS.iter() {
if Path::new(path).exists() {
for name in Path::new(path).read_dir().unwrap() {
let name = name.unwrap().path();
let key = String::from(name.file_name().unwrap().to_str().unwrap());
if default.is_none() {
default = Some(key.clone());
}
controllers.insert(key, Box::new(name));
}
}
}
(default.unwrap(), controllers)
}

61
src/main.rs Normal file
View file

@ -0,0 +1,61 @@
mod cli;
mod controllers;
use std::process::exit;
use exitcode;
use controllers::{Controller, LinController, LogController, RawController};
fn main() {
let matches = cli::parse_args();
let (default_ctrl, ctrls) = controllers::get_controllers();
let p = ctrls.get(&default_ctrl).unwrap().to_owned();
let controller: Box<dyn Controller> = match matches.value_of("ctrl_type") {
Some("raw") => Box::new(RawController::new(p)),
Some("lin") => Box::new(LinController::new(p)),
Some("log") => Box::new(LogController::new(p)),
Some(_) | None => panic!(ERROR_MSG),
};
if matches.is_present("list") {
for ctrl in ctrls.keys() {
println!("{}", ctrl);
}
exit(exitcode::OK);
} else if let Some(value) = matches.value_of("set") {
let new_value = value.parse::<i32>().unwrap();
controller.set_brightness(new_value);
} else if let Some(value) = matches.value_of("inc") {
let new_value = controller.get_brightness() + value.parse::<i32>().unwrap();
controller.set_brightness(new_value.min(controller.get_max_brightness()));
} else if let Some(value) = matches.value_of("dec") {
let new_value = controller.get_brightness() - value.parse::<i32>().unwrap();
controller.set_brightness(new_value.max(0));
} else if matches.is_present("get") {
println!("{}", controller.get_brightness());
} else if matches.is_present("zer") {
controller.set_brightness(0);
} else if matches.is_present("ful") {
controller.set_brightness(controller.get_max_brightness());
} else {
panic!(ERROR_MSG);
}
exit(exitcode::OK);
}
// https://xkcd.com/2200/
const ERROR_MSG: &str = r#"
ERROR!
If you're seeing this, the code is in what I thought was an unreachable state.
I could give you advice for what to do. but honestly, why should you trust me?
I clearly screwed this up. I'm writing a message that should never appear,
yet I know it will probably appear someday.
On a deep level, I know I'm not up to this task. I'm so sorry.
"#;