eww/src/server.rs

170 lines
6.4 KiB
Rust

use crate::{app, config, eww_state::*, ipc_server, script_var_handler, util, EwwPaths};
use anyhow::*;
use std::{collections::HashMap, os::unix::io::AsRawFd, path::Path};
use tokio::sync::mpsc::*;
pub fn initialize_server(paths: EwwPaths) -> Result<()> {
do_detach(&paths.get_log_file())?;
println!(
r#"
┏━━━━━━━━━━━━━━━━━━━━━━━┓
┃Initializing eww daemon┃
┗━━━━━━━━━━━━━━━━━━━━━━━┛
"#
);
simple_signal::set_handler(&[simple_signal::Signal::Int, simple_signal::Signal::Term], move |_| {
log::info!("Shutting down eww daemon...");
if let Err(e) = crate::application_lifecycle::send_exit() {
log::error!("Failed to send application shutdown event to workers: {:?}", e);
std::process::exit(1);
}
});
let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel();
std::env::set_current_dir(&paths.get_config_dir())
.with_context(|| format!("Failed to change working directory to {}", paths.get_config_dir().display()))?;
log::info!("Loading paths: {}", &paths);
let eww_config = config::EwwConfig::read_from_file(&paths.get_eww_xml_path())?;
gtk::init()?;
log::info!("Initializing script var handler");
let script_var_handler = script_var_handler::init(ui_send.clone());
let mut app = app::App {
eww_state: EwwState::from_default_vars(eww_config.generate_initial_state()?),
eww_config,
open_windows: HashMap::new(),
css_provider: gtk::CssProvider::new(),
script_var_handler,
app_evt_send: ui_send.clone(),
paths,
};
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(&app.paths.get_eww_scss_path()) {
app.load_css(&eww_css)?;
}
// initialize all the handlers and tasks running asyncronously
init_async_part(app.paths.clone(), ui_send);
glib::MainContext::default().spawn_local(async move {
while let Some(event) = ui_recv.recv().await {
app.handle_command(event);
}
});
gtk::main();
log::info!("main application thread finished");
Ok(())
}
fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) {
std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("Failed to initialize tokio runtime");
rt.block_on(async {
let filewatch_join_handle = {
let ui_send = ui_send.clone();
let paths = paths.clone();
tokio::spawn(async move { run_filewatch(paths.config_dir, ui_send).await })
};
let ipc_server_join_handle = {
let ui_send = ui_send.clone();
tokio::spawn(async move { ipc_server::run_server(ui_send, paths.get_ipc_socket_file()).await })
};
let forward_exit_to_app_handle = {
let ui_send = ui_send.clone();
tokio::spawn(async move {
// Wait for application exit event
let _ = crate::application_lifecycle::recv_exit().await;
log::info!("Forward task received exit event");
// Then forward that to the application
let _ = ui_send.send(app::DaemonCommand::KillServer);
})
};
let result = tokio::try_join!(filewatch_join_handle, ipc_server_join_handle, forward_exit_to_app_handle);
if let Err(e) = result {
log::error!("Eww exiting with error: {:?}", e);
}
})
});
}
/// Watch configuration files for changes, sending reload events to the eww app when the files change.
async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {
use notify::Watcher;
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let mut watcher: notify::RecommendedWatcher =
notify::Watcher::new_immediate(move |res: notify::Result<notify::Event>| match res {
Ok(event) => {
if let Err(err) = tx.send(event.paths) {
log::warn!("Error forwarding file update event: {:?}", err);
}
}
Err(e) => log::error!("Encountered Error While Watching Files: {}", e),
})?;
watcher.watch(&config_dir, notify::RecursiveMode::Recursive)?;
crate::loop_select_exiting! {
Some(paths) = rx.recv() => {
for path in paths {
let extension = path.extension().unwrap_or_default();
if extension != "xml" && extension != "scss" {
continue;
}
let (daemon_resp_sender, mut daemon_resp_response) = tokio::sync::mpsc::unbounded_channel();
evt_send.send(app::DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;
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),
None => log::error!("No response to reload configuration-reload request"),
}
});
}
},
else => break
};
return Ok(());
}
/// detach the process from the terminal, also redirecting stdout and stderr to LOG_FILE
fn do_detach(log_file_path: impl AsRef<Path>) -> 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_path)
.expect(&format!("Error opening log file ({}), for writing", log_file_path.as_ref().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(())
}