mod aerospace;
mod events;
mod workspace;
use std::io::{BufRead, Write};
use std::sync::mpsc;
use std::thread;
use events::{is_init_event, parse_switch_workspace};
fn refresh() {
let (Some(workspaces), Some(windows)) =
(aerospace::list_workspaces(), aerospace::list_windows())
else {
return;
};
let payload = workspace::payload(&workspace::build(&workspaces, &windows));
let mut stdout = std::io::stdout().lock();
if writeln!(stdout, "{payload}").is_err() || stdout.flush().is_err() {
std::process::exit(0);
}
}
fn main() {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.init();
let (hint_tx, hint_rx) = mpsc::channel::<()>();
let (init_tx, init_rx) = mpsc::channel::<()>();
let stdin_hint_tx = hint_tx.clone();
thread::spawn(move || {
let stdin = std::io::stdin();
let mut started = false;
for line in stdin.lock().lines().map_while(Result::ok) {
if !started {
if is_init_event(&line) {
started = true;
let _ = init_tx.send(());
}
continue;
}
let Ok(val) = serde_json::from_str::<serde_json::Value>(&line) else {
continue;
};
if let Some(name) = parse_switch_workspace(&val) {
aerospace::switch_workspace(&name);
let _ = stdin_hint_tx.send(());
}
}
std::process::exit(0);
});
if init_rx.recv().is_err() {
return;
}
let (mut child, reader) = match aerospace::subscribe() {
Ok(pair) => pair,
Err(e) => {
tracing::error!(error = %e, "cannot start `aerospace subscribe` — is AeroSpace running?");
std::process::exit(1);
}
};
let subscribe_tx = hint_tx.clone();
thread::spawn(move || {
aerospace::pump_events(reader, || {
let _ = subscribe_tx.send(());
});
let _ = child.wait();
tracing::error!("aerospace event stream closed");
std::process::exit(1);
});
drop(hint_tx);
while hint_rx.recv().is_ok() {
refresh();
}
}