Files
tsunagi/crates/tsunagi-tray/src/main.rs
T
UltradesuandClaude Opus 4.8 d1de61ba29 Tray UI: start hidden, fixed window, grid layout, traffic + peers
Reworks the tray window per feedback and fills it in.

- Starts hidden in the tray (no window on launch); the window opens from the
  menu. On macOS the app now uses the Accessory activation policy, so it lives
  in the menu bar with no Dock icon (via a winit event-loop hook, pinned to
  eframe's winit). Fixed, non-resizable 360x600 window; the layout targets it.
- Tidied layout: top bar (title + refresh), a bottom panel with the join form
  and the action banner, and a scrolling list of network tiles between them.
  Labels and values sit on two-column grids so they align; the join name and
  secret inputs are the same fixed width.
- A ⟳ button beside the secret generates a fresh random secret
  (NetworkSecret::generate), for creating a network.
- Each network tile shows id, range, this device's address, peers
  (connected/known), the active and broadcast switches, a Leave button, and a
  collapsing "peers (N)" section with a formatted per-peer table (short key,
  address, handshake age, tx/rx packets, rate).
- Traffic: a new sampler turns the agent's cumulative packet counters into a
  packets-per-second rate per network and per peer, from the deltas between
  snapshots (the snapshot now carries a generation and timestamp so each
  refresh is sampled once). The network tile shows the current rate and a small
  hand-drawn sparkline of recent rates (no extra dependency).

Note: rates are packets/s — the report has no byte counters yet; bytes would
need an agent change. Builds and is clippy-clean (-D warnings) on macOS; the UI
has not been run in this headless session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 15:08:29 +01:00

68 lines
2.3 KiB
Rust

//! Tray application for managing a running tsunagi agent.
//!
//! A small window (networks, their peers and addresses, join/leave, per-network
//! active and broadcast switches) plus a tray menu (Open, Quit). It is a thin
//! client over the agent's local control socket — the same interface the CLI
//! uses — and owns its own runtime, logger and event loop, like the CLI binary.
// The window is created hidden-to-tray on close rather than exiting; the binary
// itself may exit on Quit. Nothing here is a library.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod agent;
mod app;
mod stats;
mod tray;
mod ui;
use eframe::egui;
use crate::app::App;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
let _ = tracing_subscriber::fmt().with_env_filter(filter).try_init();
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
let handle = runtime.handle().clone();
#[cfg_attr(not(target_os = "macos"), expect(unused_mut))]
let mut options = eframe::NativeOptions {
// A fixed, small window; the whole layout targets this size.
viewport: egui::ViewportBuilder::default()
.with_title("tsunagi")
.with_inner_size([360.0, 600.0])
.with_resizable(false)
// Start hidden: the app lives in the tray and the window opens from
// the menu.
.with_visible(false),
..Default::default()
};
// macOS: live in the menu bar with no Dock icon (tray-only).
#[cfg(target_os = "macos")]
{
options.event_loop_builder = Some(Box::new(|builder| {
use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
builder.with_activation_policy(ActivationPolicy::Accessory);
}));
}
eframe::run_native(
"tsunagi",
options,
Box::new(move |cc| {
App::new(cc, handle.clone())
.map(|app| Box::new(app) as Box<dyn eframe::App>)
.map_err(std::convert::Into::into)
}),
)?;
// The window loop has ended (Quit); let the runtime wind down.
drop(runtime);
Ok(())
}