eww/src/server.rs
2021-01-07 21:21:08 +01:00

233 lines
8.5 KiB
Rust

use crate::{app, config, eww_state::*, opts, script_var_handler, try_logging_errors, util};
use anyhow::*;
use futures_util::StreamExt;
use std::{
collections::HashMap,
os::unix::io::AsRawFd,
path::{Path, PathBuf},
time::Duration,
};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::mpsc::*,
};
pub fn initialize_server() -> Result<()> {
do_detach()?;
simple_signal::set_handler(&[simple_signal::Signal::Int, simple_signal::Signal::Term], move |_| {
println!("Shutting down eww daemon...");
if let Err(e) = crate::application_lifecycle::send_exit() {
eprintln!("Failed to send application shutdown event to workers: {:?}", e);
std::process::exit(1);
}
});
let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel();
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();
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()?;
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,
windows: HashMap::new(),
css_provider: gtk::CssProvider::new(),
script_var_handler,
app_evt_send: ui_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)?;
}
// initialize all the handlers and tasks running asyncronously
init_async_part(config_file_path, scss_file_path, 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(config_file_path: PathBuf, scss_file_path: PathBuf, ui_send: UnboundedSender<app::EwwCommand>) {
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();
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 { run_ipc_server(ui_send).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;
// Then forward that to the application
let _ = ui_send.send(app::EwwCommand::KillServer);
})
};
let result = tokio::try_join!(filewatch_join_handle, ipc_server_join_handle, forward_exit_to_app_handle);
if let Err(e) = result {
eprintln!("Eww exiting with error: {:?}", e);
}
})
});
}
async fn run_ipc_server(evt_send: UnboundedSender<app::EwwCommand>) -> Result<()> {
let listener = tokio::net::UnixListener::bind(&*crate::IPC_SOCKET_PATH)?;
log::info!("IPC server initialized");
crate::loop_select_exiting! {
connection = listener.accept() => match connection {
Ok((stream, _addr)) => {
let evt_send = evt_send.clone();
tokio::spawn(async move {
let result = handle_connection(stream, evt_send.clone()).await;
crate::print_result_err!("while handling IPC connection with client", result);
});
},
Err(e) => eprintln!("Failed to connect to client: {:?}", e),
}
}
Ok(())
}
/// Handle a single IPC connection from start to end.
async fn handle_connection(mut stream: tokio::net::UnixStream, evt_send: UnboundedSender<app::EwwCommand>) -> Result<()> {
let (mut stream_read, mut stream_write) = stream.split();
let action: opts::ActionWithServer = {
let mut message_byte_length = [0u8; 4];
stream_read
.read_exact(&mut message_byte_length)
.await
.context("Failed to read message size header in IPC message")?;
let message_byte_length = u32::from_be_bytes(message_byte_length);
let mut raw_message = Vec::<u8>::with_capacity(message_byte_length as usize);
while raw_message.len() < message_byte_length as usize {
stream_read
.read_buf(&mut raw_message)
.await
.context("Failed to read actual IPC message")?;
}
bincode::deserialize(&raw_message).context("Failed to parse client message")?
};
log::info!("received command from IPC: {:?}", &action);
let (command, maybe_response_recv) = action.into_eww_command();
evt_send.send(command)?;
if let Some(mut response_recv) = maybe_response_recv {
log::info!("Waiting for response for IPC client");
if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), response_recv.recv()).await {
let result = &stream_write.write_all(response.as_bytes()).await;
crate::print_result_err!("sending text response to ipc client", &result);
}
}
stream_write.shutdown().await?;
Ok(())
}
/// Watch configuration files for changes, sending reload events to the eww app when the files change.
async fn run_filewatch<P: AsRef<Path>>(
config_file_path: P,
scss_file_path: P,
evt_send: UnboundedSender<app::EwwCommand>,
) -> Result<()> {
let mut inotify = inotify::Inotify::init().context("Failed to initialize inotify")?;
let config_file_descriptor = inotify
.add_watch(config_file_path.as_ref(), inotify::WatchMask::MODIFY)
.context("Failed to add inotify watch for config file")?;
let scss_file_descriptor = inotify
.add_watch(scss_file_path.as_ref(), inotify::WatchMask::MODIFY)
.context("Failed to add inotify watch for scss file")?;
let mut buffer = [0; 1024];
let mut event_stream = inotify.event_stream(&mut buffer)?;
crate::loop_select_exiting! {
Some(Ok(event)) = event_stream.next() => {
try_logging_errors!("handling change of config file" => {
if event.wd == config_file_descriptor {
log::info!("Reloading eww configuration");
let new_eww_config = config::EwwConfig::read_from_file(config_file_path.as_ref())?;
evt_send.send(app::EwwCommand::ReloadConfig(new_eww_config))?;
} else if event.wd == scss_file_descriptor {
log::info!("reloading eww css file");
let eww_css = crate::util::parse_scss_from_file(scss_file_path.as_ref())?;
evt_send.send(app::EwwCommand::ReloadCss(eww_css))?;
} else {
eprintln!("Got inotify event for unknown thing: {:?}", event);
}
});
}
else => break,
}
Ok(())
}
/// 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(())
}