use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::Infallible;
use std::error::Error;
use std::process;
use std::rc::Rc;
use std::time::Duration;
use clap::Parser;
use istat::cli::Cli;
use istat::config::{self, Item};
use istat::context::{Context, SharedState};
use istat::dispatcher::Dispatcher;
use istat::i3::header::I3BarHeader;
use istat::i3::ipc::handle_click_events;
use istat::i3::I3Item;
use istat::ipc::handle_ipc_events;
use istat::signals::handle_signals;
use tokio::sync::mpsc::{self, Receiver};
fn main() {
if let Err(err) = start_runtime() {
log::error!("{}", err);
process::exit(1);
}
}
fn start_runtime() -> Result<Infallible, Box<dyn Error>> {
pretty_env_logger::try_init()?;
let args = Cli::parse();
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
let result = tokio::task::LocalSet::new().block_on(&runtime, async_main(args));
runtime.shutdown_timeout(Duration::from_secs(1));
result
}
type Bar = Rc<RefCell<Vec<I3Item>>>;
async fn async_main(args: Cli) -> Result<Infallible, Box<dyn Error>> {
let config = config::read(args.config).await?;
println!("{}", serde_json::to_string(&I3BarHeader::default())?);
println!("[");
let item_count = config.items.len();
let state = SharedState::new();
let bar: Bar = Rc::new(RefCell::new(vec![I3Item::empty(); item_count]));
let mut bar_txs = vec![];
let (item_tx, item_rx) = mpsc::channel(item_count + 1);
for (idx, item) in config.items.iter().enumerate() {
let bar_item = item.to_bar_item();
let (event_tx, event_rx) = mpsc::channel(32);
bar_txs.push(event_tx.clone());
let ctx = Context::new(state.clone(), item_tx.clone(), event_tx, event_rx, idx);
let bar = bar.clone();
tokio::task::spawn_local(async move {
let theme = ctx.theme.clone();
let fut = bar_item.start(ctx);
match fut.await {
Ok(()) => {
log::info!("item[{}] finished running", idx);
bar.borrow_mut()[idx] = I3Item::empty();
}
Err(e) => {
log::error!("item[{}] exited with error: {}", idx, e);
bar.borrow_mut()[idx] = I3Item::new("ERROR")
.color(theme.dark1)
.background_color(theme.error);
}
}
});
}
let dispatcher = Dispatcher::new(
bar_txs
.into_iter()
.enumerate()
.map(|(idx, tx)| (idx, (tx, config.items[idx].clone())))
.collect::<HashMap<usize, _>>(),
);
handle_item_updates(config.items.clone(), item_rx, bar);
let signal_handle = handle_signals(args.socket.clone(), dispatcher.clone())?;
let err = tokio::select! {
err = handle_ipc_events(args.socket.clone(), dispatcher.clone()) => err,
err = handle_click_events(dispatcher.clone()) => err,
};
signal_handle.close();
return err;
}
fn handle_item_updates(items: Vec<Item>, mut rx: Receiver<(I3Item, usize)>, bar: Bar) {
let item_names = items
.into_iter()
.map(|item| match item.common.name {
Some(name) => name,
None => item.tag().into(),
})
.collect::<Vec<_>>();
tokio::task::spawn_local(async move {
while let Some((i3_item, idx)) = rx.recv().await {
let mut bar = bar.borrow_mut();
bar[idx] = i3_item
.name(item_names[idx].clone())
.instance(idx.to_string());
match serde_json::to_string(&*bar) {
Ok(json) => println!("{},", json),
Err(e) => {
log::error!("failed to serialise bar to json: {}", e);
println!(
r#"[{{"full_text":"FATAL ERROR: see logs in stderr","color":"black","background":"red"}}],"#
);
}
}
}
});
}