Desktop: tray icon, start at logon hidden (autostart --hidden, window state never restores visible), closing hides to the tray, background signed auto-update at start and every 30 min with restart
This commit is contained in:
parent
9f40a9ea14
commit
9a907d87e5
6 changed files with 200 additions and 13 deletions
|
|
@ -21,7 +21,10 @@ include!("commands_list.rs");
|
|||
use std::sync::Mutex;
|
||||
|
||||
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem, Submenu, WINDOW_SUBMENU_ID};
|
||||
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
|
||||
use tauri::{AppHandle, Emitter, Manager, RunEvent, Runtime, WindowEvent};
|
||||
use tauri_plugin_autostart::{MacosLauncher, ManagerExt};
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
|
||||
/// WebKitGTK's DMABUF renderer leaves the window blank on some Linux GPU / driver / VM combinations. Turn it off unless
|
||||
|
|
@ -34,6 +37,60 @@ pub fn linux_webkit_env() {
|
|||
}
|
||||
}
|
||||
|
||||
/// Started by the logon entry (`--hidden`): the app runs in the tray and shows no window.
|
||||
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).
|
||||
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 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),
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|t, e| {
|
||||
if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = e {
|
||||
show_main(t.app_handle());
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Silent self-update: at start (after a minute) and every 30 minutes, a signed update from storebound.dev is
|
||||
/// downloaded, installed and the app restarts with the same arguments (`--hidden` stays hidden). Errors are logged.
|
||||
fn auto_update(app: AppHandle) {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
|
||||
loop {
|
||||
match app.updater() {
|
||||
Ok(u) => match u.check().await {
|
||||
Ok(Some(update)) => {
|
||||
logs::host("info", &format!("auto-update: {} available, installing", update.version));
|
||||
match update.download_and_install(|_, _| {}, || {}).await {
|
||||
Ok(()) => app.restart(),
|
||||
Err(e) => logs::host("warn", &format!("auto-update: install failed: {e}")),
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => logs::host("warn", &format!("auto-update: check failed: {e}")),
|
||||
},
|
||||
Err(e) => logs::host("warn", &format!("auto-update: no updater: {e}")),
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_secs(30 * 60)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn show_main<R: Runtime>(app: &AppHandle<R>) {
|
||||
if let Some(w) = app.get_webview_window(policy::MAIN) {
|
||||
let _ = w.unminimize();
|
||||
|
|
@ -135,7 +192,9 @@ pub fn run() {
|
|||
// single-instance first: a second start (or a storebound:// link) focuses the running app instead
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| show_main(app)))
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::new().build())
|
||||
// never restore "visible": a --hidden start would otherwise pop the window up
|
||||
.plugin(tauri_plugin_window_state::Builder::new().with_state_flags(tauri_plugin_window_state::StateFlags::all() & !tauri_plugin_window_state::StateFlags::VISIBLE).build())
|
||||
.plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, Some(vec!["--hidden"])))
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
|
|
@ -178,7 +237,20 @@ pub fn run() {
|
|||
if let Err(e) = commands::sync_machine(&state) {
|
||||
logs::host("warn", &format!("accounts / app folders not synced at start: {}", e.message));
|
||||
}
|
||||
// start at logon, in the tray: set once (first run); the owner can turn it off in the OS afterwards
|
||||
let marker = state.data_dir.join("autostart-set");
|
||||
if !marker.exists() {
|
||||
match app.autolaunch().enable() {
|
||||
Ok(()) => { let _ = std::fs::write(&marker, "1"); logs::host("info", "autostart at logon enabled (--hidden, tray)"); }
|
||||
Err(e) => logs::host("warn", &format!("autostart not enabled: {e}")),
|
||||
}
|
||||
}
|
||||
app.manage(state);
|
||||
tray(app)?;
|
||||
if !started_hidden() {
|
||||
show_main(app.handle());
|
||||
}
|
||||
auto_update(app.handle().clone());
|
||||
#[cfg(target_os = "macos")]
|
||||
app.set_menu(menu(app.handle())?)?;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
|
@ -202,9 +274,9 @@ pub fn run() {
|
|||
Ok(())
|
||||
})
|
||||
.on_window_event(|w, e| {
|
||||
// macOS: the close button hides the window (the app stays in the Dock and the Window menu);
|
||||
// Cmd+Q and the app menu's Quit still quit. Elsewhere closing the window quits.
|
||||
if cfg!(target_os = "macos") && w.label() == policy::MAIN {
|
||||
// the close button hides the window: the app keeps running in the tray (macOS: also the Dock); the tray's
|
||||
// Quit (macOS: Cmd+Q) really quits
|
||||
if w.label() == policy::MAIN {
|
||||
if let WindowEvent::CloseRequested { api, .. } = e {
|
||||
api.prevent_close();
|
||||
let _ = w.hide();
|
||||
|
|
@ -267,6 +339,8 @@ pub fn run() {
|
|||
app.run(|app, e| match e {
|
||||
#[cfg(target_os = "macos")]
|
||||
RunEvent::Reopen { .. } => show_main(app),
|
||||
// no window left is not a reason to quit (tray app); an explicit exit or the updater's restart carries a code
|
||||
RunEvent::ExitRequested { code: None, api, .. } => api.prevent_exit(),
|
||||
RunEvent::Exit => {
|
||||
if let Some(s) = app.try_state::<commands::AppState>() {
|
||||
s.sidecar.shutdown_now();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue