diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3405642..2603099 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4958,6 +4958,7 @@ dependencies = [ "tauri-plugin-opener", "tauri-plugin-process", "tauri-plugin-shell", + "tauri-plugin-single-instance", "tauri-plugin-store", "tauri-plugin-updater", "tokio", @@ -5332,6 +5333,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tauri-plugin-single-instance" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33a5b7d78f0dec4406b003ea87c40bf928d801b6fd9323a556172c91d8712c1" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-plugin-deep-link", + "thiserror 2.0.18", + "tracing", + "windows-sys 0.60.2", + "zbus", +] + [[package]] name = "tauri-plugin-store" version = "2.4.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c9b5c3a..ddacfa9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -54,6 +54,11 @@ objc2-foundation = "0.3" [target.'cfg(any(target_os = "macos", windows, target_os = "linux"))'.dependencies] tauri-plugin-autostart = "2.5.1" +# macOS enforces a single app instance natively; Windows/Linux need this plugin +# so a second launch focuses the running app instead of spawning another widget. +[target.'cfg(any(windows, target_os = "linux"))'.dependencies] +tauri-plugin-single-instance = { version = "2", features = ["deep-link"] } + [target.'cfg(target_os = "linux")'.dependencies] pipewire = "0.10.0" webkit2gtk = { version = "2.0.2", features = ["v2_8"] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index cf1cc9c..ceb9533 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -22,8 +22,41 @@ use tauri::{Emitter, Manager}; use tauri_plugin_deep_link::DeepLinkExt; #[cfg_attr(mobile, tauri::mobile_entry_point)] +/// Bring the already-running instance to front when a second launch is blocked +/// by the single-instance plugin (Windows/Linux only). +#[cfg(any(windows, target_os = "linux"))] +fn focus_existing_instance(app: &tauri::AppHandle) { + use tauri::Manager; + + if let Some(win) = app.get_webview_window("settings") { + let _ = win.unminimize(); + let _ = win.show(); + let _ = win.set_focus(); + return; + } + + if let Some(win) = app.get_webview_window("widget") { + let _ = win.show(); + let _ = win.set_focus(); + } +} + pub fn run() { - tauri::Builder::default() + #[allow(unused_mut)] + let mut builder = tauri::Builder::default(); + + // The single-instance plugin must be registered first. macOS enforces one + // app instance natively (so the duplicate-widget bug never happens there); + // on Windows/Linux a second launch — autostart, deep link, or re-open — + // would otherwise start another process with its own floating widget. + #[cfg(any(windows, target_os = "linux"))] + { + builder = builder.plugin(tauri_plugin_single_instance::init( + |app, _argv, _cwd| focus_existing_instance(app), + )); + } + + builder .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init())