mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-04-21 11:26:15 +02:00
feat(single-instance): add setup() function to run flow separately
This commit is contained in:
@@ -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.
|
||||
@@ -10,7 +10,7 @@
|
||||
)]
|
||||
#![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")]
|
||||
#[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()
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
platform_impl::destroy(manager)
|
||||
}
|
||||
@@ -89,10 +100,18 @@ impl<R: Runtime> Builder<R> {
|
||||
}
|
||||
|
||||
pub fn build(self) -> TauriPlugin<R> {
|
||||
platform_impl::init(
|
||||
self.callback,
|
||||
#[cfg(target_os = "linux")]
|
||||
self.dbus_id,
|
||||
)
|
||||
let callback = self.callback;
|
||||
let dbus_id = self.dbus_id;
|
||||
plugin::Builder::new("single-instance")
|
||||
.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::SingleInstanceCallback;
|
||||
use tauri::{
|
||||
plugin::{self, TauriPlugin},
|
||||
AppHandle, Manager, RunEvent, Runtime,
|
||||
};
|
||||
use tauri::{AppHandle, Manager, Runtime};
|
||||
use zbus::{blocking::Connection, interface, names::WellKnownName};
|
||||
|
||||
struct ConnectionHandle(Connection);
|
||||
@@ -28,73 +25,68 @@ impl<R: Runtime> SingleInstanceDBus<R> {
|
||||
|
||||
struct DBusName(String);
|
||||
|
||||
pub fn init<R: Runtime>(
|
||||
pub fn setup_single_instance<R: Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
callback: Box<SingleInstanceCallback<R>>,
|
||||
dbus_id: Option<String>,
|
||||
) -> TauriPlugin<R> {
|
||||
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");
|
||||
) -> Result<(), ()> {
|
||||
let mut dbus_name = dbus_id.unwrap_or_else(|| app.config().identifier.clone());
|
||||
|
||||
#[cfg(feature = "semver")]
|
||||
{
|
||||
dbus_name.push('_');
|
||||
dbus_name.push_str(semver_compat_string(&app.package_info().version).as_str());
|
||||
}
|
||||
#[cfg(feature = "semver")]
|
||||
{
|
||||
dbus_name.push('_');
|
||||
dbus_name.push_str(semver_compat_string(&app.package_info().version).as_str());
|
||||
}
|
||||
dbus_name.push_str(".SingleInstance");
|
||||
|
||||
let dbus_path = dbus_name.replace('.', "/");
|
||||
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(),
|
||||
};
|
||||
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::<Vec<String>>(),
|
||||
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);
|
||||
}
|
||||
})
|
||||
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::<Vec<String>>(),
|
||||
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(())
|
||||
}
|
||||
|
||||
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
|
||||
|
||||
@@ -11,45 +11,37 @@ use std::{
|
||||
#[cfg(feature = "semver")]
|
||||
use crate::semver_compat::semver_compat_string;
|
||||
use crate::SingleInstanceCallback;
|
||||
use tauri::{
|
||||
plugin::{self, TauriPlugin},
|
||||
AppHandle, Config, Manager, RunEvent, Runtime,
|
||||
};
|
||||
use tauri::{AppHandle, Config, Manager, Runtime};
|
||||
|
||||
pub fn init<R: Runtime>(cb: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
|
||||
plugin::Builder::new("single-instance")
|
||||
.setup(|app, _api| {
|
||||
let socket = socket_path(app.config(), app.package_info());
|
||||
pub fn setup_single_instance<R: Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
cb: Box<SingleInstanceCallback<R>>,
|
||||
_dbus_id: Option<String>,
|
||||
) -> Result<(), ()> {
|
||||
let socket = socket_path(app.config(), app.package_info());
|
||||
|
||||
// Notify the singleton which may or may not exist.
|
||||
match notify_singleton(&socket) {
|
||||
Ok(_) => {
|
||||
std::process::exit(0);
|
||||
// Notify the singleton which may or may not exist.
|
||||
match notify_singleton(&socket) {
|
||||
Ok(_) => {
|
||||
std::process::exit(0);
|
||||
}
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
|
||||
// This process claims itself as singleton as likely none exists
|
||||
socket_cleanup(&socket);
|
||||
listen_for_other_instances(&socket, app.clone(), cb);
|
||||
}
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
|
||||
// This process claims itself as singleton as likely none exists
|
||||
socket_cleanup(&socket);
|
||||
listen_for_other_instances(&socket, app.clone(), cb);
|
||||
}
|
||||
_ => {
|
||||
tracing::debug!(
|
||||
"single_instance failed to notify - launching normally: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
tracing::debug!(
|
||||
"single_instance failed to notify - launching normally: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.on_event(|app, event| {
|
||||
if let RunEvent::Exit = event {
|
||||
destroy(app);
|
||||
}
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 std::ffi::CStr;
|
||||
use tauri::{
|
||||
plugin::{self, TauriPlugin},
|
||||
AppHandle, Manager, RunEvent, Runtime,
|
||||
};
|
||||
use tauri::{AppHandle, Manager, Runtime};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, HWND, LPARAM, LRESULT, WPARAM},
|
||||
System::{
|
||||
@@ -51,69 +48,63 @@ impl<R: Runtime> UserData<R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>(callback: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
|
||||
plugin::Builder::new("single-instance")
|
||||
.setup(|app, _api| {
|
||||
#[allow(unused_mut)]
|
||||
let mut id = app.config().identifier.clone();
|
||||
#[cfg(feature = "semver")]
|
||||
{
|
||||
id.push('_');
|
||||
id.push_str(semver_compat_string(&app.package_info().version).as_str());
|
||||
}
|
||||
pub fn setup_single_instance<R: Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
callback: Box<SingleInstanceCallback<R>>,
|
||||
_dbus_id: Option<String>,
|
||||
) -> Result<(), ()> {
|
||||
#[allow(unused_mut)]
|
||||
let mut id = app.config().identifier.clone();
|
||||
#[cfg(feature = "semver")]
|
||||
{
|
||||
id.push('_');
|
||||
id.push_str(semver_compat_string(&app.package_info().version).as_str());
|
||||
}
|
||||
|
||||
let class_name = encode_wide(format!("{id}-sic"));
|
||||
let window_name = encode_wide(format!("{id}-siw"));
|
||||
let mutex_name = encode_wide(format!("{id}-sim"));
|
||||
let class_name = encode_wide(format!("{id}-sic"));
|
||||
let window_name = encode_wide(format!("{id}-siw"));
|
||||
let mutex_name = encode_wide(format!("{id}-sim"));
|
||||
|
||||
let hmutex =
|
||||
unsafe { CreateMutexW(std::ptr::null(), true.into(), mutex_name.as_ptr()) };
|
||||
let hmutex = unsafe { CreateMutexW(std::ptr::null(), true.into(), mutex_name.as_ptr()) };
|
||||
|
||||
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
|
||||
unsafe {
|
||||
let hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr());
|
||||
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
|
||||
unsafe {
|
||||
let hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr());
|
||||
|
||||
if !hwnd.is_null() {
|
||||
let cwd = std::env::current_dir().unwrap_or_default();
|
||||
let cwd = cwd.to_str().unwrap_or_default();
|
||||
if !hwnd.is_null() {
|
||||
let cwd = std::env::current_dir().unwrap_or_default();
|
||||
let cwd = cwd.to_str().unwrap_or_default();
|
||||
|
||||
let args = std::env::args().collect::<Vec<String>>().join("|");
|
||||
let args = std::env::args().collect::<Vec<String>>().join("|");
|
||||
|
||||
let data = format!("{cwd}|{args}\0",);
|
||||
let data = format!("{cwd}|{args}\0",);
|
||||
|
||||
let bytes = data.as_bytes();
|
||||
let cds = COPYDATASTRUCT {
|
||||
dwData: WMCOPYDATA_SINGLE_INSTANCE_DATA,
|
||||
cbData: bytes.len() as _,
|
||||
lpData: bytes.as_ptr() as _,
|
||||
};
|
||||
|
||||
SendMessageW(hwnd, WM_COPYDATA, 0, &cds as *const _ as _);
|
||||
|
||||
app.cleanup_before_exit();
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
app.manage(MutexHandle(hmutex as _));
|
||||
|
||||
let userdata = UserData {
|
||||
app: app.clone(),
|
||||
callback,
|
||||
let bytes = data.as_bytes();
|
||||
let cds = COPYDATASTRUCT {
|
||||
dwData: WMCOPYDATA_SINGLE_INSTANCE_DATA,
|
||||
cbData: bytes.len() as _,
|
||||
lpData: bytes.as_ptr() as _,
|
||||
};
|
||||
let userdata = Box::into_raw(Box::new(userdata));
|
||||
let hwnd = create_event_target_window::<R>(&class_name, &window_name, userdata);
|
||||
app.manage(TargetWindowHandle(hwnd as _));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_event(|app, event| {
|
||||
if let RunEvent::Exit = event {
|
||||
destroy(app);
|
||||
SendMessageW(hwnd, WM_COPYDATA, 0, &cds as *const _ as _);
|
||||
|
||||
app.cleanup_before_exit();
|
||||
std::process::exit(0);
|
||||
}
|
||||
})
|
||||
.build()
|
||||
}
|
||||
} else {
|
||||
app.manage(MutexHandle(hmutex as _));
|
||||
|
||||
let userdata = UserData {
|
||||
app: app.clone(),
|
||||
callback,
|
||||
};
|
||||
let userdata = Box::into_raw(Box::new(userdata));
|
||||
let hwnd = create_event_target_window::<R>(&class_name, &window_name, userdata);
|
||||
app.manage(TargetWindowHandle(hwnd as _));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
|
||||
|
||||
Reference in New Issue
Block a user