eww/crates/eww/src/main.rs
Niclas 9977384b06
Cargo update and remove most duplicate dependencies (#531)
Co-authored-by: elkowar <5300871+elkowar@users.noreply.github.com>
2022-08-27 10:12:23 +02:00

279 lines
10 KiB
Rust

#![feature(trace_macros)]
#![feature(drain_filter)]
#![feature(box_syntax)]
#![feature(box_patterns)]
#![feature(slice_concat_trait)]
#![feature(try_blocks)]
#![allow(rustdoc::private_intra_doc_links)]
extern crate gtk;
#[cfg(feature = "wayland")]
extern crate gtk_layer_shell as gtk_layer_shell;
use anyhow::{bail, Context, Result};
use daemon_response::{DaemonResponse, DaemonResponseReceiver};
use opts::ActionWithServer;
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
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;
mod daemon_response;
pub mod display_backend;
pub mod error;
mod error_handling_ctx;
pub mod geometry;
pub mod ipc_server;
pub mod opts;
pub mod script_var_handler;
pub mod server;
pub mod state;
pub mod util;
pub mod widgets;
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 };
if std::env::var("RUST_LOG").is_ok() {
pretty_env_logger::init_timed();
} else {
pretty_env_logger::formatted_timed_builder().filter(Some("eww"), log_level_filter).init();
}
let result: Result<()> = try {
let paths = opts
.config_path
.map(EwwPaths::from_config_dir)
.unwrap_or_else(EwwPaths::default)
.context("Failed to initialize eww paths")?;
let should_restart = match &opts.action {
opts::Action::Daemon => opts.restart,
opts::Action::WithServer(action) => opts.restart && action.can_start_daemon(),
opts::Action::ClientOnly(_) => false,
};
if should_restart {
let response = handle_server_command(&paths, &ActionWithServer::KillServer, 1);
if let Ok(Some(response)) = response {
handle_daemon_response(response);
}
std::thread::sleep(std::time::Duration::from_millis(200));
}
let would_show_logs = match opts.action {
opts::Action::ClientOnly(action) => {
client::handle_client_only_action(&paths, action)?;
false
}
// 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, !opts.no_daemonize)?;
opts.no_daemonize || fork_result == ForkResult::Parent
}
opts::Action::WithServer(ActionWithServer::KillServer) => {
if let Some(response) = handle_server_command(&paths, &ActionWithServer::KillServer, 1)? {
handle_daemon_response(response);
}
false
}
// a running daemon is necessary for this command
opts::Action::WithServer(action) => {
// attempt to just send the command to a running daemon
match handle_server_command(&paths, &action, 5) {
Ok(Some(response)) => {
handle_daemon_response(response);
true
}
Ok(None) => true,
Err(err) if action.can_start_daemon() && !opts.no_daemonize => {
// 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);
}
let (command, response_recv) = action.into_daemon_command();
// start the daemon and give it the command
let fork_result = server::initialize_server(paths.clone(), Some(command), true)?;
let is_parent = fork_result == ForkResult::Parent;
if let (Some(recv), true) = (response_recv, is_parent) {
listen_for_daemon_response(recv);
}
is_parent
}
Err(err) => Err(err)?,
}
}
};
if would_show_logs && opts.show_logs {
client::handle_client_only_action(&paths, opts::ActionClientOnly::Logs)?;
}
};
if let Err(e) = result {
error_handling_ctx::print_error(e);
std::process::exit(1);
}
}
fn listen_for_daemon_response(mut recv: 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);
}
})
}
/// attempt to send a command to the daemon and send it the given action repeatedly.
fn handle_server_command(paths: &EwwPaths, action: &ActionWithServer, connect_attempts: usize) -> Result<Option<DaemonResponse>> {
log::debug!("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::debug!("Connected to Eww server ({}).", &paths.get_ipc_socket_file().display());
client::do_server_call(&mut stream, action).context("Error while forwarding command to server")
}
fn handle_daemon_response(res: DaemonResponse) {
match res {
DaemonResponse::Success(x) => println!("{}", x),
DaemonResponse::Failure(x) => {
eprintln!("{}", x);
std::process::exit(1);
}
}
}
fn attempt_connect(socket_path: impl AsRef<Path>, attempts: usize) -> Option<net::UnixStream> {
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<Path>) -> bool {
let response = net::UnixStream::connect(socket_path)
.ok()
.and_then(|mut stream| client::do_server_call(&mut stream, &opts::ActionWithServer::Ping).ok());
response.is_some()
}
#[derive(Debug, Clone)]
pub struct EwwPaths {
log_file: PathBuf,
ipc_socket_file: PathBuf,
config_dir: PathBuf,
}
impl EwwPaths {
pub fn from_config_dir<P: AsRef<Path>>(config_dir: P) -> Result<Self> {
let config_dir = config_dir.as_ref();
if config_dir.is_file() {
bail!("Please provide the path to the config directory, not a file within it")
}
if !config_dir.exists() {
bail!("Configuration directory {} does not exist", config_dir.display());
}
let config_dir = config_dir.canonicalize()?;
let mut hasher = DefaultHasher::new();
format!("{}", config_dir.display()).hash(&mut hasher);
// daemon_id is a hash of the config dir path to ensure that, given a normal XDG_RUNTIME_DIR,
// the absolute path to the socket stays under the 108 bytes limit. (see #387, man 7 unix)
let daemon_id = format!("{:x}", hasher.finish());
let ipc_socket_file = std::env::var("XDG_RUNTIME_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::path::PathBuf::from("/tmp"))
.join(format!("eww-server_{}", daemon_id));
// 100 as the limit isn't quite 108 everywhere (i.e 104 on BSD or mac)
if format!("{}", ipc_socket_file.display()).len() > 100 {
log::warn!("The IPC socket file's absolute path exceeds 100 bytes, the socket may fail to create.");
}
Ok(EwwPaths {
config_dir,
log_file: std::env::var("XDG_CACHE_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".cache"))
.join(format!("eww_{}.log", daemon_id)),
ipc_socket_file,
})
}
pub fn default() -> Result<Self> {
let config_dir = std::env::var("XDG_CONFIG_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".config"))
.join("eww");
Self::from_config_dir(config_dir)
}
pub fn get_log_file(&self) -> &Path {
self.log_file.as_path()
}
pub fn get_ipc_socket_file(&self) -> &Path {
self.ipc_socket_file.as_path()
}
pub fn get_config_dir(&self) -> &Path {
self.config_dir.as_path()
}
pub fn get_yuck_path(&self) -> PathBuf {
self.config_dir.join("eww.yuck")
}
pub fn get_eww_scss_path(&self) -> PathBuf {
self.config_dir.join("eww.scss")
}
}
impl std::fmt::Display for EwwPaths {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"config-dir: {}, ipc-socket: {}, log-file: {}",
self.config_dir.display(),
self.ipc_socket_file.display(),
self.log_file.display()
)
}
}