From 9b2b711a76027af666612f111df7c64ec9d17bd3 Mon Sep 17 00:00:00 2001
From: 4o3F <4o3f@proton.me>
Date: Wed, 28 Feb 2024 09:56:55 +0800
Subject: [PATCH] feat(custom-scheme): add custom scheme functionality (WIP)

---
 .gitignore                                    |   1 +
 backend/Cargo.lock                            |  56 ++++++
 .../.github/workflows/audit.yml               |  26 +++
 .../.github/workflows/format.yml              |  24 +++
 .../.github/workflows/lint.yml                |  27 +++
 .../.github/workflows/release.yml             |  33 ++++
 backend/tauri-plugin-deep-link/.gitignore     |   2 +
 backend/tauri-plugin-deep-link/CHANGELOG.md   |  34 ++++
 backend/tauri-plugin-deep-link/Cargo.toml     |  29 +++
 .../tauri-plugin-deep-link/LICENSE_APACHE-2.0 | 177 +++++++++++++++++
 backend/tauri-plugin-deep-link/LICENSE_MIT    |  21 ++
 backend/tauri-plugin-deep-link/README.md      |  20 ++
 backend/tauri-plugin-deep-link/cliff.toml     |  71 +++++++
 .../tauri-plugin-deep-link/example/Info.plist |  21 ++
 .../tauri-plugin-deep-link/example/main.rs    |  39 ++++
 backend/tauri-plugin-deep-link/renovate.json  |   3 +
 backend/tauri-plugin-deep-link/src/lib.rs     |  63 ++++++
 backend/tauri-plugin-deep-link/src/linux.rs   | 141 ++++++++++++++
 backend/tauri-plugin-deep-link/src/macos.rs   | 184 ++++++++++++++++++
 .../src/template.desktop                      |   7 +
 backend/tauri-plugin-deep-link/src/windows.rs | 145 ++++++++++++++
 backend/tauri/Cargo.toml                      |   1 +
 backend/tauri/Info.plist                      |  19 ++
 backend/tauri/src/main.rs                     |  23 ++-
 src/pages/_layout.tsx                         |   5 +
 25 files changed, 1171 insertions(+), 1 deletion(-)
 create mode 100644 backend/tauri-plugin-deep-link/.github/workflows/audit.yml
 create mode 100644 backend/tauri-plugin-deep-link/.github/workflows/format.yml
 create mode 100644 backend/tauri-plugin-deep-link/.github/workflows/lint.yml
 create mode 100644 backend/tauri-plugin-deep-link/.github/workflows/release.yml
 create mode 100644 backend/tauri-plugin-deep-link/.gitignore
 create mode 100644 backend/tauri-plugin-deep-link/CHANGELOG.md
 create mode 100644 backend/tauri-plugin-deep-link/Cargo.toml
 create mode 100644 backend/tauri-plugin-deep-link/LICENSE_APACHE-2.0
 create mode 100644 backend/tauri-plugin-deep-link/LICENSE_MIT
 create mode 100644 backend/tauri-plugin-deep-link/README.md
 create mode 100644 backend/tauri-plugin-deep-link/cliff.toml
 create mode 100644 backend/tauri-plugin-deep-link/example/Info.plist
 create mode 100644 backend/tauri-plugin-deep-link/example/main.rs
 create mode 100644 backend/tauri-plugin-deep-link/renovate.json
 create mode 100644 backend/tauri-plugin-deep-link/src/lib.rs
 create mode 100644 backend/tauri-plugin-deep-link/src/linux.rs
 create mode 100644 backend/tauri-plugin-deep-link/src/macos.rs
 create mode 100644 backend/tauri-plugin-deep-link/src/template.desktop
 create mode 100644 backend/tauri-plugin-deep-link/src/windows.rs
 create mode 100644 backend/tauri/Info.plist

diff --git a/.gitignore b/.gitignore
index bcddfa6027..51588ec2e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ scripts/_env.sh
 
 tauri.nightly.conf.json
 
