parent
496eb29038
commit
2e8b1af083
13 changed files with 402 additions and 188 deletions
50
Cargo.lock
generated
50
Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -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"
|
||||
|
|
217
src/app.rs
217
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::<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,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::<HashSet<_>>();
|
||||
|
||||
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<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 {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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)))?;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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(
|
||||
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<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(())
|
||||
}
|
||||
}
|
||||
|
||||
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<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(),
|
||||
&[
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
18
src/util.rs
18
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<T, E: std::fmt::Debug>(context: &str, result: &std::result::Result<T, E>) {
|
||||
if let Err(err) = result {
|
||||
eprintln!("Error {}: {:?}", context, err);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue