c19702ae09be | atagen | 2026-05-27 15:49 UTC | 1 | //! `headroom-gui` — native gpu-accelerated monitor for the headroom |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 2 | //! daemon, built on [iced](https://iced.rs). additive and optional, with |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 3 | //! full control parity with the ratatui tui (route/bypass/per-app/profile) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 4 | //! by mouse + keyboard; ships as its own nix package, separate from the |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 5 | //! daemon. build from the nix dev shell (supplies iced's system libs). |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 6 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 7 | mod app; |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 8 | mod io; |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 9 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 10 | use std::path::PathBuf; |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 11 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 12 | use crate::app::HeadroomApp; |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 13 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 14 | fn main() -> anyhow::Result<()> { |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 15 | init_tracing(); |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 16 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 17 | let socket = parse_socket_arg(); |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 18 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 19 | // connect + initial fetch before opening any window, so a dead daemon |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 20 | // fails fast. the reader thread lives inside the returned `Bootstrap`; |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 21 | // the iced `subscription` poll tick drains its channel. |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 22 | let bootstrap = io::start(socket)?; |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 23 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 24 | // iced's `BootFn` is `Fn` (callable repeatedly) but `Bootstrap` owns a |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 25 | // receiver and is single-use; stash it and `take()` on the first boot. |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 26 | let boot = std::sync::Mutex::new(Some(bootstrap)); |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 27 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 28 | iced::application( |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 29 | move || HeadroomApp::new(boot.lock().unwrap().take().expect("boot runs once")), |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 30 | HeadroomApp::update, |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 31 | HeadroomApp::view, |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 32 | ) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 33 | .title(HeadroomApp::title) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 34 | .subscription(HeadroomApp::subscription) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 35 | .theme(|_state: &HeadroomApp| iced::Theme::Dark) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 36 | .window_size(iced::Size::new(900.0, 680.0)) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 37 | .run() |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 38 | .map_err(|e| anyhow::anyhow!("iced: {e}"))?; |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 39 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 40 | Ok(()) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 41 | } |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 42 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 43 | /// minimal `--socket <path>` parsing — the only flag, so no clap. |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 44 | fn parse_socket_arg() -> Option<PathBuf> { |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 45 | let mut args = std::env::args().skip(1); |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 46 | while let Some(arg) = args.next() { |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 47 | match arg.as_str() { |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 48 | "--socket" => return args.next().map(PathBuf::from), |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 49 | other => { |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 50 | if let Some(path) = other.strip_prefix("--socket=") { |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 51 | return Some(PathBuf::from(path)); |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 52 | } |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 53 | } |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 54 | } |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 55 | } |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 56 | None |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 57 | } |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 58 | |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 59 | fn init_tracing() { |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 60 | tracing_subscriber::fmt() |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 61 | .with_env_filter( |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 62 | tracing_subscriber::EnvFilter::try_from_default_env() |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 63 | .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("headroom_gui=info")), |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 64 | ) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 65 | .with_writer(std::io::stderr) |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 66 | .init(); |
c19702ae09be | atagen | 2026-05-27 15:49 UTC | 67 | } |