mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-06-06 13:53:54 +02:00
feat(single-instance): add MacOs unix domain socket impl (#1035)
* feat(single-instance): add macos implementation * chore(single-instance): test MacOs by adding CLI to example * feat(single-instance): simplify macOS implementation * chore(single-instance): address remarks
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"single-instance": patch
|
||||
---
|
||||
|
||||
Added implementation for MacOS.
|
||||
Generated
+1
@@ -5641,6 +5641,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-cli",
|
||||
"tauri-plugin-single-instance",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "app",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.0-beta.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-xLAL2DNNUJWqHBKvanc3V9bG9kkwtFwc40X/DrfgEKnkajEm79wqnkaT8LUnmbe0WZ8bzBRO1fLIgKlOH6GiCA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tauri": "tauri.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-darwin-x64": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.0.0-beta.3",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.0.0-beta.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-gHcn3jI/4MDXDIlK/4Zz0ftTosgN3OimWlKxEz777QrA1hldrQweYIhdZXkqE9KgoE+u6w80vWIcr0InHAf7Iw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-kRCaukT2IAGMmNuAOUBhdZRlKujTy2lSsdNKmgGEMnzQLKJwWO9Gpq1NmPY7ZVqyXK/X8QnGHuasDEQsSO6B4w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-cpNZOQDotNSdjoZT16s1JtZvnkM0wgLwU39AhKhRCco4KEH3/8G1ngKF9JKalWUN8zDTcuCigEAr37gEv4mLAA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-8q86V6P9bkeoFcnvSsnvOwmKY6ijIN4ueRVXCj5cVpsw392VF9vud1Nq7/l+QDgn9OWbZNNVDl30iyoSuaykBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-L7fokh4aqyV6yDPoeKwFN3Yt0pCAuZMWeP5tOeSBiom1pU7ppKH+4KHeTekNEIecZG+Ah250DkVCdmWS+aRFTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-/crp3K6PathqicVWPj8Kh1120NNVV7nagJ7oZW9OFch7nBS1tmDnSB5k5LgA4yYu+lDKNUREnATMWHL6i0gNeg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-jX1ZT0UQwdBGbpCwlpv2bsLDO7KFMeDJQ/ZZVMfWyjuYrGBG5zhJ2NXwTMkHVnxfvE6BVmnybWcykeSqTATeOw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-UCEZNKocENLX3HYKid4FEbrCMjCX9e58klBIvJKxT8HTjvpgFYDoKccswDNfszLhmineKMlkUvm7j7U0sMh8MQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-O8syGXDHyKN/cv1ktD76dTcbkQ1nNEPhnT1Z+r0GKxNsw4/MyIVglzEcou3aPq0/1MQ0PEGVyG1x0JMaPw7oHQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-YDdF3XWaptjKtKz33sZhC+uNAZwp6QtAmZSRCQQlC1W7uJwLD00/3QF4vO/c6Qm+BGFsazVh1+YmBF1p0kV0rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -12,6 +12,7 @@ serde_json = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-single-instance = { path = "../../../" }
|
||||
tauri-plugin-cli = { path = "../../../../cli" }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { workspace = true }
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_cli::init())
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
println!("{}, {argv:?}, {cwd}", app.package_info().name);
|
||||
}))
|
||||
|
||||
@@ -29,5 +29,17 @@
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"cli": {
|
||||
"description": "Testing single-instance on MacOS",
|
||||
"args": [
|
||||
{
|
||||
"name": "somearg",
|
||||
"index": 1,
|
||||
"takesValue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,119 @@
|
||||
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
use std::{
|
||||
io::{BufWriter, Error, ErrorKind, Read, Write},
|
||||
os::unix::net::{UnixListener, UnixStream},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::SingleInstanceCallback;
|
||||
use tauri::{
|
||||
plugin::{self, TauriPlugin},
|
||||
Manager, Runtime,
|
||||
AppHandle, Config, Manager, RunEvent, Runtime,
|
||||
};
|
||||
pub fn init<R: Runtime>(_f: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
|
||||
plugin::Builder::new("single-instance").build()
|
||||
|
||||
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());
|
||||
|
||||
// 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);
|
||||
}
|
||||
_ => {
|
||||
log::debug!(
|
||||
"single_instance failed to notify - launching normally: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
let socket = socket_path(manager.config());
|
||||
socket_cleanup(&socket);
|
||||
}
|
||||
|
||||
fn socket_path(config: &Config) -> PathBuf {
|
||||
let identifier = config.identifier.replace(['.', '-'].as_ref(), "_");
|
||||
// Use /tmp as socket path must be shorter than 100 chars.
|
||||
PathBuf::from(format!("/tmp/{}_si.sock", identifier))
|
||||
}
|
||||
|
||||
fn socket_cleanup(socket: &PathBuf) {
|
||||
let _ = std::fs::remove_file(socket);
|
||||
}
|
||||
|
||||
fn notify_singleton(socket: &PathBuf) -> Result<(), Error> {
|
||||
let stream = UnixStream::connect(&socket)?;
|
||||
let mut bf = BufWriter::new(&stream);
|
||||
let args_joined = std::env::args().collect::<Vec<String>>().join("\0");
|
||||
bf.write_all(args_joined.as_bytes())?;
|
||||
bf.flush()?;
|
||||
drop(bf);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn listen_for_other_instances<A: Runtime>(
|
||||
socket: &PathBuf,
|
||||
app: AppHandle<A>,
|
||||
mut cb: Box<SingleInstanceCallback<A>>,
|
||||
) {
|
||||
match UnixListener::bind(&socket) {
|
||||
Ok(listener) => {
|
||||
let cwd = std::env::current_dir()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(mut stream) => {
|
||||
let mut s = String::new();
|
||||
match stream.read_to_string(&mut s) {
|
||||
Ok(_) => {
|
||||
let args: Vec<String> =
|
||||
s.split('\0').map(String::from).collect();
|
||||
cb(&app.clone().app_handle(), args, cwd.clone());
|
||||
}
|
||||
Err(e) => log::debug!("single_instance failed to be notified: {e}"),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::debug!("single_instance failed to be notified: {}", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"single_instance failed to listen to other processes - launching normally: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user