From ec4506d9e406bbc930ec2d4f1c93e115217bacd6 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Thu, 29 Jul 2021 13:03:37 +0200 Subject: [PATCH] Make daemon start on server-commands when it's not running --- crates/eww/src/client.rs | 2 +- crates/eww/src/error_handling_ctx.rs | 5 +- crates/eww/src/main.rs | 112 +++++++++++++++++++-------- crates/eww/src/opts.rs | 9 ++- crates/eww/src/server.rs | 38 ++++++--- 5 files changed, 119 insertions(+), 47 deletions(-) diff --git a/crates/eww/src/client.rs b/crates/eww/src/client.rs index 3001357..1297196 100644 --- a/crates/eww/src/client.rs +++ b/crates/eww/src/client.rs @@ -24,7 +24,7 @@ pub fn handle_client_only_action(paths: &EwwPaths, action: ActionClientOnly) -> Ok(()) } -pub fn do_server_call(mut stream: UnixStream, action: opts::ActionWithServer) -> Result> { +pub fn do_server_call(stream: &mut UnixStream, action: &opts::ActionWithServer) -> Result> { log::info!("Forwarding options to server"); stream.set_nonblocking(false).context("Failed to set stream to non-blocking")?; diff --git a/crates/eww/src/error_handling_ctx.rs b/crates/eww/src/error_handling_ctx.rs index 86f8ad4..e126dc8 100644 --- a/crates/eww/src/error_handling_ctx.rs +++ b/crates/eww/src/error_handling_ctx.rs @@ -1,6 +1,9 @@ use std::sync::{Arc, Mutex}; -use codespan_reporting::{diagnostic::Diagnostic, term, term::Chars}; +use codespan_reporting::{ + diagnostic::Diagnostic, + term::{self, Chars}, +}; use eww_shared_util::Span; use simplexpr::eval::EvalError; use yuck::{config::file_provider::YuckFiles, error::AstError, format_diagnostic::ToDiagnostic, gen_diagnostic}; diff --git a/crates/eww/src/main.rs b/crates/eww/src/main.rs index 2b35cab..956dbdd 100644 --- a/crates/eww/src/main.rs +++ b/crates/eww/src/main.rs @@ -13,17 +13,21 @@ extern crate gtk; extern crate gtk_layer_shell as gtk_layer_shell; use anyhow::*; +use opts::ActionWithServer; use std::{ os::unix::net, path::{Path, PathBuf}, + time::Duration, }; +use crate::server::ForkResult; pub mod app; pub mod application_lifecycle; pub mod client; pub mod config; pub mod display_backend; +pub mod error; mod error_handling_ctx; pub mod eww_state; pub mod geometry; @@ -33,9 +37,9 @@ pub mod script_var_handler; pub mod server; pub mod util; pub mod widgets; -pub mod error; fn main() { + let eww_binary_name = std::env::args().next().unwrap(); let opts: opts::Opt = opts::Opt::from_env(); let log_level_filter = if opts.log_debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }; @@ -52,45 +56,55 @@ fn main() { .unwrap_or_else(EwwPaths::default) .context("Failed to initialize eww paths")?; - match opts.action { + let would_show_logs = match opts.action { opts::Action::ClientOnly(action) => { client::handle_client_only_action(&paths, action)?; + false + } + opts::Action::WithServer(ActionWithServer::KillServer) => { + handle_server_command(&paths, &ActionWithServer::KillServer, 1)?; + false } opts::Action::WithServer(action) => { - log::info!("Trying to find server process at socket {}", paths.get_ipc_socket_file().display()); - match net::UnixStream::connect(&paths.get_ipc_socket_file()) { - Ok(stream) => { - log::info!("Connected to Eww server ({}).", &paths.get_ipc_socket_file().display()); - let response = - client::do_server_call(stream, action).context("Error while forwarding command to server")?; - if let Some(response) = response { - println!("{}", response); - if response.is_failure() { - std::process::exit(1); - } - } - } - Err(_) => { - eprintln!("Failed to connect to the eww daemon."); - eprintln!("Make sure to start the eww daemon process by running `eww daemon` first."); - std::process::exit(1); - } - } - } - - opts::Action::Daemon => { - // make sure that there isn't already a Eww daemon running. - if check_server_running(paths.get_ipc_socket_file()) { - eprintln!("Eww server already running."); - std::process::exit(1); - } else { - log::info!("Initializing Eww server. ({})", paths.get_ipc_socket_file().display()); + if let Err(err) = handle_server_command(&paths, &action, 5) { + // connecting to the daemon failed. Thus, start the daemon here! + log::warn!("Failed to connect to daemon: {}", err); + log::info!("Initializing eww server. ({})", paths.get_ipc_socket_file().display()); let _ = std::fs::remove_file(paths.get_ipc_socket_file()); + if !opts.show_logs { + println!("Run `{} logs` to see any errors while editing your configuration.", eww_binary_name); + } - println!("Run `eww logs` to see any errors, warnings or information while editing your configuration."); - server::initialize_server(paths)?; + let (command, response_recv) = action.into_daemon_command(); + let fork_result = server::initialize_server(paths.clone(), Some(command))?; + let is_parent = fork_result == ForkResult::Parent; + if let (Some(recv), true) = (response_recv, is_parent) { + listen_for_daemon_response(recv); + } + is_parent + } else { + true } } + + // make sure that there isn't already a Eww daemon running. + opts::Action::Daemon if check_server_running(paths.get_ipc_socket_file()) => { + eprintln!("Eww server already running."); + true + } + opts::Action::Daemon => { + log::info!("Initializing Eww server. ({})", paths.get_ipc_socket_file().display()); + let _ = std::fs::remove_file(paths.get_ipc_socket_file()); + + if !opts.show_logs { + println!("Run `{} logs` to see any errors while editing your configuration.", eww_binary_name); + } + let fork_result = server::initialize_server(paths.clone(), None)?; + fork_result == ForkResult::Parent + } + }; + if would_show_logs && opts.show_logs { + client::handle_client_only_action(&paths, opts::ActionClientOnly::Logs)?; } }; @@ -100,11 +114,43 @@ fn main() { } } +fn listen_for_daemon_response(mut recv: app::DaemonResponseReceiver) { + let rt = tokio::runtime::Builder::new_current_thread().enable_time().build().expect("Failed to initialize tokio runtime"); + rt.block_on(async { + if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), recv.recv()).await { + println!("{}", response); + } + }) +} + +fn handle_server_command(paths: &EwwPaths, action: &ActionWithServer, connect_attempts: usize) -> Result<()> { + log::info!("Trying to find server process at socket {}", paths.get_ipc_socket_file().display()); + let mut stream = attempt_connect(&paths.get_ipc_socket_file(), connect_attempts).context("Failed to connect to daemon")?; + log::info!("Connected to Eww server ({}).", &paths.get_ipc_socket_file().display()); + let response = client::do_server_call(&mut stream, action).context("Error while forwarding command to server")?; + if let Some(response) = response { + println!("{}", response); + } + Ok(()) +} + +fn attempt_connect(socket_path: impl AsRef, attempts: usize) -> Option { + for _ in 0..attempts { + if let Ok(mut con) = net::UnixStream::connect(&socket_path) { + if client::do_server_call(&mut con, &opts::ActionWithServer::Ping).is_ok() { + return net::UnixStream::connect(&socket_path).ok(); + } + } + std::thread::sleep(Duration::from_millis(200)); + } + None +} + /// Check if a eww server is currently running by trying to send a ping message to it. fn check_server_running(socket_path: impl AsRef) -> bool { let response = net::UnixStream::connect(socket_path) .ok() - .and_then(|stream| client::do_server_call(stream, opts::ActionWithServer::Ping).ok()); + .and_then(|mut stream| client::do_server_call(&mut stream, &opts::ActionWithServer::Ping).ok()); response.is_some() } diff --git a/crates/eww/src/opts.rs b/crates/eww/src/opts.rs index 9e82045..21b7517 100644 --- a/crates/eww/src/opts.rs +++ b/crates/eww/src/opts.rs @@ -11,6 +11,7 @@ use crate::app; #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Opt { pub log_debug: bool, + pub show_logs: bool, pub config_path: Option, pub action: Action, } @@ -25,6 +26,10 @@ struct RawOpt { #[structopt(short, long, global = true)] config: Option, + /// Watch the log output after executing the command + #[structopt(long = "logs", global = true)] + show_logs: bool, + #[structopt(subcommand)] action: Action, } @@ -136,8 +141,8 @@ impl Opt { impl From for Opt { fn from(other: RawOpt) -> Self { - let RawOpt { action, log_debug, config } = other; - Opt { action, log_debug, config_path: config } + let RawOpt { action, log_debug, show_logs, config } = other; + Opt { action, log_debug, show_logs, config_path: config } } } diff --git a/crates/eww/src/server.rs b/crates/eww/src/server.rs index 1457382..784e0a4 100644 --- a/crates/eww/src/server.rs +++ b/crates/eww/src/server.rs @@ -1,4 +1,9 @@ -use crate::{app, config, error_handling_ctx, eww_state::*, ipc_server, script_var_handler, util, EwwPaths}; +use crate::{ + app::{self, DaemonCommand}, + config, error_handling_ctx, + eww_state::*, + ipc_server, script_var_handler, util, EwwPaths, +}; use anyhow::*; use std::{ @@ -9,7 +14,7 @@ use std::{ }; use tokio::sync::mpsc::*; -pub fn initialize_server(paths: EwwPaths) -> Result<()> { +pub fn initialize_server(paths: EwwPaths, action: Option) -> Result { let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel(); std::env::set_current_dir(&paths.get_config_dir()) @@ -31,7 +36,11 @@ pub fn initialize_server(paths: EwwPaths) -> Result<()> { } }; - do_detach(&paths.get_log_file())?; + let fork_result = do_detach(&paths.get_log_file())?; + + if fork_result == ForkResult::Parent { + return Ok(ForkResult::Parent); + } println!( r#" @@ -76,6 +85,9 @@ pub fn initialize_server(paths: EwwPaths) -> Result<()> { init_async_part(app.paths.clone(), ui_send); glib::MainContext::default().spawn_local(async move { + if let Some(action) = action { + app.handle_command(action); + } while let Some(event) = ui_recv.recv().await { app.handle_command(event); } @@ -84,7 +96,7 @@ pub fn initialize_server(paths: EwwPaths) -> Result<()> { gtk::main(); log::info!("main application thread finished"); - Ok(()) + Ok(ForkResult::Child) } fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender) { @@ -160,7 +172,7 @@ async fn run_filewatch>(config_dir: P, evt_send: UnboundedSender< tokio::spawn(async move { match daemon_resp_response.recv().await { Some(app::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"), - Some(app::DaemonResponse::Failure(e)) => log::error!("Failed to reload config: {}", e), + Some(app::DaemonResponse::Failure(e)) => eprintln!("{}", e), None => log::error!("No response to reload configuration-reload request"), } }); @@ -171,14 +183,20 @@ async fn run_filewatch>(config_dir: P, evt_send: UnboundedSender< return Ok(()); } +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ForkResult { + Parent, + Child, +} + /// detach the process from the terminal, also redirecting stdout and stderr to LOG_FILE -fn do_detach(log_file_path: impl AsRef) -> Result<()> { +fn do_detach(log_file_path: impl AsRef) -> Result { // detach from terminal match unsafe { nix::unistd::fork()? } { - nix::unistd::ForkResult::Parent { .. } => { - std::process::exit(0); - } nix::unistd::ForkResult::Child => {} + nix::unistd::ForkResult::Parent { .. } => { + return Ok(ForkResult::Parent); + } } let file = std::fs::OpenOptions::new() @@ -195,5 +213,5 @@ fn do_detach(log_file_path: impl AsRef) -> Result<()> { nix::unistd::dup2(fd, std::io::stderr().as_raw_fd())?; } - Ok(()) + Ok(ForkResult::Child) }