Compare commits

..

2 Commits

Author SHA1 Message Date
Lucas Nogueira 57f6e45b27 Merge branch 'v2' into feat/setup-single-instance-manually 2026-02-05 10:29:41 -03:00
Lucas Nogueira b65a193e6d feat(single-instance): add setup() function to run flow separately 2026-02-05 10:28:15 -03:00
16 changed files with 179 additions and 197 deletions
@@ -0,0 +1,9 @@
---
"single-instance": minor:fix
---
**Breaking Change:** On Linux, the DBus ID/name will now be `<bundle-id>.SingleInstance` instead of `org.<bundle_id_underscores>.SingleInstance` to follow DBus specifications.
This will break the single-instance mechanism across different app versions if the app was installed multiple times.
Added `dbus_id` builder method, which can be used to restore previous behavior. For a bundle identifier of `com.tauri.my-example` this would be `dbus_id("org.com_tauri_my_example")`.
@@ -0,0 +1,5 @@
---
"single-instance": patch
---
Add `setup` function to run the single instance initialization manually, without waiting for the tauri setup hook to run.
Generated
+2 -2
View File
@@ -6683,7 +6683,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-deep-link" name = "tauri-plugin-deep-link"
version = "2.4.7" version = "2.4.6"
dependencies = [ dependencies = [
"dunce", "dunce",
"plist", "plist",
@@ -6961,7 +6961,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-single-instance" name = "tauri-plugin-single-instance"
version = "2.4.0" version = "2.3.7"
dependencies = [ dependencies = [
"semver", "semver",
"serde", "serde",
-4
View File
@@ -1,9 +1,5 @@
# Changelog # Changelog
## \[2.4.7]
- [`8374e997`](https://github.com/tauri-apps/plugins-workspace/commit/8374e997b82c95516fc0c1f6d665d9fc3b52edf8) ([#3258](https://github.com/tauri-apps/plugins-workspace/pull/3258) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix runtime deep link registration failing on Linux when the app path has spaces.
## \[2.4.6] ## \[2.4.6]
- [`28048039`](https://github.com/tauri-apps/plugins-workspace/commit/28048039496e84b46847c008416d341f1349e30e) ([#3143](https://github.com/tauri-apps/plugins-workspace/pull/3143) by [@Tunglies](https://github.com/tauri-apps/plugins-workspace/../../Tunglies)) Fix clippy warnings. No user facing changes. - [`28048039`](https://github.com/tauri-apps/plugins-workspace/commit/28048039496e84b46847c008416d341f1349e30e) ([#3143](https://github.com/tauri-apps/plugins-workspace/pull/3143) by [@Tunglies](https://github.com/tauri-apps/plugins-workspace/../../Tunglies)) Fix clippy warnings. No user facing changes.
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-deep-link" name = "tauri-plugin-deep-link"
version = "2.4.7" version = "2.4.6"
description = "Set your Tauri application as the default handler for an URL" description = "Set your Tauri application as the default handler for an URL"
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -1,11 +1,5 @@
# Changelog # Changelog
## \[2.2.10]
### Dependencies
- Upgraded to `deep-link-js@2.4.7`
## \[2.2.9] ## \[2.2.9]
### Dependencies ### Dependencies
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "deep-link-example", "name": "deep-link-example",
"private": true, "private": true,
"version": "2.2.10", "version": "2.2.9",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -11,7 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.10.1", "@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-deep-link": "2.4.7" "@tauri-apps/plugin-deep-link": "2.4.6"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.10.0", "@tauri-apps/cli": "2.10.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-deep-link", "name": "@tauri-apps/plugin-deep-link",
"version": "2.4.7", "version": "2.4.6",
"description": "Set your Tauri application as the default handler for an URL", "description": "Set your Tauri application as the default handler for an URL",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
+1 -1
View File
@@ -293,7 +293,7 @@ mod imp {
.unwrap_or_else(|| bin.into_os_string()) .unwrap_or_else(|| bin.into_os_string())
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
let qualified_exec = format!("\"{}\" %u", exec); let qualified_exec = format!("{} %u", exec);
let target = self.app.path().data_dir()?.join("applications"); let target = self.app.path().data_dir()?.join("applications");
-14
View File
@@ -1,19 +1,5 @@
# Changelog # Changelog
## \[2.4.0]
### Dependencies
- Upgraded to `deep-link@2.4.7`
### fix
- [`98e2c11e`](https://github.com/tauri-apps/plugins-workspace/commit/98e2c11eefc3ee562f1ed280efe7e8ea6ff0f3b0) ([#3194](https://github.com/tauri-apps/plugins-workspace/pull/3194) by [@mrquantumoff](https://github.com/tauri-apps/plugins-workspace/../../mrquantumoff)) **Breaking Change:** On Linux, the DBus ID/name will now be `<bundle-id>.SingleInstance` instead of `org.<bundle_id_underscores>.SingleInstance` to follow DBus specifications.
This will break the single-instance mechanism across different app versions if the app was installed multiple times.
Added `dbus_id` builder method, which can be used to restore previous behavior. For a bundle identifier of `com.tauri.my-example` this would be `dbus_id("org.com_tauri_my_example")`.
## \[2.3.7] ## \[2.3.7]
### Dependencies ### Dependencies
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-single-instance" name = "tauri-plugin-single-instance"
version = "2.4.0" version = "2.3.7"
description = "Ensure a single instance of your tauri app is running." description = "Ensure a single instance of your tauri app is running."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -22,7 +22,7 @@ serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.7", optional = true } tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.6", optional = true }
semver = { version = "1", optional = true } semver = { version = "1", optional = true }
[target."cfg(target_os = \"windows\")".dependencies.windows-sys] [target."cfg(target_os = \"windows\")".dependencies.windows-sys]
+25 -6
View File
@@ -10,7 +10,7 @@
)] )]
#![cfg(not(any(target_os = "android", target_os = "ios")))] #![cfg(not(any(target_os = "android", target_os = "ios")))]
use tauri::{plugin::TauriPlugin, AppHandle, Manager, Runtime}; use tauri::{plugin, plugin::TauriPlugin, AppHandle, Manager, RunEvent, Runtime};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[path = "platform_impl/windows.rs"] #[path = "platform_impl/windows.rs"]
@@ -34,6 +34,17 @@ pub fn init<R: Runtime, F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sy
Builder::new().callback(f).build() Builder::new().callback(f).build()
} }
/// Runs the single-instance setup flow with the given app and callback.
/// Use this when you need to run single-instance from your own plugin or app setup.
/// On Linux, pass `dbus_id` (e.g. `Some("com.mycompany.myapp".into())`); on other platforms it is ignored.
pub fn setup<R: Runtime, F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static>(
app: &AppHandle<R>,
callback: F,
dbus_id: Option<String>,
) -> Result<(), ()> {
platform_impl::setup_single_instance(app, Box::new(callback), dbus_id)
}
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) { pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
platform_impl::destroy(manager) platform_impl::destroy(manager)
} }
@@ -89,10 +100,18 @@ impl<R: Runtime> Builder<R> {
} }
pub fn build(self) -> TauriPlugin<R> { pub fn build(self) -> TauriPlugin<R> {
platform_impl::init( let callback = self.callback;
self.callback, let dbus_id = self.dbus_id;
#[cfg(target_os = "linux")] plugin::Builder::new("single-instance")
self.dbus_id, .setup(move |app, _api| {
) let _ = platform_impl::setup_single_instance(app, callback, dbus_id);
Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
destroy(app);
}
})
.build()
} }
} }
@@ -6,10 +6,7 @@
use crate::semver_compat::semver_compat_string; use crate::semver_compat::semver_compat_string;
use crate::SingleInstanceCallback; use crate::SingleInstanceCallback;
use tauri::{ use tauri::{AppHandle, Manager, Runtime};
plugin::{self, TauriPlugin},
AppHandle, Manager, RunEvent, Runtime,
};
use zbus::{blocking::Connection, interface, names::WellKnownName}; use zbus::{blocking::Connection, interface, names::WellKnownName};
struct ConnectionHandle(Connection); struct ConnectionHandle(Connection);
@@ -28,14 +25,12 @@ impl<R: Runtime> SingleInstanceDBus<R> {
struct DBusName(String); struct DBusName(String);
pub fn init<R: Runtime>( pub fn setup_single_instance<R: Runtime>(
app: &AppHandle<R>,
callback: Box<SingleInstanceCallback<R>>, callback: Box<SingleInstanceCallback<R>>,
dbus_id: Option<String>, dbus_id: Option<String>,
) -> TauriPlugin<R> { ) -> Result<(), ()> {
plugin::Builder::new("single-instance")
.setup(move |app, _api| {
let mut dbus_name = dbus_id.unwrap_or_else(|| app.config().identifier.clone()); let mut dbus_name = dbus_id.unwrap_or_else(|| app.config().identifier.clone());
dbus_name.push_str(".SingleInstance");
#[cfg(feature = "semver")] #[cfg(feature = "semver")]
{ {
@@ -43,6 +38,8 @@ pub fn init<R: Runtime>(
dbus_name.push_str(semver_compat_string(&app.package_info().version).as_str()); dbus_name.push_str(semver_compat_string(&app.package_info().version).as_str());
} }
dbus_name.push_str(".SingleInstance");
let mut dbus_path = dbus_name.replace('.', "/").replace('-', "_"); let mut dbus_path = dbus_name.replace('.', "/").replace('-', "_");
if !dbus_path.starts_with('/') { if !dbus_path.starts_with('/') {
dbus_path = format!("/{dbus_path}"); dbus_path = format!("/{dbus_path}");
@@ -91,13 +88,6 @@ pub fn init<R: Runtime>(
app.manage(DBusName(dbus_name)); app.manage(DBusName(dbus_name));
Ok(()) Ok(())
})
.on_event(move |app, event| {
if let RunEvent::Exit = event {
destroy(app);
}
})
.build()
} }
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) { pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
@@ -11,14 +11,13 @@ use std::{
#[cfg(feature = "semver")] #[cfg(feature = "semver")]
use crate::semver_compat::semver_compat_string; use crate::semver_compat::semver_compat_string;
use crate::SingleInstanceCallback; use crate::SingleInstanceCallback;
use tauri::{ use tauri::{AppHandle, Config, Manager, Runtime};
plugin::{self, TauriPlugin},
AppHandle, Config, Manager, RunEvent, Runtime,
};
pub fn init<R: Runtime>(cb: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> { pub fn setup_single_instance<R: Runtime>(
plugin::Builder::new("single-instance") app: &AppHandle<R>,
.setup(|app, _api| { cb: Box<SingleInstanceCallback<R>>,
_dbus_id: Option<String>,
) -> Result<(), ()> {
let socket = socket_path(app.config(), app.package_info()); let socket = socket_path(app.config(), app.package_info());
// Notify the singleton which may or may not exist. // Notify the singleton which may or may not exist.
@@ -43,13 +42,6 @@ pub fn init<R: Runtime>(cb: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
} }
} }
Ok(()) Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
destroy(app);
}
})
.build()
} }
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) { pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
@@ -7,10 +7,7 @@ use crate::semver_compat::semver_compat_string;
use crate::SingleInstanceCallback; use crate::SingleInstanceCallback;
use std::ffi::CStr; use std::ffi::CStr;
use tauri::{ use tauri::{AppHandle, Manager, Runtime};
plugin::{self, TauriPlugin},
AppHandle, Manager, RunEvent, Runtime,
};
use windows_sys::Win32::{ use windows_sys::Win32::{
Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, HWND, LPARAM, LRESULT, WPARAM}, Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, HWND, LPARAM, LRESULT, WPARAM},
System::{ System::{
@@ -51,9 +48,11 @@ impl<R: Runtime> UserData<R> {
} }
} }
pub fn init<R: Runtime>(callback: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> { pub fn setup_single_instance<R: Runtime>(
plugin::Builder::new("single-instance") app: &AppHandle<R>,
.setup(|app, _api| { callback: Box<SingleInstanceCallback<R>>,
_dbus_id: Option<String>,
) -> Result<(), ()> {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut id = app.config().identifier.clone(); let mut id = app.config().identifier.clone();
#[cfg(feature = "semver")] #[cfg(feature = "semver")]
@@ -66,8 +65,7 @@ pub fn init<R: Runtime>(callback: Box<SingleInstanceCallback<R>>) -> TauriPlugin
let window_name = encode_wide(format!("{id}-siw")); let window_name = encode_wide(format!("{id}-siw"));
let mutex_name = encode_wide(format!("{id}-sim")); let mutex_name = encode_wide(format!("{id}-sim"));
let hmutex = let hmutex = unsafe { CreateMutexW(std::ptr::null(), true.into(), mutex_name.as_ptr()) };
unsafe { CreateMutexW(std::ptr::null(), true.into(), mutex_name.as_ptr()) };
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS { if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
unsafe { unsafe {
@@ -107,13 +105,6 @@ pub fn init<R: Runtime>(callback: Box<SingleInstanceCallback<R>>) -> TauriPlugin
} }
Ok(()) Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
destroy(app);
}
})
.build()
} }
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) { pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
+1 -1
View File
@@ -189,7 +189,7 @@ importers:
specifier: ^2.10.1 specifier: ^2.10.1
version: 2.10.1 version: 2.10.1
'@tauri-apps/plugin-deep-link': '@tauri-apps/plugin-deep-link':
specifier: 2.4.7 specifier: 2.4.6
version: link:../.. version: link:../..
devDependencies: devDependencies:
'@tauri-apps/cli': '@tauri-apps/cli':