Skip to main content

tauri_plugin_single_instance/
lib.rs

1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Ensure a single instance of your tauri app is running.
6//!
7//! ## Cargo features
8//!
9//! - **semver**: Allows the app with SemVer incompatible versions to run alongside each other.
10//! - **deep-link**: Trigger [`tauri-plugin-deep-link`](https://crates.io/crates/tauri-plugin-deep-link) event before invoking the single-instance callback.
11
12#![doc(
13    html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png",
14    html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
15)]
16#![cfg(not(any(target_os = "android", target_os = "ios")))]
17
18use tauri::{AppHandle, Manager, Resource, ResourceId, Runtime, plugin::TauriPlugin};
19
20#[cfg(target_os = "windows")]
21#[path = "platform_impl/windows.rs"]
22mod platform_impl;
23#[cfg(target_os = "linux")]
24#[path = "platform_impl/linux.rs"]
25mod platform_impl;
26#[cfg(target_os = "macos")]
27#[path = "platform_impl/macos.rs"]
28mod platform_impl;
29
30#[cfg(feature = "semver")]
31mod semver_compat;
32
33pub(crate) type SingleInstanceCallback<R> =
34    dyn FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static;
35
36/// Initializes the plugin, calling `f` whenever a second instance of the app is started.
37///
38/// This is a shortcut for [`Builder::new`] with [`Builder::callback`] set to `f`, then
39/// [`Builder::build`]. Use [`Builder`] directly if you need to set a custom [`Builder::dbus_id`].
40///
41/// `f` is called with the app handle, the second instance's command line arguments
42/// (as collected by [`std::env::args_os`], so the first element is the executable path) and its
43/// current working directory. Arguments and the working directory that are not valid Unicode
44/// have their invalid parts replaced by U+FFFD. If the `deep-link` feature is enabled, the arguments are first
45/// forwarded to [`tauri-plugin-deep-link`](https://crates.io/crates/tauri-plugin-deep-link)
46/// before `f` runs.
47///
48/// The second instance never reaches [`tauri::Builder::run`]: it hands its arguments and working
49/// directory off to the first instance and exits immediately.
50pub fn init<R: Runtime, F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static>(
51    f: F,
52) -> TauriPlugin<R> {
53    Builder::new().callback(f).build()
54}
55
56/// Releases the resources this plugin uses to detect other instances (the named mutex on
57/// Windows, the D-Bus name on Linux or the Unix socket on macOS).
58///
59/// The plugin releases them automatically in [`AppHandle::cleanup_before_exit`], which Tauri runs
60/// right before the process exits or restarts (after your [`tauri::RunEvent::Exit`] handler
61/// returns), so you normally don't need to call this yourself and the app keeps being the single
62/// instance while your own exit cleanup runs. Call it manually to release the lock earlier, or
63/// before terminating the process through means that skip [`AppHandle::cleanup_before_exit`], such
64/// as [`std::process::exit`], so a future instance of the app isn't mistaken for a still-running
65/// one.
66pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
67    if let Some(lock) = manager.try_state::<InstanceLockId>() {
68        let _ = manager.resources_table().close(lock.0);
69    }
70}
71
72struct InstanceLockId(ResourceId);
73
74/// Stores the platform lock in the app's resource table, which Tauri clears in
75/// [`AppHandle::cleanup_before_exit`], so the lock's [`Drop`] implementation releases it only once
76/// the app is really exiting.
77pub(crate) fn manage_instance_lock<R: Runtime, M: Manager<R>>(manager: &M, lock: impl Resource) {
78    let rid = manager.resources_table().add(lock);
79    manager.manage(InstanceLockId(rid));
80}
81
82/// Builds the single-instance plugin.
83///
84/// Created with [`Builder::new`] and consumed by [`Builder::build`].
85pub struct Builder<R: Runtime> {
86    callback: Box<SingleInstanceCallback<R>>,
87    dbus_id: Option<String>,
88}
89
90impl<R: Runtime> Default for Builder<R> {
91    fn default() -> Self {
92        Self {
93            callback: Box::new(move |_app, _args, _| {
94                #[cfg(feature = "deep-link")]
95                if let Some(deep_link) = _app.try_state::<tauri_plugin_deep_link::DeepLink<R>>() {
96                    deep_link.handle_cli_arguments(_args.iter());
97                }
98            }),
99            dbus_id: None,
100        }
101    }
102}
103
104impl<R: Runtime> Builder<R> {
105    /// Creates a new builder with a no-op callback (or, when the `deep-link` feature is enabled,
106    /// a callback that only forwards the arguments to the deep-link plugin) and no custom D-Bus
107    /// ID. Use [`Builder::callback`] and [`Builder::dbus_id`] to configure it, then
108    /// [`Builder::build`] to create the plugin.
109    pub fn new() -> Self {
110        Default::default()
111    }
112
113    /// Function to call when a secondary instance was opened by the user and killed by the plugin.
114    /// If the `deep-link` feature is enabled, the plugin triggers the deep-link plugin before executing the callback.
115    pub fn callback<F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static>(
116        mut self,
117        mut f: F,
118    ) -> Self {
119        self.callback = Box::new(move |app, args, cwd| {
120            #[cfg(feature = "deep-link")]
121            if let Some(deep_link) = app.try_state::<tauri_plugin_deep_link::DeepLink<R>>() {
122                deep_link.handle_cli_arguments(args.iter());
123            }
124            f(app, args, cwd)
125        });
126        self
127    }
128
129    /// Set a custom D-Bus ID, used on Linux. The plugin will append a `.SingleInstance` subname.
130    /// For example `com.mycompany.myapp` will result in the plugin registering its D-Bus service on `com.mycompany.myapp.SingleInstance`.
131    /// Usually you want the same base ID across all components in your app.
132    ///
133    /// Defaults to the app's bundle identifier set in tauri.conf.json.
134    pub fn dbus_id(mut self, dbus_id: impl Into<String>) -> Self {
135        self.dbus_id = Some(dbus_id.into());
136        self
137    }
138
139    /// Builds the single-instance [`TauriPlugin`].
140    ///
141    /// Register it first among your app's plugins, since the plugins run in the order they were
142    /// added and a second instance must be detected before the rest of your setup runs.
143    pub fn build(self) -> TauriPlugin<R> {
144        platform_impl::init(
145            self.callback,
146            #[cfg(target_os = "linux")]
147            self.dbus_id,
148        )
149    }
150}