// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT #[cfg(feature = "semver")] use crate::semver_compat::semver_compat_string; use crate::SingleInstanceCallback; use tauri::{ plugin::{self, TauriPlugin}, AppHandle, Manager, RunEvent, Runtime, }; use zbus::{blocking::Connection, interface, names::WellKnownName}; struct ConnectionHandle(Connection); struct SingleInstanceDBus { callback: Box>, app_handle: AppHandle, } #[interface(name = "org.SingleInstance.DBus")] impl SingleInstanceDBus { fn execute_callback(&mut self, argv: Vec, cwd: String) { (self.callback)(&self.app_handle, argv, cwd); } } struct DBusName(String); pub fn init( callback: Box>, dbus_id: Option, ) -> TauriPlugin { plugin::Builder::new("single-instance") .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")] { dbus_name.push('_'); dbus_name.push_str(semver_compat_string(&app.package_info().version).as_str()); } let mut dbus_path = dbus_name.replace('.', "/").replace('-', "_"); if !dbus_path.starts_with('/') { dbus_path = format!("/{dbus_path}"); } let single_instance_dbus = SingleInstanceDBus { callback, app_handle: app.clone(), }; match zbus::blocking::connection::Builder::session() .unwrap() .name(dbus_name.as_str()) .unwrap() .replace_existing_names(false) .allow_name_replacements(false) .serve_at(dbus_path.as_str(), single_instance_dbus) .unwrap() .build() { Ok(connection) => { app.manage(ConnectionHandle(connection)); } Err(zbus::Error::NameTaken) => { if let Ok(connection) = Connection::session() { let _ = connection.call_method( Some(dbus_name.as_str()), dbus_path.as_str(), Some("org.SingleInstance.DBus"), "ExecuteCallback", &( std::env::args().collect::>(), std::env::current_dir() .unwrap_or_default() .to_str() .unwrap_or_default(), ), ); } app.cleanup_before_exit(); std::process::exit(0); } _ => {} } app.manage(DBusName(dbus_name)); Ok(()) }) .on_event(move |app, event| { if let RunEvent::Exit = event { destroy(app); } }) .build() } pub fn destroy>(manager: &M) { if let Some(connection) = manager.try_state::() { if let Some(dbus_name) = manager .try_state::() .and_then(|name| WellKnownName::try_from(name.0.clone()).ok()) { let _ = connection.0.release_name(dbus_name); } } }