Lazy variables (#58)

* Implement lazy variables

* cleanup

* refactor
This commit is contained in:
ElKowar 2020-11-08 15:08:01 +01:00 committed by GitHub
parent 496eb29038
commit 2e8b1af083
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 402 additions and 188 deletions

50
Cargo.lock generated
View file

@ -5,6 +5,9 @@ name = "ahash"
version = "0.3.8" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
dependencies = [
"const-random",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
@ -179,6 +182,26 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" 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]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.4.4" version = "0.4.4"
@ -247,6 +270,17 @@ dependencies = [
"syn 1.0.44", "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]] [[package]]
name = "debug_stub_derive" name = "debug_stub_derive"
version = "0.3.0" version = "0.3.0"
@ -300,6 +334,7 @@ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"crossbeam-channel", "crossbeam-channel",
"dashmap",
"debug_stub_derive", "debug_stub_derive",
"derive_more", "derive_more",
"extend", "extend",
@ -634,6 +669,17 @@ dependencies = [
"wasi", "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]] [[package]]
name = "gio" name = "gio"
version = "0.9.1" version = "0.9.1"
@ -1434,7 +1480,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.1.15",
"libc", "libc",
"rand_chacha", "rand_chacha",
"rand_core", "rand_core",
@ -1458,7 +1504,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.1.15",
] ]
[[package]] [[package]]

View file

@ -43,6 +43,7 @@ nix = "0.19"
smart-default = "0.6" smart-default = "0.6"
filedescriptor = "0.7" filedescriptor = "0.7"
simple-signal = "1.1" simple-signal = "1.1"
dashmap = "3.11"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"

View file

@ -3,7 +3,6 @@ use crate::{
config::{window_definition::WindowName, AnchorPoint, WindowStacking}, config::{window_definition::WindowName, AnchorPoint, WindowStacking},
eww_state, eww_state,
script_var_handler::*, script_var_handler::*,
util,
value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName}, value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName},
widgets, widgets,
}; };
@ -13,8 +12,7 @@ use debug_stub_derive::*;
use gdk::WindowExt; use gdk::WindowExt;
use gtk::{ContainerExt, CssProviderExt, GtkWindowExt, StyleContextExt, WidgetExt}; use gtk::{ContainerExt, CssProviderExt, GtkWindowExt, StyleContextExt, WidgetExt};
use itertools::Itertools; use itertools::Itertools;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
#[derive(Debug)] #[derive(Debug)]
pub enum EwwCommand { pub enum EwwCommand {
@ -42,6 +40,12 @@ pub struct EwwWindow {
pub gtk_window: gtk::Window, pub gtk_window: gtk::Window,
} }
impl EwwWindow {
pub fn close(self) {
self.gtk_window.close();
}
}
#[derive(DebugStub)] #[derive(DebugStub)]
pub struct App { pub struct App {
pub eww_state: eww_state::EwwState, pub eww_state: eww_state::EwwState,
@ -71,8 +75,8 @@ impl App {
} }
EwwCommand::KillServer => { EwwCommand::KillServer => {
log::info!("Received kill command, stopping server!"); log::info!("Received kill command, stopping server!");
self.script_var_handler.stop(); self.script_var_handler.stop_all();
self.windows.values().for_each(|w| w.gtk_window.close()); self.windows.drain().for_each(|(_, w)| w.close());
script_var_process::on_application_death(); script_var_process::on_application_death();
std::process::exit(0); 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<()> { fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) -> Result<()> {
@ -115,7 +119,26 @@ impl App {
.windows .windows
.remove(window_name) .remove(window_name)
.context(format!("No window with name '{}' is running.", 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::<HashSet<VarName>>();
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); self.eww_state.clear_window_state(window_name);
Ok(()) Ok(())
@ -133,24 +156,79 @@ impl App {
log::info!("Opening window {}", window_name); log::info!("Opening window {}", window_name);
let mut window_def = self // remember which variables are used before opening the window, to then
.eww_config // set up the necessary handlers for the newly used variables.
.get_windows() let currently_used_vars = self.get_currently_used_variables().cloned().collect::<HashSet<_>>();
.get(window_name)
.with_context(|| format!("No window named '{}' defined", window_name))? 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 root_widget = widgets::widget_use_to_gtk_widget(
&self.eww_config.get_widgets(),
&mut self.eww_state,
window_name,
&maplit::hashmap! { "window_name".into() => AttrValue::from_primitive(window_name.to_string()) },
&window_def.widget,
)?;
root_widget.get_style_context().add_class(&window_name.to_string());
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)?;
// 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(); .clone();
let display = gdk::Display::get_default().expect("could not get default display"); // TODO all of the cloning above is highly ugly.... REEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
let screen_number = &window_def for newly_used_var in newly_used_vars {
.screen_number let value = self.eww_config.get_script_var(&newly_used_var);
.unwrap_or(display.get_default_screen().get_primary_monitor()); if let Some(value) = value {
self.script_var_handler.add(value.clone());
}
}
let monitor_geometry = display.get_default_screen().get_monitor_geometry(*screen_number); self.windows.insert(window_name.clone(), eww_window);
window_def.geometry.offset = pos.unwrap_or(window_def.geometry.offset); Ok(())
window_def.geometry.size = size.unwrap_or(window_def.geometry.size); }
window_def.geometry.anchor_point = anchor.unwrap_or(window_def.geometry.anchor_point);
pub fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> {
log::info!("Reloading windows");
// refresh script-var poll stuff
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.close();
self.open_window(&window_name, None, None, None)?;
}
Ok(())
}
pub fn load_css(&mut self, css: &str) -> Result<()> {
self.css_provider.load_from_data(css.as_bytes())?;
Ok(())
}
pub fn get_currently_used_variables(&self) -> impl Iterator<Item = &VarName> {
self.eww_state.referenced_vars()
}
}
fn initialize_window(
monitor_geometry: gdk::Rectangle,
root_widget: gtk::Widget,
mut window_def: config::EwwWindowDefinition,
) -> Result<EwwWindow> {
let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry); let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry);
let window = if window_def.focusable { let window = if window_def.focusable {
@ -159,8 +237,8 @@ impl App {
gtk::Window::new(gtk::WindowType::Popup) gtk::Window::new(gtk::WindowType::Popup)
}; };
window.set_title(&format!("Eww - {}", window_name)); window.set_title(&format!("Eww - {}", window_def.name));
let wm_class_name = format!("eww-{}", window_name); let wm_class_name = format!("eww-{}", window_def.name);
window.set_wmclass(&wm_class_name, &wm_class_name); window.set_wmclass(&wm_class_name, &wm_class_name);
if !window_def.focusable { if !window_def.focusable {
window.set_type_hint(gdk::WindowTypeHint::Dock); window.set_type_hint(gdk::WindowTypeHint::Dock);
@ -175,15 +253,7 @@ impl App {
on_screen_changed(&window, None); on_screen_changed(&window, None);
window.connect_screen_changed(on_screen_changed); window.connect_screen_changed(on_screen_changed);
let root_widget = &widgets::widget_use_to_gtk_widget( window.add(&root_widget);
&self.eww_config.get_widgets(),
&mut self.eww_state,
window_name,
&maplit::hashmap! { "window_name".into() => AttrValue::from_primitive(window_name.to_string()) },
&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, // 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. // as it is sized according to how much space it's contents require.
@ -209,40 +279,11 @@ impl App {
window.set_keep_below(true); window.set_keep_below(true);
} }
let eww_window = EwwWindow { Ok(EwwWindow {
name: window_def.name.clone(),
definition: window_def, definition: window_def,
gtk_window: window, gtk_window: window,
name: window_name.clone(), })
};
self.windows.insert(window_name.clone(), eww_window);
Ok(())
}
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.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();
self.open_window(&window_name, None, None, None)?;
}
Ok(())
}
pub fn load_css(&mut self, css: &str) -> Result<()> {
self.css_provider.load_from_data(css.as_bytes())?;
Ok(())
}
} }
fn on_screen_changed(window: &gtk::Window, _old_screen: Option<&gdk::Screen>) { fn on_screen_changed(window: &gtk::Window, _old_screen: Option<&gdk::Screen>) {
@ -254,3 +295,19 @@ fn on_screen_changed(window: &gtk::Window, _old_screen: Option<&gdk::Screen>) {
}); });
window.set_visual(visual.as_ref()); 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)
}

