diff --git a/src/gui/tray.rs b/src/gui/tray.rs index a963208..49e0480 100644 --- a/src/gui/tray.rs +++ b/src/gui/tray.rs @@ -87,6 +87,10 @@ struct PixelPassTray { /// Wakes the winit loop and delivers the action — works even when the /// window has been dropped to the tray (no egui frame is running then). proxy: EventLoopProxy, + /// Shared with [`TrayHandle`]; kept in sync with the watcher's presence via + /// the `watcher_online`/`watcher_offline` callbacks so the app never diverts + /// a close to a tray that has since disappeared. + registered: Arc, } impl PixelPassTray { @@ -131,6 +135,25 @@ impl ksni::Tray for PixelPassTray { self.notify(TrayAction::Show); } + /// The StatusNotifierWatcher came back (e.g. the panel restarted). Mark the + /// tray live again so close-to-tray can resume hiding the window. + fn watcher_online(&self) { + self.registered.store(true, Ordering::Release); + } + + /// The watcher went away (panel restart, tray plugin disabled, …). Clear the + /// flag so a subsequent close quits normally instead of destroying the window + /// into a tray that no longer exists, and force the window back now in case + /// it was already hidden (otherwise it'd be stranded with no way to restore). + /// Returning `true` keeps the service alive so it re-registers if the watcher + /// returns. + fn watcher_offline(&self, reason: ksni::OfflineReason) -> bool { + tracing::warn!("tray: StatusNotifierWatcher offline ({reason:?}); restoring window"); + self.registered.store(false, Ordering::Release); + self.notify(TrayAction::Show); + true + } + fn menu(&self) -> Vec> { use ksni::menu::{MenuItem, StandardItem}; vec![ @@ -202,6 +225,7 @@ pub fn start(proxy: EventLoopProxy) -> Option { status: TrayStatus::Idle, icon, proxy, + registered: registered_thread.clone(), }; let handle = match tray.spawn().await { Ok(handle) => handle,