feat(gui): system tray with opt-in close-to-tray setting

Add a StatusNotifierItem tray (ksni — pure-Rust over the zbus stack
notify-rust already pulls; only new crate is the pastey macro helper).
The icon reflects host/viewer status via its tooltip and offers
Show / Quit; it runs on its own thread, channel-wired to the egui app.

Add a Settings screen with a persisted toggle 'keep running in the tray
when I close the window' (config.toml [gui] close_to_tray), defaulting
OFF so the close button quits as users expect. When ON, closing hides
to the tray on X11 / minimizes on Wayland (which has no protocol to hide
a toplevel) and keeps any live stream running. If no tray is present the
close behaves normally, so the window can never be stranded.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 05:54:06 -04:00
parent ad70ce5ea9
commit b260d57dc4
5 changed files with 420 additions and 5 deletions
+14 -3
View File
@@ -1,8 +1,7 @@
//! Persistent user-level config at `~/.config/pixelpass/config.toml`.
//!
//! Right now this only tracks the bandwidth pre-flight result. Future
//! preferences (default player, default bitrate, etc.) can hang off the
//! same file under their own `[section]`.
//! It tracks the bandwidth pre-flight result and the GUI's preferences.
//! Further settings can hang off the same file under their own `[section]`.
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
@@ -16,6 +15,18 @@ use std::path::PathBuf;
pub struct Config {
#[serde(default)]
pub bandwidth: BandwidthEntry,
#[serde(default)]
pub gui: GuiSettings,
}
/// Preferences for the `pixelpass --gui` front-end.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GuiSettings {
/// When true, the window's close button hides the app to the system tray
/// (keeping any live stream running) instead of quitting. Defaults to
/// false — closing quits, which is what people expect.
#[serde(default)]
pub close_to_tray: bool,
}
/// Result of the first-run upstream measurement.