From 5b3344fc5bb18a648e65c42a04e513140d66a6e5 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Tue, 2 Mar 2021 12:23:18 +0100 Subject: [PATCH] make multiple daemons possible and make commands time out --- Cargo.lock | 17 ++++++++++++++++ Cargo.toml | 2 ++ src/config/script_var.rs | 1 + src/ipc_server.rs | 5 +++-- src/main.rs | 34 +++++++++++++++++++++++-------- src/opts.rs | 23 ++++++++++++++------- src/server.rs | 4 +++- src/widgets/mod.rs | 31 ++++++++++++++++++++-------- src/widgets/widget_definitions.rs | 4 ++-- 9 files changed, 91 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76b6f6a..b4b8390 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "beef" version = "0.4.4" @@ -279,6 +285,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-stream", + "base64", "bincode", "debug_stub_derive", "derive_more", @@ -314,6 +321,7 @@ dependencies = [ "tokio-stream", "tokio-util", "unescape", + "wait-timeout", "x11rb", ] @@ -1723,6 +1731,15 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 0df621b..4330dbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,8 @@ tokio-util = "0.6" nom = "6.1" dyn-clone = "1.0" +base64 = "0.13" +wait-timeout = "0.2" [target.'cfg(target_os="linux")'.dependencies] inotify = "0.9" diff --git a/src/config/script_var.rs b/src/config/script_var.rs index b50f8b3..dc2ac6b 100644 --- a/src/config/script_var.rs +++ b/src/config/script_var.rs @@ -64,6 +64,7 @@ impl ScriptVar { /// Run a command and get the output fn run_command(cmd: &str) -> Result { + log::debug!("Running command: {}", cmd); let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?; let output = output.trim_matches('\n'); Ok(PrimitiveValue::from(output)) diff --git a/src/ipc_server.rs b/src/ipc_server.rs index 85dd209..493d10a 100644 --- a/src/ipc_server.rs +++ b/src/ipc_server.rs @@ -6,8 +6,9 @@ use tokio::{ sync::mpsc::*, }; -pub async fn run_server(evt_send: UnboundedSender) -> Result<()> { - let listener = tokio::net::UnixListener::bind(&*crate::IPC_SOCKET_PATH)?; +pub async fn run_server>(evt_send: UnboundedSender, socket_path: P) -> Result<()> { + let socket_path = socket_path.as_ref(); + let listener = { tokio::net::UnixListener::bind(socket_path)? }; log::info!("IPC server initialized"); crate::loop_select_exiting! { connection = listener.accept() => match connection { diff --git a/src/main.rs b/src/main.rs index 922a8e8..ad06baf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ pub mod value; pub mod widgets; lazy_static::lazy_static! { - pub static ref IPC_SOCKET_PATH: std::path::PathBuf = std::env::var("XDG_RUNTIME_DIR") + static ref IPC_SOCKET_PATH: std::path::PathBuf = std::env::var("XDG_RUNTIME_DIR") .map(std::path::PathBuf::from) .unwrap_or_else(|_| std::path::PathBuf::from("/tmp")) .join("eww-server"); @@ -47,6 +47,21 @@ lazy_static::lazy_static! { .join("eww.log"); } +pub struct Paths {} + +pub fn calculate_socket_path>(config_file_path: P) -> std::path::PathBuf { + let daemon_id = base64::encode(format!("{}", config_file_path.as_ref().display())); + let socket_filename = format!( + "{}_{}", + &*crate::IPC_SOCKET_PATH + .file_name() + .and_then(|x| x.to_str()) + .expect("Invalid socket path"), + daemon_id, + ); + crate::IPC_SOCKET_PATH.with_file_name(socket_filename).to_path_buf() +} + fn main() { let opts: opts::Opt = opts::Opt::from_env(); @@ -61,15 +76,16 @@ fn main() { .init(); let result: Result<_> = try { + let socket_path = calculate_socket_path(opts.config_path.clone().unwrap_or(CONFIG_DIR.join("eww.xml"))); match opts.action { opts::Action::ClientOnly(action) => { client::handle_client_only_action(action)?; } opts::Action::WithServer(action) => { - log::info!("Trying to find server process"); - match net::UnixStream::connect(&*IPC_SOCKET_PATH) { + log::info!("Trying to find server process at socket {}", socket_path.display()); + match net::UnixStream::connect(&socket_path) { Ok(stream) => { - log::info!("Connected to Eww server."); + log::info!("Connected to Eww server ({}).", &socket_path.display()); let response = client::do_server_call(stream, action).context("Error while forwarding command to server")?; if let Some(response) = response { @@ -87,17 +103,17 @@ fn main() { } } - opts::Action::Daemon { config } => { + opts::Action::Daemon => { // make sure that there isn't already a Eww daemon running. - if check_server_running(&*IPC_SOCKET_PATH) { + if check_server_running(&socket_path) { eprintln!("Eww server already running."); std::process::exit(1); } else { - log::info!("Initializing Eww server."); - let _ = std::fs::remove_file(&*crate::IPC_SOCKET_PATH); + log::info!("Initializing Eww server. ({})", socket_path.display()); + let _ = std::fs::remove_file(socket_path); println!("Run `eww logs` to see any errors, warnings or information while editing your configuration."); - server::initialize_server(config)?; + server::initialize_server(opts.config_path)?; } } } diff --git a/src/opts.rs b/src/opts.rs index e4004ef..6aee70a 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -12,6 +12,7 @@ use crate::{ #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Opt { pub log_debug: bool, + pub config_path: Option, pub action: Action, } @@ -21,6 +22,10 @@ struct RawOpt { #[structopt(long = "debug", global = true)] log_debug: bool, + /// override config-file path (path to eww.xml) + #[structopt(short, long, global = true)] + config: Option, + #[structopt(subcommand)] action: Action, } @@ -29,11 +34,7 @@ struct RawOpt { pub enum Action { /// Start the Eww daemon. #[structopt(name = "daemon", alias = "d")] - Daemon { - /// Custom Config Path - #[structopt(short, long)] - config: Option, - }, + Daemon, #[structopt(flatten)] ClientOnly(ActionClientOnly), @@ -128,8 +129,16 @@ impl Opt { impl From for Opt { fn from(other: RawOpt) -> Self { - let RawOpt { action, log_debug } = other; - Opt { action, log_debug } + let RawOpt { + action, + log_debug, + config, + } = other; + Opt { + action, + log_debug, + config_path: config, + } } } diff --git a/src/server.rs b/src/server.rs index e840570..d0ff05f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -89,12 +89,14 @@ fn init_async_part(config_file_path: PathBuf, scss_file_path: PathBuf, ui_send: rt.block_on(async { let filewatch_join_handle = { let ui_send = ui_send.clone(); + let config_file_path = config_file_path.clone(); tokio::spawn(async move { run_filewatch(config_file_path, scss_file_path, ui_send).await }) }; let ipc_server_join_handle = { let ui_send = ui_send.clone(); - tokio::spawn(async move { ipc_server::run_server(ui_send).await }) + let socket_path = crate::calculate_socket_path(config_file_path); + tokio::spawn(async move { ipc_server::run_server(ui_send, socket_path).await }) }; let forward_exit_to_app_handle = { diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 3b6ff19..64659d3 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,4 +1,4 @@ -use crate::{config::window_definition::WindowName, eww_state::*, print_result_err, value::AttrName}; +use crate::{config::window_definition::WindowName, eww_state::*, value::AttrName}; use anyhow::*; use gtk::prelude::*; use itertools::Itertools; @@ -13,14 +13,27 @@ const CMD_STRING_PLACEHODLER: &str = "{}"; /// Run a command that was provided as an attribute. This command may use a /// placeholder ('{}') which will be replaced by the value provided as [`arg`] -pub(self) fn run_command(cmd: &str, arg: T) { - let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg)); - let command_result = Command::new("/bin/sh") - .arg("-c") - .arg(&cmd) - .spawn() - .and_then(|mut child| child.wait()); - print_result_err!(format!("executing command {}", &cmd), command_result); +pub(self) fn run_command(cmd: &str, arg: T) { + use wait_timeout::ChildExt; + let cmd = cmd.to_string(); + std::thread::spawn(move || { + let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg)); + log::debug!("Running command from widget: {}", cmd); + let child = Command::new("/bin/sh").arg("-c").arg(&cmd).spawn(); + match child { + Ok(mut child) => match child.wait_timeout(std::time::Duration::from_millis(200)) { + // child timed out + Ok(None) => { + eprintln!("WARNING: command {} timed out", &cmd); + let _ = child.kill(); + let _ = child.wait(); + } + Err(err) => eprintln!("Failed to execute command {}: {}", cmd, err), + Ok(Some(_)) => {} + }, + Err(err) => eprintln!("Failed to launch child process: {}", err), + } + }); } struct BuilderArgs<'a, 'b, 'c, 'd> { diff --git a/src/widgets/widget_definitions.rs b/src/widgets/widget_definitions.rs index d92dc34..b7f97d6 100644 --- a/src/widgets/widget_definitions.rs +++ b/src/widgets/widget_definitions.rs @@ -258,8 +258,8 @@ fn build_gtk_color_chooser(bargs: &mut BuilderArgs) -> Result