#[macro_use] extern crate anyhow; mod bar; mod blocks_cache; mod button_manager; mod color; mod config; mod event_loop; mod i3bar_protocol; mod icon; mod output; mod pointer_btn; mod protocol; mod shared_state; mod state; mod status_cmd; mod text; mod tray; mod utils; mod wm_info_provider; use std::env; use std::io::{self, ErrorKind, Read}; use std::os::fd::{AsRawFd, RawFd}; use std::path::PathBuf; use anyhow::Context; use clap::Parser; use daemonize::Daemonize; use signal_hook::consts::*; use wayrs_client::{Connection, IoMode}; use config::{Config, ConfigWatcher}; use event_loop::{Action, Callback, EventLoop}; use state::State; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { #[arg(short, long, value_name = "FILE", help = "The path to a config file")] config: Option, #[arg(long, help = "Keep the bar attached to the current terminal")] foreground: bool, } fn status_callback(fd: RawFd) -> Callback { Box::new(move |ctx| { if ctx.state.status_cmd_fd() != Some(fd) { return Ok(Action::Unregister); } match ctx .state .shared_state .status_cmd .as_mut() .unwrap() .receive_blocks() { Ok(None) => Ok(Action::Keep), Ok(Some(blocks)) => { ctx.state.set_blocks(ctx.conn, blocks); Ok(Action::Keep) } Err(e) => { let _ = ctx .state .shared_state .status_cmd .take() .unwrap() .child .kill(); ctx.state.set_error(ctx.conn, "status", e); Ok(Action::Unregister) } } }) } fn main() -> anyhow::Result<()> { let args = Cli::parse(); let config_path = Config::path(args.config.as_deref()); if !args.foreground { let working_directory = env::current_dir().context("failed to read working directory")?; Daemonize::new() .working_directory(working_directory) .start() .context("failed to start in the background")?; } let (mut sig_read, sig_write) = io::pipe()?; signal_hook::low_level::pipe::register(SIGUSR1, sig_write)?; let mut conn = Connection::connect()?; conn.blocking_roundtrip()?; let mut el = EventLoop::new(); let mut state = State::new(&mut conn, &mut el, config_path.as_deref()); conn.flush(IoMode::Blocking)?; el.add_on_idle(|ctx| { ctx.conn.flush(IoMode::Blocking)?; Ok(event_loop::Action::Keep) }); el.register_with_fd(sig_read.as_raw_fd(), move |ctx| { sig_read.read_exact(&mut [0u8]).unwrap(); ctx.state.toggle_visibility(ctx.conn); Ok(event_loop::Action::Keep) }); el.register_with_fd(conn.as_raw_fd(), |ctx| { match ctx.conn.recv_events(IoMode::NonBlocking) { Ok(()) => ctx.conn.dispatch_events(ctx.state), Err(e) if e.kind() == ErrorKind::WouldBlock => (), Err(e) => bail!(e), } Ok(event_loop::Action::Keep) }); if let Some(fd) = state.status_cmd_fd() { el.register_with_fd(fd, status_callback(fd)); } if let Some(config_path) = config_path { let mut watcher = ConfigWatcher::new(&config_path) .with_context(|| format!("failed to watch {}", config_path.display()))?; let watcher_fd = watcher.fd(); el.register_with_fd(watcher_fd, move |ctx| { if !watcher.changed()? { return Ok(Action::Keep); } match ctx.state.reload_config(ctx.conn, &config_path) { Ok(Some(fd)) => Ok(Action::KeepAndRegister(fd, status_callback(fd))), Ok(None) => Ok(Action::Keep), Err(error) => { ctx.state.set_error(ctx.conn, "config", error); Ok(Action::Keep) } } }); } el.run(&mut conn, &mut state)?; unreachable!(); }