From 3d31788b2f65274af8f31ccf70192b1830c37b2f Mon Sep 17 00:00:00 2001 From: ElKowar <5300871+elkowar@users.noreply.github.com> Date: Sun, 25 Oct 2020 11:33:37 +0100 Subject: [PATCH] Cleanup the file structure of main (#43) --- src/client.rs | 32 +++++ src/main.rs | 365 +++++--------------------------------------------- src/opts.rs | 88 ++++++++++++ src/server.rs | 185 +++++++++++++++++++++++++ src/util.rs | 10 ++ 5 files changed, 347 insertions(+), 333 deletions(-) create mode 100644 src/client.rs create mode 100644 src/opts.rs create mode 100644 src/server.rs diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..4cf9c28 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,32 @@ +use std::process::Stdio; + +use crate::opts::{self, ActionClientOnly}; +use anyhow::*; +use std::{ + io::{Read, Write}, + os::unix::net::UnixStream, +}; + +pub fn handle_client_only_action(action: ActionClientOnly) -> Result<()> { + match action { + ActionClientOnly::Logs => { + std::process::Command::new("tail") + .args(["-f", crate::LOG_FILE.to_string_lossy().as_ref()].iter()) + .stdin(Stdio::null()) + .spawn()? + .wait()?; + } + } + Ok(()) +} + +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)?)?; + + let mut buf = String::new(); + stream.set_read_timeout(Some(std::time::Duration::from_millis(100)))?; + stream.read_to_string(&mut buf)?; + println!("{}", buf); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index e8c5063..7c87308 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,61 +8,36 @@ extern crate gio; extern crate gtk; -use crate::value::{PrimitiveValue, VarName}; use anyhow::*; -use config::window_definition::WindowName; -use eww_state::*; + use log; use pretty_env_logger; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - io::{Read, Write}, - os::unix::{io::AsRawFd, net}, - path::{Path, PathBuf}, - process::Stdio, -}; +use std::{os::unix::net, path::PathBuf}; use structopt::StructOpt; -use value::Coords; pub mod app; +pub mod client; pub mod config; pub mod eww_state; +pub mod opts; pub mod script_var_handler; +pub mod server; pub mod util; pub mod value; pub mod widgets; -#[macro_export] -macro_rules! build { - ($var_name:ident = $value:expr ; $($code:tt)*) => {{ - let mut $var_name = $value; - $($code)* - $var_name - }}; -} - -macro_rules! try_logging_errors { - ($context:literal => $code:block) => {{ - let result: Result<_> = try { $code }; - if let Err(err) = result { - eprintln!("Error while {}: {:?}", $context, err); - } - }}; -} - lazy_static::lazy_static! { - static ref IPC_SOCKET_PATH: std::path::PathBuf = std::env::var("XDG_RUNTIME_DIR") + pub 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"); - static ref CONFIG_DIR: std::path::PathBuf = std::env::var("XDG_CONFIG_HOME") + pub static ref CONFIG_DIR: std::path::PathBuf = std::env::var("XDG_CONFIG_HOME") .map(|v| PathBuf::from(v)) .unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".config")) .join("eww"); - static ref LOG_FILE: std::path::PathBuf = std::env::var("XDG_CACHE_HOME") + pub static ref LOG_FILE: std::path::PathBuf = std::env::var("XDG_CACHE_HOME") .map(|v| PathBuf::from(v)) .unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".cache")) .join("eww.log"); @@ -71,306 +46,30 @@ lazy_static::lazy_static! { fn main() { pretty_env_logger::init(); - if let Err(e) = try_main() { + let result: Result<_> = try { + let opts: opts::Opt = StructOpt::from_args(); + + match opts.action { + opts::Action::ClientOnly(action) => { + client::handle_client_only_action(action)?; + } + 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)?; + } else { + if action.needs_server_running() { + println!("No eww server running"); + } else { + log::info!("No server running, initializing server..."); + server::initialize_server(opts.should_detach, action)?; + } + } + } + } + }; + + if let Err(e) = result { eprintln!("{:?}", e); } } - -#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] -pub struct Opt { - #[structopt(subcommand)] - action: OptAction, - - #[structopt(short = "-d", long = "--detach")] - should_detach: bool, -} - -#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] -pub enum OptAction { - #[structopt(flatten)] - ClientOnly(OptActionClientOnly), - #[structopt(flatten)] - WithServer(OptActionWithServer), -} - -#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] -pub enum OptActionClientOnly { - #[structopt(name = "logs", help = "Print and watch the eww logs")] - Logs, -} - -#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] -pub enum OptActionWithServer { - #[structopt(name = "update", help = "update the value of a variable, in a running eww instance")] - Update { fieldname: VarName, value: PrimitiveValue }, - - #[structopt(name = "open", help = "open a window")] - OpenWindow { - window_name: WindowName, - - #[structopt(short, long, help = "The position of the window, where it should open.")] - pos: Option, - - #[structopt(short, long, help = "The size of the window to open")] - size: Option, - }, - - #[structopt(name = "close", help = "close the window with the given name")] - CloseWindow { window_name: WindowName }, - - #[structopt(name = "kill", help("kill the eww daemon"))] - KillServer, - - #[structopt(name = "state", help = "Print the current eww-state")] - ShowState, - - #[structopt(name = "debug", help = "Print out the widget structure as seen by eww")] - ShowDebug, -} - -impl OptActionWithServer { - fn into_eww_command(self) -> (app::EwwCommand, Option>) { - let command = match self { - OptActionWithServer::Update { fieldname, value } => app::EwwCommand::UpdateVar(fieldname, value), - OptActionWithServer::OpenWindow { window_name, pos, size } => app::EwwCommand::OpenWindow { window_name, pos, size }, - OptActionWithServer::CloseWindow { window_name } => app::EwwCommand::CloseWindow { window_name }, - OptActionWithServer::KillServer => app::EwwCommand::KillServer, - OptActionWithServer::ShowState => { - let (send, recv) = crossbeam_channel::unbounded(); - return (app::EwwCommand::PrintState(send), Some(recv)); - } - OptActionWithServer::ShowDebug => { - let (send, recv) = crossbeam_channel::unbounded(); - return (app::EwwCommand::PrintDebug(send), Some(recv)); - } - }; - (command, None) - } - - /// returns true if this command requires a server to already be running - fn needs_server_running(&self) -> bool { - match self { - OptActionWithServer::OpenWindow { .. } => false, - _ => true, - } - } -} - -fn try_main() -> Result<()> { - let opts: Opt = StructOpt::from_args(); - - match opts.action { - OptAction::ClientOnly(action) => { - handle_client_only_action(action)?; - } - OptAction::WithServer(action) => { - log::info!("Trying to find server process"); - if let Ok(mut stream) = net::UnixStream::connect(&*IPC_SOCKET_PATH) { - log::info!("Forwarding options to server"); - stream.write_all(&bincode::serialize(&action)?)?; - - let mut buf = String::new(); - stream.set_read_timeout(Some(std::time::Duration::from_millis(100)))?; - stream.read_to_string(&mut buf)?; - println!("{}", buf); - } else { - log::info!("No instance found... Initializing server."); - - let _ = std::fs::remove_file(&*IPC_SOCKET_PATH); - - if opts.should_detach { - do_detach()?; - } - - ctrlc::set_handler(|| { - println!("Shutting down eww daemon..."); - script_var_handler::script_var_process::on_application_death(); - std::process::exit(0); - }) - .context("Error setting signal hook")?; - - initialize_server(action)?; - } - } - } - Ok(()) -} - -fn handle_client_only_action(action: OptActionClientOnly) -> Result<()> { - match action { - OptActionClientOnly::Logs => { - std::process::Command::new("tail") - .args(["-f", LOG_FILE.to_string_lossy().as_ref()].iter()) - .stdin(Stdio::null()) - .spawn()? - .wait()?; - } - } - Ok(()) -} - -fn initialize_server(action: OptActionWithServer) -> Result<()> { - if action.needs_server_running() { - println!("No eww server running"); - return Ok(()); - } - - let config_file_path = CONFIG_DIR.join("eww.xml"); - let config_dir = config_file_path - .parent() - .context("config file did not have a parent?!")? - .to_owned() - .to_path_buf(); - let scss_file_path = config_dir.join("eww.scss"); - - log::info!("reading configuration from {:?}", &config_file_path); - let eww_config = config::EwwConfig::read_from_file(&config_file_path)?; - - gtk::init()?; - 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 mut app = app::App { - eww_state: EwwState::from_default_vars(eww_config.generate_initial_state()?.clone()), - eww_config, - windows: HashMap::new(), - css_provider: gtk::CssProvider::new(), - script_var_handler, - app_evt_send: evt_send.clone(), - }; - - if let Some(screen) = gdk::Screen::get_default() { - gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); - } - - if let Ok(eww_css) = util::parse_scss_from_file(&scss_file_path) { - app.load_css(&eww_css)?; - } - - // run the command that eww was started with - log::info!("running command: {:?}", &action); - let (command, maybe_response_recv) = action.into_eww_command(); - app.handle_command(command); - if let Some(response_recv) = maybe_response_recv { - if let Ok(response) = response_recv.recv_timeout(std::time::Duration::from_millis(100)) { - println!("{}", response); - } - } - - run_server_thread(evt_send.clone())?; - let _hotwatch = run_filewatch_thread(&config_file_path, &scss_file_path, evt_send.clone())?; - - evt_recv.attach(None, move |msg| { - app.handle_command(msg); - glib::Continue(true) - }); - - gtk::main(); - - Ok(()) -} - -fn run_server_thread(evt_send: glib::Sender) -> Result<()> { - std::thread::spawn(move || { - let result: Result<_> = try { - log::info!("Starting up eww server"); - let listener = net::UnixListener::bind(&*IPC_SOCKET_PATH)?; - for stream in listener.incoming() { - try_logging_errors!("handling message from IPC client" => { - let mut stream = stream?; - let action: OptActionWithServer = bincode::deserialize_from(&stream)?; - 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); - } - } - }); - } - }; - if let Err(err) = result { - eprintln!("error in server thread: {}", err); - std::process::exit(1); - } - }); - Ok(()) -} - -fn run_filewatch_thread>( - config_file_path: P, - scss_file_path: P, - evt_send: glib::Sender, -) -> Result { - log::info!("Initializing config file watcher"); - let mut hotwatch = hotwatch::Hotwatch::new()?; - - let config_file_change_send = evt_send.clone(); - hotwatch.watch_file_changes(config_file_path, move |path| { - try_logging_errors!("handling change of config file" => { - log::info!("Reloading eww configuration"); - let new_eww_config = config::EwwConfig::read_from_file(path)?; - config_file_change_send.send(app::EwwCommand::ReloadConfig(new_eww_config))?; - }); - })?; - - let result = hotwatch.watch_file_changes(scss_file_path, move |path| { - try_logging_errors!("handling change of scss file" => { - log::info!("reloading eww css file"); - let eww_css = util::parse_scss_from_file(path)?; - evt_send.send(app::EwwCommand::ReloadCss(eww_css))?; - }) - }); - util::print_result_err("while loading CSS file for hot-reloading", &result); - Ok(hotwatch) -} - -/// detach the process from the terminal, also redirecting stdout and stderr to -/// LOG_FILE -fn do_detach() -> Result<()> { - // detach from terminal - match unsafe { nix::unistd::fork()? } { - nix::unistd::ForkResult::Parent { .. } => { - std::process::exit(0); - } - nix::unistd::ForkResult::Child => {} - } - - let file = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&*LOG_FILE) - .expect(&format!( - "Error opening log file ({}), for writing", - &*LOG_FILE.to_string_lossy() - )); - let fd = file.as_raw_fd(); - - if nix::unistd::isatty(1)? { - nix::unistd::dup2(fd, std::io::stdout().as_raw_fd())?; - } - if nix::unistd::isatty(2)? { - nix::unistd::dup2(fd, std::io::stderr().as_raw_fd())?; - } - - Ok(()) -} - -#[extend::ext(pub)] -impl hotwatch::Hotwatch { - fn watch_file_changes(&mut self, file_path: P, callback: F) -> Result<()> - where - P: AsRef, - F: 'static + Fn(PathBuf) + Send, - { - Ok(self.watch(file_path, move |evt| match evt { - hotwatch::Event::Write(path) | hotwatch::Event::NoticeWrite(path) => callback(path), - _ => {} - })?) - } -} diff --git a/src/opts.rs b/src/opts.rs new file mode 100644 index 0000000..442b2f6 --- /dev/null +++ b/src/opts.rs @@ -0,0 +1,88 @@ +use serde::{Deserialize, Serialize}; +use structopt::StructOpt; + +use crate::{ + app, + config::WindowName, + value::{Coords, PrimitiveValue, VarName}, +}; + +#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] +pub struct Opt { + #[structopt(subcommand)] + pub action: Action, + + #[structopt(short = "-d", long = "--detach")] + pub should_detach: bool, +} + +#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] +pub enum Action { + #[structopt(flatten)] + ClientOnly(ActionClientOnly), + #[structopt(flatten)] + WithServer(ActionWithServer), +} + +#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] +pub enum ActionClientOnly { + #[structopt(name = "logs", help = "Print and watch the eww logs")] + Logs, +} + +#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] +pub enum ActionWithServer { + #[structopt(name = "update", help = "update the value of a variable, in a running eww instance")] + Update { fieldname: VarName, value: PrimitiveValue }, + + #[structopt(name = "open", help = "open a window")] + OpenWindow { + window_name: WindowName, + + #[structopt(short, long, help = "The position of the window, where it should open.")] + pos: Option, + + #[structopt(short, long, help = "The size of the window to open")] + size: Option, + }, + + #[structopt(name = "close", help = "close the window with the given name")] + CloseWindow { window_name: WindowName }, + + #[structopt(name = "kill", help("kill the eww daemon"))] + KillServer, + + #[structopt(name = "state", help = "Print the current eww-state")] + ShowState, + + #[structopt(name = "debug", help = "Print out the widget structure as seen by eww")] + ShowDebug, +} + +impl ActionWithServer { + pub fn into_eww_command(self) -> (app::EwwCommand, Option>) { + let command = match self { + ActionWithServer::Update { fieldname, value } => app::EwwCommand::UpdateVar(fieldname, value), + ActionWithServer::OpenWindow { window_name, pos, size } => app::EwwCommand::OpenWindow { window_name, pos, size }, + ActionWithServer::CloseWindow { window_name } => app::EwwCommand::CloseWindow { window_name }, + ActionWithServer::KillServer => app::EwwCommand::KillServer, + ActionWithServer::ShowState => { + let (send, recv) = crossbeam_channel::unbounded(); + return (app::EwwCommand::PrintState(send), Some(recv)); + } + ActionWithServer::ShowDebug => { + let (send, recv) = crossbeam_channel::unbounded(); + return (app::EwwCommand::PrintDebug(send), Some(recv)); + } + }; + (command, None) + } + + /// returns true if this command requires a server to already be running + pub fn needs_server_running(&self) -> bool { + match self { + ActionWithServer::OpenWindow { .. } => false, + _ => true, + } + } +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..5f79d4b --- /dev/null +++ b/src/server.rs @@ -0,0 +1,185 @@ +use crate::{app, config, eww_state::*, opts, script_var_handler, try_logging_errors, util}; +use anyhow::*; +use log; +use std::{ + collections::HashMap, + io::Write, + os::unix::{io::AsRawFd, net}, + path::{Path, PathBuf}, +}; + +pub fn initialize_server(should_detach: bool, action: opts::ActionWithServer) -> Result<()> { + let _ = std::fs::remove_file(&*crate::IPC_SOCKET_PATH); + + if should_detach { + do_detach()?; + } + + ctrlc::set_handler(|| { + println!("Shutting down eww daemon..."); + script_var_handler::script_var_process::on_application_death(); + std::process::exit(0); + }) + .context("Error setting signal hook")?; + + let config_file_path = crate::CONFIG_DIR.join("eww.xml"); + let config_dir = config_file_path + .parent() + .context("config file did not have a parent?!")? + .to_owned() + .to_path_buf(); + let scss_file_path = config_dir.join("eww.scss"); + + log::info!("reading configuration from {:?}", &config_file_path); + let eww_config = config::EwwConfig::read_from_file(&config_file_path)?; + + gtk::init()?; + 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 mut app = app::App { + eww_state: EwwState::from_default_vars(eww_config.generate_initial_state()?.clone()), + eww_config, + windows: HashMap::new(), + css_provider: gtk::CssProvider::new(), + script_var_handler, + app_evt_send: evt_send.clone(), + }; + + if let Some(screen) = gdk::Screen::get_default() { + gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + if let Ok(eww_css) = util::parse_scss_from_file(&scss_file_path) { + app.load_css(&eww_css)?; + } + + // run the command that eww was started with + log::info!("running command: {:?}", &action); + let (command, maybe_response_recv) = action.into_eww_command(); + app.handle_command(command); + + // print out the response of this initial command, if there is any + if let Some(response_recv) = maybe_response_recv { + if let Ok(response) = response_recv.recv_timeout(std::time::Duration::from_millis(100)) { + println!("{}", response); + } + } + + run_server_thread(evt_send.clone())?; + let _hotwatch = run_filewatch_thread(&config_file_path, &scss_file_path, evt_send.clone())?; + + evt_recv.attach(None, move |msg| { + app.handle_command(msg); + glib::Continue(true) + }); + + gtk::main(); + + Ok(()) +} + +fn run_server_thread(evt_send: glib::Sender) -> Result<()> { + std::thread::spawn(move || { + let result: Result<_> = try { + log::info!("Starting up eww server"); + let listener = net::UnixListener::bind(&*crate::IPC_SOCKET_PATH)?; + 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)?; + 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); + } + } + }); + } + }; + if let Err(err) = result { + eprintln!("error in server thread: {}", err); + std::process::exit(1); + } + }); + Ok(()) +} + +fn run_filewatch_thread>( + config_file_path: P, + scss_file_path: P, + evt_send: glib::Sender, +) -> Result { + log::info!("Initializing config file watcher"); + let mut hotwatch = hotwatch::Hotwatch::new()?; + + let config_file_change_send = evt_send.clone(); + hotwatch.watch_file_changes(config_file_path, move |path| { + try_logging_errors!("handling change of config file" => { + log::info!("Reloading eww configuration"); + let new_eww_config = config::EwwConfig::read_from_file(path)?; + config_file_change_send.send(app::EwwCommand::ReloadConfig(new_eww_config))?; + }); + })?; + + let result = hotwatch.watch_file_changes(scss_file_path, move |path| { + try_logging_errors!("handling change of scss file" => { + log::info!("reloading eww css file"); + let eww_css = util::parse_scss_from_file(path)?; + evt_send.send(app::EwwCommand::ReloadCss(eww_css))?; + }) + }); + util::print_result_err("while loading CSS file for hot-reloading", &result); + Ok(hotwatch) +} + +/// detach the process from the terminal, also redirecting stdout and stderr to +/// LOG_FILE +fn do_detach() -> Result<()> { + // detach from terminal + match unsafe { nix::unistd::fork()? } { + nix::unistd::ForkResult::Parent { .. } => { + std::process::exit(0); + } + nix::unistd::ForkResult::Child => {} + } + + let file = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&*crate::LOG_FILE) + .expect(&format!( + "Error opening log file ({}), for writing", + &*crate::LOG_FILE.to_string_lossy() + )); + let fd = file.as_raw_fd(); + + if nix::unistd::isatty(1)? { + nix::unistd::dup2(fd, std::io::stdout().as_raw_fd())?; + } + if nix::unistd::isatty(2)? { + nix::unistd::dup2(fd, std::io::stderr().as_raw_fd())?; + } + + Ok(()) +} + +#[extend::ext(pub)] +impl hotwatch::Hotwatch { + fn watch_file_changes(&mut self, file_path: P, callback: F) -> Result<()> + where + P: AsRef, + F: 'static + Fn(PathBuf) + Send, + { + Ok(self.watch(file_path, move |evt| match evt { + hotwatch::Event::Write(path) | hotwatch::Event::NoticeWrite(path) => callback(path), + _ => {} + })?) + } +} diff --git a/src/util.rs b/src/util.rs index 3217f68..0903e07 100644 --- a/src/util.rs +++ b/src/util.rs @@ -21,6 +21,16 @@ macro_rules! impl_try_from { }; } +#[macro_export] +macro_rules! try_logging_errors { + ($context:literal => $code:block) => {{ + let result: Result<_> = try { $code }; + if let Err(err) = result { + eprintln!("Error while {}: {:?}", $context, err); + } + }}; +} + /// read an scss file, replace all environment variable references within it and /// then parse it into css. pub fn parse_scss_from_file>(path: P) -> Result {