hinoirisetr/src/main.rs

264 lines
9 KiB
Rust

use std::env;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use hinoirisetr::time::Time;
use hinoirisetr::{Config, apply_settings, compute_settings, debug, error, info, trace, warn};
use notify_rust::Notification;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::net::UnixListener;
use tokio::signal::unix::{SignalKind, signal};
use tokio::sync::{Notify, RwLock};
use tokio::time::sleep;
const SOCKET_PATH: &str = "/tmp/hinoirisetr.sock";
static CONFIG: OnceLock<Arc<RwLock<Config>>> = OnceLock::new();
async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
let listener = UnixListener::bind(SOCKET_PATH).expect("Failed to bind socket");
trace!("socket server bound");
loop {
let (stream, _) = listener.accept().await.unwrap();
let mut lines = BufReader::new(stream).lines();
trace!("socket server accepted connection");
if let Ok(Some(line)) = lines.next_line().await {
match line.trim() {
"disable" => {
trace!("disable dispatched");
disabled.store(true, Ordering::SeqCst);
notify.notify_one();
debug!("dimming is disabled");
}
"enable" => {
trace!("enable dispatched");
disabled.store(false, Ordering::SeqCst);
notify.notify_one();
debug!("dimming is enabled");
}
"toggle" => {
trace!("toggle dispatched");
let now = !disabled.load(Ordering::SeqCst);
disabled.store(now, Ordering::SeqCst);
notify.notify_one();
debug!("dimming is {}", if now { "enabled" } else { "disabled" });
}
"status" => {
trace!("status dispatched");
// compute current temp/gamma
let now = get_time();
let (cur_temp, cur_gamma) = compute_settings(now, &*config_guard().await);
info!(
"dimming is {} — temp: {}K, gamma: {}%",
if disabled.load(Ordering::SeqCst) {
"disabled"
} else {
"enabled"
},
cur_temp,
cur_gamma
);
}
"status_notify" => {
trace!("status_notify dispatched");
let now = get_time();
let (cur_temp, cur_gamma) = compute_settings(now, &*config_guard().await);
let body = if disabled.load(Ordering::SeqCst) {
"disabled".to_string()
} else {
format!("temp: {cur_temp}K, gamma: {cur_gamma}%")
};
match Notification::new()
.summary("Sunsetting")
.body(body.as_str())
.timeout(notify_rust::Timeout::Milliseconds(
config_guard().await.notification_timeout,
))
.show_async()
.await
{
Ok(_) => {}
Err(err) => {
error!("Erorr occured while sending a notification: {err}")
}
}
}
_ => error!("unknown command: {}", line.trim()),
}
}
}
}
#[tokio::main]
async fn main() {
match env::var("RUST_LOG") {
Ok(val) => match val.parse::<hinoirisetr::log::LogLevel>() {
Ok(level) => hinoirisetr::log::set_log_level(level),
Err(err) => error!("Failed to parse RUST_LOG: {err}"),
},
Err(_) => {
if cfg!(debug_assertions) {
hinoirisetr::log::set_log_level(hinoirisetr::log::LogLevel::Trace);
} else {
hinoirisetr::log::set_log_level(hinoirisetr::log::LogLevel::Info);
}
}
}
info!("starting the daemon");
if !is_binary_available("hyprctl") {
error!("hyprctl is not available, exiting.");
std::process::exit(1);
}
let disabled = Arc::new(AtomicBool::new(false));
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() {
match std::os::unix::net::UnixStream::connect(SOCKET_PATH) {
Ok(_) => {
error!("Another instance is running.");
std::process::exit(1);
}
Err(_) => {
warn!("Stale socket found, removing.");
let _ = std::fs::remove_file(SOCKET_PATH);
}
}
}
// Spawn control socket server
{
let disabled = Arc::clone(&disabled);
let notify = Arc::clone(&notify);
tokio::spawn(async move {
socket_server(disabled, notify).await;
});
}
// Signal handling
let mut sigint = signal(SignalKind::interrupt()).unwrap();
let mut sigterm = signal(SignalKind::terminate()).unwrap();
// set initial settings
{
let now = get_time();
let (temp, gamma) = compute_settings(now, &*config_guard().await);
apply_settings(temp, gamma);
trace!("initial settings applied: {temp}K, {gamma}%");
}
// Main loop with shutdown support
tokio::select! {
_ = async {
loop {
if disabled.load(Ordering::SeqCst) {
apply_settings(config_guard().await.temp_day, config_guard().await.gamma_day);
} else {
let now = get_time();
let (temp, gamma) = compute_settings(now,&*config_guard().await);
apply_settings(temp, gamma);
}
tokio::select! {
_ = sleep(Duration::from_secs(300)) => {trace!("main loop tick");},
_ = notify.notified() => {trace!("main loop woke up via notify");},
}
}
} => {},
_ = sigint.recv() => {
info!("Received SIGINT, shutting down...");
},
_ = sigterm.recv() => {
info!("Received SIGTERM, shutting down...");
},
}
// Cleanup the socket file on shutdown
if std::path::Path::new(SOCKET_PATH).exists() {
match std::fs::remove_file(SOCKET_PATH) {
Ok(_) => info!("Socket file {SOCKET_PATH} removed."),
Err(e) => warn!("Failed to remove socket file {SOCKET_PATH}: {e}"),
}
}
}
fn is_binary_available(binary_name: &str) -> bool {
use std::fs;
if let Ok(paths) = env::var("PATH") {
for path in env::split_paths(&paths) {
let full_path = path.join(binary_name);
if full_path.exists()
&& fs::metadata(&full_path)
.map(|m| m.is_file())
.unwrap_or(false)
{
return true;
}
}
}
false
}
#[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()
// }
fn get_time() -> Time {
Time::now().expect("Failed to get local time")
}