+.idea
\ No newline at end of file
diff --git a/backend/Cargo.lock b/backend/Cargo.lock
index 3c00bbfae7..0369865b2b 100644
--- a/backend/Cargo.lock
+++ b/backend/Cargo.lock
@@ -711,6 +711,7 @@ dependencies = [
  "sysproxy",
  "tauri",
  "tauri-build",
+ "tauri-plugin-deep-link",
  "tempfile",
  "thiserror",
  "tokio",
@@ -2353,6 +2354,19 @@ dependencies = [
  "serde_derive",
 ]
 
+[[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 0.4.0",
+ "to_method",
+ "winapi",
+]
+
 [[package]]
 name = "io-lifetimes"
 version = "1.0.11"
@@ -3145,6 +3159,28 @@ dependencies = [
  "objc_id",
 ]
 
+[[package]]
+name = "objc-sys"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459"
+
+[[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"
@@ -5189,6 +5225,20 @@ dependencies = [
  "tauri-utils",
 ]
 
+[[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.52.0",
+ "winreg 0.52.0",
+]
+
 [[package]]
 name = "tauri-runtime"
 version = "0.14.2"
@@ -5449,6 +5499,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.36.0"
diff --git a/backend/tauri-plugin-deep-link/.github/workflows/audit.yml b/backend/tauri-plugin-deep-link/.github/workflows/audit.yml
new file mode 100644
index 0000000000..59952e51f1
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/.github/workflows/audit.yml
@@ -0,0 +1,26 @@
+name: Audit
+
+on:
+  schedule:
+    - cron: "0 0 * * *"
+  push:
+    branches:
+      - main
+    paths:
+      - "**/Cargo.lock"
+      - "**/Cargo.toml"
+  pull_request:
+    branches:
+      - main
+    paths:
+      - "**/Cargo.lock"
+      - "**/Cargo.toml"
+
+jobs:
+  audit:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions-rs/audit-check@v1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/backend/tauri-plugin-deep-link/.github/workflows/format.yml b/backend/tauri-plugin-deep-link/.github/workflows/format.yml
new file mode 100644
index 0000000000..6f7db438b6
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/.github/workflows/format.yml
@@ -0,0 +1,24 @@
+name: Format
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+      - dev
+
+jobs:
+  format:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+
+    steps:
+      - uses: actions/checkout@v4
+      - uses: dtolnay/rust-toolchain@stable
+        with:
+          components: rustfmt
+      - uses: Swatinem/rust-cache@v2
+      - run: cargo fmt --all -- --check
diff --git a/backend/tauri-plugin-deep-link/.github/workflows/lint.yml b/backend/tauri-plugin-deep-link/.github/workflows/lint.yml
new file mode 100644
index 0000000000..26f18d1060
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/.github/workflows/lint.yml
@@ -0,0 +1,27 @@
+name: Clippy
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+      - dev
+
+jobs:
+  clippy:
+    strategy:
+      fail-fast: false
+      matrix:
+        platform: [ubuntu-latest, windows-latest, macos-latest]
+
+    runs-on: ${{ matrix.platform }}
+
+    steps:
+      - uses: actions/checkout@v4
+      - uses: dtolnay/rust-toolchain@stable
+        with:
+          components: clippy
+      - uses: Swatinem/rust-cache@v2
+      - run: cargo clippy --all-targets --all-features -- -D warnings
diff --git a/backend/tauri-plugin-deep-link/.github/workflows/release.yml b/backend/tauri-plugin-deep-link/.github/workflows/release.yml
new file mode 100644
index 0000000000..7358b25eaa
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/.github/workflows/release.yml
@@ -0,0 +1,33 @@
+name: Publish
+
+on:
+  push:
+    tags:
+      - "v*.*.*"
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Release to crates.io
+        run: |
+          cargo login ${{ secrets.CRATES_IO }}
+          cargo publish
+
+      - name: Generate Changelog
+        uses: orhun/git-cliff-action@v2
+        id: git-cliff
+        with:
+          config: cliff.toml
+          args: -vv --latest --strip header
+
+      - name: Create GitHub Release
+        uses: softprops/action-gh-release@v1
+        with:
+          body: ${{ steps.git-cliff.outputs.content }}
diff --git a/backend/tauri-plugin-deep-link/.gitignore b/backend/tauri-plugin-deep-link/.gitignore
new file mode 100644
index 0000000000..4fffb2f89c
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/.gitignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/backend/tauri-plugin-deep-link/CHANGELOG.md b/backend/tauri-plugin-deep-link/CHANGELOG.md
new file mode 100644
index 0000000000..373e11ea21
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/CHANGELOG.md
@@ -0,0 +1,34 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.2] - 2023-08-15
+
+**This plugin will be migrated to https://github.com/tauri-apps/plugins-workspace/.** Therefore, this will likely be the last release in this repository.
+
+### Miscellaneous Tasks
+
+- Update rust crate objc2 to 0.4.0 (#30)
+- Update rust crate objc2 to 0.4.1 (#31)
+
+## [0.1.1] - 2023-04-04
+
+### Bug Fixes
+
+- Info.plist formatting (#22)
+- Fixed inability to focus when launched from a Windows notification. (#27)
+
+### Documentation
+
+- Add env::args getter to example
+
+### Miscellaneous Tasks
+
+- Update rust crate winreg to 0.50.0 (#28)
+- Switch from dirs-next to dirs
+
+## [0.1.0] - 2023-02-27
+
+### Features
+
+- Initial release
diff --git a/backend/tauri-plugin-deep-link/Cargo.toml b/backend/tauri-plugin-deep-link/Cargo.toml
new file mode 100644
index 0000000000..e3282684f2
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "tauri-plugin-deep-link"
+version = "0.1.2"
+authors = ["FabianLars <fabianlars@fabianlars.de>"]
+description = "A Tauri plugin for deep linking support"
+repository = "https://github.com/FabianLars/tauri-plugin-deep-link"
+edition = "2021"
+rust-version = "1.64"
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+include = ["src/**", "Cargo.toml", "LICENSE_*"]
+
+[dependencies]
+dirs = "5"
+log = "0.4"
+once_cell = "1"
+tauri-utils = { version = "1" }
+
+[target.'cfg(windows)'.dependencies]
+interprocess = { version = "1.2", default-features = false }
+windows-sys = { version = "0.52.0", features = [
+  "Win32_Foundation",
+  "Win32_UI_Input_KeyboardAndMouse",
+  "Win32_UI_WindowsAndMessaging",
+] }
+winreg = "0.52.0"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+objc2 = "0.4.1"
diff --git a/backend/tauri-plugin-deep-link/LICENSE_APACHE-2.0 b/backend/tauri-plugin-deep-link/LICENSE_APACHE-2.0
new file mode 100644
index 0000000000..4947287f7b
--- /dev/null
+++ b/backend/tauri-plugin-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/backend/tauri-plugin-deep-link/LICENSE_MIT b/backend/tauri-plugin-deep-link/LICENSE_MIT
new file mode 100644
index 0000000000..01c355f865
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/LICENSE_MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 - Present 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.
diff --git a/backend/tauri-plugin-deep-link/README.md b/backend/tauri-plugin-deep-link/README.md
new file mode 100644
index 0000000000..6ca6f07646
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/README.md
@@ -0,0 +1,20 @@
+# Deep link plugin for Tauri
+
+[![](https://img.shields.io/crates/v/tauri-plugin-deep-link.svg)](https://crates.io/crates/tauri-plugin-deep-link) [![](https://img.shields.io/docsrs/tauri-plugin-deep-link)](https://docs.rs/tauri-plugin-deep-link)
+
+**This plugin will be migrated to https://github.com/tauri-apps/plugins-workspace/.** `0.1.2` will be the last release in this repo.
+
+~~Temporary solution until https://github.com/tauri-apps/tauri/issues/323 lands.~~
+
+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/FabianLars/tauri-plugin-deep-link/tree/main/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!
diff --git a/backend/tauri-plugin-deep-link/cliff.toml b/backend/tauri-plugin-deep-link/cliff.toml
new file mode 100644
index 0000000000..1c186416d6
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/cliff.toml
@@ -0,0 +1,71 @@
+# configuration file for git-cliff
+# see https://github.com/orhun/git-cliff#configuration-file
+
+[changelog]
+# changelog header
+header = """
+# Changelog\n
+All notable changes to this project will be documented in this file.\n
+"""
+# template for the changelog body
+# https://tera.netlify.app/docs/#introduction
+body = """
+{% if version %}\
+    ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
+{% else %}\
+    ## [unreleased]
+{% endif %}\
+{% for group, commits in commits | group_by(attribute="group") %}
+    ### {{ group | upper_first }}
+    {% for commit in commits %}
+        - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
+    {% endfor %}
+{% endfor %}\n
+"""
+# remove the leading and trailing whitespace from the template
+trim = true
+# changelog footer
+footer = ""
+
+[git]
+# parse the commits based on https://www.conventionalcommits.org
+conventional_commits = true
+# filter out the commits that are not conventional
+filter_unconventional = true
+# process each line of a commit as an individual commit
+split_commits = false
+# regex for preprocessing the commit messages
+commit_preprocessors = [
+  # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers
+
+]
+# regex for parsing and grouping commits
+commit_parsers = [
+  { message = "^feat", group = "Features" },
+  { message = "^fix", group = "Bug Fixes" },
+  { message = "^doc", group = "Documentation" },
+  { message = "^perf", group = "Performance" },
+  { message = "^refactor", group = "Refactor" },
+  { message = "^style", group = "Styling" },
+  { message = "^test", group = "Testing" },
+  { message = "^chore\\(release\\): [Pp]repare for", skip = true },
+  { message = "^chore", group = "Miscellaneous Tasks" },
+  { message = "^ci", group = "CI", skip = true },
+  { body = ".*security", group = "Security" },
+]
+# protect breaking changes from being skipped due to matching a skipping commit_parser
+protect_breaking_commits = false
+# filter out the commits that are not matched by commit parsers
+filter_commits = false
+# glob pattern for matching git tags
+tag_pattern = "v[0-9]*"
+# regex for skipping tags
+# skip_tags = "v0.1.0-beta.1"
+# regex for ignoring tags
+ignore_tags = ""
+# sort the tags topologically
+topo_order = false
+# sort the commits inside sections by oldest/newest order
+sort_commits = "oldest"
+# limit the number of commits included in the changelog.
+# limit_commits = 42
diff --git a/backend/tauri-plugin-deep-link/example/Info.plist b/backend/tauri-plugin-deep-link/example/Info.plist
new file mode 100644
index 0000000000..ae51fe52f2
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/example/Info.plist
@@ -0,0 +1,21 @@
+<!-- Add this file next to your tauri.conf.json file -->
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+    <dict>
+        <key>CFBundleURLTypes</key>
+        <array>
+            <dict>
+                <key>CFBundleURLName</key>
+                <!-- Obviously needs to be replaced with your app's bundle identifier -->
+                <string>de.fabianlars.deep-link-test</string>
+                <key>CFBundleURLSchemes</key>
+                <array>
+                    <!-- register the myapp:// and myscheme:// schemes -->
+                    <string>myapp</string>
+                    <string>myscheme</string>
+                </array>
+            </dict>
+        </array>
+    </dict>
+</plist>
diff --git a/backend/tauri-plugin-deep-link/example/main.rs b/backend/tauri-plugin-deep-link/example/main.rs
new file mode 100644
index 0000000000..af1b08a2f9
--- /dev/null
+++ b/backend/tauri-plugin-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/backend/tauri-plugin-deep-link/renovate.json b/backend/tauri-plugin-deep-link/renovate.json
new file mode 100644
index 0000000000..297b7b5d3c
--- /dev/null
+++ b/backend/tauri-plugin-deep-link/renovate.json
@@ -0,0 +1,3 @@
+{
+  "extends": ["config:base", ":semanticCommitTypeAll(chore)"]
+}
diff --git a/backend/tauri-plugin-deep-link/src/lib.rs b/backend/tauri-plugin-deep-link/src/lib.rs
new file mode 100644
index 0000000000..2cdc550d39
--- /dev/null
+++ b/backend/tauri-plugin-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<String> = 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<F: FnMut(String) + Send + 'static>(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<F: FnMut(String) + Send + 'static>(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/backend/tauri-plugin-deep-link/src/linux.rs b/backend/tauri-plugin-deep-link/src/linux.rs
new file mode 100644
index 0000000000..0f849a2908
--- /dev/null
+++ b/backend/tauri-plugin-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<F: FnMut(String) + Send + 'static>(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<F: FnMut(String) + Send + 'static>(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/backend/tauri-plugin-deep-link/src/macos.rs b/backend/tauri-plugin-deep-link/src/macos.rs
new file mode 100644
index 0000000000..b1206e7eda
--- /dev/null
+++ b/backend/tauri-plugin-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<Mutex<Box<dyn FnMut(String) + Send + 'static>>>;
+
+// 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<F: FnMut(String) + Send + 'static>(_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<String> {
+    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<Self> {
+        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<F: FnMut(String) + Send + 'static>(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<AnyObject> =
+            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/backend/tauri-plugin-deep-link/src/template.desktop b/backend/tauri-plugin-deep-link/src/template.desktop
new file mode 100644
index 0000000000..9e790c7893
--- /dev/null
+++ b/backend/tauri-plugin-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/backend/tauri-plugin-deep-link/src/windows.rs b/backend/tauri-plugin-deep-link/src/windows.rs
new file mode 100644
index 0000000000..93fba2bf49
--- /dev/null
+++ b/backend/tauri-plugin-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<F: FnMut(String) + Send + 'static>(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<F: FnMut(String) + Send + 'static>(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::<INPUT>() as i32;
+    unsafe {
+        SendInput(2, [input_down, input_up].as_ptr(), ipsize);
+    };
+}
diff --git a/backend/tauri/Cargo.toml b/backend/tauri/Cargo.toml
index 12d056add5..d686d7d284 100644
--- a/backend/tauri/Cargo.toml
+++ b/backend/tauri/Cargo.toml
@@ -81,6 +81,7 @@ tracing-error = "0.2"
 tracing-log = { version = "0.2" }
 tracing-appender = { version = "0.2", features = ["parking_lot"] }
 single-instance = "0.3.3"
+tauri-plugin-deep-link = { path = "../tauri-plugin-deep-link", version = "0.1.2" }
 
 [target.'cfg(windows)'.dependencies]
 deelevate = "0.2.0"
diff --git a/backend/tauri/Info.plist b/backend/tauri/Info.plist
new file mode 100644
index 0000000000..12dc88d5d5
--- /dev/null
+++ b/backend/tauri/Info.plist
@@ -0,0 +1,19 @@
+<!-- Add this file next to your tauri.conf.json file -->
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+    <dict>
+        <key>CFBundleURLTypes</key>
+        <array>
+            <dict>
+                <key>CFBundleURLName</key>
+                <!-- Obviously needs to be replaced with your app's bundle identifier -->
+                <string>moe.elaina.clash.nyanpasu</string>
+                <key>CFBundleURLSchemes</key>
+                <array>
+                    <string>test-url</string>
+                </array>
+            </dict>
+        </array>
+    </dict>
+</plist>
\ No newline at end of file
diff --git a/backend/tauri/src/main.rs b/backend/tauri/src/main.rs
index 16229e2cda..f1f1614487 100644
--- a/backend/tauri/src/main.rs
+++ b/backend/tauri/src/main.rs
@@ -14,7 +14,7 @@ use crate::{
     config::Config,
     utils::{init, resolve},
 };
-use tauri::{api, SystemTray};
+use tauri::{api, Manager, SystemTray};
 
 rust_i18n::i18n!("../../locales");
 
@@ -45,6 +45,9 @@ fn main() -> std::io::Result<()> {
     #[cfg(feature = "deadlock-detection")]
     deadlock_detection();
 
+    // Should be in first place in order prevent single instance check block everything
+    tauri_plugin_deep_link::prepare("moe.elaina.clash.nyanpasu");
+
     #[cfg(not(feature = "verge-dev"))]
     let instance_id = "clash-nyanpasu";
     #[cfg(feature = "verge-dev")]
@@ -82,6 +85,24 @@ fn main() -> std::io::Result<()> {
         .system_tray(SystemTray::new())
         .setup(|app| {
             resolve::resolve_setup(app);
+            // setup custom scheme
+            let handle = app.handle().clone();
+            log_err!(tauri_plugin_deep_link::register(
+                "clash-nyanpasu",
+                move |request| {
+                    log::info!(target: "app", "scheme request received: {:?}", &request);
+                    resolve::create_window(&handle.clone()); // create window if not exists
+                    println!("Scheme request received: {:?}", &request);
+                    handle
+                        .get_window("main")
+                        .unwrap()
+                        .emit("scheme-request-received", request.clone())
+                        .unwrap();
+                    handle.emit_all("scheme-request-received", request).unwrap();
+                    println!("Message emited");
+                    // handle.emit_all("scheme-request-received", request).unwrap();
+                }
+            ));
             Ok(())
         })
         .on_system_tray_event(core::tray::Tray::on_system_tray_event)
diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx
index ef6c31adc2..6d513fb966 100644
--- a/src/pages/_layout.tsx
+++ b/src/pages/_layout.tsx
@@ -87,6 +87,11 @@ export default function Layout() {
       mutate("getProviders");
     });
 
+    listen("scheme-request-received", (req) => {
+      console.log("Received event");
+      console.log(req);
+    });
+
     setTimeout(() => {
       appWindow.show();
       appWindow.unminimize();