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
pull/1079/head
Wito 1 year ago committed by GitHub
parent a2339195aa
commit dabac0eedf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"single-instance": patch
---
Added implementation for MacOS.

1
Cargo.lock generated

@ -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) {
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);
}
pub fn destroy<R: Runtime, M: Manager<R>>(_manager: &M) {}
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
);
}
}
}

Loading…
Cancel
Save