feat: read config from file
This commit is contained in:
parent
62e054c7fe
commit
e828df41ea
5 changed files with 170 additions and 35 deletions
28
Cargo.lock
generated
28
Cargo.lock
generated
|
@ -544,7 +544,9 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -997,6 +999,15 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -1140,11 +1151,26 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
|
@ -1153,6 +1179,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,6 +8,8 @@ chrono = "0.4.40"
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
notify-rust = "4.11.7"
|
notify-rust = "4.11.7"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
toml = "0.8"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time","io-util","net","signal" ] }
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time","io-util","net","signal" ] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -21,7 +21,12 @@
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system overlays;
|
inherit system overlays;
|
||||||
};
|
};
|
||||||
rust = pkgs.rust-bin.nightly.latest.default;
|
rust = pkgs.rust-bin.selectLatestNightlyWith (
|
||||||
|
toolchain:
|
||||||
|
toolchain.default.override {
|
||||||
|
extensions = [ "rust-analyzer" ];
|
||||||
|
}
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.default = pkgs.mkShell rec {
|
devShells.default = pkgs.mkShell rec {
|
||||||
|
|
95
src/lib.rs
95
src/lib.rs
|
@ -1,64 +1,109 @@
|
||||||
//! hinoirisetr library
|
//! hinoirisetr library
|
||||||
//! Contains core logic for computing temperature and gamma and applying settings.
|
//! Contains core logic for computing temperature and gamma and applying settings.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use chrono::{DateTime, Local, Timelike};
|
use chrono::{DateTime, Local, Timelike};
|
||||||
|
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
// Constants for colors and timings
|
#[derive(Debug, Deserialize, Copy, Clone)]
|
||||||
pub const TEMP_DAY: i32 = 6500;
|
#[serde(default)]
|
||||||
pub const TEMP_NIGHT: i32 = 2700;
|
pub struct Config {
|
||||||
|
pub temp_day: u16,
|
||||||
|
pub temp_night: u16,
|
||||||
|
|
||||||
pub const GAMMA_DAY: i32 = 100;
|
pub gamma_day: u16,
|
||||||
pub const GAMMA_NIGHT: i32 = 95;
|
pub gamma_night: u16,
|
||||||
|
|
||||||
pub const SUNSET_START: u32 = 19;
|
pub sunset_start: u8,
|
||||||
pub const SUNSET_END: u32 = 22;
|
pub sunset_end: u8,
|
||||||
pub const SUNRISE_START: u32 = 14;
|
pub sunrise_start: u8,
|
||||||
pub const SUNRISE_END: u32 = 17;
|
pub sunrise_end: u8,
|
||||||
|
|
||||||
|
pub notification_timeout: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
temp_day: 6500,
|
||||||
|
temp_night: 2700,
|
||||||
|
gamma_day: 100,
|
||||||
|
gamma_night: 95,
|
||||||
|
sunset_start: 19,
|
||||||
|
sunset_end: 22,
|
||||||
|
sunrise_start: 4,
|
||||||
|
sunrise_end: 7,
|
||||||
|
notification_timeout: 5000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn load<P: AsRef<Path> + std::fmt::Debug>(
|
||||||
|
path: P,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
trace!("Config::load({path:?})");
|
||||||
|
let content = fs::read_to_string(path)?;
|
||||||
|
let config = toml::from_str(&content)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Linearly interpolate between start and end by factor [0.0, 1.0]
|
/// Linearly interpolate between start and end by factor [0.0, 1.0]
|
||||||
pub fn interpolate(start: i32, end: i32, factor: f64) -> i32 {
|
pub fn interpolate(start: u16, end: u16, factor: f64) -> u16 {
|
||||||
trace!("interpolate({start}, {end}, {factor})");
|
trace!("interpolate({start}, {end}, {factor})");
|
||||||
(start as f64 + (end - start) as f64 * factor).round() as i32
|
if end < start {
|
||||||
|
(end as f64 + (start - end) as f64 * (1.0 - factor)).round() as u16
|
||||||
|
} else {
|
||||||
|
(start as f64 + (end - start) as f64 * factor).round() as u16
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute current temperature and gamma based on provided time
|
/// Compute current temperature and gamma based on provided time
|
||||||
pub fn compute_settings(now: DateTime<Local>) -> (i32, i32) {
|
pub fn compute_settings(now: DateTime<Local>, config: &Config) -> (u16, u16) {
|
||||||
trace!("compute_settings({now:?})");
|
trace!("compute_settings({now:?})");
|
||||||
let time_in_hours = now.hour() as f64 + now.minute() as f64 / 60.0;
|
let time_in_hours = now.hour() as f64 + now.minute() as f64 / 60.0;
|
||||||
trace!("time_in_hours: {time_in_hours}");
|
trace!("time_in_hours: {time_in_hours}");
|
||||||
|
|
||||||
if (time_in_hours >= SUNSET_START as f64) && (time_in_hours <= SUNSET_END as f64) {
|
if (time_in_hours >= config.sunset_start as f64) && (time_in_hours <= config.sunset_end as f64)
|
||||||
|
{
|
||||||
trace!("time_in_hours is within sunset");
|
trace!("time_in_hours is within sunset");
|
||||||
let factor = ((time_in_hours - SUNSET_START as f64) / (SUNSET_END - SUNSET_START) as f64)
|
let factor = ((time_in_hours - config.sunset_start as f64)
|
||||||
|
/ (config.sunset_end - config.sunset_start) as f64)
|
||||||
.clamp(0.0, 1.0);
|
.clamp(0.0, 1.0);
|
||||||
(
|
(
|
||||||
interpolate(TEMP_DAY, TEMP_NIGHT, factor),
|
interpolate(config.temp_day, config.temp_night, factor),
|
||||||
interpolate(GAMMA_DAY, GAMMA_NIGHT, factor),
|
interpolate(config.gamma_day, config.gamma_night, factor),
|
||||||
)
|
)
|
||||||
} else if (time_in_hours >= SUNRISE_START as f64) && (time_in_hours <= SUNRISE_END as f64) {
|
} else if (time_in_hours >= config.sunrise_start as f64)
|
||||||
|
&& (time_in_hours <= config.sunrise_end as f64)
|
||||||
|
{
|
||||||
trace!("time_in_hours is within sunrise");
|
trace!("time_in_hours is within sunrise");
|
||||||
let factor = 1.0
|
let factor = 1.0
|
||||||
- ((time_in_hours - SUNRISE_START as f64) / (SUNRISE_END - SUNRISE_START) as f64)
|
- ((time_in_hours - config.sunrise_start as f64)
|
||||||
|
/ (config.sunrise_end - config.sunrise_start) as f64)
|
||||||
.clamp(0.0, 1.0);
|
.clamp(0.0, 1.0);
|
||||||
(
|
(
|
||||||
interpolate(TEMP_DAY, TEMP_NIGHT, factor),
|
interpolate(config.temp_day, config.temp_night, factor),
|
||||||
interpolate(GAMMA_DAY, GAMMA_NIGHT, factor),
|
interpolate(config.gamma_day, config.gamma_night, factor),
|
||||||
)
|
)
|
||||||
} else if time_in_hours > SUNSET_END as f64 || time_in_hours < SUNRISE_START as f64 {
|
} else if time_in_hours > config.sunset_end as f64
|
||||||
|
|| time_in_hours < config.sunrise_start as f64
|
||||||
|
{
|
||||||
trace!("time_in_hours is within night");
|
trace!("time_in_hours is within night");
|
||||||
(TEMP_NIGHT, GAMMA_NIGHT)
|
(config.temp_night, config.gamma_night)
|
||||||
} else {
|
} else {
|
||||||
trace!("time_in_hours is within day");
|
trace!("time_in_hours is within day");
|
||||||
(TEMP_DAY, GAMMA_DAY)
|
(config.temp_day, config.gamma_day)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply given temperature (Kelvin) and gamma (%) via hyprctl commands
|
/// Apply given temperature (Kelvin) and gamma (%) via hyprctl commands
|
||||||
pub fn apply_settings(temp: i32, gamma: i32) {
|
pub fn apply_settings(temp: u16, gamma: u16) {
|
||||||
trace!("apply_settings({temp}, {gamma})");
|
trace!("apply_settings({temp}, {gamma})");
|
||||||
debug!("applying temperature: {temp}");
|
debug!("applying temperature: {temp}");
|
||||||
debug!("applying gamma: {gamma}");
|
debug!("applying gamma: {gamma}");
|
||||||
|
|
73
src/main.rs
73
src/main.rs
|
@ -1,19 +1,22 @@
|
||||||
use std::sync::Arc;
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, OnceLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use hinoirisetr::{apply_settings, compute_settings};
|
use hinoirisetr::{Config, apply_settings, compute_settings};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use notify_rust::Notification;
|
use notify_rust::Notification;
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
use tokio::signal::unix::{SignalKind, signal};
|
use tokio::signal::unix::{SignalKind, signal};
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::{Notify, RwLock};
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
const SOCKET_PATH: &str = "/tmp/hinoirisetr.sock";
|
const SOCKET_PATH: &str = "/tmp/hinoirisetr.sock";
|
||||||
const NOTIFICATION_TIMEOUT: u32 = 5000;
|
|
||||||
|
static CONFIG: OnceLock<Arc<RwLock<Config>>> = OnceLock::new();
|
||||||
|
|
||||||
async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
|
async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
|
||||||
let listener = UnixListener::bind(SOCKET_PATH).expect("Failed to bind socket");
|
let listener = UnixListener::bind(SOCKET_PATH).expect("Failed to bind socket");
|
||||||
|
@ -50,7 +53,7 @@ async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
|
||||||
trace!("status dispatched");
|
trace!("status dispatched");
|
||||||
// compute current temp/gamma
|
// compute current temp/gamma
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
let (cur_temp, cur_gamma) = compute_settings(now);
|
let (cur_temp, cur_gamma) = compute_settings(now, &*config_guard().await);
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"dimming is {} — temp: {}K, gamma: {}%",
|
"dimming is {} — temp: {}K, gamma: {}%",
|
||||||
|
@ -66,7 +69,7 @@ async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
|
||||||
"status_notify" => {
|
"status_notify" => {
|
||||||
trace!("status_notify dispatched");
|
trace!("status_notify dispatched");
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
let (cur_temp, cur_gamma) = compute_settings(now);
|
let (cur_temp, cur_gamma) = compute_settings(now, &*config_guard().await);
|
||||||
|
|
||||||
let body = if disabled.load(Ordering::SeqCst) {
|
let body = if disabled.load(Ordering::SeqCst) {
|
||||||
"disabled".to_string()
|
"disabled".to_string()
|
||||||
|
@ -77,7 +80,9 @@ async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
|
||||||
match Notification::new()
|
match Notification::new()
|
||||||
.summary("Sunsetting")
|
.summary("Sunsetting")
|
||||||
.body(body.as_str())
|
.body(body.as_str())
|
||||||
.timeout(notify_rust::Timeout::Milliseconds(NOTIFICATION_TIMEOUT))
|
.timeout(notify_rust::Timeout::Milliseconds(
|
||||||
|
config_guard().await.notification_timeout,
|
||||||
|
))
|
||||||
.show_async()
|
.show_async()
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -101,6 +106,24 @@ async fn main() {
|
||||||
let disabled = Arc::new(AtomicBool::new(false));
|
let disabled = Arc::new(AtomicBool::new(false));
|
||||||
let notify = Arc::new(Notify::new());
|
let notify = Arc::new(Notify::new());
|
||||||
|
|
||||||
|
let cfg: Config = if get_config_path().exists() {
|
||||||
|
debug!("Config file found, loading...");
|
||||||
|
match Config::load(get_config_path()) {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to load config: {err}");
|
||||||
|
warn!("Using default config.");
|
||||||
|
Config::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Config file not found, using default config.");
|
||||||
|
warn!("Config path {}", get_config_path().display());
|
||||||
|
Config::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
CONFIG.set(Arc::new(RwLock::new(cfg))).unwrap();
|
||||||
|
|
||||||
if std::path::Path::new(SOCKET_PATH).exists() {
|
if std::path::Path::new(SOCKET_PATH).exists() {
|
||||||
match std::os::unix::net::UnixStream::connect(SOCKET_PATH) {
|
match std::os::unix::net::UnixStream::connect(SOCKET_PATH) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
@ -132,10 +155,10 @@ async fn main() {
|
||||||
_ = async {
|
_ = async {
|
||||||
loop {
|
loop {
|
||||||
if disabled.load(Ordering::SeqCst) {
|
if disabled.load(Ordering::SeqCst) {
|
||||||
apply_settings(hinoirisetr::TEMP_DAY, hinoirisetr::GAMMA_DAY);
|
apply_settings(config_guard().await.temp_day, config_guard().await.gamma_day);
|
||||||
} else {
|
} else {
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
let (temp, gamma) = compute_settings(now);
|
let (temp, gamma) = compute_settings(now,&*config_guard().await);
|
||||||
apply_settings(temp, gamma);
|
apply_settings(temp, gamma);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,3 +184,35 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_config_path() -> PathBuf {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
let username = env::var("USERNAME").unwrap_or_else(|_| "Default".to_string());
|
||||||
|
PathBuf::from(format!(
|
||||||
|
"C:\\Users\\{username}\\AppData\\Local\\hinoirisetr.toml"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let xdg_config_home = env::var("XDG_CONFIG_HOME").ok();
|
||||||
|
let home = env::var("HOME").ok();
|
||||||
|
let user = env::var("USER").unwrap_or_else(|_| "default".to_string());
|
||||||
|
|
||||||
|
xdg_config_home
|
||||||
|
.map(|x| PathBuf::from(format!("{x}/hinoirisetr.toml")))
|
||||||
|
.or_else(|| home.map(|h| PathBuf::from(format!("{h}/.config/hinoirisetr.toml"))))
|
||||||
|
.unwrap_or_else(|| PathBuf::from(format!("/home/{user}/.config/hinoirisetr.toml")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn config_guard() -> tokio::sync::RwLockReadGuard<'static, Config> {
|
||||||
|
CONFIG.get().expect("config not init").read().await
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn config_handle() -> Arc<RwLock<Config>> {
|
||||||
|
// CONFIG.get().expect("config not init").clone()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async fn get_config() -> Config {
|
||||||
|
// let lock = CONFIG.get().expect("config not initialized").read().await;
|
||||||
|
// lock.clone()
|
||||||
|
// }
|
||||||
|
|
Loading…
Reference in a new issue