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"
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]]

View file

@ -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"

View file

@ -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::<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);
Ok(())
@ -133,24 +156,79 @@ 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))?
// 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::<HashSet<_>>();
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();
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());
// 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 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);
window_def.geometry.size = size.unwrap_or(window_def.geometry.size);
window_def.geometry.anchor_point = anchor.unwrap_or(window_def.geometry.anchor_point);
Ok(())
}
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 window = if window_def.focusable {
@ -159,8 +237,8 @@ impl App {
gtk::Window::new(gtk::WindowType::Popup)
};
window.set_title(&format!("Eww - {}", window_name));
let wm_class_name = format!("eww-{}", window_name);
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);
@ -175,15 +253,7 @@ impl App {
on_screen_changed(&window, None);
window.connect_screen_changed(on_screen_changed);
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());
window.add(root_widget);
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.
@ -209,40 +279,11 @@ impl App {
window.set_keep_below(true);
}
let eww_window = EwwWindow {
Ok(EwwWindow {
name: window_def.name.clone(),
definition: window_def,
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>) {
@ -254,3 +295,19 @@ fn on_screen_changed(window: &gtk::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)
}

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<()> {
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)))?;

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)]
@ -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<Item = &VarName> {
self.attrs.iter().flat_map(|(_, value)| value.var_refs())
}
}
#[cfg(test)]

View file

@ -17,6 +17,8 @@ pub struct EwwConfig {
widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<WindowName, EwwWindowDefinition>,
initial_variables: HashMap<VarName, PrimitiveValue>,
// TODO make this a hashmap
script_vars: Vec<ScriptVar>,
}
@ -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::<Result<HashMap<_, _>>>()
.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<VarName, PrimitiveValue> {
&self.initial_variables
}
@ -111,4 +117,8 @@ impl EwwConfig {
pub fn get_script_vars(&self) -> &Vec<ScriptVar> {
&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)]
pub struct EwwWindowDefinition {
pub name: WindowName,
pub geometry: EwwWindowGeometry,
pub stacking: WindowStacking,
pub screen_number: Option<i32>,
@ -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<Item = &VarName> {
self.widget.referenced_vars()
}
}
#[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 {

View file

@ -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<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) => {
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");

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 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<EwwCommand>,
pub poll_handles: Vec<scheduled_executor::executor::TaskHandle>,
pub poll_executor: scheduled_executor::CoreExecutor,
pub tail_handler_thread: Option<stoppable_thread::StoppableHandle<()>>,
tail_handler: TailVarHandler,
poll_handler: PollVarHandler,
}
impl ScriptVarHandler {
pub fn new(evt_send: glib::Sender<EwwCommand>) -> Result<Self> {
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<config::ScriptVar>) -> 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<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();
self.poll_handles = poll_script_vars
.iter()
.map(|var| {
self.poll_executor.schedule_fixed_interval(
let handle = self.poll_executor.schedule_fixed_interval(
Duration::from_secs(0),
var.interval,
glib::clone!(@strong var, @strong evt_send => move |_| {
glib::clone!(@strong var => 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);
crate::print_result_err!("while running script-var command", &result);
}),
)
})
.collect_vec();
log::info!("finished setting up poll tasks");
);
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<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");
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::<VarName>::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(())
}
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 drop(&mut self) {
self.stop();
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<Vec<u32>> = 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<filedescriptor::FileDescriptor>,
}
impl ScriptVarProcess {
pub(super) fn run(command: &str) -> Result<Self> {
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(),
&[

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);
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<app::EwwCommand>) -> 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<P: AsRef<Path>>(
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)
}

View file

@ -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<T, E: std::fmt::Debug>(context: &str, result: &std::result::Result<T, E>) {
if let Err(err) = result {
eprintln!("Error {}: {:?}", context, err);
}
}