From bbce4a29e073a21dec107a1cddb3363c70576c37 Mon Sep 17 00:00:00 2001 From: FabianLars Date: Tue, 15 Aug 2023 20:56:15 +0200 Subject: [PATCH] feat: Migrate deep-link plugin to workspace repo --- Cargo.lock | 102 +++++++++++++- plugins/deep-link/Cargo.toml | 28 ++++ plugins/deep-link/LICENSE.spdx | 20 +++ plugins/deep-link/LICENSE_APACHE-2.0 | 177 ++++++++++++++++++++++++ plugins/deep-link/LICENSE_MIT | 22 +++ plugins/deep-link/README.md | 51 +++++++ plugins/deep-link/banner.png | Bin 0 -> 45169 bytes plugins/deep-link/example/Info.plist | 21 +++ plugins/deep-link/example/main.rs | 39 ++++++ plugins/deep-link/src/lib.rs | 63 +++++++++ plugins/deep-link/src/linux.rs | 141 +++++++++++++++++++ plugins/deep-link/src/macos.rs | 184 +++++++++++++++++++++++++ plugins/deep-link/src/template.desktop | 7 + plugins/deep-link/src/windows.rs | 145 +++++++++++++++++++ 14 files changed, 995 insertions(+), 5 deletions(-) create mode 100644 plugins/deep-link/Cargo.toml create mode 100644 plugins/deep-link/LICENSE.spdx create mode 100644 plugins/deep-link/LICENSE_APACHE-2.0 create mode 100644 plugins/deep-link/LICENSE_MIT create mode 100644 plugins/deep-link/README.md create mode 100644 plugins/deep-link/banner.png create mode 100644 plugins/deep-link/example/Info.plist create mode 100644 plugins/deep-link/example/main.rs create mode 100644 plugins/deep-link/src/lib.rs create mode 100644 plugins/deep-link/src/linux.rs create mode 100644 plugins/deep-link/src/macos.rs create mode 100644 plugins/deep-link/src/template.desktop create mode 100644 plugins/deep-link/src/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 6dc49095..bff20aef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,9 +328,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5904a4d734f0235edf29aab320a14899f3e090446e594ff96508a6215f76f89c" dependencies = [ - "dirs", + "dirs 4.0.0", "thiserror", - "winreg", + "winreg 0.10.1", ] [[package]] @@ -1048,7 +1048,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", ] [[package]] @@ -1072,6 +1081,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -2113,6 +2134,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interprocess" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb" +dependencies = [ + "cfg-if", + "libc", + "rustc_version", + "to_method", + "winapi", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -2835,6 +2869,28 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845" + +[[package]] +name = "objc2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + [[package]] name = "objc_exception" version = "0.1.2" @@ -2918,6 +2974,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3533,7 +3595,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", + "winreg 0.10.1", ] [[package]] @@ -4370,7 +4432,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93abb10fbd11335d31c33a70b2523c0caab348215caa2ce6da04a268c30afcb" dependencies = [ - "dirs", + "dirs 4.0.0", "iota-crypto 0.15.3", "libc", "libsodium-sys", @@ -4663,6 +4725,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tauri-plugin-deep-link" +version = "0.1.2" +dependencies = [ + "dirs 5.0.1", + "interprocess", + "log", + "objc2", + "once_cell", + "tauri-utils", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + [[package]] name = "tauri-plugin-fs-extra" version = "0.0.0" @@ -5039,6 +5115,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "to_method" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" + [[package]] name = "tokio" version = "1.29.1" @@ -5953,6 +6035,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wry" version = "0.24.3" diff --git a/plugins/deep-link/Cargo.toml b/plugins/deep-link/Cargo.toml new file mode 100644 index 00000000..41304d40 --- /dev/null +++ b/plugins/deep-link/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "tauri-plugin-deep-link" +version = "0.1.2" +description = "Set your Tauri application as the default handler for an URL." +authors = [ "FabianLars ", "Tauri Programme within The Commons Conservancy" ] +license.workspace = true +edition.workspace = true +rust-version.workspace = true +readme = "README.md" +include = ["src/**", "Cargo.toml", "LICENSE_*"] + +[dependencies] +log.workspace = true +dirs = "5" +once_cell = "1" +tauri-utils = "1" + +[target.'cfg(windows)'.dependencies] +interprocess = { version = "1.2", default-features = false } +windows-sys = { version = "0.48.0", features = [ + "Win32_Foundation", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_WindowsAndMessaging", +] } +winreg = "0.50.0" + +[target.'cfg(target_os = "macos")'.dependencies] +objc2 = "0.4.1" diff --git a/plugins/deep-link/LICENSE.spdx b/plugins/deep-link/LICENSE.spdx new file mode 100644 index 00000000..b7f201ce --- /dev/null +++ b/plugins/deep-link/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2023, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/plugins/deep-link/LICENSE_APACHE-2.0 b/plugins/deep-link/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/plugins/deep-link/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/plugins/deep-link/LICENSE_MIT b/plugins/deep-link/LICENSE_MIT new file mode 100644 index 00000000..04eda94a --- /dev/null +++ b/plugins/deep-link/LICENSE_MIT @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023 - Present Tauri Apps Contributors +Copyright (c) 2022 - 2023 FabianLars + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/plugins/deep-link/README.md b/plugins/deep-link/README.md new file mode 100644 index 00000000..18ab1a0f --- /dev/null +++ b/plugins/deep-link/README.md @@ -0,0 +1,51 @@ +![plugin-deep-link](banner.png) + +Set your Tauri application as the default handler for an URL. + +> Note: This plugins brings considerable security risks and you should only use it if you know what your are doing. If in doubt, use the default custom protocol implementation. + +## Install + +_This plugin requires a Rust version of at least **1.64**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-deep-link = "0.1.2" +# alternatively with Git: +tauri-plugin-deep-link = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } +``` + +## Usage + +Depending on your use case, for example a `Login with Google` button, you may want to take a look at https://github.com/FabianLars/tauri-plugin-oauth instead. It uses a minimalistic localhost server for the OAuth process instead of custom uri schemes because some oauth providers, like the aforementioned Google, require this setup. Personally, I think it's easier to use too. + +Check out the [`example/`](https://github.com/tauri-apps/plugins-workspace/tree/v1/plugins/deep-link/example) directory for a minimal example. You must copy it into an actual tauri app first! + +## macOS + +In case you're one of the very few people that didn't know this already: macOS hates developers! Not only is that why the macOS implementation took me so long, it also means _you_ have to be a bit more careful if your app targets macOS: + +- Read through the methods' platform-specific notes. +- On macOS you need to register the schemes in a `Info.plist` file at build time, the plugin can't change the schemes at runtime. +- macOS apps are in single-instance by default so this plugin will not manually shut down secondary instances in release mode. + - To make development via `tauri dev` a little bit more pleasant, the plugin will work similar-ish to Linux and Windows _in debug mode_ but you will see secondary instances show on the screen for a split second and the event will trigger twice in the primary instance (one of these events will be an empty string). You still have to install a `.app` bundle you got from `tauri build --debug` for this to work! + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/plugins/deep-link/banner.png b/plugins/deep-link/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..3fe615c16e0417bfbe7ee5e1996d8709cf5fbd8b GIT binary patch literal 45169 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 Mv~FCjxQvbZ9~-R-ng9R* literal 0 HcmV?d00001 diff --git a/plugins/deep-link/example/Info.plist b/plugins/deep-link/example/Info.plist new file mode 100644 index 00000000..ae51fe52 --- /dev/null +++ b/plugins/deep-link/example/Info.plist @@ -0,0 +1,21 @@ + + + + + + CFBundleURLTypes + + + CFBundleURLName + + de.fabianlars.deep-link-test + CFBundleURLSchemes + + + myapp + myscheme + + + + + diff --git a/plugins/deep-link/example/main.rs b/plugins/deep-link/example/main.rs new file mode 100644 index 00000000..af1b08a2 --- /dev/null +++ b/plugins/deep-link/example/main.rs @@ -0,0 +1,39 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +use tauri::Manager; + +fn main() { + // prepare() checks if it's a single instance and tries to send the args otherwise. + // It should always be the first line in your main function (with the exception of loggers or similar) + tauri_plugin_deep_link::prepare("de.fabianlars.deep-link-test"); + // It's expected to use the identifier from tauri.conf.json + // Unfortuenetly getting it is pretty ugly without access to sth that implements `Manager`. + + tauri::Builder::default() + .setup(|app| { + // If you need macOS support this must be called in .setup() ! + // Otherwise this could be called right after prepare() but then you don't have access to tauri APIs + let handle = app.handle(); + tauri_plugin_deep_link::register( + "my-scheme", + move |request| { + dbg!(&request); + handle.emit_all("scheme-request-received", request).unwrap(); + }, + ) + .unwrap(/* If listening to the scheme is optional for your app, you don't want to unwrap here. */); + + // If you also need the url when the primary instance was started by the custom scheme, you currently have to read it yourself + /* + #[cfg(not(target_os = "macos"))] // on macos the plugin handles this (macos doesn't use cli args for the url) + if let Some(url) = std::env::args().nth(1) { + app.emit_all("scheme-request-received", url).unwrap(); + } + */ + + Ok(()) + }) + // .plugin(tauri_plugin_deep_link::init()) // consider adding a js api later + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/plugins/deep-link/src/lib.rs b/plugins/deep-link/src/lib.rs new file mode 100644 index 00000000..2cdc550d --- /dev/null +++ b/plugins/deep-link/src/lib.rs @@ -0,0 +1,63 @@ +use std::io::{ErrorKind, Result}; + +use once_cell::sync::OnceCell; + +#[cfg(target_os = "windows")] +#[path = "windows.rs"] +mod platform_impl; +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +mod platform_impl; +#[cfg(target_os = "macos")] +#[path = "macos.rs"] +mod platform_impl; + +static ID: OnceCell = OnceCell::new(); + +/// This function is meant for use-cases where the default [`prepare()`] function can't be used. +/// +/// # Errors +/// If ID was already set this functions returns an AlreadyExists error. +pub fn set_identifier(identifier: &str) -> Result<()> { + ID.set(identifier.to_string()) + .map_err(|_| ErrorKind::AlreadyExists.into()) +} + +// Consider adding a function to register without starting the listener. + +/// Registers a handler for the given scheme. +/// +/// ## Platform-specific: +/// +/// - **macOS**: On macOS schemes must be defined in an Info.plist file, therefore this function only calls [`listen()`] without registering the scheme. This function can only be called once on macOS. +pub fn register(scheme: &str, handler: F) -> Result<()> { + platform_impl::register(scheme, handler) +} + +/// Starts the event listener without registering any schemes. +/// +/// ## Platform-specific: +/// +/// - **macOS**: This function can only be called once on macOS. +pub fn listen(handler: F) -> Result<()> { + platform_impl::listen(handler) +} + +/// Unregister a previously registered scheme. +/// +/// ## Platform-specific: +/// +/// - **macOS**: This function has no effect on macOS. +pub fn unregister(scheme: &str) -> Result<()> { + platform_impl::unregister(scheme) +} + +/// Checks if current instance is the primary instance. +/// Also sends the URL event data to the primary instance and stops the process afterwards. +/// +/// ## Platform-specific: +/// +/// - **macOS**: Only registers the identifier (only relevant in debug mode). It does not interact with the primary instance and does not exit the app. +pub fn prepare(identifier: &str) { + platform_impl::prepare(identifier) +} diff --git a/plugins/deep-link/src/linux.rs b/plugins/deep-link/src/linux.rs new file mode 100644 index 00000000..0f849a29 --- /dev/null +++ b/plugins/deep-link/src/linux.rs @@ -0,0 +1,141 @@ +use std::{ + fs::{create_dir_all, remove_file, File}, + io::{Error, ErrorKind, Read, Result, Write}, + os::unix::net::{UnixListener, UnixStream}, + process::Command, +}; + +use dirs::data_dir; + +use crate::ID; + +pub fn register(scheme: &str, handler: F) -> Result<()> { + listen(handler)?; + + let mut target = data_dir() + .ok_or_else(|| Error::new(ErrorKind::NotFound, "data directory not found."))? + .join("applications"); + + create_dir_all(&target)?; + + let exe = tauri_utils::platform::current_exe()?; + + let file_name = format!( + "{}-handler.desktop", + exe.file_name() + .ok_or_else(|| Error::new( + ErrorKind::NotFound, + "Couldn't get file name of curent executable.", + ))? + .to_string_lossy() + ); + + target.push(&file_name); + + let mime_types = format!("x-scheme-handler/{};", scheme); + + let mut file = File::create(&target)?; + file.write_all( + format!( + include_str!("template.desktop"), + name = ID + .get() + .expect("Called register() before prepare()") + .split('.') + .last() + .unwrap(), + exec = std::env::var("APPIMAGE").unwrap_or_else(|_| exe.display().to_string()), + mime_types = mime_types + ) + .as_bytes(), + )?; + + target.pop(); + + Command::new("update-desktop-database") + .arg(target) + .status()?; + + Command::new("xdg-mime") + .args(["default", &file_name, scheme]) + .status()?; + + Ok(()) +} + +pub fn unregister(_scheme: &str) -> Result<()> { + let mut target = + data_dir().ok_or_else(|| Error::new(ErrorKind::NotFound, "data directory not found."))?; + + target.push("applications"); + target.push(format!( + "{}-handler.desktop", + tauri_utils::platform::current_exe()? + .file_name() + .ok_or_else(|| Error::new( + ErrorKind::NotFound, + "Couldn't get file name of curent executable.", + ))? + .to_string_lossy() + )); + + remove_file(&target)?; + + Ok(()) +} + +pub fn listen(mut handler: F) -> Result<()> { + std::thread::spawn(move || { + let addr = format!( + "/tmp/{}-deep-link.sock", + ID.get().expect("listen() called before prepare()") + ); + + let listener = UnixListener::bind(addr).expect("Can't create listener"); + + for stream in listener.incoming() { + match stream { + Ok(mut stream) => { + let mut buffer = String::new(); + if let Err(io_err) = stream.read_to_string(&mut buffer) { + log::error!("Error reading incoming connection: {}", io_err.to_string()); + }; + + handler(dbg!(buffer)); + } + Err(err) => { + log::error!("Incoming connection failed: {}", err); + continue; + } + } + } + }); + + Ok(()) +} + +pub fn prepare(identifier: &str) { + let addr = format!("/tmp/{}-deep-link.sock", identifier); + + match UnixStream::connect(&addr) { + Ok(mut stream) => { + if let Err(io_err) = + stream.write_all(std::env::args().nth(1).unwrap_or_default().as_bytes()) + { + log::error!( + "Error sending message to primary instance: {}", + io_err.to_string() + ); + }; + std::process::exit(0); + } + Err(err) => { + log::error!("Error creating socket listener: {}", err.to_string()); + if err.kind() == ErrorKind::ConnectionRefused { + let _ = remove_file(&addr); + } + } + }; + ID.set(identifier.to_string()) + .expect("prepare() called more than once with different identifiers."); +} diff --git a/plugins/deep-link/src/macos.rs b/plugins/deep-link/src/macos.rs new file mode 100644 index 00000000..b1206e7e --- /dev/null +++ b/plugins/deep-link/src/macos.rs @@ -0,0 +1,184 @@ +use std::{ + fs::remove_file, + io::{ErrorKind, Read, Result, Write}, + os::unix::net::{UnixListener, UnixStream}, + sync::Mutex, +}; + +use objc2::{ + class, declare_class, msg_send, msg_send_id, + mutability::Immutable, + rc::Id, + runtime::{AnyObject, NSObject}, + sel, ClassType, +}; +use once_cell::sync::OnceCell; + +use crate::ID; + +type THandler = OnceCell>>; + +// If the Mutex turns out to be a problem, or FnMut turns out to be useless, we can remove the Mutex and turn FnMut into Fn +static HANDLER: THandler = OnceCell::new(); + +pub fn register(_scheme: &str, handler: F) -> Result<()> { + listen(handler)?; + + Ok(()) +} + +pub fn unregister(_scheme: &str) -> Result<()> { + Ok(()) +} + +// kInternetEventClass +const EVENT_CLASS: u32 = 0x4755524c; +// kAEGetURL +const EVENT_GET_URL: u32 = 0x4755524c; + +// Adapted from https://github.com/mrmekon/fruitbasket/blob/aad14e400d710d1d46317c0d8c55ff742bfeaadd/src/osx.rs#L848 +fn parse_url_event(event: *mut AnyObject) -> Option { + if event as u64 == 0u64 { + return None; + } + unsafe { + let class: u32 = msg_send![event, eventClass]; + let id: u32 = msg_send![event, eventID]; + if class != EVENT_CLASS || id != EVENT_GET_URL { + return None; + } + + let subevent: *mut AnyObject = msg_send![event, paramDescriptorForKeyword: 0x2d2d2d2d_u32]; + let nsstring: *mut AnyObject = msg_send![subevent, stringValue]; + let cstr: *const i8 = msg_send![nsstring, UTF8String]; + if !cstr.is_null() { + Some(std::ffi::CStr::from_ptr(cstr).to_string_lossy().to_string()) + } else { + None + } + } +} + +declare_class!( + struct Handler; + + unsafe impl ClassType for Handler { + type Super = NSObject; + type Mutability = Immutable; + const NAME: &'static str = "TauriPluginDeepLinkHandler"; + } + + unsafe impl Handler { + #[method(handleEvent:withReplyEvent:)] + fn handle_event(&self, event: *mut AnyObject, _replace: *const AnyObject) { + let s = parse_url_event(event).unwrap_or_default(); + let mut cb = HANDLER.get().unwrap().lock().unwrap(); + cb(s); + } + } +); + +impl Handler { + pub fn new() -> Id { + let cls = Self::class(); + unsafe { msg_send_id![msg_send_id![cls, alloc], init] } + } +} + +#[cfg(debug_assertions)] +fn secondary_handler(s: String) { + let addr = format!( + "/tmp/{}-deep-link.sock", + ID.get() + .expect("URL event received before prepare() was called") + ); + if let Ok(mut stream) = UnixStream::connect(addr) { + if let Err(io_err) = stream.write_all(s.as_bytes()) { + log::error!( + "Error sending message to primary instance: {}", + io_err.to_string() + ); + }; + } + std::process::exit(0); +} + +pub fn listen(handler: F) -> Result<()> { + #[cfg(debug_assertions)] + let addr = format!( + "/tmp/{}-deep-link.sock", + ID.get().expect("listen() called before prepare()") + ); + + #[cfg(debug_assertions)] + if HANDLER + .set(match UnixStream::connect(&addr) { + Ok(_) => Mutex::new(Box::new(secondary_handler)), + Err(err) => { + log::error!("Error creating socket listener: {}", err.to_string()); + if err.kind() == ErrorKind::ConnectionRefused { + let _ = remove_file(&addr); + } + Mutex::new(Box::new(handler)) + } + }) + .is_err() + { + return Err(std::io::Error::new( + ErrorKind::AlreadyExists, + "Handler was already set", + )); + } + + #[cfg(not(debug_assertions))] + if HANDLER.set(Mutex::new(Box::new(handler))).is_err() { + return Err(std::io::Error::new( + ErrorKind::AlreadyExists, + "Handler was already set", + )); + } + + unsafe { + let event_manager: Id = + msg_send_id![class!(NSAppleEventManager), sharedAppleEventManager]; + + let handler = Handler::new(); + let handler_boxed = Box::into_raw(Box::new(handler)); + + let _: () = msg_send![&event_manager, + setEventHandler: &**handler_boxed + andSelector: sel!(handleEvent:withReplyEvent:) + forEventClass:EVENT_CLASS + andEventID:EVENT_GET_URL]; + } + + #[cfg(debug_assertions)] + std::thread::spawn(move || { + let listener = UnixListener::bind(addr).expect("Can't create listener"); + + for stream in listener.incoming() { + match stream { + Ok(mut stream) => { + let mut buffer = String::new(); + if let Err(io_err) = stream.read_to_string(&mut buffer) { + log::error!("Error reading incoming connection: {}", io_err.to_string()); + }; + + let mut cb = HANDLER.get().unwrap().lock().unwrap(); + cb(buffer); + } + Err(err) => { + log::error!("Incoming connection failed: {}", err); + continue; + } + } + } + }); + + Ok(()) +} + +pub fn prepare(identifier: &str) { + ID.set(identifier.to_string()) + .expect("prepare() called more than once with different identifiers."); +} diff --git a/plugins/deep-link/src/template.desktop b/plugins/deep-link/src/template.desktop new file mode 100644 index 00000000..9e790c78 --- /dev/null +++ b/plugins/deep-link/src/template.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name={name} +Exec={exec} %u +Terminal=false +MimeType={mime_types} +NoDisplay=true \ No newline at end of file diff --git a/plugins/deep-link/src/windows.rs b/plugins/deep-link/src/windows.rs new file mode 100644 index 00000000..0ed3bb42 --- /dev/null +++ b/plugins/deep-link/src/windows.rs @@ -0,0 +1,145 @@ +use std::{ + io::{BufRead, BufReader, Result, Write}, + path::Path, +}; + +use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; +use windows_sys::Win32::UI::{ + Input::KeyboardAndMouse::{SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT}, + WindowsAndMessaging::{AllowSetForegroundWindow, ASFW_ANY}, +}; +use winreg::{enums::HKEY_CURRENT_USER, RegKey}; + +use crate::ID; + +pub fn register(scheme: &str, handler: F) -> Result<()> { + listen(handler)?; + + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let base = Path::new("Software").join("Classes").join(scheme); + + let exe = tauri_utils::platform::current_exe()? + .display() + .to_string() + .replace("\\\\?\\", ""); + + let (key, _) = hkcu.create_subkey(&base)?; + key.set_value( + "", + &format!( + "URL:{}", + ID.get().expect("register() called before prepare()") + ), + )?; + key.set_value("URL Protocol", &"")?; + + let (icon, _) = hkcu.create_subkey(base.join("DefaultIcon"))?; + icon.set_value("", &format!("{},0", &exe))?; + + let (cmd, _) = hkcu.create_subkey(base.join("shell").join("open").join("command"))?; + + cmd.set_value("", &format!("{} \"%1\"", &exe))?; + + Ok(()) +} + +pub fn unregister(scheme: &str) -> Result<()> { + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let base = Path::new("Software").join("Classes").join(scheme); + + hkcu.delete_subkey_all(base)?; + + Ok(()) +} + +pub fn listen(mut handler: F) -> Result<()> { + std::thread::spawn(move || { + let listener = + LocalSocketListener::bind(ID.get().expect("listen() called before prepare()").as_str()) + .expect("Can't create listener"); + + for conn in listener.incoming().filter_map(|c| { + c.map_err(|error| log::error!("Incoming connection failed: {}", error)) + .ok() + }) { + // Listen for the launch arguments + let mut conn = BufReader::new(conn); + let mut buffer = String::new(); + if let Err(io_err) = conn.read_line(&mut buffer) { + log::error!("Error reading incoming connection: {}", io_err.to_string()); + }; + buffer.pop(); + + handler(buffer); + } + }); + + Ok(()) +} + +pub fn prepare(identifier: &str) { + if let Ok(mut conn) = LocalSocketStream::connect(identifier) { + // We are the secondary instance. + // Prep to activate primary instance by allowing another process to take focus. + + // A workaround to allow AllowSetForegroundWindow to succeed - press a key. + // This was originally used by Chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=837796 + dummy_keypress(); + + let primary_instance_pid = conn.peer_pid().unwrap_or(ASFW_ANY); + unsafe { + let success = AllowSetForegroundWindow(primary_instance_pid) != 0; + if !success { + log::warn!("AllowSetForegroundWindow failed."); + } + } + + if let Err(io_err) = conn.write_all(std::env::args().nth(1).unwrap_or_default().as_bytes()) + { + log::error!( + "Error sending message to primary instance: {}", + io_err.to_string() + ); + }; + let _ = conn.write_all(b"\n"); + std::process::exit(0); + }; + ID.set(identifier.to_string()) + .expect("prepare() called more than once with different identifiers."); +} + +/// Send a dummy keypress event so AllowSetForegroundWindow can succeed +fn dummy_keypress() { + let keyboard_input_down = KEYBDINPUT { + wVk: 0, // This doesn't correspond to any actual keyboard key, but should still function for the workaround. + dwExtraInfo: 0, + wScan: 0, + time: 0, + dwFlags: 0, + }; + + let mut keyboard_input_up = keyboard_input_down; + keyboard_input_up.dwFlags = 0x0002; // KEYUP flag + + let input_down_u = INPUT_0 { + ki: keyboard_input_down, + }; + let input_up_u = INPUT_0 { + ki: keyboard_input_up, + }; + + let input_down = INPUT { + r#type: INPUT_KEYBOARD, + Anonymous: input_down_u, + }; + + let input_up = INPUT { + r#type: INPUT_KEYBOARD, + Anonymous: input_up_u, + }; + + let ipsize = std::mem::size_of::() as i32; + unsafe { + SendInput(2, [input_down, input_up].as_ptr(), ipsize); + }; +}