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>> = OnceLock::new(); async fn socket_server(disabled: Arc, notify: Arc) { 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::() { 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(¬ify); 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> { // 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") }