From 10551db08df0a970e19a4ee73b511d887e8c0e3f Mon Sep 17 00:00:00 2001 From: Amadej Kastelic Date: Sat, 14 Sep 2024 11:09:27 +0200 Subject: [PATCH 1/7] Refactor config --- Cargo.lock | 179 ++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/config.rs | 58 ++++++++---- src/main.rs | 62 +++++++------ src/shaders/night_light.rs | 1 + src/shaders/shader.rs | 36 ++++---- src/shaders/vibrance.rs | 12 ++- src/utils.rs | 5 ++ 8 files changed, 282 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 553ad00..d0e4fe8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,18 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bumpalo" version = "3.16.0" @@ -198,6 +210,21 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "derive_more" version = "0.99.18" @@ -238,6 +265,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "gimli" version = "0.29.0" @@ -297,6 +345,7 @@ dependencies = [ "colog", "hyprland", "log", + "notify", "regex", "serde", "strfmt", @@ -337,6 +386,26 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -358,6 +427,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -370,6 +459,17 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall", +] + [[package]] name = "log" version = "0.4.22" @@ -391,6 +491,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.2" @@ -403,6 +515,25 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.6.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -457,6 +588,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "regex" version = "1.10.6" @@ -498,6 +638,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.209" @@ -592,7 +741,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.2", "pin-project-lite", "socket2", "tokio-macros", @@ -662,6 +811,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -723,6 +882,15 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -750,6 +918,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 3e6ef1b..e92cb27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ hyprland = { git = "https://github.com/hyprland-community/hyprland-rs", branch = "silent", ] } log = "0.4.22" +notify = "6.1.1" regex = "1.10.6" serde = { version = "1.0", features = ["derive"] } strfmt = "0.2.4" diff --git a/src/config.rs b/src/config.rs index 4590a3f..3cfdbd8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,9 @@ use log::{error, info}; +use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use serde::Deserialize; -use std::{env, fs}; +use std::{env, fs, path::Path, time::Duration}; + +const DEFAULT_CONFIG_PATH: &str = "/etc/hyprlux/config.toml"; #[derive(Debug, Deserialize)] pub struct Config { @@ -53,27 +56,30 @@ impl Default for VibranceConfig { } } -fn load_config() -> Result> { - let config_file_path: String; - - // If config file path provided as arg +pub fn path() -> String { let args: Vec = env::args().collect(); if args.len() > 1 { - config_file_path = args[1].clone(); - } else { - config_file_path = xdg::BaseDirectories::with_prefix("hypr") - .unwrap() - .place_config_file("hyprlux.toml") - .unwrap() + return args[1].clone().to_string(); + } + + let config_file_path = xdg::BaseDirectories::with_prefix("hypr") + .unwrap() + .get_config_file("hyprlux.toml"); + + if config_file_path.exists() { + return config_file_path .into_os_string() .into_string() - .unwrap(); + .unwrap_or(DEFAULT_CONFIG_PATH.to_string()); } - info!("Loading config file at {}", &config_file_path); + DEFAULT_CONFIG_PATH.to_string() +} + +pub fn load(config_path: String) -> Result> { + info!("Loading config file at {}", &config_path); - let contents = fs::read_to_string(config_file_path) - .unwrap_or(fs::read_to_string("/etc/hyprlux/config.toml").unwrap_or("".to_string())); + let contents = fs::read_to_string(config_path).unwrap(); // Return default config if no config file exists if contents.is_empty() { @@ -84,6 +90,24 @@ fn load_config() -> Result> { Ok(toml::from_str(&contents).unwrap()) } -pub fn get_config() -> Result> { - load_config() +pub fn watch>(path: P) -> notify::Result<()> { + let (tx, rx) = std::sync::mpsc::channel(); + + let mut watcher = RecommendedWatcher::new( + tx, + notify::Config::default() + .with_poll_interval(Duration::from_secs(2)) + .with_compare_contents(false), + )?; + + watcher.watch(path.as_ref(), RecursiveMode::NonRecursive)?; + + for res in rx { + match res { + Ok(event) => log::info!("Change: {event:?}"), + Err(error) => log::error!("Error: {error:?}"), + } + } + + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 1671453..098f990 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,8 @@ mod config; mod shaders; mod utils; -use std::sync::{Arc, Mutex}; - -use config::get_config; use hyprland::event_listener::EventListener; -use log::{debug, info}; +use log::info; use shaders::shader::{self, Shader}; fn main() -> hyprland::Result<()> { @@ -14,12 +11,12 @@ fn main() -> hyprland::Result<()> { let mut event_listener = EventListener::new(); + let config_path = config::path(); + // Load config - let cfg = get_config().unwrap(); + let cfg = config::load(config_path).unwrap(); info!("Config loaded: {:?}", cfg); - let applied_shader = Arc::new(Mutex::new(String::from(""))); - // Create shaders let night_light_shader = shaders::night_light::new( cfg.night_light.enabled, @@ -43,32 +40,41 @@ fn main() -> hyprland::Result<()> { event_listener.add_active_window_change_handler(move |data| { let data = data.unwrap(); - let mut applied = false; - let mut applied_shader = applied_shader.lock().unwrap(); - debug!("Curent shader: {}", applied_shader.to_string()); + let applied_shader = shader::get().unwrap_or("null".to_string()); + info!("Curent shader: {}", applied_shader); + let mut shader_to_apply: Option> = None; + + // Should apply night light shader? + if night_light_shader.should_apply( + Some(data.window_class.to_string()), + Some(data.window_title.to_string()), + ) { + shader_to_apply = Some(Box::new(night_light_shader.clone())) + } + + // Should apply vibrance shader? for vibrance_shader in &vibrance_shaders { - if shader::apply_if_should( - vibrance_shader, - Some(data.window_class.clone()), - Some(data.window_title.clone()), - applied_shader.to_string(), - ) - .unwrap() - { - *applied_shader = vibrance_shader.hash(); - applied = true; + if vibrance_shader.should_apply( + Some(data.window_class.to_string()), + Some(data.window_title.to_string()), + ) { + shader_to_apply = Some(Box::new(vibrance_shader.clone())); break; } } - if !applied && night_light_shader.should_apply(None, None) { - if shader::apply_if_should(&night_light_shader, None, None, applied_shader.to_string()) - .unwrap() - { - *applied_shader = night_light_shader.hash(); - } - } else if !applied { + + // Remove current shader + if shader_to_apply.is_none() && applied_shader != "null".to_string() { shader::remove().unwrap(); - *applied_shader = "".to_string(); + return; + } else if shader_to_apply.is_none() { + return; + } + + let shader_to_apply = shader_to_apply.unwrap(); + // Apply shader if needed + if shader_to_apply.hash() != applied_shader { + shader::apply(shader_to_apply.as_ref()).unwrap(); } }); diff --git a/src/shaders/night_light.rs b/src/shaders/night_light.rs index ba79a03..77ed6b0 100644 --- a/src/shaders/night_light.rs +++ b/src/shaders/night_light.rs @@ -45,6 +45,7 @@ void main() {{ "; const TIME_FMT: &str = "%H:%M"; +#[derive(Clone)] pub struct NightLightShader { enabled: bool, start_time: NaiveTime, diff --git a/src/shaders/shader.rs b/src/shaders/shader.rs index 5a3e287..4d990b0 100644 --- a/src/shaders/shader.rs +++ b/src/shaders/shader.rs @@ -2,6 +2,7 @@ use std::env; use std::fs::File; use std::io::Write; +use super::super::utils; use hyprland::keyword::Keyword; use log::info; @@ -14,10 +15,12 @@ pub trait Shader { fn hash(&self) -> String; } -pub fn apply(shader: &impl Shader) -> Result<(), Box> { +pub fn apply(shader: &dyn Shader) -> Result<(), Box> { + info!("Applying shader: {}", shader.hash()); + let output = shader.get().unwrap(); - let path = env::temp_dir().join("shader.glsl").to_owned(); + let path = env::temp_dir().join(shader.hash()).to_owned(); let mut shader_file = File::create(path.clone())?; shader_file.write_all(output.as_bytes())?; @@ -29,23 +32,18 @@ pub fn apply(shader: &impl Shader) -> Result<(), Box> { )?) } -pub fn apply_if_should( - shader: &impl Shader, - window_class: Option, - window_title: Option, - last_applied_shader_hash: String, -) -> Result> { - if last_applied_shader_hash != shader.hash() && shader.should_apply(window_class, window_title) - { - info!("Applying shader {}", shader.hash()); - remove().unwrap(); - apply(shader)?; - return Ok(true); - } - - Ok(false) -} - pub fn remove() -> Result<(), Box> { + info!("Removing active shader"); Ok(Keyword::set(SHADER_KEY, NO_SHADER)?) } + +pub fn get() -> Option { + let shader = Keyword::get(SHADER_KEY).unwrap(); + info!("Getting shader {}", shader.value.to_string()); + + if shader.value.to_string() == NO_SHADER { + return None; + } + + Some(utils::shader_hash_from_path(shader.value.to_string()).unwrap()) +} diff --git a/src/shaders/vibrance.rs b/src/shaders/vibrance.rs index 301f1dc..a662d78 100644 --- a/src/shaders/vibrance.rs +++ b/src/shaders/vibrance.rs @@ -40,6 +40,7 @@ void main() {{ }} "; +#[derive(Clone)] pub struct VibranceShader { window_class: String, window_title: String, @@ -87,10 +88,7 @@ impl Shader for VibranceShader { } fn hash(&self) -> String { - format!( - "vibrance_{}_{}_{}", - self.window_class, self.window_title, self.strength - ) + format!("vibrance_{}", self.strength) } } @@ -195,15 +193,15 @@ mod tests { let shaders = [ ( new("class".to_string(), "title".to_string(), 100), - "vibrance_class_title_100".to_string(), + "vibrance_100".to_string(), ), ( new("firefox".to_string(), "".to_string(), 10), - "vibrance_firefox__10".to_string(), + "vibrance_10".to_string(), ), ( new("firefox".to_string(), "firefox".to_string(), 15), - "vibrance_firefox_firefox_15".to_string(), + "vibrance_15".to_string(), ), ]; for (shader, expected) in shaders { diff --git a/src/utils.rs b/src/utils.rs index ec3911d..072acf0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ use chrono::{Local, NaiveTime}; +#[derive(Clone)] pub struct Time { mock_time: Option, } @@ -28,6 +29,10 @@ pub fn int_in_range(value: i32, min: i32, max: i32) -> i32 { value } +pub fn shader_hash_from_path(path: String) -> Option { + Some(path.split("/").last().unwrap().to_string()) +} + #[cfg(test)] mod tests { From c594291d0d1b7debae7e47c05f0a2405d28d64da Mon Sep 17 00:00:00 2001 From: Amadej Kastelic Date: Sat, 14 Sep 2024 13:27:40 +0200 Subject: [PATCH 2/7] config watch --- Cargo.lock | 2 +- src/config.rs | 33 ++------- src/main.rs | 146 ++++++++++++++++++++++++++++--------- src/shaders/night_light.rs | 2 +- src/shaders/shader.rs | 2 - src/shaders/vibrance.rs | 2 +- src/utils.rs | 2 +- 7 files changed, 122 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0e4fe8..99eb690 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -888,7 +888,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/src/config.rs b/src/config.rs index 3cfdbd8..8baad85 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,6 @@ use log::{error, info}; -use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use serde::Deserialize; -use std::{env, fs, path::Path, time::Duration}; +use std::{env, fs}; const DEFAULT_CONFIG_PATH: &str = "/etc/hyprlux/config.toml"; @@ -76,38 +75,16 @@ pub fn path() -> String { DEFAULT_CONFIG_PATH.to_string() } -pub fn load(config_path: String) -> Result> { +pub fn load(config_path: String) -> Option { info!("Loading config file at {}", &config_path); - let contents = fs::read_to_string(config_path).unwrap(); + let contents = fs::read_to_string(config_path).unwrap_or("".to_string()); // Return default config if no config file exists if contents.is_empty() { error!("No config file found. Using default config."); - return Ok(Config::default()); + return None; } - Ok(toml::from_str(&contents).unwrap()) -} - -pub fn watch>(path: P) -> notify::Result<()> { - let (tx, rx) = std::sync::mpsc::channel(); - - let mut watcher = RecommendedWatcher::new( - tx, - notify::Config::default() - .with_poll_interval(Duration::from_secs(2)) - .with_compare_contents(false), - )?; - - watcher.watch(path.as_ref(), RecursiveMode::NonRecursive)?; - - for res in rx { - match res { - Ok(event) => log::info!("Change: {event:?}"), - Err(error) => log::error!("Error: {error:?}"), - } - } - - Ok(()) + Some(toml::from_str(&contents).unwrap()) } diff --git a/src/main.rs b/src/main.rs index 098f990..d1fc0ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,57 +3,93 @@ mod shaders; mod utils; use hyprland::event_listener::EventListener; -use log::info; +use log::{debug, error, info}; +use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use shaders::shader::{self, Shader}; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; fn main() -> hyprland::Result<()> { colog::init(); - let mut event_listener = EventListener::new(); - let config_path = config::path(); - // Load config - let cfg = config::load(config_path).unwrap(); - info!("Config loaded: {:?}", cfg); + // Channel for notifying when the config file changes + let (tx, rx) = mpsc::channel(); - // Create shaders - let night_light_shader = shaders::night_light::new( - cfg.night_light.enabled, - cfg.night_light.start_time, - cfg.night_light.end_time, - cfg.night_light.temperature, - None, - ); + let mut watcher = RecommendedWatcher::new( + tx, + notify::Config::default() + .with_poll_interval(Duration::from_secs(2)) + .with_compare_contents(false), + ) + .unwrap(); - let vibrance_shaders: Vec = cfg - .vibrance_configs - .into_iter() - .map(|vibrance_cfg| { - shaders::vibrance::new( - vibrance_cfg.window_class, - vibrance_cfg.window_title, - vibrance_cfg.strength, - ) - }) - .collect(); + watcher + .watch(config_path.as_ref(), RecursiveMode::NonRecursive) + .unwrap(); + + let config_data = Arc::new(Mutex::new(load_config_and_shaders(&config_path))); + + let config_data_clone = Arc::clone(&config_data); + + // Spawn a thread to watch for config changes and reload shaders + let debounce_delay = Duration::from_millis(2000); + let mut last_event_time = Instant::now(); + thread::spawn(move || loop { + match rx.recv() { + Ok(_) => { + let now = Instant::now(); + if now.duration_since(last_event_time) > debounce_delay { + info!("Config file changed. Reloading..."); + + let new_config = load_config_and_shaders(&config_path); + + let mut config_data = config_data_clone.lock().unwrap(); + + // Only load config if it's not the same and it contains data + if new_config != *config_data + && new_config.night_light_shader != None + && new_config.vibrance_shaders.len() > 0 + { + *config_data = load_config_and_shaders(&config_path); + last_event_time = now; + } + } else { + info!("Ignoring duplicate event within debounce period"); + } + } + Err(error) => error!("Watch error: {:?}", error), + } + }); + + // Setup the event listener + let mut event_listener = EventListener::new(); + // Event handler logic event_listener.add_active_window_change_handler(move |data| { let data = data.unwrap(); let applied_shader = shader::get().unwrap_or("null".to_string()); - info!("Curent shader: {}", applied_shader); + debug!("Current shader: {}", applied_shader); let mut shader_to_apply: Option> = None; + // Access the current config and shaders + let config_data = config_data.lock().unwrap(); + // Should apply night light shader? - if night_light_shader.should_apply( - Some(data.window_class.to_string()), - Some(data.window_title.to_string()), - ) { - shader_to_apply = Some(Box::new(night_light_shader.clone())) + if config_data.night_light_shader.is_some() { + let shader = config_data.night_light_shader.clone().unwrap(); + if shader.should_apply( + Some(data.window_class.to_string()), + Some(data.window_title.to_string()), + ) { + shader_to_apply = Some(Box::new(shader)); + } } // Should apply vibrance shader? - for vibrance_shader in &vibrance_shaders { + for vibrance_shader in &config_data.vibrance_shaders { if vibrance_shader.should_apply( Some(data.window_class.to_string()), Some(data.window_title.to_string()), @@ -63,7 +99,7 @@ fn main() -> hyprland::Result<()> { } } - // Remove current shader + // Remove current shader if none should apply if shader_to_apply.is_none() && applied_shader != "null".to_string() { shader::remove().unwrap(); return; @@ -82,3 +118,47 @@ fn main() -> hyprland::Result<()> { Ok(()) } + +fn load_config_and_shaders(config_path: &str) -> ConfigData { + let cfg = config::load(config_path.to_string()); + if cfg.is_none() { + return ConfigData { + night_light_shader: None, + vibrance_shaders: [].to_vec(), + }; + } + + let cfg = cfg.unwrap(); + info!("Config loaded: {:?}", cfg); + + let night_light_shader = shaders::night_light::new( + cfg.night_light.enabled, + cfg.night_light.start_time, + cfg.night_light.end_time, + cfg.night_light.temperature, + None, + ); + + let vibrance_shaders: Vec = cfg + .vibrance_configs + .into_iter() + .map(|vibrance_cfg| { + shaders::vibrance::new( + vibrance_cfg.window_class, + vibrance_cfg.window_title, + vibrance_cfg.strength, + ) + }) + .collect(); + + ConfigData { + night_light_shader: Some(night_light_shader), + vibrance_shaders, + } +} + +#[derive(PartialEq)] +struct ConfigData { + night_light_shader: Option, + vibrance_shaders: Vec, +} diff --git a/src/shaders/night_light.rs b/src/shaders/night_light.rs index 77ed6b0..2e8b94e 100644 --- a/src/shaders/night_light.rs +++ b/src/shaders/night_light.rs @@ -45,7 +45,7 @@ void main() {{ "; const TIME_FMT: &str = "%H:%M"; -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct NightLightShader { enabled: bool, start_time: NaiveTime, diff --git a/src/shaders/shader.rs b/src/shaders/shader.rs index 4d990b0..f231f0f 100644 --- a/src/shaders/shader.rs +++ b/src/shaders/shader.rs @@ -33,13 +33,11 @@ pub fn apply(shader: &dyn Shader) -> Result<(), Box> { } pub fn remove() -> Result<(), Box> { - info!("Removing active shader"); Ok(Keyword::set(SHADER_KEY, NO_SHADER)?) } pub fn get() -> Option { let shader = Keyword::get(SHADER_KEY).unwrap(); - info!("Getting shader {}", shader.value.to_string()); if shader.value.to_string() == NO_SHADER { return None; diff --git a/src/shaders/vibrance.rs b/src/shaders/vibrance.rs index a662d78..d762c59 100644 --- a/src/shaders/vibrance.rs +++ b/src/shaders/vibrance.rs @@ -40,7 +40,7 @@ void main() {{ }} "; -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct VibranceShader { window_class: String, window_title: String, diff --git a/src/utils.rs b/src/utils.rs index 072acf0..4eb33df 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,6 @@ use chrono::{Local, NaiveTime}; -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct Time { mock_time: Option, } From 041a18b26b3955dda7bf0dcc7b7dd68fa2a7af21 Mon Sep 17 00:00:00 2001 From: Amadej Kastelic Date: Sat, 14 Sep 2024 13:42:09 +0200 Subject: [PATCH 3/7] Systemd service --- nix/hm-module.nix | 57 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 504d6e2..9a8e617 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -65,6 +65,21 @@ in { default = pkg; }; + systemd.enable = lib.mkEnableOption "Use as a systemd service"; + + systemd.target = lib.mkOption { + type = lib.types.str; + default = "graphical-session.target"; + example = "hyprland-session.target"; + description = '' + The systemd target that will automatically start the Hyprlux service. + + When setting this value to `"hyprland-session.target"`, + make sure to also enable {option}`wayland.windowManager.hyprland.systemd.enable`, + otherwise the service may never be started. + ''; + }; + night_light = lib.mkOption { type = nightLightSubmodule; description = "Night light settings"; @@ -101,16 +116,38 @@ in { }; }; - config = lib.mkIf cfg.enable { - home.packages = [ - cfg.package - ]; + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + home.packages = [cfg.package]; - xdg.configFile."hypr/hyprlux.toml" = { - source = cfgFormat.generate "hyprlux.toml" { - night_light = cfg.night_light; - vibrance_configs = cfg.vibrance_configs; + xdg.configFile."hypr/hyprlux.toml" = { + source = cfgFormat.generate "hyprlux.toml" { + night_light = cfg.night_light; + vibrance_configs = cfg.vibrance_configs; + }; }; - }; - }; + } + + (lib.mkIf cfg.systemd.enable { + systemd.user.services.hyprlux = { + Unit = { + Description = "Hyprlux shader manager service"; + Documentation = "https://github.com/amadejkastelic/Hyprlux"; + PartOf = ["graphical-session.target"]; + After = ["graphical-session-pre.target"]; + }; + + Service = { + ExecStart = "${cfg.package}/bin/hyprlux"; + ExecReload = "${cfg.coreutils}/bin/kill -SIGUSR2 $MAINPID"; + Restart = "on-failure"; + KillMode = "mixed"; + }; + + Install = { + WantedBy = [cfg.systemd.target]; + }; + }; + }) + ]); } From 6bd2afbe04964b08ebf27ff6ffe58eefbd3e3abc Mon Sep 17 00:00:00 2001 From: Amadej Kastelic Date: Sat, 14 Sep 2024 13:48:58 +0200 Subject: [PATCH 4/7] fix module --- nix/hm-module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 9a8e617..6eb2624 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -139,7 +139,7 @@ in { Service = { ExecStart = "${cfg.package}/bin/hyprlux"; - ExecReload = "${cfg.coreutils}/bin/kill -SIGUSR2 $MAINPID"; + ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR2 $MAINPID"; Restart = "on-failure"; KillMode = "mixed"; }; From 76cd8be63ae9979f8c24e9ae2be6e64a9b2e916c Mon Sep 17 00:00:00 2001 From: Amadej Kastelic Date: Sat, 14 Sep 2024 13:56:43 +0200 Subject: [PATCH 5/7] Readme, bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 13 +++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99eb690..e8bfcc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,7 +339,7 @@ dependencies = [ [[package]] name = "hyprlux" -version = "0.1.1" +version = "0.1.2" dependencies = [ "chrono", "colog", diff --git a/Cargo.toml b/Cargo.toml index e92cb27..2bceb70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hyprlux" -version = "0.1.1" +version = "0.1.2" edition = "2021" [dependencies] diff --git a/README.md b/README.md index 3d99b41..3dd80e0 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,11 @@ And configure it: programs.hyprlux = { enable = true; + systemd = { + enable = true; + target = "hyprland-session.target"; + }; + night_light = { enabled = true; start_time = "22:00"; @@ -51,16 +56,12 @@ And configure it: ]; }; } -``` -Add it to your exec-once to automatically start it with Hyprland: -```nix -exec-once=hyprlux > /tmp/hyprlux.log 2>&1 -``` ## Building Run `cargo build` ## TODO - [ ] Toggle night light based on location and time of day -- [ ] Allow config reload +- [x] Allow config reload - [ ] Allow stop and resume - [ ] Publish to aur and crate +- [x] Add nix module systemd service support From d1abbca71ee900dc06abcb773b11988cd27de583 Mon Sep 17 00:00:00 2001 From: Amadej Kastelic Date: Sat, 14 Sep 2024 13:59:04 +0200 Subject: [PATCH 6/7] Fix workflow --- .github/workflows/rustfmt.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rustfmt.yml b/.github/workflows/rustfmt.yml index 7cd2176..0a44d1b 100644 --- a/.github/workflows/rustfmt.yml +++ b/.github/workflows/rustfmt.yml @@ -1,4 +1,8 @@ -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: name: Rustfmt From 3e1007d37bd3d16ca750ce0a02c42d61aae62f68 Mon Sep 17 00:00:00 2001 From: Amadej Kastelic Date: Sat, 14 Sep 2024 13:59:40 +0200 Subject: [PATCH 7/7] Fix workflow --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d3f1ef5..c7849c3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,10 @@ name: Test -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: env: CARGO_TERM_COLOR: always