View file

@ -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<()> { pub fn forward_command_to_server(mut stream: UnixStream, action: opts::ActionWithServer) -> Result<()> {
log::info!("Forwarding options to server"); 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(); let mut buf = String::new();
stream.set_read_timeout(Some(std::time::Duration::from_millis(100)))?; stream.set_read_timeout(Some(std::time::Duration::from_millis(100)))?;

View file

@ -35,6 +35,11 @@ impl WidgetDefinition {
} }
} }
} }
/// returns all the variables that are referenced in this widget
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
self.structure.referenced_vars()
}
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -107,6 +112,11 @@ impl WidgetUse {
.get(key) .get(key)
.context(format!("attribute '{}' missing from widgetuse of '{}'", key, &self.name)) .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<Item = &VarName> {
self.attrs.iter().flat_map(|(_, value)| value.var_refs())
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -17,6 +17,8 @@ pub struct EwwConfig {
widgets: HashMap<String, WidgetDefinition>, widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<WindowName, EwwWindowDefinition>, windows: HashMap<WindowName, EwwWindowDefinition>,
initial_variables: HashMap<VarName, PrimitiveValue>, initial_variables: HashMap<VarName, PrimitiveValue>,
// TODO make this a hashmap
script_vars: Vec<ScriptVar>, script_vars: Vec<ScriptVar>,
} }
@ -44,10 +46,8 @@ impl EwwConfig {
.child("windows")? .child("windows")?
.child_elements() .child_elements()
.map(|child| { .map(|child| {
Ok(( let def = EwwWindowDefinition::from_xml_element(child)?;
WindowName::from(child.attr("name")?.to_owned()), Ok((def.name.to_owned(), def))
EwwWindowDefinition::from_xml_element(child)?,
))
}) })
.collect::<Result<HashMap<_, _>>>() .collect::<Result<HashMap<_, _>>>()
.context("error parsing window definitions")?; .context("error parsing window definitions")?;
@ -104,6 +104,12 @@ impl EwwConfig {
&self.windows &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<VarName, PrimitiveValue> { pub fn get_default_vars(&self) -> &HashMap<VarName, PrimitiveValue> {
&self.initial_variables &self.initial_variables
} }
@ -111,4 +117,8 @@ impl EwwConfig {
pub fn get_script_vars(&self) -> &Vec<ScriptVar> { pub fn get_script_vars(&self) -> &Vec<ScriptVar> {
&self.script_vars &self.script_vars
} }
pub fn get_script_var(&self, name: &VarName) -> Option<&ScriptVar> {
self.script_vars.iter().find(|x| x.name() == name)
}
} }

View file

@ -8,6 +8,7 @@ use super::*;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct EwwWindowDefinition { pub struct EwwWindowDefinition {
pub name: WindowName,
pub geometry: EwwWindowGeometry, pub geometry: EwwWindowGeometry,
pub stacking: WindowStacking, pub stacking: WindowStacking,
pub screen_number: Option<i32>, pub screen_number: Option<i32>,
@ -26,6 +27,7 @@ impl EwwWindowDefinition {
let struts = xml.child("struts").ok().map(Struts::from_xml_element).transpose()?; let struts = xml.child("struts").ok().map(Struts::from_xml_element).transpose()?;
Ok(EwwWindowDefinition { Ok(EwwWindowDefinition {
name: WindowName(xml.attr("name")?.to_owned()),
geometry: match xml.child("geometry") { geometry: match xml.child("geometry") {
Ok(node) => EwwWindowGeometry::from_xml_element(node)?, Ok(node) => EwwWindowGeometry::from_xml_element(node)?,
Err(_) => EwwWindowGeometry::default(), Err(_) => EwwWindowGeometry::default(),
@ -37,6 +39,11 @@ impl EwwWindowDefinition {
struts: struts.unwrap_or_default(), struts: struts.unwrap_or_default(),
}) })
} }
/// returns all the variables that are referenced in this window
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
self.widget.referenced_vars()
}
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]

View file

@ -131,6 +131,14 @@ impl EwwWindowGeometry {
}, },
}) })
} }
pub fn override_if_given(&mut self, anchor_point: Option<AnchorPoint>, offset: Option<Coords>, size: Option<Coords>) -> 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 { impl std::fmt::Display for EwwWindowGeometry {

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
config::window_definition::WindowName, config::window_definition::WindowName,
util,
value::{AttrName, AttrValueElement, VarName}, value::{AttrName, AttrValueElement, VarName},
}; };
use anyhow::*; use anyhow::*;
@ -33,7 +32,7 @@ impl StateChangeHandler {
match resolved_attrs { match resolved_attrs {
Ok(resolved_attrs) => { Ok(resolved_attrs) => {
let result: Result<_> = (self.func)(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) => { Err(err) => {
eprintln!("Error while resolving attributes: {:?}", err); eprintln!("Error while resolving attributes: {:?}", err);
@ -170,4 +169,15 @@ impl EwwState {
window_state.put_handler(handler); window_state.put_handler(handler);
} }
} }
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
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()
}
} }

View file

@ -56,7 +56,7 @@ fn main() {
opts::Action::WithServer(action) => { opts::Action::WithServer(action) => {
log::info!("Trying to find server process"); log::info!("Trying to find server process");
if let Ok(stream) = net::UnixStream::connect(&*IPC_SOCKET_PATH) { 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 { } else {
if action.needs_server_running() { if action.needs_server_running() {
println!("No eww server running"); println!("No eww server running");

View file

@ -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 anyhow::*;
use app::EwwCommand; use app::EwwCommand;
use dashmap::DashMap;
use glib; use glib;
use itertools::Itertools;
use scheduled_executor; use scheduled_executor;
use std::io::BufRead; use std::io::BufRead;
@ -12,102 +19,130 @@ use self::script_var_process::ScriptVarProcess;
/// Handler that manages running and updating [ScriptVar]s /// Handler that manages running and updating [ScriptVar]s
pub struct ScriptVarHandler { pub struct ScriptVarHandler {
evt_send: glib::Sender<EwwCommand>, tail_handler: TailVarHandler,
pub poll_handles: Vec<scheduled_executor::executor::TaskHandle>, poll_handler: PollVarHandler,
pub poll_executor: scheduled_executor::CoreExecutor,
pub tail_handler_thread: Option<stoppable_thread::StoppableHandle<()>>,
} }
impl ScriptVarHandler { impl ScriptVarHandler {
pub fn new(evt_send: glib::Sender<EwwCommand>) -> Result<Self> { pub fn new(evt_send: glib::Sender<EwwCommand>) -> Result<Self> {
log::info!("initializing handler for poll script vars");
Ok(ScriptVarHandler { Ok(ScriptVarHandler {
evt_send, tail_handler: TailVarHandler::new(evt_send.clone())?,
poll_handles: Vec::new(), poll_handler: PollVarHandler::new(evt_send)?,
poll_executor: scheduled_executor::CoreExecutor::new()?,
tail_handler_thread: None,
}) })
} }
/// stop all running handlers pub fn add(&mut self, script_var: config::ScriptVar) {
pub fn stop(&mut self) { match script_var {
self.poll_handles.iter().for_each(|handle| handle.stop()); config::ScriptVar::Poll(var) => self.poll_handler.start(&var),
self.poll_handles.clear(); config::ScriptVar::Tail(var) => self.tail_handler.start(&var),
self.tail_handler_thread.take().map(|handle| handle.stop()); };
} }
/// initialize this handler, cleaning up any previously ran executors and /// Stop the handler that is responsible for a given variable.
/// threads. pub fn stop_for_variable(&mut self, name: &VarName) -> Result<()> {
pub fn initialize_clean(&mut self, script_vars: Vec<config::ScriptVar>) -> Result<()> { log::debug!("Stopping script var process for variable {}", name);
self.stop(); self.tail_handler.stop_for_variable(name)?;
self.poll_handler.stop_for_variable(name)?;
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");
Ok(()) Ok(())
} }
/// initialize the poll handler thread. /// stop all running scripts and schedules
fn setup_poll_tasks(&mut self, poll_script_vars: &[config::PollScriptVar]) -> Result<()> { pub fn stop_all(&mut self) {
log::info!("initializing handler for poll script vars"); log::debug!("Stopping script-var-handlers");
self.poll_handles.iter().for_each(|handle| handle.stop()); self.tail_handler.stop_all();
self.poll_handles.clear(); self.poll_handler.stop_all();
}
}
impl Drop for ScriptVarHandler {
fn drop(&mut self) {
self.stop_all();
}
}
struct PollVarHandler {
evt_send: glib::Sender<EwwCommand>,
poll_handles: HashMap<VarName, scheduled_executor::executor::TaskHandle>,
poll_executor: scheduled_executor::CoreExecutor,
}
impl PollVarHandler {
fn new(evt_send: glib::Sender<EwwCommand>) -> Result<Self> {
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(); let evt_send = self.evt_send.clone();
self.poll_handles = poll_script_vars let handle = self.poll_executor.schedule_fixed_interval(
.iter()
.map(|var| {
self.poll_executor.schedule_fixed_interval(
Duration::from_secs(0), Duration::from_secs(0),
var.interval, var.interval,
glib::clone!(@strong var, @strong evt_send => move |_| { glib::clone!(@strong var => move |_| {
let result: Result<_> = try { let result: Result<_> = try {
evt_send.send(app::EwwCommand::UpdateVars(vec![(var.name.clone(), var.run_once()?)]))?; evt_send.send(app::EwwCommand::UpdateVars(vec![(var.name.clone(), var.run_once()?)]))?;
}; };
util::print_result_err("while running script-var command", &result); crate::print_result_err!("while running script-var command", &result);
}), }),
) );
}) self.poll_handles.insert(var.name.clone(), handle);
.collect_vec(); }
log::info!("finished setting up poll tasks");
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(()) Ok(())
} }
/// initialize the tail_var handler thread pub fn stop_all(&mut self) {
pub fn setup_tail_tasks(&mut self, tail_script_vars: &[config::TailScriptVar]) -> Result<()> { self.poll_handles.drain().for_each(|(_, handle)| handle.stop());
}
}
struct TailVarHandler {
evt_send: glib::Sender<EwwCommand>,
tail_handler_thread: Option<stoppable_thread::StoppableHandle<()>>,
tail_process_handles: Arc<DashMap<VarName, script_var_process::ScriptVarProcess>>,
tail_sources: Arc<RwLock<popol::Sources<VarName>>>,
}
impl TailVarHandler {
fn new(evt_send: glib::Sender<EwwCommand>) -> Result<Self> {
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"); 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(); let mut events = popol::Events::<VarName>::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 evt_send = self.evt_send.clone(); 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| { let thread_handle = stoppable_thread::spawn(move |stopped| {
while !stopped.get() { while !stopped.get() {
let result: Result<_> = try { 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() { for (var_name, event) in events.iter() {
if event.readable { if event.readable {
let handle = script_var_processes let mut handle = script_var_processes
.get_mut(var_name) .get_mut(var_name)
.with_context(|| format!("No command output handle found for variable '{}'", var_name))?; .with_context(|| format!("No command output handle found for variable '{}'", var_name))?;
let mut buffer = String::new(); let mut buffer = String::new();
@ -118,24 +153,45 @@ impl ScriptVarHandler {
)]))?; )]))?;
} else if event.hangup { } else if event.hangup {
script_var_processes.remove(var_name); 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() { for process in script_var_processes.iter() {
util::print_result_err("While killing tail-var process at the end of tail task", &process.kill()); 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); self.tail_handler_thread = Some(thread_handle);
Ok(()) Ok(())
} }
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),
}
} }
impl Drop for ScriptVarHandler { fn stop_for_variable(&mut self, name: &VarName) -> Result<()> {
fn drop(&mut self) { if let Some((_, process)) = self.tail_process_handles.remove(name) {
self.stop(); 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 std::{ffi::CString, io::BufReader, sync::Mutex};
use crate::util;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref SCRIPT_VAR_CHILDREN: Mutex<Vec<u32>> = Mutex::new(Vec::new()); static ref SCRIPT_VAR_CHILDREN: Mutex<Vec<u32>> = Mutex::new(Vec::new());
} }
@ -163,18 +217,19 @@ pub mod script_var_process {
pub fn on_application_death() { pub fn on_application_death() {
SCRIPT_VAR_CHILDREN.lock().unwrap().drain(..).for_each(|pid| { SCRIPT_VAR_CHILDREN.lock().unwrap().drain(..).for_each(|pid| {
let result = terminate_pid(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 { pub struct ScriptVarProcess {
pid: i32, pub pid: i32,
pub stdout_reader: BufReader<filedescriptor::FileDescriptor>, pub stdout_reader: BufReader<filedescriptor::FileDescriptor>,
} }
impl ScriptVarProcess { impl ScriptVarProcess {
pub(super) fn run(command: &str) -> Result<Self> { pub(super) fn run(command: &str) -> Result<Self> {
use nix::unistd::*; use nix::unistd::*;
use std::os::unix::io::AsRawFd;
let pipe = filedescriptor::Pipe::new()?; let pipe = filedescriptor::Pipe::new()?;
@ -182,6 +237,8 @@ pub mod script_var_process {
ForkResult::Parent { child, .. } => { ForkResult::Parent { child, .. } => {
SCRIPT_VAR_CHILDREN.lock().unwrap().push(child.as_raw() as u32); SCRIPT_VAR_CHILDREN.lock().unwrap().push(child.as_raw() as u32);
close(pipe.write.as_raw_fd())?;
Ok(ScriptVarProcess { Ok(ScriptVarProcess {
stdout_reader: BufReader::new(pipe.read), stdout_reader: BufReader::new(pipe.read),
pid: child.as_raw(), pid: child.as_raw(),
@ -199,6 +256,9 @@ pub mod script_var_process {
loop {} loop {}
} }
ForkResult::Child => { 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( execv(
CString::new("/bin/sh").unwrap().as_ref(), CString::new("/bin/sh").unwrap().as_ref(),
&[ &[

View file

@ -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); let (evt_send, evt_recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
log::info!("Initializing script var handler"); log::info!("Initializing script var handler");
let mut script_var_handler = script_var_handler::ScriptVarHandler::new(evt_send.clone())?; let script_var_handler = script_var_handler::ScriptVarHandler::new(evt_send.clone())?;
script_var_handler.initialize_clean(eww_config.get_script_vars().clone())?;
let mut app = app::App { let mut app = app::App {
eww_state: EwwState::from_default_vars(eww_config.generate_initial_state()?.clone()), eww_state: EwwState::from_default_vars(eww_config.generate_initial_state()?.clone()),
@ -89,14 +88,15 @@ fn run_server_thread(evt_send: glib::Sender<app::EwwCommand>) -> Result<()> {
for stream in listener.incoming() { for stream in listener.incoming() {
try_logging_errors!("handling message from IPC client" => { try_logging_errors!("handling message from IPC client" => {
let mut stream = stream?; 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); log::info!("received command from IPC: {:?}", &action);
let (command, maybe_response_recv) = action.into_eww_command(); let (command, maybe_response_recv) = action.into_eww_command();
evt_send.send(command)?; evt_send.send(command)?;
if let Some(response_recv) = maybe_response_recv { if let Some(response_recv) = maybe_response_recv {
if let Ok(response) = response_recv.recv_timeout(std::time::Duration::from_millis(100)) { if let Ok(response) = response_recv.recv_timeout(std::time::Duration::from_millis(100)) {
let result = &stream.write_all(response.as_bytes()); 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<P: AsRef<Path>>(
evt_send.send(app::EwwCommand::ReloadCss(eww_css))?; 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) Ok(hotwatch)
} }

View file

@ -26,7 +26,16 @@ macro_rules! try_logging_errors {
($context:literal => $code:block) => {{ ($context:literal => $code:block) => {{
let result: Result<_> = try { $code }; let result: Result<_> = try { $code };
if let Err(err) = result { 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() .into_owned()
} }
/// If the given result is `Err`, prints out the error value using `{:?}`
pub fn print_result_err<T, E: std::fmt::Debug>(context: &str, result: &std::result::Result<T, E>) {
if let Err(err) = result {
eprintln!("Error {}: {:?}", context, err);
}
}