Desktop: background store reads from the tray (no document.hidden skip, backoff), notifications by kind (rejected, approved, live, halted, crash, reviews) with switches, quiet hours and mute, click-through on next show; tray menu with apps and their states, read now, mute, updates, settings, tooltip summary and attention dot; tray_update command; tests

This commit is contained in:
CRTkafa 2026-10-22 22:24:05 +08:00
parent 5d8a0f5e9a
commit 03ef999dac
27 changed files with 1050 additions and 55 deletions

View file

@ -14,6 +14,7 @@ pub mod selftest;
pub mod updates;
pub mod sidecar;
pub mod store;
pub mod tray;
pub mod workspace;
include!("commands_list.rs");
@ -42,20 +43,27 @@ fn started_hidden() -> bool {
std::env::args().any(|a| a == "--hidden")
}
/// Tray icon: a click shows the window; the menu has Open and Quit (the only real quit on Windows and Linux, where
/// closing the window keeps the app running in the tray).
/// Tray icon: a click shows the window; the menu (tray.rs) opens the window, reads the stores, lists the apps with their
/// state, mutes notifications, checks for updates, opens Settings and quits (the only real quit on Windows and Linux,
/// where closing the window keeps the app running in the tray). The window refines tooltip, menu and icon after every
/// read (`tray_update`).
fn tray(app: &tauri::App) -> tauri::Result<()> {
let open = MenuItem::with_id(app, "tray-open", "Open storebound", true, None::<&str>)?;
let quit = MenuItem::with_id(app, "tray-quit", "Quit storebound", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&open, &PredefinedMenuItem::separator(app)?, &quit])?;
let menu = tray::initial_menu(app.handle())?;
let mut b = TrayIconBuilder::with_id("main").tooltip("storebound").menu(&menu).show_menu_on_left_click(false);
if let Some(icon) = app.default_window_icon() {
b = b.icon(icon.clone());
}
b.on_menu_event(|app, e| match e.id().as_ref() {
"tray-open" => show_main(app),
"tray-quit" => app.exit(0),
_ => {}
b.on_menu_event(|app, e| match tray::action_of(e.id().as_ref()) {
Some(tray::Action::Open) => show_main(app),
Some(tray::Action::Quit) => app.exit(0),
Some(tray::Action::Event(word)) => {
let _ = app.emit_to(policy::MAIN, "tray-action", word);
}
Some(tray::Action::Route(route)) => {
show_main(app);
let _ = app.emit_to(policy::MAIN, "tray-route", route);
}
None => {}
})
.on_tray_icon_event(|t, e| {
if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = e {
@ -96,6 +104,10 @@ fn show_main<R: Runtime>(app: &AppHandle<R>) {
let _ = w.unminimize();
let _ = w.show();
let _ = w.set_focus();
// the last notification's target (the plugin has no click callback on desktop): the window goes there now
if let Some(route) = tray::take_pending() {
let _ = app.emit_to(policy::MAIN, "tray-route", route);
}
}
}
@ -318,6 +330,7 @@ pub fn run() {
commands::cache_info,
commands::data_reveal,
commands::notify,
commands::tray_update,
commands::app_reset,
commands::update_check,
commands::update_install,