diff --git a/Cargo.lock b/Cargo.lock index d56b392..b73b1c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,9 @@ name = "ahash" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" +dependencies = [ + "const-random", +] [[package]] name = "aho-corasick" @@ -179,6 +182,26 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" +[[package]] +name = "const-random" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02dc82c12dc2ee6e1ded861cf7d582b46f66f796d1b6c93fa28b911ead95da02" +dependencies = [ + "const-random-macro", + "proc-macro-hack", +] + +[[package]] +name = "const-random-macro" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc757bbb9544aa296c2ae00c679e81f886b37e28e59097defe0cf524306f6685" +dependencies = [ + "getrandom 0.2.0", + "proc-macro-hack", +] + [[package]] name = "crossbeam-channel" version = "0.4.4" @@ -247,6 +270,17 @@ dependencies = [ "syn 1.0.44", ] +[[package]] +name = "dashmap" +version = "3.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f260e2fc850179ef410018660006951c1b55b79e8087e87111a2c388994b9b5" +dependencies = [ + "ahash", + "cfg-if", + "num_cpus", +] + [[package]] name = "debug_stub_derive" version = "0.3.0" @@ -300,6 +334,7 @@ dependencies = [ "anyhow", "bincode", "crossbeam-channel", + "dashmap", "debug_stub_derive", "derive_more", "extend", @@ -634,6 +669,17 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gio" version = "0.9.1" @@ -1434,7 +1480,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.15", "libc", "rand_chacha", "rand_core", @@ -1458,7 +1504,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.15", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 242782c..ea778f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ nix = "0.19" smart-default = "0.6" filedescriptor = "0.7" simple-signal = "1.1" +dashmap = "3.11" [dev-dependencies] pretty_assertions = "0.6.1" diff --git a/src/app.rs b/src/app.rs index 24007e4..d3eeedb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,7 +3,6 @@ use crate::{ config::{window_definition::WindowName, AnchorPoint, WindowStacking}, eww_state, script_var_handler::*, - util, value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName}, widgets, }; @@ -13,8 +12,7 @@ use debug_stub_derive::*; use gdk::WindowExt; use gtk::{ContainerExt, CssProviderExt, GtkWindowExt, StyleContextExt, WidgetExt}; use itertools::Itertools; - -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; #[derive(Debug)] pub enum EwwCommand { @@ -42,6 +40,12 @@ pub struct EwwWindow { pub gtk_window: gtk::Window, } +impl EwwWindow { + pub fn close(self) { + self.gtk_window.close(); + } +} + #[derive(DebugStub)] pub struct App { pub eww_state: eww_state::EwwState, @@ -71,8 +75,8 @@ impl App { } EwwCommand::KillServer => { log::info!("Received kill command, stopping server!"); - self.script_var_handler.stop(); - self.windows.values().for_each(|w| w.gtk_window.close()); + self.script_var_handler.stop_all(); + self.windows.drain().for_each(|(_, w)| w.close()); script_var_process::on_application_death(); std::process::exit(0); } @@ -103,7 +107,7 @@ impl App { } }; - util::print_result_err("while handling event", &result); + crate::print_result_err!("while handling event", &result); } fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) -> Result<()> { @@ -115,7 +119,26 @@ impl App { .windows .remove(window_name) .context(format!("No window with name '{}' is running.", window_name))?; - window.gtk_window.close(); + + // Stop script-var handlers for variables that where only referenced by this window + // TODO somehow make this whole process less shit. + let currently_used_vars = self.get_currently_used_variables().cloned().collect::>(); + + for unused_var in self + .eww_state + .vars_referenced_in(window_name) + .into_iter() + .filter(|var| !currently_used_vars.contains(*var)) + { + println!("stopping for {}", &unused_var); + let result = self.script_var_handler.stop_for_variable(unused_var); + crate::print_result_err!( + "While stopping script-var processes while cleaning up after the last window referencing them closed", + &result + ); + } + + window.close(); self.eww_state.clear_window_state(window_name); Ok(()) @@ -133,49 +156,14 @@ impl App { log::info!("Opening window {}", window_name); - let mut window_def = self - .eww_config - .get_windows() - .get(window_name) - .with_context(|| format!("No window named '{}' defined", window_name))? - .clone(); + // remember which variables are used before opening the window, to then + // set up the necessary handlers for the newly used variables. + let currently_used_vars = self.get_currently_used_variables().cloned().collect::>(); - let display = gdk::Display::get_default().expect("could not get default display"); - let screen_number = &window_def - .screen_number - .unwrap_or(display.get_default_screen().get_primary_monitor()); + let mut window_def = self.eww_config.get_window(window_name)?.clone(); + window_def.geometry = window_def.geometry.override_if_given(anchor, pos, size); - let monitor_geometry = display.get_default_screen().get_monitor_geometry(*screen_number); - - window_def.geometry.offset = pos.unwrap_or(window_def.geometry.offset); - window_def.geometry.size = size.unwrap_or(window_def.geometry.size); - window_def.geometry.anchor_point = anchor.unwrap_or(window_def.geometry.anchor_point); - - let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry); - - let window = if window_def.focusable { - gtk::Window::new(gtk::WindowType::Toplevel) - } else { - gtk::Window::new(gtk::WindowType::Popup) - }; - - window.set_title(&format!("Eww - {}", window_name)); - let wm_class_name = format!("eww-{}", window_name); - window.set_wmclass(&wm_class_name, &wm_class_name); - if !window_def.focusable { - window.set_type_hint(gdk::WindowTypeHint::Dock); - } - window.set_position(gtk::WindowPosition::Center); - window.set_default_size(actual_window_rect.width, actual_window_rect.height); - window.set_size_request(actual_window_rect.width, actual_window_rect.height); - window.set_decorated(false); - window.set_resizable(false); - - // run on_screen_changed to set the visual correctly initially. - on_screen_changed(&window, None); - window.connect_screen_changed(on_screen_changed); - - let root_widget = &widgets::widget_use_to_gtk_widget( + let root_widget = widgets::widget_use_to_gtk_widget( &self.eww_config.get_widgets(), &mut self.eww_state, window_name, @@ -183,38 +171,28 @@ impl App { &window_def.widget, )?; root_widget.get_style_context().add_class(&window_name.to_string()); - window.add(root_widget); - // Handle the fact that the gtk window will have a different size than specified, - // as it is sized according to how much space it's contents require. - // This is necessary to handle different anchors correctly in case the size was wrong. - let (gtk_window_width, gtk_window_height) = window.get_size(); - window_def.geometry.size = Coords { - x: NumWithUnit::Pixels(gtk_window_width), - y: NumWithUnit::Pixels(gtk_window_height), - }; - let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry); + let monitor_geometry = get_monitor_geometry(window_def.screen_number.unwrap_or_else(get_default_monitor_index)); + let eww_window = initialize_window(monitor_geometry, root_widget, window_def)?; - window.show_all(); + // initialize script var handlers for variables that where not used before opening this window. + // TODO somehow make this less shit + let newly_used_vars = self + .eww_state + .vars_referenced_in(window_name) + .into_iter() + .filter(|x| !currently_used_vars.contains(*x)) + .collect_vec() + .clone(); - let gdk_window = window.get_window().context("couldn't get gdk window from gtk window")?; - gdk_window.set_override_redirect(!window_def.focusable); - gdk_window.move_(actual_window_rect.x, actual_window_rect.y); - - if window_def.stacking == WindowStacking::Foreground { - gdk_window.raise(); - window.set_keep_above(true); - } else { - gdk_window.lower(); - window.set_keep_below(true); + // TODO all of the cloning above is highly ugly.... REEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + for newly_used_var in newly_used_vars { + let value = self.eww_config.get_script_var(&newly_used_var); + if let Some(value) = value { + self.script_var_handler.add(value.clone()); + } } - let eww_window = EwwWindow { - definition: window_def, - gtk_window: window, - name: window_name.clone(), - }; - self.windows.insert(window_name.clone(), eww_window); Ok(()) @@ -223,17 +201,14 @@ impl App { pub fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> { log::info!("Reloading windows"); // refresh script-var poll stuff - util::print_result_err( - "while setting up script-var commands", - &self.script_var_handler.initialize_clean(config.get_script_vars().clone()), - ); + self.script_var_handler.stop_all(); self.eww_config = config; self.eww_state.clear_all_window_states(); let windows = self.windows.clone(); for (window_name, window) in windows { - window.gtk_window.close(); + window.close(); self.open_window(&window_name, None, None, None)?; } Ok(()) @@ -243,6 +218,72 @@ impl App { self.css_provider.load_from_data(css.as_bytes())?; Ok(()) } + + pub fn get_currently_used_variables(&self) -> impl Iterator { + self.eww_state.referenced_vars() + } +} + +fn initialize_window( + monitor_geometry: gdk::Rectangle, + root_widget: gtk::Widget, + mut window_def: config::EwwWindowDefinition, +) -> Result { + let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry); + + let window = if window_def.focusable { + gtk::Window::new(gtk::WindowType::Toplevel) + } else { + gtk::Window::new(gtk::WindowType::Popup) + }; + + window.set_title(&format!("Eww - {}", window_def.name)); + let wm_class_name = format!("eww-{}", window_def.name); + window.set_wmclass(&wm_class_name, &wm_class_name); + if !window_def.focusable { + window.set_type_hint(gdk::WindowTypeHint::Dock); + } + window.set_position(gtk::WindowPosition::Center); + window.set_default_size(actual_window_rect.width, actual_window_rect.height); + window.set_size_request(actual_window_rect.width, actual_window_rect.height); + window.set_decorated(false); + window.set_resizable(false); + + // run on_screen_changed to set the visual correctly initially. + on_screen_changed(&window, None); + window.connect_screen_changed(on_screen_changed); + + window.add(&root_widget); + + // Handle the fact that the gtk window will have a different size than specified, + // as it is sized according to how much space it's contents require. + // This is necessary to handle different anchors correctly in case the size was wrong. + let (gtk_window_width, gtk_window_height) = window.get_size(); + window_def.geometry.size = Coords { + x: NumWithUnit::Pixels(gtk_window_width), + y: NumWithUnit::Pixels(gtk_window_height), + }; + let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry); + + window.show_all(); + + let gdk_window = window.get_window().context("couldn't get gdk window from gtk window")?; + gdk_window.set_override_redirect(!window_def.focusable); + gdk_window.move_(actual_window_rect.x, actual_window_rect.y); + + if window_def.stacking == WindowStacking::Foreground { + gdk_window.raise(); + window.set_keep_above(true); + } else { + gdk_window.lower(); + window.set_keep_below(true); + } + + Ok(EwwWindow { + name: window_def.name.clone(), + definition: window_def, + gtk_window: window, + }) } fn on_screen_changed(window: >k::Window, _old_screen: Option<&gdk::Screen>) { @@ -254,3 +295,19 @@ fn on_screen_changed(window: >k::Window, _old_screen: Option<&gdk::Screen>) { }); window.set_visual(visual.as_ref()); } + +/// get the index of the default monitor +fn get_default_monitor_index() -> i32 { + gdk::Display::get_default() + .expect("could not get default display") + .get_default_screen() + .get_primary_monitor() +} + +/// Get the monitor geometry of a given monitor number +fn get_monitor_geometry(n: i32) -> gdk::Rectangle { + gdk::Display::get_default() + .expect("could not get default display") + .get_default_screen() + .get_monitor_geometry(n) +} diff --git a/src/client.rs b/src/client.rs index 4cf9c28..06f8fe9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -22,7 +22,10 @@ pub fn handle_client_only_action(action: ActionClientOnly) -> Result<()> { pub fn forward_command_to_server(mut stream: UnixStream, action: opts::ActionWithServer) -> Result<()> { log::info!("Forwarding options to server"); - stream.write_all(&bincode::serialize(&action)?)?; + stream.set_nonblocking(false)?; + stream + .write_all(&bincode::serialize(&action)?) + .context("Failed to write command to IPC stream")?; let mut buf = String::new(); stream.set_read_timeout(Some(std::time::Duration::from_millis(100)))?; diff --git a/src/config/element.rs b/src/config/element.rs index db06204..21dd5e4 100644 --- a/src/config/element.rs +++ b/src/config/element.rs @@ -35,6 +35,11 @@ impl WidgetDefinition { } } } + + /// returns all the variables that are referenced in this widget + pub fn referenced_vars(&self) -> impl Iterator { + self.structure.referenced_vars() + } } #[derive(Debug, Clone, Default)] @@ -107,6 +112,11 @@ impl WidgetUse { .get(key) .context(format!("attribute '{}' missing from widgetuse of '{}'", key, &self.name)) } + + /// returns all the variables that are referenced in this widget + pub fn referenced_vars(&self) -> impl Iterator { + self.attrs.iter().flat_map(|(_, value)| value.var_refs()) + } } #[cfg(test)] diff --git a/src/config/eww_config.rs b/src/config/eww_config.rs index 4bf6756..7f170b4 100644 --- a/src/config/eww_config.rs +++ b/src/config/eww_config.rs @@ -17,6 +17,8 @@ pub struct EwwConfig { widgets: HashMap, windows: HashMap, initial_variables: HashMap, + + // TODO make this a hashmap script_vars: Vec, } @@ -44,10 +46,8 @@ impl EwwConfig { .child("windows")? .child_elements() .map(|child| { - Ok(( - WindowName::from(child.attr("name")?.to_owned()), - EwwWindowDefinition::from_xml_element(child)?, - )) + let def = EwwWindowDefinition::from_xml_element(child)?; + Ok((def.name.to_owned(), def)) }) .collect::>>() .context("error parsing window definitions")?; @@ -104,6 +104,12 @@ impl EwwConfig { &self.windows } + pub fn get_window(&self, name: &WindowName) -> Result<&EwwWindowDefinition> { + self.windows + .get(name) + .with_context(|| format!("No window named '{}' exists", name)) + } + pub fn get_default_vars(&self) -> &HashMap { &self.initial_variables } @@ -111,4 +117,8 @@ impl EwwConfig { pub fn get_script_vars(&self) -> &Vec { &self.script_vars } + + pub fn get_script_var(&self, name: &VarName) -> Option<&ScriptVar> { + self.script_vars.iter().find(|x| x.name() == name) + } } diff --git a/src/config/window_definition.rs b/src/config/window_definition.rs index d692b66..81f5731 100644 --- a/src/config/window_definition.rs +++ b/src/config/window_definition.rs @@ -8,6 +8,7 @@ use super::*; #[derive(Debug, Clone, PartialEq)] pub struct EwwWindowDefinition { + pub name: WindowName, pub geometry: EwwWindowGeometry, pub stacking: WindowStacking, pub screen_number: Option, @@ -26,6 +27,7 @@ impl EwwWindowDefinition { let struts = xml.child("struts").ok().map(Struts::from_xml_element).transpose()?; Ok(EwwWindowDefinition { + name: WindowName(xml.attr("name")?.to_owned()), geometry: match xml.child("geometry") { Ok(node) => EwwWindowGeometry::from_xml_element(node)?, Err(_) => EwwWindowGeometry::default(), @@ -37,6 +39,11 @@ impl EwwWindowDefinition { struts: struts.unwrap_or_default(), }) } + + /// returns all the variables that are referenced in this window + pub fn referenced_vars(&self) -> impl Iterator { + self.widget.referenced_vars() + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] diff --git a/src/config/window_geometry.rs b/src/config/window_geometry.rs index a19989e..baad2c1 100644 --- a/src/config/window_geometry.rs +++ b/src/config/window_geometry.rs @@ -131,6 +131,14 @@ impl EwwWindowGeometry { }, }) } + + pub fn override_if_given(&mut self, anchor_point: Option, offset: Option, size: Option) -> Self { + EwwWindowGeometry { + anchor_point: anchor_point.unwrap_or(self.anchor_point), + offset: offset.unwrap_or(self.offset), + size: size.unwrap_or(self.size), + } + } } impl std::fmt::Display for EwwWindowGeometry { diff --git a/src/eww_state.rs b/src/eww_state.rs index b9aa901..dbfc76b 100644 --- a/src/eww_state.rs +++ b/src/eww_state.rs @@ -1,6 +1,5 @@ use crate::{ config::window_definition::WindowName, - util, value::{AttrName, AttrValueElement, VarName}, }; use anyhow::*; @@ -33,7 +32,7 @@ impl StateChangeHandler { match resolved_attrs { Ok(resolved_attrs) => { let result: Result<_> = (self.func)(resolved_attrs); - util::print_result_err("while updating UI based after state change", &result); + crate::print_result_err!("while updating UI based after state change", &result); } Err(err) => { eprintln!("Error while resolving attributes: {:?}", err); @@ -170,4 +169,15 @@ impl EwwState { window_state.put_handler(handler); } } + + pub fn referenced_vars(&self) -> impl Iterator { + self.windows.values().flat_map(|w| w.state_change_handlers.keys()) + } + + pub fn vars_referenced_in(&self, window_name: &WindowName) -> std::collections::HashSet<&VarName> { + self.windows + .get(window_name) + .map(|window| window.state_change_handlers.keys().collect()) + .unwrap_or_default() + } } diff --git a/src/main.rs b/src/main.rs index 7c87308..fd1dc14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,7 @@ fn main() { opts::Action::WithServer(action) => { log::info!("Trying to find server process"); if let Ok(stream) = net::UnixStream::connect(&*IPC_SOCKET_PATH) { - client::forward_command_to_server(stream, action)?; + client::forward_command_to_server(stream, action).context("Error while forwarding command to server")?; } else { if action.needs_server_running() { println!("No eww server running"); diff --git a/src/script_var_handler.rs b/src/script_var_handler.rs index b445a69..509fc74 100644 --- a/src/script_var_handler.rs +++ b/src/script_var_handler.rs @@ -1,10 +1,17 @@ -use std::{collections::HashMap, time::Duration}; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, + time::Duration, +}; -use crate::{app, config, util, value::PrimitiveValue}; +use crate::{ + app, config, + value::{PrimitiveValue, VarName}, +}; use anyhow::*; use app::EwwCommand; +use dashmap::DashMap; use glib; -use itertools::Itertools; use scheduled_executor; use std::io::BufRead; @@ -12,102 +19,130 @@ use self::script_var_process::ScriptVarProcess; /// Handler that manages running and updating [ScriptVar]s pub struct ScriptVarHandler { - evt_send: glib::Sender, - pub poll_handles: Vec, - pub poll_executor: scheduled_executor::CoreExecutor, - pub tail_handler_thread: Option>, + tail_handler: TailVarHandler, + poll_handler: PollVarHandler, } impl ScriptVarHandler { pub fn new(evt_send: glib::Sender) -> Result { - log::info!("initializing handler for poll script vars"); Ok(ScriptVarHandler { - evt_send, - poll_handles: Vec::new(), - poll_executor: scheduled_executor::CoreExecutor::new()?, - tail_handler_thread: None, + tail_handler: TailVarHandler::new(evt_send.clone())?, + poll_handler: PollVarHandler::new(evt_send)?, }) } - /// stop all running handlers - pub fn stop(&mut self) { - self.poll_handles.iter().for_each(|handle| handle.stop()); - self.poll_handles.clear(); - self.tail_handler_thread.take().map(|handle| handle.stop()); + pub fn add(&mut self, script_var: config::ScriptVar) { + match script_var { + config::ScriptVar::Poll(var) => self.poll_handler.start(&var), + config::ScriptVar::Tail(var) => self.tail_handler.start(&var), + }; } - /// initialize this handler, cleaning up any previously ran executors and - /// threads. - pub fn initialize_clean(&mut self, script_vars: Vec) -> Result<()> { - self.stop(); - - let mut poll_script_vars = Vec::new(); - let mut tail_script_vars = Vec::new(); - for var in script_vars { - match var { - config::ScriptVar::Poll(x) => poll_script_vars.push(x), - config::ScriptVar::Tail(x) => tail_script_vars.push(x), - } - } - self.setup_poll_tasks(&poll_script_vars)?; - self.setup_tail_tasks(&tail_script_vars)?; - log::info!("Finished initializing script-var-handler"); + /// Stop the handler that is responsible for a given variable. + pub fn stop_for_variable(&mut self, name: &VarName) -> Result<()> { + log::debug!("Stopping script var process for variable {}", name); + self.tail_handler.stop_for_variable(name)?; + self.poll_handler.stop_for_variable(name)?; Ok(()) } - /// initialize the poll handler thread. - fn setup_poll_tasks(&mut self, poll_script_vars: &[config::PollScriptVar]) -> Result<()> { - log::info!("initializing handler for poll script vars"); - self.poll_handles.iter().for_each(|handle| handle.stop()); - self.poll_handles.clear(); + /// stop all running scripts and schedules + pub fn stop_all(&mut self) { + log::debug!("Stopping script-var-handlers"); + self.tail_handler.stop_all(); + self.poll_handler.stop_all(); + } +} +impl Drop for ScriptVarHandler { + fn drop(&mut self) { + self.stop_all(); + } +} + +struct PollVarHandler { + evt_send: glib::Sender, + poll_handles: HashMap, + poll_executor: scheduled_executor::CoreExecutor, +} + +impl PollVarHandler { + fn new(evt_send: glib::Sender) -> Result { + Ok(PollVarHandler { + evt_send, + poll_handles: HashMap::new(), + poll_executor: scheduled_executor::CoreExecutor::new()?, + }) + } + + fn start(&mut self, var: &config::PollScriptVar) { let evt_send = self.evt_send.clone(); - self.poll_handles = poll_script_vars - .iter() - .map(|var| { - self.poll_executor.schedule_fixed_interval( - Duration::from_secs(0), - var.interval, - glib::clone!(@strong var, @strong evt_send => move |_| { - let result: Result<_> = try { - evt_send.send(app::EwwCommand::UpdateVars(vec![(var.name.clone(), var.run_once()?)]))?; - }; - util::print_result_err("while running script-var command", &result); - }), - ) - }) - .collect_vec(); - log::info!("finished setting up poll tasks"); + let handle = self.poll_executor.schedule_fixed_interval( + Duration::from_secs(0), + var.interval, + glib::clone!(@strong var => move |_| { + let result: Result<_> = try { + evt_send.send(app::EwwCommand::UpdateVars(vec![(var.name.clone(), var.run_once()?)]))?; + }; + crate::print_result_err!("while running script-var command", &result); + }), + ); + self.poll_handles.insert(var.name.clone(), handle); + } + + pub fn stop_for_variable(&mut self, name: &VarName) -> Result<()> { + if let Some(handle) = self.poll_handles.remove(name) { + log::debug!("stopped poll var {}", name); + handle.stop(); + } Ok(()) } - /// initialize the tail_var handler thread - pub fn setup_tail_tasks(&mut self, tail_script_vars: &[config::TailScriptVar]) -> Result<()> { + pub fn stop_all(&mut self) { + self.poll_handles.drain().for_each(|(_, handle)| handle.stop()); + } +} + +struct TailVarHandler { + evt_send: glib::Sender, + tail_handler_thread: Option>, + tail_process_handles: Arc>, + tail_sources: Arc>>, +} + +impl TailVarHandler { + fn new(evt_send: glib::Sender) -> Result { + let mut handler = TailVarHandler { + evt_send, + tail_handler_thread: None, + tail_process_handles: Arc::new(DashMap::new()), + tail_sources: Arc::new(RwLock::new(popol::Sources::new())), + }; + handler.setup_tail_tasks()?; + Ok(handler) + } + + fn setup_tail_tasks(&mut self) -> Result<()> { log::info!("initializing handler for tail script vars"); - let mut sources = popol::Sources::with_capacity(tail_script_vars.len()); - let mut script_var_processes: HashMap<_, ScriptVarProcess> = HashMap::new(); - - for var in tail_script_vars { - match ScriptVarProcess::run(&var.command) { - Ok(process) => { - sources.register(var.name.clone(), process.stdout_reader.get_ref(), popol::interest::READ); - script_var_processes.insert(var.name.clone(), process); - } - Err(err) => eprintln!("Failed to launch script-var command for tail: {:?}", err), - } - } - - let mut events = popol::Events::with_capacity(tail_script_vars.len()); + let mut events = popol::Events::::new(); let evt_send = self.evt_send.clone(); - // TODO this is rather ugly + + // TODO all of this is rather ugly + let script_var_processes = self.tail_process_handles.clone(); + let sources = self.tail_sources.clone(); let thread_handle = stoppable_thread::spawn(move |stopped| { while !stopped.get() { let result: Result<_> = try { - sources.wait(&mut events)?; + { + let _ = sources + .write() + .unwrap() + .wait_timeout(&mut events, std::time::Duration::from_millis(50)); + } for (var_name, event) in events.iter() { if event.readable { - let handle = script_var_processes + let mut handle = script_var_processes .get_mut(var_name) .with_context(|| format!("No command output handle found for variable '{}'", var_name))?; let mut buffer = String::new(); @@ -118,24 +153,45 @@ impl ScriptVarHandler { )]))?; } else if event.hangup { script_var_processes.remove(var_name); - sources.unregister(var_name); + sources.write().unwrap().unregister(var_name); } } }; - util::print_result_err("in script-var tail handler thread", &result); + crate::print_result_err!("in script-var tail handler thread", &result); } - for process in script_var_processes.values() { - util::print_result_err("While killing tail-var process at the end of tail task", &process.kill()); + for process in script_var_processes.iter() { + crate::print_result_err!("While killing tail-var process at the end of tail task", &process.kill()); } + script_var_processes.clear(); }); self.tail_handler_thread = Some(thread_handle); Ok(()) } -} -impl Drop for ScriptVarHandler { - fn drop(&mut self) { - self.stop(); + fn start(&mut self, var: &config::TailScriptVar) { + match ScriptVarProcess::run(&var.command) { + Ok(process) => { + self.tail_sources.write().unwrap().register( + var.name.clone(), + process.stdout_reader.get_ref(), + popol::interest::READ, + ); + self.tail_process_handles.insert(var.name.clone(), process); + } + Err(err) => eprintln!("Failed to launch script-var command for tail: {:?}", err), + } + } + + fn stop_for_variable(&mut self, name: &VarName) -> Result<()> { + if let Some((_, process)) = self.tail_process_handles.remove(name) { + log::debug!("stopped tail var {}", name); + process.kill()?; + } + Ok(()) + } + + fn stop_all(&mut self) { + self.tail_handler_thread.take().map(|handle| handle.stop()); } } @@ -147,8 +203,6 @@ pub mod script_var_process { }; use std::{ffi::CString, io::BufReader, sync::Mutex}; - use crate::util; - lazy_static::lazy_static! { static ref SCRIPT_VAR_CHILDREN: Mutex> = Mutex::new(Vec::new()); } @@ -163,18 +217,19 @@ pub mod script_var_process { pub fn on_application_death() { SCRIPT_VAR_CHILDREN.lock().unwrap().drain(..).for_each(|pid| { let result = terminate_pid(pid); - util::print_result_err("While killing process '{}' during cleanup", &result); + crate::print_result_err!("While killing process '{}' during cleanup", &result); }); } pub struct ScriptVarProcess { - pid: i32, + pub pid: i32, pub stdout_reader: BufReader, } impl ScriptVarProcess { pub(super) fn run(command: &str) -> Result { use nix::unistd::*; + use std::os::unix::io::AsRawFd; let pipe = filedescriptor::Pipe::new()?; @@ -182,6 +237,8 @@ pub mod script_var_process { ForkResult::Parent { child, .. } => { SCRIPT_VAR_CHILDREN.lock().unwrap().push(child.as_raw() as u32); + close(pipe.write.as_raw_fd())?; + Ok(ScriptVarProcess { stdout_reader: BufReader::new(pipe.read), pid: child.as_raw(), @@ -199,6 +256,9 @@ pub mod script_var_process { loop {} } ForkResult::Child => { + close(pipe.read.as_raw_fd()).unwrap(); + dup2(pipe.write.as_raw_fd(), std::io::stdout().as_raw_fd()).unwrap(); + dup2(pipe.write.as_raw_fd(), std::io::stderr().as_raw_fd()).unwrap(); execv( CString::new("/bin/sh").unwrap().as_ref(), &[ diff --git a/src/server.rs b/src/server.rs index 0559889..4984e8c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -36,8 +36,7 @@ pub fn initialize_server(should_detach: bool, action: opts::ActionWithServer) -> let (evt_send, evt_recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); log::info!("Initializing script var handler"); - let mut script_var_handler = script_var_handler::ScriptVarHandler::new(evt_send.clone())?; - script_var_handler.initialize_clean(eww_config.get_script_vars().clone())?; + let script_var_handler = script_var_handler::ScriptVarHandler::new(evt_send.clone())?; let mut app = app::App { eww_state: EwwState::from_default_vars(eww_config.generate_initial_state()?.clone()), @@ -89,14 +88,15 @@ fn run_server_thread(evt_send: glib::Sender) -> Result<()> { for stream in listener.incoming() { try_logging_errors!("handling message from IPC client" => { let mut stream = stream?; - let action: opts::ActionWithServer = bincode::deserialize_from(&stream)?; + let action: opts::ActionWithServer = bincode::deserialize_from(&stream) + .context("Failed to read or deserialize message from client")?; log::info!("received command from IPC: {:?}", &action); let (command, maybe_response_recv) = action.into_eww_command(); evt_send.send(command)?; if let Some(response_recv) = maybe_response_recv { if let Ok(response) = response_recv.recv_timeout(std::time::Duration::from_millis(100)) { let result = &stream.write_all(response.as_bytes()); - util::print_result_err("Sending text response to ipc client", &result); + crate::print_result_err!("Sending text response to ipc client", &result); } } }); @@ -134,7 +134,7 @@ fn run_filewatch_thread>( evt_send.send(app::EwwCommand::ReloadCss(eww_css))?; }) }); - util::print_result_err("while loading CSS file for hot-reloading", &result); + crate::print_result_err!("while loading CSS file for hot-reloading", &result); Ok(hotwatch) } diff --git a/src/util.rs b/src/util.rs index 0903e07..a6afd1f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -26,7 +26,16 @@ macro_rules! try_logging_errors { ($context:literal => $code:block) => {{ let result: Result<_> = try { $code }; if let Err(err) = result { - eprintln!("Error while {}: {:?}", $context, err); + eprintln!("[{}:{}] Error while {}: {:?}", ::std::file!(), ::std::line!(), $context, err); + } + }}; +} + +#[macro_export] +macro_rules! print_result_err { + ($context:expr, $result:expr $(,)?) => {{ + if let Err(err) = $result { + eprintln!("[{}:{}] Error {}: {:?}", ::std::file!(), ::std::line!(), $context, err); } }}; } @@ -81,10 +90,3 @@ pub fn replace_env_var_references(input: String) -> String { }) .into_owned() } - -/// If the given result is `Err`, prints out the error value using `{:?}` -pub fn print_result_err(context: &str, result: &std::result::Result) { - if let Err(err) = result { - eprintln!("Error {}: {:?}", context, err); - } -}