mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-04-21 11:26:15 +02:00
Co-authored-by: FabianLars <github@fabianlars.de> Co-authored-by: Amr Bashir <github@amrbashir.me>
This commit is contained in:
@@ -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")`.
|
||||
@@ -21,6 +21,7 @@ target/
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
bun.lockb
|
||||
bun.lock
|
||||
|
||||
# rust compiled folders
|
||||
target/
|
||||
|
||||
@@ -59,6 +59,10 @@ fn main() {
|
||||
|
||||
Note that currently, plugins run in the order they were added in to the builder, so make sure that this plugin is registered first.
|
||||
|
||||
## Usage with Flatpak/Snap
|
||||
|
||||
If you use Flatpak/Snap to publish your package and your Tauri identifier doesn't match the package id, set the `DBUS_ID` variable using the builder for the plugin, look at example.
|
||||
|
||||
## Contributing
|
||||
|
||||
PRs accepted. Please make sure to read the Contributing Guide before making a pull request.
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
println!("{}, {argv:?}, {cwd}", app.package_info().name);
|
||||
}))
|
||||
.plugin(
|
||||
tauri_plugin_single_instance::Builder::new()
|
||||
.callback(move |app, argv, cwd| {
|
||||
println!("{}, {argv:?}, {cwd}", app.package_info().name);
|
||||
})
|
||||
.dbus_id("org.Tauri.SIExampleApp".to_owned())
|
||||
.build(),
|
||||
)
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -29,17 +29,70 @@ pub(crate) type SingleInstanceCallback<R> =
|
||||
dyn FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static;
|
||||
|
||||
pub fn init<R: Runtime, F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static>(
|
||||
mut f: F,
|
||||
f: F,
|
||||
) -> TauriPlugin<R> {
|
||||
platform_impl::init(Box::new(move |app, args, cwd| {
|
||||
#[cfg(feature = "deep-link")]
|
||||
if let Some(deep_link) = app.try_state::<tauri_plugin_deep_link::DeepLink<R>>() {
|
||||
deep_link.handle_cli_arguments(args.iter());
|
||||
}
|
||||
f(app, args, cwd)
|
||||
}))
|
||||
Builder::new().callback(f).build()
|
||||
}
|
||||
|
||||
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
|
||||
platform_impl::destroy(manager)
|
||||
}
|
||||
|
||||
pub struct Builder<R: Runtime> {
|
||||
callback: Box<SingleInstanceCallback<R>>,
|
||||
dbus_id: Option<String>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Default for Builder<R> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
callback: Box::new(move |_app, _args, _| {
|
||||
#[cfg(feature = "deep-link")]
|
||||
if let Some(deep_link) = _app.try_state::<tauri_plugin_deep_link::DeepLink<R>>() {
|
||||
deep_link.handle_cli_arguments(_args.iter());
|
||||
}
|
||||
}),
|
||||
dbus_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Builder<R> {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Function to call when a secondary instance was opened by the user and killed by the plugin.
|
||||
/// If the `deep-link` feature is enabled, the plugin triggers the deep-link plugin before executing the callback.
|
||||
pub fn callback<F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static>(
|
||||
mut self,
|
||||
mut f: F,
|
||||
) -> Self {
|
||||
self.callback = Box::new(move |app, args, cwd| {
|
||||
#[cfg(feature = "deep-link")]
|
||||
if let Some(deep_link) = app.try_state::<tauri_plugin_deep_link::DeepLink<R>>() {
|
||||
deep_link.handle_cli_arguments(args.iter());
|
||||
}
|
||||
f(app, args, cwd)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom D-Bus ID, used on Linux. The plugin will append a `.SingleInstance` subname.
|
||||
/// For example `com.mycompany.myapp` will result in the plugin registering its D-Bus service on `com.mycompany.myapp.SingleInstance`.
|
||||
/// Usually you want the same base ID across all components in your app.
|
||||
///
|
||||
/// Defaults to the app's bundle identifier set in tauri.conf.json.
|
||||
pub fn dbus_id(mut self, dbus_id: impl Into<String>) -> Self {
|
||||
self.dbus_id = Some(dbus_id.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> TauriPlugin<R> {
|
||||
platform_impl::init(
|
||||
self.callback,
|
||||
#[cfg(target_os = "linux")]
|
||||
self.dbus_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,9 @@ use crate::semver_compat::semver_compat_string;
|
||||
use crate::SingleInstanceCallback;
|
||||
use tauri::{
|
||||
plugin::{self, TauriPlugin},
|
||||
AppHandle, Config, Manager, RunEvent, Runtime,
|
||||
};
|
||||
use zbus::{
|
||||
blocking::{connection::Builder, Connection},
|
||||
interface,
|
||||
AppHandle, Manager, RunEvent, Runtime,
|
||||
};
|
||||
use zbus::{blocking::Connection, interface, names::WellKnownName};
|
||||
|
||||
struct ConnectionHandle(Connection);
|
||||
|
||||
@@ -29,35 +26,31 @@ impl<R: Runtime> SingleInstanceDBus<R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "semver")]
|
||||
fn dbus_id(config: &Config, version: semver::Version) -> String {
|
||||
let mut id = config.identifier.replace(['.', '-'], "_");
|
||||
id.push('_');
|
||||
id.push_str(semver_compat_string(version).as_str());
|
||||
id
|
||||
}
|
||||
struct DBusName(String);
|
||||
|
||||
#[cfg(not(feature = "semver"))]
|
||||
fn dbus_id(config: &Config) -> String {
|
||||
config.identifier.replace(['.', '-'], "_")
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>(f: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
|
||||
pub fn init<R: Runtime>(
|
||||
callback: Box<SingleInstanceCallback<R>>,
|
||||
dbus_id: Option<String>,
|
||||
) -> TauriPlugin<R> {
|
||||
plugin::Builder::new("single-instance")
|
||||
.setup(|app, _api| {
|
||||
.setup(move |app, _api| {
|
||||
let mut dbus_name = dbus_id.unwrap_or_else(|| app.config().identifier.clone());
|
||||
dbus_name.push_str(".SingleInstance");
|
||||
|
||||
#[cfg(feature = "semver")]
|
||||
let id = dbus_id(app.config(), app.package_info().version.clone());
|
||||
#[cfg(not(feature = "semver"))]
|
||||
let id = dbus_id(app.config());
|
||||
{
|
||||
dbus_name.push('_');
|
||||
dbus_name.push_str(semver_compat_string(&app.package_info().version).as_str());
|
||||
}
|
||||
|
||||
let dbus_path = dbus_name.replace('.', "/");
|
||||
|
||||
let single_instance_dbus = SingleInstanceDBus {
|
||||
callback: f,
|
||||
callback,
|
||||
app_handle: app.clone(),
|
||||
};
|
||||
let dbus_name = format!("org.{id}.SingleInstance");
|
||||
let dbus_path = format!("/org/{id}/SingleInstance");
|
||||
|
||||
match Builder::session()
|
||||
match zbus::blocking::connection::Builder::session()
|
||||
.unwrap()
|
||||
.name(dbus_name.as_str())
|
||||
.unwrap()
|
||||
@@ -92,9 +85,11 @@ pub fn init<R: Runtime>(f: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
app.manage(DBusName(dbus_name));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_event(|app, event| {
|
||||
.on_event(move |app, event| {
|
||||
if let RunEvent::Exit = event {
|
||||
destroy(app);
|
||||
}
|
||||
@@ -104,15 +99,11 @@ pub fn init<R: Runtime>(f: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
|
||||
|
||||
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
|
||||
if let Some(connection) = manager.try_state::<ConnectionHandle>() {
|
||||
#[cfg(feature = "semver")]
|
||||
let id = dbus_id(
|
||||
manager.config(),
|
||||
manager.app_handle().package_info().version.clone(),
|
||||
);
|
||||
#[cfg(not(feature = "semver"))]
|
||||
let id = dbus_id(manager.config());
|
||||
|
||||
let dbus_name = format!("org.{id}.SingleInstance",);
|
||||
let _ = connection.0.release_name(dbus_name);
|
||||
if let Some(dbus_name) = manager
|
||||
.try_state::<DBusName>()
|
||||
.and_then(|name| WellKnownName::try_from(name.0.clone()).ok())
|
||||
{
|
||||
let _ = connection.0.release_name(dbus_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ fn socket_path(config: &Config, _package_info: &tauri::PackageInfo) -> PathBuf {
|
||||
#[cfg(feature = "semver")]
|
||||
let identifier = format!(
|
||||
"{identifier}_{}",
|
||||
semver_compat_string(_package_info.version.clone()),
|
||||
semver_compat_string(&_package_info.version),
|
||||
);
|
||||
|
||||
// Use /tmp as socket path must be shorter than 100 chars.
|
||||
|
||||
@@ -59,7 +59,7 @@ pub fn init<R: Runtime>(callback: Box<SingleInstanceCallback<R>>) -> TauriPlugin
|
||||
#[cfg(feature = "semver")]
|
||||
{
|
||||
id.push('_');
|
||||
id.push_str(semver_compat_string(app.package_info().version.clone()).as_str());
|
||||
id.push_str(semver_compat_string(&app.package_info().version).as_str());
|
||||
}
|
||||
|
||||
let class_name = encode_wide(format!("{id}-sic"));
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/// Takes a version and spits out a String with trailing _x, thus only considering the digits
|
||||
/// relevant regarding semver compatibility
|
||||
pub fn semver_compat_string(version: semver::Version) -> String {
|
||||
pub fn semver_compat_string(version: &semver::Version) -> String {
|
||||
// for pre-release always treat each version separately
|
||||
if !version.pre.is_empty() {
|
||||
return version.to_string().replace(['.', '-'], "_");
|
||||
|
||||
Reference in New Issue
Block a user