From 7a32ccae67d3efcbe20c5748c1f85a23655b9294 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:34:00 +0100 Subject: [PATCH 1/4] fix(desktop): isolate blocking folder reveal from Tokio --- .github/workflows/ci.yml | 6 +- Cargo.lock | 4 +- Cargo.toml | 2 + apps/desktop/src-tauri/src/diagnostics.rs | 5 +- vendor/tauri-plugin-opener/.gitignore | 1 + vendor/tauri-plugin-opener/CAP-CHANGES.md | 33 ++ vendor/tauri-plugin-opener/CHANGELOG.md | 59 ++++ vendor/tauri-plugin-opener/Cargo.toml | 135 ++++++++ vendor/tauri-plugin-opener/LICENSE.spdx | 20 ++ vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 | 177 +++++++++++ vendor/tauri-plugin-opener/LICENSE_MIT | 21 ++ vendor/tauri-plugin-opener/README.md | 143 +++++++++ vendor/tauri-plugin-opener/SECURITY.md | 23 ++ vendor/tauri-plugin-opener/android/.gitignore | 2 + .../android/build.gradle.kts | 40 +++ .../android/proguard-rules.pro | 21 ++ .../android/settings.gradle | 2 + .../android/src/main/AndroidManifest.xml | 3 + .../android/src/main/java/OpenerPlugin.kt | 44 +++ vendor/tauri-plugin-opener/api-iife.js | 26 ++ vendor/tauri-plugin-opener/build.rs | 136 ++++++++ .../tauri-plugin-opener/ios/Package.resolved | 16 + vendor/tauri-plugin-opener/ios/Package.swift | 34 ++ .../ios/Sources/OpenerPlugin.swift | 46 +++ .../permissions/allow-default-urls.toml | 17 + .../autogenerated/commands/open_path.toml | 13 + .../autogenerated/commands/open_url.toml | 13 + .../commands/reveal_item_in_dir.toml | 13 + .../permissions/autogenerated/reference.md | 111 +++++++ .../permissions/default.toml | 10 + vendor/tauri-plugin-opener/src/commands.rs | 158 +++++++++ vendor/tauri-plugin-opener/src/config.rs | 19 ++ vendor/tauri-plugin-opener/src/error.rs | 57 ++++ vendor/tauri-plugin-opener/src/init-iife.js | 19 ++ vendor/tauri-plugin-opener/src/lib.rs | 238 ++++++++++++++ vendor/tauri-plugin-opener/src/open.rs | 61 ++++ .../src/reveal_item_in_dir.rs | 299 ++++++++++++++++++ vendor/tauri-plugin-opener/src/scope.rs | 141 +++++++++ vendor/tauri-plugin-opener/src/scope_entry.rs | 36 +++ 39 files changed, 2198 insertions(+), 6 deletions(-) create mode 100644 vendor/tauri-plugin-opener/.gitignore create mode 100644 vendor/tauri-plugin-opener/CAP-CHANGES.md create mode 100644 vendor/tauri-plugin-opener/CHANGELOG.md create mode 100644 vendor/tauri-plugin-opener/Cargo.toml create mode 100644 vendor/tauri-plugin-opener/LICENSE.spdx create mode 100644 vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 create mode 100644 vendor/tauri-plugin-opener/LICENSE_MIT create mode 100644 vendor/tauri-plugin-opener/README.md create mode 100644 vendor/tauri-plugin-opener/SECURITY.md create mode 100644 vendor/tauri-plugin-opener/android/.gitignore create mode 100644 vendor/tauri-plugin-opener/android/build.gradle.kts create mode 100644 vendor/tauri-plugin-opener/android/proguard-rules.pro create mode 100644 vendor/tauri-plugin-opener/android/settings.gradle create mode 100644 vendor/tauri-plugin-opener/android/src/main/AndroidManifest.xml create mode 100644 vendor/tauri-plugin-opener/android/src/main/java/OpenerPlugin.kt create mode 100644 vendor/tauri-plugin-opener/api-iife.js create mode 100644 vendor/tauri-plugin-opener/build.rs create mode 100644 vendor/tauri-plugin-opener/ios/Package.resolved create mode 100644 vendor/tauri-plugin-opener/ios/Package.swift create mode 100644 vendor/tauri-plugin-opener/ios/Sources/OpenerPlugin.swift create mode 100644 vendor/tauri-plugin-opener/permissions/allow-default-urls.toml create mode 100644 vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_path.toml create mode 100644 vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_url.toml create mode 100644 vendor/tauri-plugin-opener/permissions/autogenerated/commands/reveal_item_in_dir.toml create mode 100644 vendor/tauri-plugin-opener/permissions/autogenerated/reference.md create mode 100644 vendor/tauri-plugin-opener/permissions/default.toml create mode 100644 vendor/tauri-plugin-opener/src/commands.rs create mode 100644 vendor/tauri-plugin-opener/src/config.rs create mode 100644 vendor/tauri-plugin-opener/src/error.rs create mode 100644 vendor/tauri-plugin-opener/src/init-iife.js create mode 100644 vendor/tauri-plugin-opener/src/lib.rs create mode 100644 vendor/tauri-plugin-opener/src/open.rs create mode 100644 vendor/tauri-plugin-opener/src/reveal_item_in_dir.rs create mode 100644 vendor/tauri-plugin-opener/src/scope.rs create mode 100644 vendor/tauri-plugin-opener/src/scope_entry.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1bdd0e32d7..303be58e72d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,7 @@ jobs: - 'scripts/run-gpui-build*' - 'scripts/sync-desktop-versions.mjs' - 'scripts/verify-gpui-release-inputs.mjs' + - 'vendor/tauri-plugin-opener/**' - 'Cargo.toml' - 'Cargo.lock' - 'bun.lock' @@ -63,6 +64,7 @@ jobs: - 'apps/desktop-gpui/**' - 'crates/**' - 'apps/desktop/src-tauri/**' + - 'vendor/tauri-plugin-opener/**' - 'Cargo.toml' - 'Cargo.lock' tauri-plugins: @@ -222,7 +224,7 @@ jobs: - name: Test desktop installation and storage helpers shell: bash - run: cargo test --locked -p cap-utils -p cap-cli-install --lib + run: cargo test --locked -p cap-utils -p cap-cli-install -p tauri-plugin-opener --lib - name: Build desktop binaries shell: bash @@ -318,7 +320,7 @@ jobs: run: | node --test scripts/linux-bundle-config.test.mjs scripts/finalize-linux-appimage.test.mjs bun run --cwd apps/desktop vitest run scripts/prepare.test.js - cargo test --locked -p cap-utils -p cap-cli-install --lib + cargo test --locked -p cap-utils -p cap-cli-install -p tauri-plugin-opener --lib cargo test --locked -p cap --bin cap record::tests cargo test --locked -p cap-recording --lib -- --test-threads=1 cargo test --locked -p cap-enc-ffmpeg diff --git a/Cargo.lock b/Cargo.lock index ffb628ea4f4..90c88f49856 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10802,8 +10802,6 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" dependencies = [ "dunce", "glob", @@ -10815,7 +10813,9 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", + "tempfile", "thiserror 2.0.16", + "tokio", "url", "windows 0.61.3", "zbus", diff --git a/Cargo.toml b/Cargo.toml index 8a9be0ffb64..2749ec83681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "apps/desktop/src-tauri", "crates/*", "crates/workspace-hack", + "vendor/tauri-plugin-opener", ] [workspace.dependencies] @@ -132,6 +133,7 @@ cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8" } # wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "cd41a6e32a6239b65d1cecbeccde6a43a100914a" } wgpu-hal = { path = "vendor/wgpu-hal" } tao = { path = "vendor/tao" } +tauri-plugin-opener = { path = "vendor/tauri-plugin-opener" } # https://github.com/CapSoftware/reqwest/commit/9b5ecbd5210a9510fde766015cabb724c1e70d2e reqwest = { git = "https://github.com/CapSoftware/reqwest", rev = "9b5ecbd5210a9510fde766015cabb724c1e70d2e" } diff --git a/apps/desktop/src-tauri/src/diagnostics.rs b/apps/desktop/src-tauri/src/diagnostics.rs index 286023f0add..761b4375e3a 100644 --- a/apps/desktop/src-tauri/src/diagnostics.rs +++ b/apps/desktop/src-tauri/src/diagnostics.rs @@ -548,8 +548,9 @@ pub async fn upload_diagnostic_report(app: AppHandle, report_path: String) -> Re pub async fn reveal_diagnostic_report(app: AppHandle, report_path: String) -> Result<(), String> { let path = validate_report_path(&app, &report_path)?; - app.opener() - .reveal_item_in_dir(&path) + tokio::task::spawn_blocking(move || app.opener().reveal_item_in_dir(&path)) + .await + .map_err(|e| format!("Failed to reveal diagnostic report: {e}"))? .map_err(|e| format!("Failed to reveal diagnostic report: {e}")) } diff --git a/vendor/tauri-plugin-opener/.gitignore b/vendor/tauri-plugin-opener/.gitignore new file mode 100644 index 00000000000..53a0a824d96 --- /dev/null +++ b/vendor/tauri-plugin-opener/.gitignore @@ -0,0 +1 @@ +/permissions/schemas/ diff --git a/vendor/tauri-plugin-opener/CAP-CHANGES.md b/vendor/tauri-plugin-opener/CAP-CHANGES.md new file mode 100644 index 00000000000..1f0056ea099 --- /dev/null +++ b/vendor/tauri-plugin-opener/CAP-CHANGES.md @@ -0,0 +1,33 @@ +# Cap changes to tauri-plugin-opener + +Vendored from the crates.io `tauri-plugin-opener` 2.5.0 archive, SHA-256 +`786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5`. +The archive identifies upstream commit +`2371804172fa852ef3c127b2398c54bc35f74950` in +[tauri-apps/plugins-workspace](https://github.com/tauri-apps/plugins-workspace/tree/2371804172fa852ef3c127b2398c54bc35f74950/plugins/opener). + +The async `reveal_item_in_dir` IPC command previously called the synchronous +reveal implementation on a Tokio worker. On Linux, blocking zbus can enter +its own Tokio runtime and panic. The command now awaits a blocking worker +and propagates both backend errors and worker failures. Its name, arguments, +permission definitions, path canonicalization, and native backends are unchanged. +All existing JavaScript callers retain the same IPC and permission boundary. + +Tests cover both Tokio runtime flavors, exact path forwarding, backend errors, +worker panics, validation of all paths, and the real Linux zbus backend with the +Tokio feature enabled. The Linux test permits a missing session bus or file +manager; it does not establish successful graphical file selection. + +The workspace uses this source through `[patch.crates-io]`. The crate is also a +workspace member so Cargo can run its regression tests. Dev dependencies reuse +versions already in the workspace lockfile. No runtime dependency was upgraded. + +Packaging omits the upstream lockfile, original workspace manifest, npm build +configuration, and guest TypeScript sources. The shipped JavaScript bundles are +retained with formatting only. One trailing space in the Android build file is +removed. Rust source is rustfmt-formatted, and +the build regenerates the ignored permission schema. Both upstream licenses and +all platform source files are retained. + +Current upstream still has the direct synchronous command call. Revisit this +patch when upstream supplies an equivalent async boundary and retain the tests. diff --git a/vendor/tauri-plugin-opener/CHANGELOG.md b/vendor/tauri-plugin-opener/CHANGELOG.md new file mode 100644 index 00000000000..94c5c1bc0c3 --- /dev/null +++ b/vendor/tauri-plugin-opener/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +## \[2.5.0] + +### enhance + +- [`b8056f48`](https://github.com/tauri-apps/plugins-workspace/commit/b8056f484c7144af095d4d6ded1e8adbb9b8a865) ([#2897](https://github.com/tauri-apps/plugins-workspace/pull/2897) by [@petersamokhin](https://github.com/tauri-apps/plugins-workspace/../../petersamokhin)) Allow reveal multiple items in the file explorer. + +## \[2.4.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.3.1] + +### feat + +- [`2aec8ff4`](https://github.com/tauri-apps/plugins-workspace/commit/2aec8ff4c41d178ea9804f7b6eff343c726be015) ([#2803](https://github.com/tauri-apps/plugins-workspace/pull/2803) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Add `inAppBrowser` option to open URLs in an in-app browser on Android and iOS. + +## \[2.3.0] + +- [`ce9888a2`](https://github.com/tauri-apps/plugins-workspace/commit/ce9888a2d4c9b449bd2a306f0fa6c76507fd46d3) ([#2762](https://github.com/tauri-apps/plugins-workspace/pull/2762)) Similar to the `fs` plugin the `opener` plugin now supports a `requireLiteralLeadingDot` configuration in `tauri.conf.json`. + +## \[2.2.7] + +- [`6c9e08dc`](https://github.com/tauri-apps/plugins-workspace/commit/6c9e08dccb3ac99fccfce586fa2b69717ba81b52) ([#2695](https://github.com/tauri-apps/plugins-workspace/pull/2695) by [@ShaunSHamilton](https://github.com/tauri-apps/plugins-workspace/../../ShaunSHamilton)) Adjust `open_url` url type to allow `URL` +- [`dde6f3c3`](https://github.com/tauri-apps/plugins-workspace/commit/dde6f3c31c1b79942abb556be31757dc583f509e) ([#2549](https://github.com/tauri-apps/plugins-workspace/pull/2549) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update `windows` crate to 0.61 to align with Tauri 2.5 + +## \[2.2.6] + +- [`1a984659`](https://github.com/tauri-apps/plugins-workspace/commit/1a9846599b6a71faf330845847a30f6bf9735898) ([#2469](https://github.com/tauri-apps/plugins-workspace/pull/2469) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Update `objc2` crate to 0.6. No user facing changes. +- [`71f95c9f`](https://github.com/tauri-apps/plugins-workspace/commit/71f95c9f05b29cf1be586849614c0b007757c15d) ([#2445](https://github.com/tauri-apps/plugins-workspace/pull/2445) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `windows` crate to 0.60 to match Tauri 2.3.0. No user facing changes. + +## \[2.2.5] + +- [`5b821181`](https://github.com/tauri-apps/plugins-workspace/commit/5b8211815825ddae2dcc0c00520e0cfdff002763) ([#2332](https://github.com/tauri-apps/plugins-workspace/pull/2332) by [@betamos](https://github.com/tauri-apps/plugins-workspace/../../betamos)) Fix broken JS commands `opener.openPath` and `opener.openUrl` on mobile. + +## \[2.2.4] + +- [`da5c59e2`](https://github.com/tauri-apps/plugins-workspace/commit/da5c59e2fe879d177e3cfd52fcacce85440423cb) ([#2271](https://github.com/tauri-apps/plugins-workspace/pull/2271) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `zbus` dependency to version 5. No API changes. + +## \[2.2.3] + +- [`a9ac1e3c`](https://github.com/tauri-apps/plugins-workspace/commit/a9ac1e3c939cec4338a9422ef02323c1d4dde6cd) ([#2253](https://github.com/tauri-apps/plugins-workspace/pull/2253) by [@betamos](https://github.com/tauri-apps/plugins-workspace/../../betamos)) Return an error in `open_path` if the file does not exist when opening with default application. + +## \[2.2.2] + +- [`ee0f65de`](https://github.com/tauri-apps/plugins-workspace/commit/ee0f65de5c645c244c5f0b638e0e0aab687cb9bf) ([#2207](https://github.com/tauri-apps/plugins-workspace/pull/2207) by [@universalappfactory](https://github.com/tauri-apps/plugins-workspace/../../universalappfactory)) Fixed OpenerPlugin packagename for android + +## \[2.2.1] + +- [`18dffc9d`](https://github.com/tauri-apps/plugins-workspace/commit/18dffc9dfecaf0c900e233e041d9ca36c92834b5) ([#2189](https://github.com/tauri-apps/plugins-workspace/pull/2189) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix usage on iOS. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.0] + +- [`383e636a`](https://github.com/tauri-apps/plugins-workspace/commit/383e636a8e595aec1300999a8aeb7d9bf8c14632) ([#2019](https://github.com/tauri-apps/plugins-workspace/pull/2019) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Initial Release diff --git a/vendor/tauri-plugin-opener/Cargo.toml b/vendor/tauri-plugin-opener/Cargo.toml new file mode 100644 index 00000000000..35229ce3291 --- /dev/null +++ b/vendor/tauri-plugin-opener/Cargo.toml @@ -0,0 +1,135 @@ +[package] +edition = "2021" +name = "tauri-plugin-opener" +version = "2.5.0" +authors = ["Tauri Programme within The Commons Conservancy"] +build = "build.rs" +links = "tauri-plugin-opener" +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Open files and URLs using their default application." +readme = "README.md" +license = "Apache-2.0 OR MIT" +repository = "https://github.com/tauri-apps/plugins-workspace" + +[package.metadata.docs.rs] +rustc-args = [ + "--cfg", + "docsrs", +] +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[package.metadata.platforms.support.android] +level = "partial" +notes = "Only allows to open URLs via `open`" + +[package.metadata.platforms.support.ios] +level = "partial" +notes = "Only allows to open URLs via `open`" + +[package.metadata.platforms.support.linux] +level = "full" +notes = "" + +[package.metadata.platforms.support.macos] +level = "full" +notes = "" + +[package.metadata.platforms.support.windows] +level = "full" +notes = "" + +[lib] +name = "tauri_plugin_opener" +path = "src/lib.rs" + +[dependencies.dunce] +version = "1" + +[dependencies.glob] +version = "0.3" + +[dependencies.open] +version = "5" +features = ["shellexecute-on-windows"] + +[dependencies.serde] +version = "1" +features = ["derive"] + +[dependencies.serde_json] +version = "1" + +[dependencies.tauri] +version = "2.8.2" +default-features = false + +[dependencies.thiserror] +version = "2" + +[build-dependencies.schemars] +version = "0.8" + +[build-dependencies.serde] +version = "1" +features = ["derive"] + +[build-dependencies.tauri-plugin] +version = "2.4" +features = ["build"] + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies.url] +version = "2" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies.zbus] +version = "5.9" + +[target.'cfg(target_os = "ios")'.dependencies.tauri] +version = "2.8.2" +features = ["wry"] +default-features = false + +[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit] +version = "0.3" +features = [ + "std", + "NSWorkspace", +] +default-features = false + +[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] +version = "0.3" +features = [ + "std", + "NSURL", + "NSArray", + "NSString", +] +default-features = false + +[target."cfg(windows)".dependencies.windows] +version = "0.61" +features = [ + "Win32_Foundation", + "Win32_UI_Shell_Common", + "Win32_UI_WindowsAndMessaging", + "Win32_System_Com", + "Win32_System_Registry", +] + +[dev-dependencies.tokio] +version = "1" +features = ["macros", "rt-multi-thread"] + +[dev-dependencies.tempfile] +version = "3" + +[target.'cfg(target_os = "linux")'.dev-dependencies.zbus] +version = "5.9" +features = ["tokio"] diff --git a/vendor/tauri-plugin-opener/LICENSE.spdx b/vendor/tauri-plugin-opener/LICENSE.spdx new file mode 100644 index 00000000000..cdd0df5ad76 --- /dev/null +++ b/vendor/tauri-plugin-opener/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-2022, 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/vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 b/vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 new file mode 100644 index 00000000000..4947287f7b5 --- /dev/null +++ b/vendor/tauri-plugin-opener/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/vendor/tauri-plugin-opener/LICENSE_MIT b/vendor/tauri-plugin-opener/LICENSE_MIT new file mode 100644 index 00000000000..4d754725663 --- /dev/null +++ b/vendor/tauri-plugin-opener/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +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/vendor/tauri-plugin-opener/README.md b/vendor/tauri-plugin-opener/README.md new file mode 100644 index 00000000000..bcb9265ca48 --- /dev/null +++ b/vendor/tauri-plugin-opener/README.md @@ -0,0 +1,143 @@ +![opener](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/opener/banner.png) + + + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ? | +| iOS | ? | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +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-opener = "2.0.0" +# alternatively with Git: +tauri-plugin-opener = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + + + +```sh +pnpm add @tauri-apps/plugin-opener +# or +npm add @tauri-apps/plugin-opener +# or +yarn add @tauri-apps/plugin-opener +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { openUrl, openPath, revealItemInDir } from '@tauri-apps/plugin-opener' + +// Opens the URL in the default browser +await openUrl('https://example.com') +// Or with a specific browser/app +await openUrl('https://example.com', 'firefox') + +// Opens the path with the system's default app +await openPath('/path/to/file') +// Or with a specific app +await openPath('/path/to/file', 'firefox') + +// Reveal a path with the system's default explorer +await revealItemInDir('/path/to/file') + +// Reveal multiple paths with the system's default explorer +// Note: will be renamed to `revealItemsInDir` in the next major version +await revealItemInDir(['/path/to/file', '/path/to/another/file']) +``` + +### Usage from Rust + +You can also use those APIs from Rust: + +```rust +use tauri_plugin_opener::OpenerExt; + +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .setup(|app| { + let opener = app.opener(); + + // Opens the URL in the default browser + opener.open_url("https://example.com", None::<&str>)?; + // Or with a specific browser/app + opener.open_url("https://example.com", Some("firefox"))?; + + // Opens the path with the system's default app + opener.open_path("/path/to/file", None::<&str>)?; + // Or with a specific app + opener.open_path("/path/to/file", Some("firefox"))?; + + // Reveal a path with the system's default explorer + opener.reveal_item_in_dir("/path/to/file")?; + + // Reveal multiple paths with the system's default explorer + opener.reveal_items_in_dir(["/path/to/file"])?; + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/vendor/tauri-plugin-opener/SECURITY.md b/vendor/tauri-plugin-opener/SECURITY.md new file mode 100644 index 00000000000..4f09bbacd45 --- /dev/null +++ b/vendor/tauri-plugin-opener/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/vendor/tauri-plugin-opener/android/.gitignore b/vendor/tauri-plugin-opener/android/.gitignore new file mode 100644 index 00000000000..c0f21ec2fd9 --- /dev/null +++ b/vendor/tauri-plugin-opener/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/vendor/tauri-plugin-opener/android/build.gradle.kts b/vendor/tauri-plugin-opener/android/build.gradle.kts new file mode 100644 index 00000000000..0c0612a49d4 --- /dev/null +++ b/vendor/tauri-plugin-opener/android/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.opener" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.9.0") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + implementation("androidx.browser:browser:1.8.0") + implementation(project(":tauri-android")) +} diff --git a/vendor/tauri-plugin-opener/android/proguard-rules.pro b/vendor/tauri-plugin-opener/android/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/vendor/tauri-plugin-opener/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/vendor/tauri-plugin-opener/android/settings.gradle b/vendor/tauri-plugin-opener/android/settings.gradle new file mode 100644 index 00000000000..14a752e4336 --- /dev/null +++ b/vendor/tauri-plugin-opener/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/vendor/tauri-plugin-opener/android/src/main/AndroidManifest.xml b/vendor/tauri-plugin-opener/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..9a40236b947 --- /dev/null +++ b/vendor/tauri-plugin-opener/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/vendor/tauri-plugin-opener/android/src/main/java/OpenerPlugin.kt b/vendor/tauri-plugin-opener/android/src/main/java/OpenerPlugin.kt new file mode 100644 index 00000000000..a65a2f52a13 --- /dev/null +++ b/vendor/tauri-plugin-opener/android/src/main/java/OpenerPlugin.kt @@ -0,0 +1,44 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.opener + +import android.app.Activity +import android.content.Intent +import androidx.browser.customtabs.CustomTabsIntent +import app.tauri.annotation.Command +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.Plugin +import androidx.core.net.toUri +import app.tauri.annotation.InvokeArg + +@InvokeArg +class OpenArgs { + lateinit var url: String + var with: String? = null +} + +@TauriPlugin +class OpenerPlugin(private val activity: Activity) : Plugin(activity) { + @Command + fun open(invoke: Invoke) { + try { + val args = invoke.parseArgs(OpenArgs::class.java) + + if (args.with == "inAppBrowser") { + val builder = CustomTabsIntent.Builder() + val intent = builder.build() + intent.launchUrl(activity, args.url.toUri()) + } else { + val intent = Intent(Intent.ACTION_VIEW, args.url.toUri()) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.applicationContext?.startActivity(intent) + } + invoke.resolve() + } catch (ex: Exception) { + invoke.reject(ex.message) + } + } +} diff --git a/vendor/tauri-plugin-opener/api-iife.js b/vendor/tauri-plugin-opener/api-iife.js new file mode 100644 index 00000000000..847e54ea557 --- /dev/null +++ b/vendor/tauri-plugin-opener/api-iife.js @@ -0,0 +1,26 @@ +if ("__TAURI__" in window) { + var __TAURI_PLUGIN_OPENER__ = (function (n) { + "use strict"; + async function e(n, e = {}, r) { + return window.__TAURI_INTERNALS__.invoke(n, e, r); + } + return ( + "function" == typeof SuppressedError && SuppressedError, + (n.openPath = async function (n, r) { + await e("plugin:opener|open_path", { path: n, with: r }); + }), + (n.openUrl = async function (n, r) { + await e("plugin:opener|open_url", { url: n, with: r }); + }), + (n.revealItemInDir = async function (n) { + return e("plugin:opener|reveal_item_in_dir", { + paths: "string" == typeof n ? [n] : n, + }); + }), + n + ); + })({}); + Object.defineProperty(window.__TAURI__, "opener", { + value: __TAURI_PLUGIN_OPENER__, + }); +} diff --git a/vendor/tauri-plugin-opener/build.rs b/vendor/tauri-plugin-opener/build.rs new file mode 100644 index 00000000000..fbad4d3ac35 --- /dev/null +++ b/vendor/tauri-plugin-opener/build.rs @@ -0,0 +1,136 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +#[path = "src/scope_entry.rs"] +#[allow(dead_code)] +mod scope; + +/// Opener scope application. +#[derive(schemars::JsonSchema)] +#[serde(untagged)] +#[allow(unused)] +enum Application { + /// Open in default application. + Default, + /// If true, allow open with any application. + Enable(bool), + /// Allow specific application to open with. + App(String), +} + +impl Default for Application { + fn default() -> Self { + Self::Default + } +} + +/// Opener scope entry. +#[derive(schemars::JsonSchema)] +#[serde(untagged)] +#[allow(unused)] +enum OpenerScopeEntry { + Url { + /// A URL that can be opened by the webview when using the Opener APIs. + /// + /// Wildcards can be used following the UNIX glob pattern. + /// + /// Examples: + /// + /// - "https://*" : allows all HTTPS origin + /// + /// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path + /// + /// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/" + url: String, + /// An application to open this url with, for example: firefox. + #[serde(default)] + app: Application, + }, + Path { + /// A path that can be opened by the webview when using the Opener APIs. + /// + /// The pattern can start with a variable that resolves to a system base directory. + /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, + /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, + /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, + /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`. + path: PathBuf, + /// An application to open this path with, for example: xdg-open. + #[serde(default)] + app: Application, + }, +} + +// Ensure `OpenerScopeEntry` and `scope::EntryRaw` is kept in sync +fn _f() { + match (scope::EntryRaw::Url { + url: String::new(), + app: scope::Application::Enable(true), + }) { + scope::EntryRaw::Url { url, app } => OpenerScopeEntry::Url { + url, + app: match app { + scope::Application::Enable(p) => Application::Enable(p), + scope::Application::App(p) => Application::App(p), + scope::Application::Default => Application::Default, + }, + }, + scope::EntryRaw::Path { path, app } => OpenerScopeEntry::Path { + path, + app: match app { + scope::Application::Enable(p) => Application::Enable(p), + scope::Application::App(p) => Application::App(p), + scope::Application::Default => Application::Default, + }, + }, + }; + match (OpenerScopeEntry::Url { + url: String::new(), + app: Application::Enable(true), + }) { + OpenerScopeEntry::Url { url, app } => scope::EntryRaw::Url { + url, + app: match app { + Application::Enable(p) => scope::Application::Enable(p), + Application::App(p) => scope::Application::App(p), + Application::Default => scope::Application::Default, + }, + }, + OpenerScopeEntry::Path { path, app } => scope::EntryRaw::Path { + path, + app: match app { + Application::Enable(p) => scope::Application::Enable(p), + Application::App(p) => scope::Application::App(p), + Application::Default => scope::Application::Default, + }, + }, + }; +} + +const COMMANDS: &[&str] = &["open_url", "open_path", "reveal_item_in_dir"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .global_scope_schema(schemars::schema_for!(OpenerScopeEntry)) + .build(); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "ios" || target_os == "android"; + alias("desktop", !mobile); + alias("mobile", mobile); +} + +// creates a cfg alias if `has_feature` is true. +// `alias` must be a snake case string. +fn alias(alias: &str, has_feature: bool) { + println!("cargo:rustc-check-cfg=cfg({alias})"); + if has_feature { + println!("cargo:rustc-cfg={alias}"); + } +} diff --git a/vendor/tauri-plugin-opener/ios/Package.resolved b/vendor/tauri-plugin-opener/ios/Package.resolved new file mode 100644 index 00000000000..5f998e0e66b --- /dev/null +++ b/vendor/tauri-plugin-opener/ios/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftRs", + "repositoryURL": "https://github.com/Brendonovich/swift-rs", + "state": { + "branch": null, + "revision": "b5ed223fcdab165bc21219c1925dc1e77e2bef5e", + "version": "1.0.6" + } + } + ] + }, + "version": 1 +} diff --git a/vendor/tauri-plugin-opener/ios/Package.swift b/vendor/tauri-plugin-opener/ios/Package.swift new file mode 100644 index 00000000000..8d06936e614 --- /dev/null +++ b/vendor/tauri-plugin-opener/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-opener", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-opener", + type: .static, + targets: ["tauri-plugin-opener"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-opener", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/vendor/tauri-plugin-opener/ios/Sources/OpenerPlugin.swift b/vendor/tauri-plugin-opener/ios/Sources/OpenerPlugin.swift new file mode 100644 index 00000000000..2311a3b1b74 --- /dev/null +++ b/vendor/tauri-plugin-opener/ios/Sources/OpenerPlugin.swift @@ -0,0 +1,46 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import SafariServices +import SwiftRs +import Tauri +import UIKit +import WebKit + +struct OpenArgs: Decodable { + let url: String + let with: String? +} + +class OpenerPlugin: Plugin { + @objc public func open(_ invoke: Invoke) throws { + do { + let args = try invoke.parseArgs(OpenArgs.self) + if let url = URL(string: args.url) { + if args.with == "inAppBrowser" { + DispatchQueue.main.async { + let safariVC = SFSafariViewController(url: url) + self.manager.viewController?.present(safariVC, animated: true) + } + } else { + if #available(iOS 10, *) { + UIApplication.shared.open(url, options: [:]) + } else { + UIApplication.shared.openURL(url) + } + } + + } + invoke.resolve() + } catch { + invoke.reject(error.localizedDescription) + } + } +} + +@_cdecl("init_plugin_opener") +func initPlugin() -> Plugin { + return OpenerPlugin() +} diff --git a/vendor/tauri-plugin-opener/permissions/allow-default-urls.toml b/vendor/tauri-plugin-opener/permissions/allow-default-urls.toml new file mode 100644 index 00000000000..93495b8970e --- /dev/null +++ b/vendor/tauri-plugin-opener/permissions/allow-default-urls.toml @@ -0,0 +1,17 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "allow-default-urls" +description = "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + +[[permission.scope.allow]] +url = "mailto:*" + +[[permission.scope.allow]] +url = "tel:*" + +[[permission.scope.allow]] +url = "http://*" + +[[permission.scope.allow]] +url = "https://*" diff --git a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_path.toml b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_path.toml new file mode 100644 index 00000000000..ae67b939428 --- /dev/null +++ b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_path.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open-path" +description = "Enables the open_path command without any pre-configured scope." +commands.allow = ["open_path"] + +[[permission]] +identifier = "deny-open-path" +description = "Denies the open_path command without any pre-configured scope." +commands.deny = ["open_path"] diff --git a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_url.toml b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_url.toml new file mode 100644 index 00000000000..f1e694b1b0e --- /dev/null +++ b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_url.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open-url" +description = "Enables the open_url command without any pre-configured scope." +commands.allow = ["open_url"] + +[[permission]] +identifier = "deny-open-url" +description = "Denies the open_url command without any pre-configured scope." +commands.deny = ["open_url"] diff --git a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/reveal_item_in_dir.toml b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/reveal_item_in_dir.toml new file mode 100644 index 00000000000..e669620ffd8 --- /dev/null +++ b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/reveal_item_in_dir.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-reveal-item-in-dir" +description = "Enables the reveal_item_in_dir command without any pre-configured scope." +commands.allow = ["reveal_item_in_dir"] + +[[permission]] +identifier = "deny-reveal-item-in-dir" +description = "Denies the reveal_item_in_dir command without any pre-configured scope." +commands.deny = ["reveal_item_in_dir"] diff --git a/vendor/tauri-plugin-opener/permissions/autogenerated/reference.md b/vendor/tauri-plugin-opener/permissions/autogenerated/reference.md new file mode 100644 index 00000000000..6ad6ba1fbf6 --- /dev/null +++ b/vendor/tauri-plugin-opener/permissions/autogenerated/reference.md @@ -0,0 +1,111 @@ +## Default Permission + +This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application +as well as reveal file in directories using default file explorer + +#### This default permission set includes the following: + +- `allow-open-url` +- `allow-reveal-item-in-dir` +- `allow-default-urls` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`opener:allow-default-urls` + + + +This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application. + +
+ +`opener:allow-open-path` + + + +Enables the open_path command without any pre-configured scope. + +
+ +`opener:deny-open-path` + + + +Denies the open_path command without any pre-configured scope. + +
+ +`opener:allow-open-url` + + + +Enables the open_url command without any pre-configured scope. + +
+ +`opener:deny-open-url` + + + +Denies the open_url command without any pre-configured scope. + +
+ +`opener:allow-reveal-item-in-dir` + + + +Enables the reveal_item_in_dir command without any pre-configured scope. + +
+ +`opener:deny-reveal-item-in-dir` + + + +Denies the reveal_item_in_dir command without any pre-configured scope. + +
diff --git a/vendor/tauri-plugin-opener/permissions/default.toml b/vendor/tauri-plugin-opener/permissions/default.toml new file mode 100644 index 00000000000..846d6e51be0 --- /dev/null +++ b/vendor/tauri-plugin-opener/permissions/default.toml @@ -0,0 +1,10 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application +as well as reveal file in directories using default file explorer""" +permissions = [ + "allow-open-url", + "allow-reveal-item-in-dir", + "allow-default-urls", +] diff --git a/vendor/tauri-plugin-opener/src/commands.rs b/vendor/tauri-plugin-opener/src/commands.rs new file mode 100644 index 00000000000..343aa441a29 --- /dev/null +++ b/vendor/tauri-plugin-opener/src/commands.rs @@ -0,0 +1,158 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::{Path, PathBuf}; + +use tauri::{ + ipc::{CommandScope, GlobalScope}, + AppHandle, Runtime, +}; + +use crate::{scope::Scope, Error, OpenerExt}; + +#[tauri::command] +pub async fn open_url( + app: AppHandle, + command_scope: CommandScope, + global_scope: GlobalScope, + url: String, + with: Option, +) -> crate::Result<()> { + let scope = Scope::new( + &app, + command_scope + .allows() + .iter() + .chain(global_scope.allows()) + .collect(), + command_scope + .denies() + .iter() + .chain(global_scope.denies()) + .collect(), + ); + + if scope.is_url_allowed(&url, with.as_deref()) { + app.opener().open_url(url, with) + } else { + Err(Error::ForbiddenUrl { url, with }) + } +} + +#[tauri::command] +pub async fn open_path( + app: AppHandle, + command_scope: CommandScope, + global_scope: GlobalScope, + path: String, + with: Option, +) -> crate::Result<()> { + let scope = Scope::new( + &app, + command_scope + .allows() + .iter() + .chain(global_scope.allows()) + .collect(), + command_scope + .denies() + .iter() + .chain(global_scope.denies()) + .collect(), + ); + + if scope.is_path_allowed(Path::new(&path), with.as_deref())? { + app.opener().open_path(path, with) + } else { + Err(Error::ForbiddenPath { path, with }) + } +} + +/// TODO: in the next major version, rename to `reveal_items_in_dir` +#[tauri::command] +pub async fn reveal_item_in_dir(paths: Vec) -> crate::Result<()> { + reveal_item_in_dir_with(paths, crate::reveal_items_in_dir).await +} + +async fn reveal_item_in_dir_with( + paths: Vec, + reveal: impl FnOnce(Vec) -> crate::Result<()> + Send + 'static, +) -> crate::Result<()> { + // The Linux backend uses blocking zbus, which may enter its own Tokio runtime. + tauri::async_runtime::spawn_blocking(move || reveal(paths)).await? +} + +#[cfg(test)] +mod tests { + use super::*; + + async fn assert_reveal_can_enter_runtime() { + let paths = vec![ + PathBuf::from("recording with spaces.cap/"), + PathBuf::from("截图.png"), + ]; + let expected = paths.clone(); + reveal_item_in_dir_with(paths, move |actual| { + assert_eq!(actual, expected); + let runtime = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); + runtime.block_on(async { Ok(()) }) + }) + .await + .unwrap(); + } + + #[tokio::test] + async fn reveal_from_current_thread_runtime() { + assert_reveal_can_enter_runtime().await; + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn reveal_from_multi_thread_runtime() { + assert_reveal_can_enter_runtime().await; + } + + #[tokio::test] + async fn preserves_backend_errors() { + let error = reveal_item_in_dir_with(Vec::new(), |_| { + Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "reveal denied").into()) + }) + .await + .unwrap_err(); + assert!(matches!(&error, Error::Io(e) if e.kind() == std::io::ErrorKind::PermissionDenied)); + assert_eq!(error.to_string(), "reveal denied"); + } + + #[tokio::test] + async fn returns_worker_panics_as_errors() { + let error = reveal_item_in_dir_with(Vec::new(), |_| panic!("reveal worker failed")) + .await + .unwrap_err(); + assert!(matches!(error, Error::Tauri(tauri::Error::JoinError(_)))); + } + + #[tokio::test] + async fn validates_every_path_before_revealing() { + let directory = tempfile::tempdir().unwrap(); + let existing = directory.path().join("recording with spaces.cap"); + std::fs::write(&existing, []).unwrap(); + let missing = directory.path().join("missing.png"); + let error = reveal_item_in_dir(vec![existing, missing]) + .await + .unwrap_err(); + assert!(matches!(error, Error::Io(e) if e.kind() == std::io::ErrorKind::NotFound)); + } + + #[cfg(target_os = "linux")] + #[tokio::test] + async fn linux_zbus_backend_from_tokio_runtime() { + let result = reveal_item_in_dir(Vec::new()).await; + // A headless host may lack a session bus or file manager; neither is a worker panic. + assert!(!matches!( + result, + Err(Error::Tauri(tauri::Error::JoinError(_))) + )); + } +} diff --git a/vendor/tauri-plugin-opener/src/config.rs b/vendor/tauri-plugin-opener/src/config.rs new file mode 100644 index 00000000000..db3bae45e51 --- /dev/null +++ b/vendor/tauri-plugin-opener/src/config.rs @@ -0,0 +1,19 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Config { + /// Whether or not paths that contain components that start with a `.` + /// will require that `.` appears literally in the pattern; `*`, `?`, `**`, + /// or `[...]` will not match. This is useful because such files are + /// conventionally considered hidden on Unix systems and it might be + /// desirable to skip them when listing files. + /// + /// Defaults to `true` on Unix systems and `false` on Windows + // dotfiles are not supposed to be exposed by default on unix + pub require_literal_leading_dot: Option, +} diff --git a/vendor/tauri-plugin-opener/src/error.rs b/vendor/tauri-plugin-opener/src/error.rs new file mode 100644 index 00000000000..c5a4dde3cce --- /dev/null +++ b/vendor/tauri-plugin-opener/src/error.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use serde::{Serialize, Serializer}; + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error("unknown program {0}")] + UnknownProgramName(String), + #[error("Not allowed to open path {}{}", .path, .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())] + ForbiddenPath { path: String, with: Option }, + #[error("Not allowed to open url {}{}", .url, .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())] + ForbiddenUrl { url: String, with: Option }, + #[error("API not supported on the current platform")] + UnsupportedPlatform, + #[error(transparent)] + #[cfg(windows)] + Win32Error(#[from] windows::core::Error), + #[error("Path doesn't have a parent: {0}")] + NoParent(PathBuf), + #[cfg(windows)] + #[error("Failed to convert path '{0}' to ITEMIDLIST")] + FailedToConvertPathToItemIdList(PathBuf), + #[error("Failed to convert path to file:// url")] + FailedToConvertPathToFileUrl, + #[error(transparent)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + Zbus(#[from] zbus::Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/vendor/tauri-plugin-opener/src/init-iife.js b/vendor/tauri-plugin-opener/src/init-iife.js new file mode 100644 index 00000000000..26b3118ec47 --- /dev/null +++ b/vendor/tauri-plugin-opener/src/init-iife.js @@ -0,0 +1,19 @@ +!(function () { + "use strict"; + "function" == typeof SuppressedError && SuppressedError, + window.addEventListener("click", function (e) { + if (e.defaultPrevented || 0 !== e.button || e.metaKey || e.altKey) return; + const t = e + .composedPath() + .find((e) => e instanceof Node && "A" === e.nodeName.toUpperCase()); + if (!t || !t.href || ("_blank" !== t.target && !e.ctrlKey && !e.shiftKey)) + return; + const n = new URL(t.href); + n.origin === window.location.origin || + ["http:", "https:", "mailto:", "tel:"].every((e) => n.protocol !== e) || + (e.preventDefault(), + (async function (e, t = {}, n) { + window.__TAURI_INTERNALS__.invoke(e, t, n); + })("plugin:opener|open_url", { url: n })); + }); +})(); diff --git a/vendor/tauri-plugin-opener/src/lib.rs b/vendor/tauri-plugin-opener/src/lib.rs new file mode 100644 index 00000000000..343e915580b --- /dev/null +++ b/vendor/tauri-plugin-opener/src/lib.rs @@ -0,0 +1,238 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::Path; + +use tauri::{plugin::TauriPlugin, Manager, Runtime}; + +#[cfg(mobile)] +use tauri::plugin::PluginHandle; +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.opener"; +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_opener); + +mod commands; +mod config; +mod error; +mod open; +mod reveal_item_in_dir; +mod scope; +mod scope_entry; + +pub use error::Error; +type Result = std::result::Result; + +pub use open::{open_path, open_url}; +pub use reveal_item_in_dir::{reveal_item_in_dir, reveal_items_in_dir}; + +pub struct Opener { + // we use `fn() -> R` to silence the unused generic error + // while keeping this struct `Send + Sync` without requiring `R` to be + #[cfg(not(mobile))] + _marker: std::marker::PhantomData R>, + #[cfg(mobile)] + mobile_plugin_handle: PluginHandle, + require_literal_leading_dot: Option, +} + +impl Opener { + /// Open a url with a default or specific program. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_opener::OpenerExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// // open the given URL on the system default browser + /// app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser". + #[cfg(desktop)] + pub fn open_url(&self, url: impl Into, with: Option>) -> Result<()> { + crate::open::open(url.into(), with.map(Into::into)) + } + + /// Open a url with a default or specific program. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_opener::OpenerExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// // open the given URL on the system default browser + /// app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser". + #[cfg(mobile)] + pub fn open_url(&self, url: impl Into, with: Option>) -> Result<()> { + self.mobile_plugin_handle + .run_mobile_plugin( + "open", + serde_json::json!({ "url": url.into(), "with": with.map(Into::into) }), + ) + .map_err(Into::into) + } + + /// Open a path with a default or specific program. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_opener::OpenerExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// // open the given path on the system default explorer + /// app.opener().open_path("/path/to/file", None::<&str>)?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Android / iOS**: Always opens using default program. + #[cfg(desktop)] + pub fn open_path( + &self, + path: impl Into, + with: Option>, + ) -> Result<()> { + crate::open::open(path.into(), with.map(Into::into)) + } + + /// Open a path with a default or specific program. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_opener::OpenerExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// // open the given path on the system default explorer + /// app.opener().open_path("/path/to/file", None::<&str>)?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Android / iOS**: Always opens using default program. + #[cfg(mobile)] + pub fn open_path( + &self, + path: impl Into, + _with: Option>, + ) -> Result<()> { + self.mobile_plugin_handle + .run_mobile_plugin("open", path.into()) + .map_err(Into::into) + } + + pub fn reveal_item_in_dir>(&self, p: P) -> Result<()> { + reveal_item_in_dir(p) + } + + pub fn reveal_items_in_dir(&self, paths: I) -> Result<()> + where + I: IntoIterator, + P: AsRef, + { + reveal_items_in_dir(paths) + } +} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the opener APIs. +pub trait OpenerExt { + fn opener(&self) -> &Opener; +} + +impl> OpenerExt for T { + fn opener(&self) -> &Opener { + self.state::>().inner() + } +} + +/// The opener plugin Builder. +pub struct Builder { + open_js_links_on_click: bool, +} + +impl Default for Builder { + fn default() -> Self { + Self { + open_js_links_on_click: true, + } + } +} + +impl Builder { + /// Create a new opener plugin Builder. + pub fn new() -> Self { + Self::default() + } + + /// Whether the plugin should inject a JS script to open URLs in default browser + /// when clicking on `` elements that has `_blank` target, or when pressing `Ctrl` or `Shift` while clicking it. + /// + /// Enabled by default for `http:`, `https:`, `mailto:`, `tel:` links. + pub fn open_js_links_on_click(mut self, open: bool) -> Self { + self.open_js_links_on_click = open; + self + } + + /// Build and Initializes the plugin. + pub fn build(self) -> TauriPlugin> { + let mut builder = tauri::plugin::Builder::>::new("opener") + .setup(|app, api| { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "OpenerPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_opener)?; + + app.manage(Opener { + #[cfg(not(mobile))] + _marker: std::marker::PhantomData:: R>, + #[cfg(mobile)] + mobile_plugin_handle: handle, + require_literal_leading_dot: api + .config() + .as_ref() + .and_then(|c| c.require_literal_leading_dot), + }); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + commands::open_url, + commands::open_path, + commands::reveal_item_in_dir, + ]); + + if self.open_js_links_on_click { + builder = builder.js_init_script(include_str!("init-iife.js").to_string()); + } + + builder.build() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin> { + Builder::default().build() +} diff --git a/vendor/tauri-plugin-opener/src/open.rs b/vendor/tauri-plugin-opener/src/open.rs new file mode 100644 index 00000000000..a3d46c508d4 --- /dev/null +++ b/vendor/tauri-plugin-opener/src/open.rs @@ -0,0 +1,61 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Types and functions related to shell. + +use std::{ffi::OsStr, path::Path}; + +pub(crate) fn open, S: AsRef>(path: P, with: Option) -> crate::Result<()> { + match with { + Some(program) => ::open::with_detached(path, program.as_ref()), + None => ::open::that_detached(path), + } + .map_err(Into::into) +} + +/// Opens URL with the program specified in `with`, or system default if `None`. +/// +/// ## Platform-specific: +/// +/// - **Android / iOS**: Always opens using default program. +/// +/// # Examples +/// +/// ```rust,no_run +/// tauri::Builder::default() +/// .setup(|app| { +/// // open the given URL on the system default browser +/// tauri_plugin_opener::open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; +/// Ok(()) +/// }); +/// ``` +pub fn open_url, S: AsRef>(url: P, with: Option) -> crate::Result<()> { + let url = url.as_ref(); + open(url, with) +} + +/// Opens path with the program specified in `with`, or system default if `None`. +/// +/// ## Platform-specific: +/// +/// - **Android / iOS**: Always opens using default program. +/// +/// # Examples +/// +/// ```rust,no_run +/// tauri::Builder::default() +/// .setup(|app| { +/// // open the given URL on the system default explorer +/// tauri_plugin_opener::open_path("/path/to/file", None::<&str>)?; +/// Ok(()) +/// }); +/// ``` +pub fn open_path, S: AsRef>(path: P, with: Option) -> crate::Result<()> { + let path = path.as_ref(); + if with.is_none() { + // Returns an IO error if not exists, and besides `exists()` is a shorthand for `metadata()` + _ = path.metadata()?; + } + open(path, with) +} diff --git a/vendor/tauri-plugin-opener/src/reveal_item_in_dir.rs b/vendor/tauri-plugin-opener/src/reveal_item_in_dir.rs new file mode 100644 index 00000000000..6112fb8b19b --- /dev/null +++ b/vendor/tauri-plugin-opener/src/reveal_item_in_dir.rs @@ -0,0 +1,299 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::Path; + +/// Reveal a path the system's default explorer. +/// +/// ## Platform-specific: +/// +/// - **Android / iOS:** Unsupported. +pub fn reveal_item_in_dir>(path: P) -> crate::Result<()> { + let path = dunce::canonicalize(path.as_ref())?; + + #[cfg(any( + windows, + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + return imp::reveal_items_in_dir(&[path]); + + #[cfg(not(any( + windows, + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )))] + Err(crate::Error::UnsupportedPlatform) +} + +/// Reveal the paths the system's default explorer. +/// +/// ## Platform-specific: +/// +/// - **Android / iOS:** Unsupported. +pub fn reveal_items_in_dir(paths: I) -> crate::Result<()> +where + I: IntoIterator, + P: AsRef, +{ + let mut canonicalized = vec![]; + + for path in paths { + let path = dunce::canonicalize(path.as_ref())?; + canonicalized.push(path); + } + + #[cfg(any( + windows, + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + return imp::reveal_items_in_dir(&canonicalized); + + #[cfg(not(any( + windows, + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )))] + Err(crate::Error::UnsupportedPlatform) +} + +#[cfg(windows)] +mod imp { + use std::collections::HashMap; + use std::path::{Path, PathBuf}; + + use windows::Win32::UI::Shell::Common::ITEMIDLIST; + use windows::{ + core::{w, HSTRING, PCWSTR}, + Win32::{ + Foundation::ERROR_FILE_NOT_FOUND, + System::Com::CoInitialize, + UI::{ + Shell::{ + ILCreateFromPathW, ILFree, SHOpenFolderAndSelectItems, ShellExecuteExW, + SHELLEXECUTEINFOW, + }, + WindowsAndMessaging::SW_SHOWNORMAL, + }, + }, + }; + + pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { + if paths.is_empty() { + return Ok(()); + } + + let mut grouped_paths: HashMap<&Path, Vec<&Path>> = HashMap::new(); + for path in paths { + let parent = path + .parent() + .ok_or_else(|| crate::Error::NoParent(path.to_path_buf()))?; + grouped_paths.entry(parent).or_default().push(path); + } + + let _ = unsafe { CoInitialize(None) }; + + for (parent, to_reveals) in grouped_paths { + let parent_item_id_list = OwnedItemIdList::new(parent)?; + let to_reveals_item_id_list = to_reveals + .iter() + .map(|to_reveal| OwnedItemIdList::new(*to_reveal)) + .collect::>>()?; + if let Err(e) = unsafe { + SHOpenFolderAndSelectItems( + parent_item_id_list.item, + Some( + &to_reveals_item_id_list + .iter() + .map(|item| item.item) + .collect::>(), + ), + 0, + ) + } { + // from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302 + // On some systems, the above call mysteriously fails with "file not + // found" even though the file is there. In these cases, ShellExecute() + // seems to work as a fallback (although it won't select the file). + // + // Note: we only handle the first file here if multiple of are present + if e.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 { + let first_path = to_reveals[0]; + let is_dir = first_path.is_dir(); + let mut info = SHELLEXECUTEINFOW { + cbSize: std::mem::size_of::() as _, + nShow: SW_SHOWNORMAL.0, + lpFile: PCWSTR(parent_item_id_list.hstring.as_ptr()), + lpClass: if is_dir { w!("folder") } else { PCWSTR::null() }, + lpVerb: if is_dir { + w!("explore") + } else { + PCWSTR::null() + }, + ..Default::default() + }; + + unsafe { ShellExecuteExW(&mut info) }?; + } + } + } + + Ok(()) + } + + struct OwnedItemIdList { + hstring: HSTRING, + item: *const ITEMIDLIST, + } + + impl OwnedItemIdList { + fn new(path: &Path) -> crate::Result { + let path_hstring = HSTRING::from(path); + let item_id_list = unsafe { ILCreateFromPathW(&path_hstring) }; + if item_id_list.is_null() { + Err(crate::Error::FailedToConvertPathToItemIdList( + path.to_owned(), + )) + } else { + Ok(Self { + hstring: path_hstring, + item: item_id_list, + }) + } + } + } + + impl Drop for OwnedItemIdList { + fn drop(&mut self) { + if !self.item.is_null() { + unsafe { ILFree(Some(self.item)) }; + } + } + } +} + +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +mod imp { + use super::*; + use std::collections::HashMap; + use std::path::PathBuf; + + pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { + let connection = zbus::blocking::Connection::session()?; + + reveal_with_filemanager1(paths, &connection).or_else(|e| { + // Fallback to opening the directory of the first item if revealing multiple items fails. + if let Some(first_path) = paths.first() { + reveal_with_open_uri_portal(first_path, &connection) + } else { + Err(e) + } + }) + } + + fn reveal_with_filemanager1( + paths: &[PathBuf], + connection: &zbus::blocking::Connection, + ) -> crate::Result<()> { + let uris: Result, _> = paths + .iter() + .map(|path| { + url::Url::from_file_path(path) + .map_err(|_| crate::Error::FailedToConvertPathToFileUrl) + }) + .collect(); + let uris = uris?; + let uri_strs: Vec<&str> = uris.iter().map(|uri| uri.as_str()).collect(); + + #[zbus::proxy( + interface = "org.freedesktop.FileManager1", + default_service = "org.freedesktop.FileManager1", + default_path = "/org/freedesktop/FileManager1" + )] + trait FileManager1 { + async fn ShowItems(&self, name: Vec<&str>, arg2: &str) -> crate::Result<()>; + } + + let proxy = FileManager1ProxyBlocking::new(connection)?; + + proxy.ShowItems(uri_strs, "") + } + + fn reveal_with_open_uri_portal( + path: &Path, + connection: &zbus::blocking::Connection, + ) -> crate::Result<()> { + let uri = url::Url::from_file_path(path) + .map_err(|_| crate::Error::FailedToConvertPathToFileUrl)?; + + #[zbus::proxy( + interface = "org.freedesktop.portal.Desktop", + default_service = "org.freedesktop.portal.OpenURI", + default_path = "/org/freedesktop/portal/desktop" + )] + trait PortalDesktop { + async fn OpenDirectory( + &self, + arg1: &str, + name: &str, + arg3: HashMap<&str, &str>, + ) -> crate::Result<()>; + } + + let proxy = PortalDesktopProxyBlocking::new(connection)?; + + proxy.OpenDirectory("", uri.as_str(), HashMap::new()) + } +} + +#[cfg(target_os = "macos")] +mod imp { + use objc2_app_kit::NSWorkspace; + use objc2_foundation::{NSArray, NSString, NSURL}; + use std::path::PathBuf; + + pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { + unsafe { + let mut urls = Vec::new(); + + for path in paths { + let path = path.to_string_lossy(); + let path = NSString::from_str(&path); + let url = NSURL::fileURLWithPath(&path); + + urls.push(url); + } + + let urls = NSArray::from_retained_slice(&urls); + + let workspace = NSWorkspace::new(); + workspace.activateFileViewerSelectingURLs(&urls); + } + + Ok(()) + } +} diff --git a/vendor/tauri-plugin-opener/src/scope.rs b/vendor/tauri-plugin-opener/src/scope.rs new file mode 100644 index 00000000000..275913d1ba5 --- /dev/null +++ b/vendor/tauri-plugin-opener/src/scope.rs @@ -0,0 +1,141 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + marker::PhantomData, + path::{Path, PathBuf}, + sync::Arc, +}; + +use tauri::{ipc::ScopeObject, utils::acl::Value, AppHandle, Manager, Runtime}; + +use crate::{scope_entry::EntryRaw, Error}; + +pub use crate::scope_entry::Application; + +#[derive(Debug)] +pub enum Entry { + Url { + url: glob::Pattern, + app: Application, + }, + Path { + path: Option, + app: Application, + }, +} + +impl ScopeObject for Entry { + type Error = Error; + + fn deserialize( + app_handle: &AppHandle, + raw: Value, + ) -> std::result::Result { + serde_json::from_value(raw.into()) + .and_then(|raw| { + let entry = match raw { + EntryRaw::Url { url, app } => Entry::Url { + url: glob::Pattern::new(&url) + .map_err(|e| serde::de::Error::custom(e.to_string()))?, + app, + }, + EntryRaw::Path { path, app } => { + let path = match app_handle.path().parse(path) { + Ok(path) => Some(path), + #[cfg(not(target_os = "android"))] + Err(tauri::Error::UnknownPath) => None, + Err(err) => return Err(serde::de::Error::custom(err.to_string())), + }; + + Entry::Path { path, app } + } + }; + + Ok(entry) + }) + .map_err(Into::into) + } +} + +impl Application { + fn matches(&self, a: Option<&str>) -> bool { + match self { + Self::Default => a.is_none(), + Self::Enable(enable) => *enable, + Self::App(program) => Some(program.as_str()) == a, + } + } +} + +impl Entry { + fn path(&self) -> Option { + match self { + Self::Url { .. } => None, + Self::Path { path, .. } => path.clone(), + } + } + + fn matches_url(&self, u: &str, a: Option<&str>) -> bool { + match self { + Self::Url { url, app } => url.matches(u) && app.matches(a), + Self::Path { .. } => false, + } + } + + fn matches_path_program(&self, a: Option<&str>) -> bool { + match self { + Self::Url { .. } => false, + Self::Path { app, .. } => app.matches(a), + } + } +} + +#[derive(Debug)] +pub struct Scope<'a, R: Runtime, M: Manager> { + allowed: Vec<&'a Arc>, + denied: Vec<&'a Arc>, + manager: &'a M, + _marker: PhantomData, +} + +impl<'a, R: Runtime, M: Manager> Scope<'a, R, M> { + pub(crate) fn new( + manager: &'a M, + allowed: Vec<&'a Arc>, + denied: Vec<&'a Arc>, + ) -> Self { + Self { + manager, + allowed, + denied, + _marker: PhantomData, + } + } + + pub fn is_url_allowed(&self, url: &str, with: Option<&str>) -> bool { + let denied = self.denied.iter().any(|e| e.matches_url(url, with)); + if denied { + false + } else { + self.allowed.iter().any(|e| e.matches_url(url, with)) + } + } + + pub fn is_path_allowed(&self, path: &Path, with: Option<&str>) -> crate::Result { + let fs_scope = tauri::fs::Scope::new( + self.manager, + &tauri::utils::config::FsScope::Scope { + allow: self.allowed.iter().filter_map(|e| e.path()).collect(), + deny: self.denied.iter().filter_map(|e| e.path()).collect(), + require_literal_leading_dot: self + .manager + .state::>() + .require_literal_leading_dot, + }, + )?; + + Ok(fs_scope.is_allowed(path) && self.allowed.iter().any(|e| e.matches_path_program(with))) + } +} diff --git a/vendor/tauri-plugin-opener/src/scope_entry.rs b/vendor/tauri-plugin-opener/src/scope_entry.rs new file mode 100644 index 00000000000..cf9004a2b50 --- /dev/null +++ b/vendor/tauri-plugin-opener/src/scope_entry.rs @@ -0,0 +1,36 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub enum Application { + Default, + Enable(bool), + App(String), +} + +impl Default for Application { + fn default() -> Self { + Self::Default + } +} + +#[derive(Deserialize)] +#[serde(untagged, rename_all = "camelCase")] +pub(crate) enum EntryRaw { + Url { + url: String, + #[serde(default)] + app: Application, + }, + Path { + path: PathBuf, + #[serde(default)] + app: Application, + }, +} From 3858e0f26172ffeec300191fa7fb32aa1092bf7b Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:02:02 +0800 Subject: [PATCH 2/4] feat(utils): run blocking native work on a dedicated thread --- crates/utils/Cargo.toml | 3 ++ crates/utils/src/lib.rs | 83 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 622f6ca9421..fe0e2b2e33d 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -38,5 +38,8 @@ workspace-hack = { version = "0.1", path = "../workspace-hack" } tracing-appender = "0.2.3" tempfile = "3" +[target.'cfg(target_os = "linux")'.dev-dependencies] +zbus = { version = "5.18.0", default-features = false, features = ["tokio", "blocking-api"] } + [lints] workspace = true diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 1e510081e11..9a4e9e0149b 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -34,6 +34,25 @@ where tokio::spawn(future.with_current_subscriber().in_current_span()) } +/// Runs blocking native work on a new thread outside every Tokio runtime context. +/// Some blocking APIs, such as zbus on Linux, start their own runtime and panic when +/// called from an async task. +pub async fn run_on_dedicated_thread(name: &str, work: F) -> std::io::Result +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + let (sender, receiver) = tokio::sync::oneshot::channel(); + std::thread::Builder::new() + .name(name.to_string()) + .spawn(move || { + let _ = sender.send(work()); + })?; + receiver + .await + .map_err(|_| std::io::Error::other(format!("{name} thread stopped before finishing"))) +} + pub fn ensure_dir(path: &PathBuf) -> Result { std::fs::create_dir_all(path)?; Ok(path.clone()) @@ -427,3 +446,67 @@ mod tests { assert_eq!(result, "test (2).cap"); } } + +#[cfg(test)] +mod dedicated_thread_tests { + use super::run_on_dedicated_thread; + + async fn assert_work_can_start_its_own_runtime() { + let value = run_on_dedicated_thread("runtime-test", || { + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(async { 7 }) + }) + .await + .unwrap(); + assert_eq!(value, 7); + } + + #[tokio::test] + async fn work_runs_outside_a_current_thread_runtime() { + assert_work_can_start_its_own_runtime().await; + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn work_runs_outside_a_multi_thread_runtime() { + assert_work_can_start_its_own_runtime().await; + } + + #[tokio::test] + async fn work_results_are_returned_unchanged() { + let result: Result<(), std::io::Error> = run_on_dedicated_thread("error-test", || { + Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "denied", + )) + }) + .await + .unwrap(); + let error = result.unwrap_err(); + assert_eq!(error.kind(), std::io::ErrorKind::PermissionDenied); + assert_eq!(error.to_string(), "denied"); + } + + #[tokio::test] + async fn panicking_work_becomes_an_error() { + let error = run_on_dedicated_thread::<(), _>("panic-test", || panic!("worker failed")) + .await + .unwrap_err(); + assert_eq!( + error.to_string(), + "panic-test thread stopped before finishing" + ); + } + + #[cfg(target_os = "linux")] + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn blocking_zbus_runs_from_an_async_task() { + // A headless host may have no session bus; only a thread failure is a regression. + let result = run_on_dedicated_thread("zbus-test", || { + zbus::blocking::Connection::session().map(drop) + }) + .await; + assert!(result.is_ok()); + } +} From 03d6bf9a751ba096f4bd608ddea4a2727cf2fc0d Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:12:49 +0800 Subject: [PATCH 3/4] fix(desktop): reveal items through a dedicated thread command --- .../src-tauri/capabilities/default.json | 3 +- apps/desktop/src-tauri/src/diagnostics.rs | 4 +- apps/desktop/src-tauri/src/lib.rs | 20 ++ .../src/routes/editor/EditorErrorScreen.tsx | 3 +- apps/desktop/src/routes/editor/Header.tsx | 3 +- .../src/routes/screenshot-editor/Header.tsx | 4 +- apps/desktop/src/utils/recording.test.ts | 1 - apps/desktop/src/utils/recording.ts | 3 +- apps/desktop/src/utils/tauri.ts | 3 + vendor/tauri-plugin-opener/.gitignore | 1 - vendor/tauri-plugin-opener/CAP-CHANGES.md | 33 -- vendor/tauri-plugin-opener/CHANGELOG.md | 59 ---- vendor/tauri-plugin-opener/Cargo.toml | 135 -------- vendor/tauri-plugin-opener/LICENSE.spdx | 20 -- vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 | 177 ----------- vendor/tauri-plugin-opener/LICENSE_MIT | 21 -- vendor/tauri-plugin-opener/README.md | 143 --------- vendor/tauri-plugin-opener/SECURITY.md | 23 -- vendor/tauri-plugin-opener/android/.gitignore | 2 - .../android/build.gradle.kts | 40 --- .../android/proguard-rules.pro | 21 -- .../android/settings.gradle | 2 - .../android/src/main/AndroidManifest.xml | 3 - .../android/src/main/java/OpenerPlugin.kt | 44 --- vendor/tauri-plugin-opener/api-iife.js | 26 -- vendor/tauri-plugin-opener/build.rs | 136 -------- .../tauri-plugin-opener/ios/Package.resolved | 16 - vendor/tauri-plugin-opener/ios/Package.swift | 34 -- .../ios/Sources/OpenerPlugin.swift | 46 --- .../permissions/allow-default-urls.toml | 17 - .../autogenerated/commands/open_path.toml | 13 - .../autogenerated/commands/open_url.toml | 13 - .../commands/reveal_item_in_dir.toml | 13 - .../permissions/autogenerated/reference.md | 111 ------- .../permissions/default.toml | 10 - vendor/tauri-plugin-opener/src/commands.rs | 158 --------- vendor/tauri-plugin-opener/src/config.rs | 19 -- vendor/tauri-plugin-opener/src/error.rs | 57 ---- vendor/tauri-plugin-opener/src/init-iife.js | 19 -- vendor/tauri-plugin-opener/src/lib.rs | 238 -------------- vendor/tauri-plugin-opener/src/open.rs | 61 ---- .../src/reveal_item_in_dir.rs | 299 ------------------ vendor/tauri-plugin-opener/src/scope.rs | 141 --------- vendor/tauri-plugin-opener/src/scope_entry.rs | 36 --- 44 files changed, 30 insertions(+), 2201 deletions(-) delete mode 100644 vendor/tauri-plugin-opener/.gitignore delete mode 100644 vendor/tauri-plugin-opener/CAP-CHANGES.md delete mode 100644 vendor/tauri-plugin-opener/CHANGELOG.md delete mode 100644 vendor/tauri-plugin-opener/Cargo.toml delete mode 100644 vendor/tauri-plugin-opener/LICENSE.spdx delete mode 100644 vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 delete mode 100644 vendor/tauri-plugin-opener/LICENSE_MIT delete mode 100644 vendor/tauri-plugin-opener/README.md delete mode 100644 vendor/tauri-plugin-opener/SECURITY.md delete mode 100644 vendor/tauri-plugin-opener/android/.gitignore delete mode 100644 vendor/tauri-plugin-opener/android/build.gradle.kts delete mode 100644 vendor/tauri-plugin-opener/android/proguard-rules.pro delete mode 100644 vendor/tauri-plugin-opener/android/settings.gradle delete mode 100644 vendor/tauri-plugin-opener/android/src/main/AndroidManifest.xml delete mode 100644 vendor/tauri-plugin-opener/android/src/main/java/OpenerPlugin.kt delete mode 100644 vendor/tauri-plugin-opener/api-iife.js delete mode 100644 vendor/tauri-plugin-opener/build.rs delete mode 100644 vendor/tauri-plugin-opener/ios/Package.resolved delete mode 100644 vendor/tauri-plugin-opener/ios/Package.swift delete mode 100644 vendor/tauri-plugin-opener/ios/Sources/OpenerPlugin.swift delete mode 100644 vendor/tauri-plugin-opener/permissions/allow-default-urls.toml delete mode 100644 vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_path.toml delete mode 100644 vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_url.toml delete mode 100644 vendor/tauri-plugin-opener/permissions/autogenerated/commands/reveal_item_in_dir.toml delete mode 100644 vendor/tauri-plugin-opener/permissions/autogenerated/reference.md delete mode 100644 vendor/tauri-plugin-opener/permissions/default.toml delete mode 100644 vendor/tauri-plugin-opener/src/commands.rs delete mode 100644 vendor/tauri-plugin-opener/src/config.rs delete mode 100644 vendor/tauri-plugin-opener/src/error.rs delete mode 100644 vendor/tauri-plugin-opener/src/init-iife.js delete mode 100644 vendor/tauri-plugin-opener/src/lib.rs delete mode 100644 vendor/tauri-plugin-opener/src/open.rs delete mode 100644 vendor/tauri-plugin-opener/src/reveal_item_in_dir.rs delete mode 100644 vendor/tauri-plugin-opener/src/scope.rs delete mode 100644 vendor/tauri-plugin-opener/src/scope_entry.rs diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json index 745254f6eb4..3c4d5646057 100644 --- a/apps/desktop/src-tauri/capabilities/default.json +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -68,7 +68,6 @@ { "url": "http://localhost:*" } ] }, - "clipboard-manager:allow-write-text", - "opener:allow-reveal-item-in-dir" + "clipboard-manager:allow-write-text" ] } diff --git a/apps/desktop/src-tauri/src/diagnostics.rs b/apps/desktop/src-tauri/src/diagnostics.rs index 761b4375e3a..102c735e423 100644 --- a/apps/desktop/src-tauri/src/diagnostics.rs +++ b/apps/desktop/src-tauri/src/diagnostics.rs @@ -6,7 +6,6 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; use tauri::{AppHandle, Manager}; -use tauri_plugin_opener::OpenerExt; use tauri_specta::Event; use tokio::io::AsyncBufReadExt; use tracing::{error, info, warn}; @@ -548,9 +547,8 @@ pub async fn upload_diagnostic_report(app: AppHandle, report_path: String) -> Re pub async fn reveal_diagnostic_report(app: AppHandle, report_path: String) -> Result<(), String> { let path = validate_report_path(&app, &report_path)?; - tokio::task::spawn_blocking(move || app.opener().reveal_item_in_dir(&path)) + crate::reveal_in_dir(app, path) .await - .map_err(|e| format!("Failed to reveal diagnostic report: {e}"))? .map_err(|e| format!("Failed to reveal diagnostic report: {e}")) } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index a78c370641e..b97277971b9 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -4656,6 +4656,25 @@ async fn open_file_path(_app: AppHandle, path: PathBuf) -> Result<(), String> { Ok(()) } +/// The opener's Linux backend makes blocking zbus calls, which panic inside an async task. +pub(crate) async fn reveal_in_dir(app: AppHandle, path: PathBuf) -> Result<(), String> { + cap_utils::run_on_dedicated_thread("reveal-item-in-dir", move || { + app.opener().reveal_item_in_dir(path) + }) + .await + .map_err(|e| e.to_string())? + .map_err(|e| e.to_string()) +} + +#[tauri::command] +#[specta::specta] +#[instrument(skip(app))] +async fn reveal_item_in_dir(app: AppHandle, path: PathBuf) -> Result<(), String> { + reveal_in_dir(app, path) + .await + .map_err(|e| format!("Failed to reveal item: {e}")) +} + #[derive(Deserialize, specta::Type, tauri_specta::Event, Debug, Clone)] struct RenderFrameEvent { frame_number: u32, @@ -6810,6 +6829,7 @@ fn specta_builder() -> tauri_specta::Builder { copy_image_to_clipboard, copy_rendered_screenshot_to_clipboard, open_file_path, + reveal_item_in_dir, get_video_metadata, create_editor_instance, editor_preparing::create_preparing_editor_frame, diff --git a/apps/desktop/src/routes/editor/EditorErrorScreen.tsx b/apps/desktop/src/routes/editor/EditorErrorScreen.tsx index cc2a21478c0..b22fe189a0c 100644 --- a/apps/desktop/src/routes/editor/EditorErrorScreen.tsx +++ b/apps/desktop/src/routes/editor/EditorErrorScreen.tsx @@ -2,7 +2,6 @@ import { Button } from "@cap/ui-solid"; import { createMutation } from "@tanstack/solid-query"; import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; -import { revealItemInDir } from "@tauri-apps/plugin-opener"; import { type as ostype } from "@tauri-apps/plugin-os"; import { createEffect, onCleanup, Show } from "solid-js"; import CaptionControlsWindows11 from "~/components/titlebar/controls/CaptionControlsWindows11"; @@ -165,7 +164,7 @@ export function EditorErrorScreen(props: { }); const handleOpenFolder = () => { - revealItemInDir(props.projectPath); + commands.revealItemInDir(props.projectPath); }; return ( diff --git a/apps/desktop/src/routes/editor/Header.tsx b/apps/desktop/src/routes/editor/Header.tsx index db72fa9ac19..a49d41889e6 100644 --- a/apps/desktop/src/routes/editor/Header.tsx +++ b/apps/desktop/src/routes/editor/Header.tsx @@ -1,5 +1,4 @@ import { ask } from "@tauri-apps/plugin-dialog"; -import { revealItemInDir } from "@tauri-apps/plugin-opener"; import { type as ostype } from "@tauri-apps/plugin-os"; import { cx } from "cva"; import { @@ -129,7 +128,7 @@ export function Header(props: { clearTimelineSelection(); console.log({ path: `${editorInstance.path}/` }); - revealItemInDir(`${editorInstance.path}/`); + commands.revealItemInDir(`${editorInstance.path}/`); }} tooltipText="Open recording bundle" leftIcon={} diff --git a/apps/desktop/src/routes/screenshot-editor/Header.tsx b/apps/desktop/src/routes/screenshot-editor/Header.tsx index a41171e16a5..8489ffcf573 100644 --- a/apps/desktop/src/routes/screenshot-editor/Header.tsx +++ b/apps/desktop/src/routes/screenshot-editor/Header.tsx @@ -2,12 +2,12 @@ import { DropdownMenu } from "@kobalte/core/dropdown-menu"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { ask } from "@tauri-apps/plugin-dialog"; import { remove } from "@tauri-apps/plugin-fs"; -import { revealItemInDir } from "@tauri-apps/plugin-opener"; import { type as ostype } from "@tauri-apps/plugin-os"; import { cx } from "cva"; import { createEffect, onCleanup, Suspense } from "solid-js"; import CaptionControlsMacOS from "~/components/titlebar/controls/CaptionControlsMacOS"; import CaptionControlsWindows11 from "~/components/titlebar/controls/CaptionControlsWindows11"; +import { commands } from "~/utils/tauri"; import IconCapCrop from "~icons/cap/crop"; import IconCapTrash from "~icons/cap/trash"; import IconLucideCopy from "~icons/lucide/copy"; @@ -184,7 +184,7 @@ export function Header() { > { - revealItemInDir(path()); + commands.revealItemInDir(path()); }} > diff --git a/apps/desktop/src/utils/recording.test.ts b/apps/desktop/src/utils/recording.test.ts index b4e37244490..8766b5a0478 100644 --- a/apps/desktop/src/utils/recording.test.ts +++ b/apps/desktop/src/utils/recording.test.ts @@ -8,7 +8,6 @@ import { vi.mock("@tauri-apps/api/event", () => ({ emit: vi.fn() })); vi.mock("@tauri-apps/plugin-dialog", () => ({ message: vi.fn() })); -vi.mock("@tauri-apps/plugin-opener", () => ({ revealItemInDir: vi.fn() })); vi.mock("./tauri", () => ({ commands: {} })); describe("recording start cancellation", () => { diff --git a/apps/desktop/src/utils/recording.ts b/apps/desktop/src/utils/recording.ts index 4fde8b32777..4cb91207973 100644 --- a/apps/desktop/src/utils/recording.ts +++ b/apps/desktop/src/utils/recording.ts @@ -1,6 +1,5 @@ import { emit } from "@tauri-apps/api/event"; import * as dialog from "@tauri-apps/plugin-dialog"; -import { revealItemInDir } from "@tauri-apps/plugin-opener"; import type { createOptionsQuery } from "./queries"; import { commands, @@ -125,5 +124,5 @@ export async function openRecordingFolder( if (openedContent) return; - await revealItemInDir(`${path}/`); + await commands.revealItemInDir(`${path}/`); } diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts index 6f27d9856cb..767b14c177a 100644 --- a/apps/desktop/src/utils/tauri.ts +++ b/apps/desktop/src/utils/tauri.ts @@ -217,6 +217,9 @@ async copyRenderedScreenshotToClipboard() : Promise { async openFilePath(path: string) : Promise { return await TAURI_INVOKE("open_file_path", { path }); }, +async revealItemInDir(path: string) : Promise { + return await TAURI_INVOKE("reveal_item_in_dir", { path }); +}, async getVideoMetadata(path: string) : Promise { return await TAURI_INVOKE("get_video_metadata", { path }); }, diff --git a/vendor/tauri-plugin-opener/.gitignore b/vendor/tauri-plugin-opener/.gitignore deleted file mode 100644 index 53a0a824d96..00000000000 --- a/vendor/tauri-plugin-opener/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/permissions/schemas/ diff --git a/vendor/tauri-plugin-opener/CAP-CHANGES.md b/vendor/tauri-plugin-opener/CAP-CHANGES.md deleted file mode 100644 index 1f0056ea099..00000000000 --- a/vendor/tauri-plugin-opener/CAP-CHANGES.md +++ /dev/null @@ -1,33 +0,0 @@ -# Cap changes to tauri-plugin-opener - -Vendored from the crates.io `tauri-plugin-opener` 2.5.0 archive, SHA-256 -`786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5`. -The archive identifies upstream commit -`2371804172fa852ef3c127b2398c54bc35f74950` in -[tauri-apps/plugins-workspace](https://github.com/tauri-apps/plugins-workspace/tree/2371804172fa852ef3c127b2398c54bc35f74950/plugins/opener). - -The async `reveal_item_in_dir` IPC command previously called the synchronous -reveal implementation on a Tokio worker. On Linux, blocking zbus can enter -its own Tokio runtime and panic. The command now awaits a blocking worker -and propagates both backend errors and worker failures. Its name, arguments, -permission definitions, path canonicalization, and native backends are unchanged. -All existing JavaScript callers retain the same IPC and permission boundary. - -Tests cover both Tokio runtime flavors, exact path forwarding, backend errors, -worker panics, validation of all paths, and the real Linux zbus backend with the -Tokio feature enabled. The Linux test permits a missing session bus or file -manager; it does not establish successful graphical file selection. - -The workspace uses this source through `[patch.crates-io]`. The crate is also a -workspace member so Cargo can run its regression tests. Dev dependencies reuse -versions already in the workspace lockfile. No runtime dependency was upgraded. - -Packaging omits the upstream lockfile, original workspace manifest, npm build -configuration, and guest TypeScript sources. The shipped JavaScript bundles are -retained with formatting only. One trailing space in the Android build file is -removed. Rust source is rustfmt-formatted, and -the build regenerates the ignored permission schema. Both upstream licenses and -all platform source files are retained. - -Current upstream still has the direct synchronous command call. Revisit this -patch when upstream supplies an equivalent async boundary and retain the tests. diff --git a/vendor/tauri-plugin-opener/CHANGELOG.md b/vendor/tauri-plugin-opener/CHANGELOG.md deleted file mode 100644 index 94c5c1bc0c3..00000000000 --- a/vendor/tauri-plugin-opener/CHANGELOG.md +++ /dev/null @@ -1,59 +0,0 @@ -# Changelog - -## \[2.5.0] - -### enhance - -- [`b8056f48`](https://github.com/tauri-apps/plugins-workspace/commit/b8056f484c7144af095d4d6ded1e8adbb9b8a865) ([#2897](https://github.com/tauri-apps/plugins-workspace/pull/2897) by [@petersamokhin](https://github.com/tauri-apps/plugins-workspace/../../petersamokhin)) Allow reveal multiple items in the file explorer. - -## \[2.4.0] - -- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - -## \[2.3.1] - -### feat - -- [`2aec8ff4`](https://github.com/tauri-apps/plugins-workspace/commit/2aec8ff4c41d178ea9804f7b6eff343c726be015) ([#2803](https://github.com/tauri-apps/plugins-workspace/pull/2803) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Add `inAppBrowser` option to open URLs in an in-app browser on Android and iOS. - -## \[2.3.0] - -- [`ce9888a2`](https://github.com/tauri-apps/plugins-workspace/commit/ce9888a2d4c9b449bd2a306f0fa6c76507fd46d3) ([#2762](https://github.com/tauri-apps/plugins-workspace/pull/2762)) Similar to the `fs` plugin the `opener` plugin now supports a `requireLiteralLeadingDot` configuration in `tauri.conf.json`. - -## \[2.2.7] - -- [`6c9e08dc`](https://github.com/tauri-apps/plugins-workspace/commit/6c9e08dccb3ac99fccfce586fa2b69717ba81b52) ([#2695](https://github.com/tauri-apps/plugins-workspace/pull/2695) by [@ShaunSHamilton](https://github.com/tauri-apps/plugins-workspace/../../ShaunSHamilton)) Adjust `open_url` url type to allow `URL` -- [`dde6f3c3`](https://github.com/tauri-apps/plugins-workspace/commit/dde6f3c31c1b79942abb556be31757dc583f509e) ([#2549](https://github.com/tauri-apps/plugins-workspace/pull/2549) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update `windows` crate to 0.61 to align with Tauri 2.5 - -## \[2.2.6] - -- [`1a984659`](https://github.com/tauri-apps/plugins-workspace/commit/1a9846599b6a71faf330845847a30f6bf9735898) ([#2469](https://github.com/tauri-apps/plugins-workspace/pull/2469) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Update `objc2` crate to 0.6. No user facing changes. -- [`71f95c9f`](https://github.com/tauri-apps/plugins-workspace/commit/71f95c9f05b29cf1be586849614c0b007757c15d) ([#2445](https://github.com/tauri-apps/plugins-workspace/pull/2445) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `windows` crate to 0.60 to match Tauri 2.3.0. No user facing changes. - -## \[2.2.5] - -- [`5b821181`](https://github.com/tauri-apps/plugins-workspace/commit/5b8211815825ddae2dcc0c00520e0cfdff002763) ([#2332](https://github.com/tauri-apps/plugins-workspace/pull/2332) by [@betamos](https://github.com/tauri-apps/plugins-workspace/../../betamos)) Fix broken JS commands `opener.openPath` and `opener.openUrl` on mobile. - -## \[2.2.4] - -- [`da5c59e2`](https://github.com/tauri-apps/plugins-workspace/commit/da5c59e2fe879d177e3cfd52fcacce85440423cb) ([#2271](https://github.com/tauri-apps/plugins-workspace/pull/2271) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `zbus` dependency to version 5. No API changes. - -## \[2.2.3] - -- [`a9ac1e3c`](https://github.com/tauri-apps/plugins-workspace/commit/a9ac1e3c939cec4338a9422ef02323c1d4dde6cd) ([#2253](https://github.com/tauri-apps/plugins-workspace/pull/2253) by [@betamos](https://github.com/tauri-apps/plugins-workspace/../../betamos)) Return an error in `open_path` if the file does not exist when opening with default application. - -## \[2.2.2] - -- [`ee0f65de`](https://github.com/tauri-apps/plugins-workspace/commit/ee0f65de5c645c244c5f0b638e0e0aab687cb9bf) ([#2207](https://github.com/tauri-apps/plugins-workspace/pull/2207) by [@universalappfactory](https://github.com/tauri-apps/plugins-workspace/../../universalappfactory)) Fixed OpenerPlugin packagename for android - -## \[2.2.1] - -- [`18dffc9d`](https://github.com/tauri-apps/plugins-workspace/commit/18dffc9dfecaf0c900e233e041d9ca36c92834b5) ([#2189](https://github.com/tauri-apps/plugins-workspace/pull/2189) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix usage on iOS. - -## \[2.2.0] - -- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. - -## \[2.0.0] - -- [`383e636a`](https://github.com/tauri-apps/plugins-workspace/commit/383e636a8e595aec1300999a8aeb7d9bf8c14632) ([#2019](https://github.com/tauri-apps/plugins-workspace/pull/2019) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Initial Release diff --git a/vendor/tauri-plugin-opener/Cargo.toml b/vendor/tauri-plugin-opener/Cargo.toml deleted file mode 100644 index 35229ce3291..00000000000 --- a/vendor/tauri-plugin-opener/Cargo.toml +++ /dev/null @@ -1,135 +0,0 @@ -[package] -edition = "2021" -name = "tauri-plugin-opener" -version = "2.5.0" -authors = ["Tauri Programme within The Commons Conservancy"] -build = "build.rs" -links = "tauri-plugin-opener" -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false -description = "Open files and URLs using their default application." -readme = "README.md" -license = "Apache-2.0 OR MIT" -repository = "https://github.com/tauri-apps/plugins-workspace" - -[package.metadata.docs.rs] -rustc-args = [ - "--cfg", - "docsrs", -] -rustdoc-args = [ - "--cfg", - "docsrs", -] - -[package.metadata.platforms.support.android] -level = "partial" -notes = "Only allows to open URLs via `open`" - -[package.metadata.platforms.support.ios] -level = "partial" -notes = "Only allows to open URLs via `open`" - -[package.metadata.platforms.support.linux] -level = "full" -notes = "" - -[package.metadata.platforms.support.macos] -level = "full" -notes = "" - -[package.metadata.platforms.support.windows] -level = "full" -notes = "" - -[lib] -name = "tauri_plugin_opener" -path = "src/lib.rs" - -[dependencies.dunce] -version = "1" - -[dependencies.glob] -version = "0.3" - -[dependencies.open] -version = "5" -features = ["shellexecute-on-windows"] - -[dependencies.serde] -version = "1" -features = ["derive"] - -[dependencies.serde_json] -version = "1" - -[dependencies.tauri] -version = "2.8.2" -default-features = false - -[dependencies.thiserror] -version = "2" - -[build-dependencies.schemars] -version = "0.8" - -[build-dependencies.serde] -version = "1" -features = ["derive"] - -[build-dependencies.tauri-plugin] -version = "2.4" -features = ["build"] - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies.url] -version = "2" - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies.zbus] -version = "5.9" - -[target.'cfg(target_os = "ios")'.dependencies.tauri] -version = "2.8.2" -features = ["wry"] -default-features = false - -[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit] -version = "0.3" -features = [ - "std", - "NSWorkspace", -] -default-features = false - -[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] -version = "0.3" -features = [ - "std", - "NSURL", - "NSArray", - "NSString", -] -default-features = false - -[target."cfg(windows)".dependencies.windows] -version = "0.61" -features = [ - "Win32_Foundation", - "Win32_UI_Shell_Common", - "Win32_UI_WindowsAndMessaging", - "Win32_System_Com", - "Win32_System_Registry", -] - -[dev-dependencies.tokio] -version = "1" -features = ["macros", "rt-multi-thread"] - -[dev-dependencies.tempfile] -version = "3" - -[target.'cfg(target_os = "linux")'.dev-dependencies.zbus] -version = "5.9" -features = ["tokio"] diff --git a/vendor/tauri-plugin-opener/LICENSE.spdx b/vendor/tauri-plugin-opener/LICENSE.spdx deleted file mode 100644 index cdd0df5ad76..00000000000 --- a/vendor/tauri-plugin-opener/LICENSE.spdx +++ /dev/null @@ -1,20 +0,0 @@ -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-2022, 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/vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 b/vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 deleted file mode 100644 index 4947287f7b5..00000000000 --- a/vendor/tauri-plugin-opener/LICENSE_APACHE-2.0 +++ /dev/null @@ -1,177 +0,0 @@ - - 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/vendor/tauri-plugin-opener/LICENSE_MIT b/vendor/tauri-plugin-opener/LICENSE_MIT deleted file mode 100644 index 4d754725663..00000000000 --- a/vendor/tauri-plugin-opener/LICENSE_MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 - Present Tauri Apps Contributors - -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/vendor/tauri-plugin-opener/README.md b/vendor/tauri-plugin-opener/README.md deleted file mode 100644 index bcb9265ca48..00000000000 --- a/vendor/tauri-plugin-opener/README.md +++ /dev/null @@ -1,143 +0,0 @@ -![opener](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/opener/banner.png) - - - -| Platform | Supported | -| -------- | --------- | -| Linux | ✓ | -| Windows | ✓ | -| macOS | ✓ | -| Android | ? | -| iOS | ? | - -## Install - -_This plugin requires a Rust version of at least **1.77.2**_ - -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-opener = "2.0.0" -# alternatively with Git: -tauri-plugin-opener = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } -``` - -You can install the JavaScript Guest bindings using your preferred JavaScript package manager: - - - -```sh -pnpm add @tauri-apps/plugin-opener -# or -npm add @tauri-apps/plugin-opener -# or -yarn add @tauri-apps/plugin-opener -``` - -## Usage - -First you need to register the core plugin with Tauri: - -`src-tauri/src/lib.rs` - -```rust -fn main() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} -``` - -Afterwards all the plugin's APIs are available through the JavaScript guest bindings: - -```javascript -import { openUrl, openPath, revealItemInDir } from '@tauri-apps/plugin-opener' - -// Opens the URL in the default browser -await openUrl('https://example.com') -// Or with a specific browser/app -await openUrl('https://example.com', 'firefox') - -// Opens the path with the system's default app -await openPath('/path/to/file') -// Or with a specific app -await openPath('/path/to/file', 'firefox') - -// Reveal a path with the system's default explorer -await revealItemInDir('/path/to/file') - -// Reveal multiple paths with the system's default explorer -// Note: will be renamed to `revealItemsInDir` in the next major version -await revealItemInDir(['/path/to/file', '/path/to/another/file']) -``` - -### Usage from Rust - -You can also use those APIs from Rust: - -```rust -use tauri_plugin_opener::OpenerExt; - -fn main() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .setup(|app| { - let opener = app.opener(); - - // Opens the URL in the default browser - opener.open_url("https://example.com", None::<&str>)?; - // Or with a specific browser/app - opener.open_url("https://example.com", Some("firefox"))?; - - // Opens the path with the system's default app - opener.open_path("/path/to/file", None::<&str>)?; - // Or with a specific app - opener.open_path("/path/to/file", Some("firefox"))?; - - // Reveal a path with the system's default explorer - opener.reveal_item_in_dir("/path/to/file")?; - - // Reveal multiple paths with the system's default explorer - opener.reveal_items_in_dir(["/path/to/file"])?; - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} -``` - -## Contributing - -PRs accepted. Please make sure to read the Contributing Guide before making a pull request. - -## Partners - - - - - - - -
- - CrabNebula - -
- -For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). - -## License - -Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. - -MIT or MIT/Apache 2.0 where applicable. diff --git a/vendor/tauri-plugin-opener/SECURITY.md b/vendor/tauri-plugin-opener/SECURITY.md deleted file mode 100644 index 4f09bbacd45..00000000000 --- a/vendor/tauri-plugin-opener/SECURITY.md +++ /dev/null @@ -1,23 +0,0 @@ -# Security Policy - -**Do not report security vulnerabilities through public GitHub issues.** - -**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** - -Include as much of the following information: - -- Type of issue (e.g. improper input parsing, privilege escalation, etc.) -- The location of the affected source code (tag/branch/commit or direct URL) -- Any special configuration required to reproduce the issue -- The distribution affected or used to help us with reproduction of the issue -- Step-by-step instructions to reproduce the issue -- Ideally a reproduction repository -- Impact of the issue, including how an attacker might exploit the issue - -We prefer to receive reports in English. - -## Contact - -Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). - -Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/vendor/tauri-plugin-opener/android/.gitignore b/vendor/tauri-plugin-opener/android/.gitignore deleted file mode 100644 index c0f21ec2fd9..00000000000 --- a/vendor/tauri-plugin-opener/android/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/.tauri diff --git a/vendor/tauri-plugin-opener/android/build.gradle.kts b/vendor/tauri-plugin-opener/android/build.gradle.kts deleted file mode 100644 index 0c0612a49d4..00000000000 --- a/vendor/tauri-plugin-opener/android/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") -} - -android { - namespace = "app.tauri.opener" - compileSdk = 34 - - defaultConfig { - minSdk = 24 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } -} - -dependencies { - implementation("androidx.core:core-ktx:1.9.0") - implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") - implementation("androidx.browser:browser:1.8.0") - implementation(project(":tauri-android")) -} diff --git a/vendor/tauri-plugin-opener/android/proguard-rules.pro b/vendor/tauri-plugin-opener/android/proguard-rules.pro deleted file mode 100644 index 481bb434814..00000000000 --- a/vendor/tauri-plugin-opener/android/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/vendor/tauri-plugin-opener/android/settings.gradle b/vendor/tauri-plugin-opener/android/settings.gradle deleted file mode 100644 index 14a752e4336..00000000000 --- a/vendor/tauri-plugin-opener/android/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -include ':tauri-android' -project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/vendor/tauri-plugin-opener/android/src/main/AndroidManifest.xml b/vendor/tauri-plugin-opener/android/src/main/AndroidManifest.xml deleted file mode 100644 index 9a40236b947..00000000000 --- a/vendor/tauri-plugin-opener/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/vendor/tauri-plugin-opener/android/src/main/java/OpenerPlugin.kt b/vendor/tauri-plugin-opener/android/src/main/java/OpenerPlugin.kt deleted file mode 100644 index a65a2f52a13..00000000000 --- a/vendor/tauri-plugin-opener/android/src/main/java/OpenerPlugin.kt +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -package app.tauri.opener - -import android.app.Activity -import android.content.Intent -import androidx.browser.customtabs.CustomTabsIntent -import app.tauri.annotation.Command -import app.tauri.annotation.TauriPlugin -import app.tauri.plugin.Invoke -import app.tauri.plugin.Plugin -import androidx.core.net.toUri -import app.tauri.annotation.InvokeArg - -@InvokeArg -class OpenArgs { - lateinit var url: String - var with: String? = null -} - -@TauriPlugin -class OpenerPlugin(private val activity: Activity) : Plugin(activity) { - @Command - fun open(invoke: Invoke) { - try { - val args = invoke.parseArgs(OpenArgs::class.java) - - if (args.with == "inAppBrowser") { - val builder = CustomTabsIntent.Builder() - val intent = builder.build() - intent.launchUrl(activity, args.url.toUri()) - } else { - val intent = Intent(Intent.ACTION_VIEW, args.url.toUri()) - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - activity.applicationContext?.startActivity(intent) - } - invoke.resolve() - } catch (ex: Exception) { - invoke.reject(ex.message) - } - } -} diff --git a/vendor/tauri-plugin-opener/api-iife.js b/vendor/tauri-plugin-opener/api-iife.js deleted file mode 100644 index 847e54ea557..00000000000 --- a/vendor/tauri-plugin-opener/api-iife.js +++ /dev/null @@ -1,26 +0,0 @@ -if ("__TAURI__" in window) { - var __TAURI_PLUGIN_OPENER__ = (function (n) { - "use strict"; - async function e(n, e = {}, r) { - return window.__TAURI_INTERNALS__.invoke(n, e, r); - } - return ( - "function" == typeof SuppressedError && SuppressedError, - (n.openPath = async function (n, r) { - await e("plugin:opener|open_path", { path: n, with: r }); - }), - (n.openUrl = async function (n, r) { - await e("plugin:opener|open_url", { url: n, with: r }); - }), - (n.revealItemInDir = async function (n) { - return e("plugin:opener|reveal_item_in_dir", { - paths: "string" == typeof n ? [n] : n, - }); - }), - n - ); - })({}); - Object.defineProperty(window.__TAURI__, "opener", { - value: __TAURI_PLUGIN_OPENER__, - }); -} diff --git a/vendor/tauri-plugin-opener/build.rs b/vendor/tauri-plugin-opener/build.rs deleted file mode 100644 index fbad4d3ac35..00000000000 --- a/vendor/tauri-plugin-opener/build.rs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::path::PathBuf; - -#[path = "src/scope_entry.rs"] -#[allow(dead_code)] -mod scope; - -/// Opener scope application. -#[derive(schemars::JsonSchema)] -#[serde(untagged)] -#[allow(unused)] -enum Application { - /// Open in default application. - Default, - /// If true, allow open with any application. - Enable(bool), - /// Allow specific application to open with. - App(String), -} - -impl Default for Application { - fn default() -> Self { - Self::Default - } -} - -/// Opener scope entry. -#[derive(schemars::JsonSchema)] -#[serde(untagged)] -#[allow(unused)] -enum OpenerScopeEntry { - Url { - /// A URL that can be opened by the webview when using the Opener APIs. - /// - /// Wildcards can be used following the UNIX glob pattern. - /// - /// Examples: - /// - /// - "https://*" : allows all HTTPS origin - /// - /// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path - /// - /// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/" - url: String, - /// An application to open this url with, for example: firefox. - #[serde(default)] - app: Application, - }, - Path { - /// A path that can be opened by the webview when using the Opener APIs. - /// - /// The pattern can start with a variable that resolves to a system base directory. - /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, - /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, - /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, - /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`. - path: PathBuf, - /// An application to open this path with, for example: xdg-open. - #[serde(default)] - app: Application, - }, -} - -// Ensure `OpenerScopeEntry` and `scope::EntryRaw` is kept in sync -fn _f() { - match (scope::EntryRaw::Url { - url: String::new(), - app: scope::Application::Enable(true), - }) { - scope::EntryRaw::Url { url, app } => OpenerScopeEntry::Url { - url, - app: match app { - scope::Application::Enable(p) => Application::Enable(p), - scope::Application::App(p) => Application::App(p), - scope::Application::Default => Application::Default, - }, - }, - scope::EntryRaw::Path { path, app } => OpenerScopeEntry::Path { - path, - app: match app { - scope::Application::Enable(p) => Application::Enable(p), - scope::Application::App(p) => Application::App(p), - scope::Application::Default => Application::Default, - }, - }, - }; - match (OpenerScopeEntry::Url { - url: String::new(), - app: Application::Enable(true), - }) { - OpenerScopeEntry::Url { url, app } => scope::EntryRaw::Url { - url, - app: match app { - Application::Enable(p) => scope::Application::Enable(p), - Application::App(p) => scope::Application::App(p), - Application::Default => scope::Application::Default, - }, - }, - OpenerScopeEntry::Path { path, app } => scope::EntryRaw::Path { - path, - app: match app { - Application::Enable(p) => scope::Application::Enable(p), - Application::App(p) => scope::Application::App(p), - Application::Default => scope::Application::Default, - }, - }, - }; -} - -const COMMANDS: &[&str] = &["open_url", "open_path", "reveal_item_in_dir"]; - -fn main() { - tauri_plugin::Builder::new(COMMANDS) - .global_api_script_path("./api-iife.js") - .android_path("android") - .ios_path("ios") - .global_scope_schema(schemars::schema_for!(OpenerScopeEntry)) - .build(); - - let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); - let mobile = target_os == "ios" || target_os == "android"; - alias("desktop", !mobile); - alias("mobile", mobile); -} - -// creates a cfg alias if `has_feature` is true. -// `alias` must be a snake case string. -fn alias(alias: &str, has_feature: bool) { - println!("cargo:rustc-check-cfg=cfg({alias})"); - if has_feature { - println!("cargo:rustc-cfg={alias}"); - } -} diff --git a/vendor/tauri-plugin-opener/ios/Package.resolved b/vendor/tauri-plugin-opener/ios/Package.resolved deleted file mode 100644 index 5f998e0e66b..00000000000 --- a/vendor/tauri-plugin-opener/ios/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "SwiftRs", - "repositoryURL": "https://github.com/Brendonovich/swift-rs", - "state": { - "branch": null, - "revision": "b5ed223fcdab165bc21219c1925dc1e77e2bef5e", - "version": "1.0.6" - } - } - ] - }, - "version": 1 -} diff --git a/vendor/tauri-plugin-opener/ios/Package.swift b/vendor/tauri-plugin-opener/ios/Package.swift deleted file mode 100644 index 8d06936e614..00000000000 --- a/vendor/tauri-plugin-opener/ios/Package.swift +++ /dev/null @@ -1,34 +0,0 @@ -// swift-tools-version:5.3 -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -import PackageDescription - -let package = Package( - name: "tauri-plugin-opener", - platforms: [ - .macOS(.v10_13), - .iOS(.v13), - ], - products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "tauri-plugin-opener", - type: .static, - targets: ["tauri-plugin-opener"]) - ], - dependencies: [ - .package(name: "Tauri", path: "../.tauri/tauri-api") - ], - targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. - .target( - name: "tauri-plugin-opener", - dependencies: [ - .byName(name: "Tauri") - ], - path: "Sources") - ] -) diff --git a/vendor/tauri-plugin-opener/ios/Sources/OpenerPlugin.swift b/vendor/tauri-plugin-opener/ios/Sources/OpenerPlugin.swift deleted file mode 100644 index 2311a3b1b74..00000000000 --- a/vendor/tauri-plugin-opener/ios/Sources/OpenerPlugin.swift +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -import Foundation -import SafariServices -import SwiftRs -import Tauri -import UIKit -import WebKit - -struct OpenArgs: Decodable { - let url: String - let with: String? -} - -class OpenerPlugin: Plugin { - @objc public func open(_ invoke: Invoke) throws { - do { - let args = try invoke.parseArgs(OpenArgs.self) - if let url = URL(string: args.url) { - if args.with == "inAppBrowser" { - DispatchQueue.main.async { - let safariVC = SFSafariViewController(url: url) - self.manager.viewController?.present(safariVC, animated: true) - } - } else { - if #available(iOS 10, *) { - UIApplication.shared.open(url, options: [:]) - } else { - UIApplication.shared.openURL(url) - } - } - - } - invoke.resolve() - } catch { - invoke.reject(error.localizedDescription) - } - } -} - -@_cdecl("init_plugin_opener") -func initPlugin() -> Plugin { - return OpenerPlugin() -} diff --git a/vendor/tauri-plugin-opener/permissions/allow-default-urls.toml b/vendor/tauri-plugin-opener/permissions/allow-default-urls.toml deleted file mode 100644 index 93495b8970e..00000000000 --- a/vendor/tauri-plugin-opener/permissions/allow-default-urls.toml +++ /dev/null @@ -1,17 +0,0 @@ -"$schema" = "schemas/schema.json" - -[[permission]] -identifier = "allow-default-urls" -description = "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." - -[[permission.scope.allow]] -url = "mailto:*" - -[[permission.scope.allow]] -url = "tel:*" - -[[permission.scope.allow]] -url = "http://*" - -[[permission.scope.allow]] -url = "https://*" diff --git a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_path.toml b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_path.toml deleted file mode 100644 index ae67b939428..00000000000 --- a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_path.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-open-path" -description = "Enables the open_path command without any pre-configured scope." -commands.allow = ["open_path"] - -[[permission]] -identifier = "deny-open-path" -description = "Denies the open_path command without any pre-configured scope." -commands.deny = ["open_path"] diff --git a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_url.toml b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_url.toml deleted file mode 100644 index f1e694b1b0e..00000000000 --- a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/open_url.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-open-url" -description = "Enables the open_url command without any pre-configured scope." -commands.allow = ["open_url"] - -[[permission]] -identifier = "deny-open-url" -description = "Denies the open_url command without any pre-configured scope." -commands.deny = ["open_url"] diff --git a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/reveal_item_in_dir.toml b/vendor/tauri-plugin-opener/permissions/autogenerated/commands/reveal_item_in_dir.toml deleted file mode 100644 index e669620ffd8..00000000000 --- a/vendor/tauri-plugin-opener/permissions/autogenerated/commands/reveal_item_in_dir.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-reveal-item-in-dir" -description = "Enables the reveal_item_in_dir command without any pre-configured scope." -commands.allow = ["reveal_item_in_dir"] - -[[permission]] -identifier = "deny-reveal-item-in-dir" -description = "Denies the reveal_item_in_dir command without any pre-configured scope." -commands.deny = ["reveal_item_in_dir"] diff --git a/vendor/tauri-plugin-opener/permissions/autogenerated/reference.md b/vendor/tauri-plugin-opener/permissions/autogenerated/reference.md deleted file mode 100644 index 6ad6ba1fbf6..00000000000 --- a/vendor/tauri-plugin-opener/permissions/autogenerated/reference.md +++ /dev/null @@ -1,111 +0,0 @@ -## Default Permission - -This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application -as well as reveal file in directories using default file explorer - -#### This default permission set includes the following: - -- `allow-open-url` -- `allow-reveal-item-in-dir` -- `allow-default-urls` - -## Permission Table - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IdentifierDescription
- -`opener:allow-default-urls` - - - -This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application. - -
- -`opener:allow-open-path` - - - -Enables the open_path command without any pre-configured scope. - -
- -`opener:deny-open-path` - - - -Denies the open_path command without any pre-configured scope. - -
- -`opener:allow-open-url` - - - -Enables the open_url command without any pre-configured scope. - -
- -`opener:deny-open-url` - - - -Denies the open_url command without any pre-configured scope. - -
- -`opener:allow-reveal-item-in-dir` - - - -Enables the reveal_item_in_dir command without any pre-configured scope. - -
- -`opener:deny-reveal-item-in-dir` - - - -Denies the reveal_item_in_dir command without any pre-configured scope. - -
diff --git a/vendor/tauri-plugin-opener/permissions/default.toml b/vendor/tauri-plugin-opener/permissions/default.toml deleted file mode 100644 index 846d6e51be0..00000000000 --- a/vendor/tauri-plugin-opener/permissions/default.toml +++ /dev/null @@ -1,10 +0,0 @@ -"$schema" = "schemas/schema.json" - -[default] -description = """This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application -as well as reveal file in directories using default file explorer""" -permissions = [ - "allow-open-url", - "allow-reveal-item-in-dir", - "allow-default-urls", -] diff --git a/vendor/tauri-plugin-opener/src/commands.rs b/vendor/tauri-plugin-opener/src/commands.rs deleted file mode 100644 index 343aa441a29..00000000000 --- a/vendor/tauri-plugin-opener/src/commands.rs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::path::{Path, PathBuf}; - -use tauri::{ - ipc::{CommandScope, GlobalScope}, - AppHandle, Runtime, -}; - -use crate::{scope::Scope, Error, OpenerExt}; - -#[tauri::command] -pub async fn open_url( - app: AppHandle, - command_scope: CommandScope, - global_scope: GlobalScope, - url: String, - with: Option, -) -> crate::Result<()> { - let scope = Scope::new( - &app, - command_scope - .allows() - .iter() - .chain(global_scope.allows()) - .collect(), - command_scope - .denies() - .iter() - .chain(global_scope.denies()) - .collect(), - ); - - if scope.is_url_allowed(&url, with.as_deref()) { - app.opener().open_url(url, with) - } else { - Err(Error::ForbiddenUrl { url, with }) - } -} - -#[tauri::command] -pub async fn open_path( - app: AppHandle, - command_scope: CommandScope, - global_scope: GlobalScope, - path: String, - with: Option, -) -> crate::Result<()> { - let scope = Scope::new( - &app, - command_scope - .allows() - .iter() - .chain(global_scope.allows()) - .collect(), - command_scope - .denies() - .iter() - .chain(global_scope.denies()) - .collect(), - ); - - if scope.is_path_allowed(Path::new(&path), with.as_deref())? { - app.opener().open_path(path, with) - } else { - Err(Error::ForbiddenPath { path, with }) - } -} - -/// TODO: in the next major version, rename to `reveal_items_in_dir` -#[tauri::command] -pub async fn reveal_item_in_dir(paths: Vec) -> crate::Result<()> { - reveal_item_in_dir_with(paths, crate::reveal_items_in_dir).await -} - -async fn reveal_item_in_dir_with( - paths: Vec, - reveal: impl FnOnce(Vec) -> crate::Result<()> + Send + 'static, -) -> crate::Result<()> { - // The Linux backend uses blocking zbus, which may enter its own Tokio runtime. - tauri::async_runtime::spawn_blocking(move || reveal(paths)).await? -} - -#[cfg(test)] -mod tests { - use super::*; - - async fn assert_reveal_can_enter_runtime() { - let paths = vec![ - PathBuf::from("recording with spaces.cap/"), - PathBuf::from("截图.png"), - ]; - let expected = paths.clone(); - reveal_item_in_dir_with(paths, move |actual| { - assert_eq!(actual, expected); - let runtime = tokio::runtime::Builder::new_current_thread() - .build() - .unwrap(); - runtime.block_on(async { Ok(()) }) - }) - .await - .unwrap(); - } - - #[tokio::test] - async fn reveal_from_current_thread_runtime() { - assert_reveal_can_enter_runtime().await; - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn reveal_from_multi_thread_runtime() { - assert_reveal_can_enter_runtime().await; - } - - #[tokio::test] - async fn preserves_backend_errors() { - let error = reveal_item_in_dir_with(Vec::new(), |_| { - Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "reveal denied").into()) - }) - .await - .unwrap_err(); - assert!(matches!(&error, Error::Io(e) if e.kind() == std::io::ErrorKind::PermissionDenied)); - assert_eq!(error.to_string(), "reveal denied"); - } - - #[tokio::test] - async fn returns_worker_panics_as_errors() { - let error = reveal_item_in_dir_with(Vec::new(), |_| panic!("reveal worker failed")) - .await - .unwrap_err(); - assert!(matches!(error, Error::Tauri(tauri::Error::JoinError(_)))); - } - - #[tokio::test] - async fn validates_every_path_before_revealing() { - let directory = tempfile::tempdir().unwrap(); - let existing = directory.path().join("recording with spaces.cap"); - std::fs::write(&existing, []).unwrap(); - let missing = directory.path().join("missing.png"); - let error = reveal_item_in_dir(vec![existing, missing]) - .await - .unwrap_err(); - assert!(matches!(error, Error::Io(e) if e.kind() == std::io::ErrorKind::NotFound)); - } - - #[cfg(target_os = "linux")] - #[tokio::test] - async fn linux_zbus_backend_from_tokio_runtime() { - let result = reveal_item_in_dir(Vec::new()).await; - // A headless host may lack a session bus or file manager; neither is a worker panic. - assert!(!matches!( - result, - Err(Error::Tauri(tauri::Error::JoinError(_))) - )); - } -} diff --git a/vendor/tauri-plugin-opener/src/config.rs b/vendor/tauri-plugin-opener/src/config.rs deleted file mode 100644 index db3bae45e51..00000000000 --- a/vendor/tauri-plugin-opener/src/config.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use serde::Deserialize; - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct Config { - /// Whether or not paths that contain components that start with a `.` - /// will require that `.` appears literally in the pattern; `*`, `?`, `**`, - /// or `[...]` will not match. This is useful because such files are - /// conventionally considered hidden on Unix systems and it might be - /// desirable to skip them when listing files. - /// - /// Defaults to `true` on Unix systems and `false` on Windows - // dotfiles are not supposed to be exposed by default on unix - pub require_literal_leading_dot: Option, -} diff --git a/vendor/tauri-plugin-opener/src/error.rs b/vendor/tauri-plugin-opener/src/error.rs deleted file mode 100644 index c5a4dde3cce..00000000000 --- a/vendor/tauri-plugin-opener/src/error.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::path::PathBuf; - -use serde::{Serialize, Serializer}; - -#[derive(Debug, thiserror::Error)] -#[non_exhaustive] -pub enum Error { - #[cfg(mobile)] - #[error(transparent)] - PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), - #[error(transparent)] - Tauri(#[from] tauri::Error), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - Json(#[from] serde_json::Error), - #[error("unknown program {0}")] - UnknownProgramName(String), - #[error("Not allowed to open path {}{}", .path, .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())] - ForbiddenPath { path: String, with: Option }, - #[error("Not allowed to open url {}{}", .url, .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())] - ForbiddenUrl { url: String, with: Option }, - #[error("API not supported on the current platform")] - UnsupportedPlatform, - #[error(transparent)] - #[cfg(windows)] - Win32Error(#[from] windows::core::Error), - #[error("Path doesn't have a parent: {0}")] - NoParent(PathBuf), - #[cfg(windows)] - #[error("Failed to convert path '{0}' to ITEMIDLIST")] - FailedToConvertPathToItemIdList(PathBuf), - #[error("Failed to convert path to file:// url")] - FailedToConvertPathToFileUrl, - #[error(transparent)] - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - Zbus(#[from] zbus::Error), -} - -impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} diff --git a/vendor/tauri-plugin-opener/src/init-iife.js b/vendor/tauri-plugin-opener/src/init-iife.js deleted file mode 100644 index 26b3118ec47..00000000000 --- a/vendor/tauri-plugin-opener/src/init-iife.js +++ /dev/null @@ -1,19 +0,0 @@ -!(function () { - "use strict"; - "function" == typeof SuppressedError && SuppressedError, - window.addEventListener("click", function (e) { - if (e.defaultPrevented || 0 !== e.button || e.metaKey || e.altKey) return; - const t = e - .composedPath() - .find((e) => e instanceof Node && "A" === e.nodeName.toUpperCase()); - if (!t || !t.href || ("_blank" !== t.target && !e.ctrlKey && !e.shiftKey)) - return; - const n = new URL(t.href); - n.origin === window.location.origin || - ["http:", "https:", "mailto:", "tel:"].every((e) => n.protocol !== e) || - (e.preventDefault(), - (async function (e, t = {}, n) { - window.__TAURI_INTERNALS__.invoke(e, t, n); - })("plugin:opener|open_url", { url: n })); - }); -})(); diff --git a/vendor/tauri-plugin-opener/src/lib.rs b/vendor/tauri-plugin-opener/src/lib.rs deleted file mode 100644 index 343e915580b..00000000000 --- a/vendor/tauri-plugin-opener/src/lib.rs +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::path::Path; - -use tauri::{plugin::TauriPlugin, Manager, Runtime}; - -#[cfg(mobile)] -use tauri::plugin::PluginHandle; -#[cfg(target_os = "android")] -const PLUGIN_IDENTIFIER: &str = "app.tauri.opener"; -#[cfg(target_os = "ios")] -tauri::ios_plugin_binding!(init_plugin_opener); - -mod commands; -mod config; -mod error; -mod open; -mod reveal_item_in_dir; -mod scope; -mod scope_entry; - -pub use error::Error; -type Result = std::result::Result; - -pub use open::{open_path, open_url}; -pub use reveal_item_in_dir::{reveal_item_in_dir, reveal_items_in_dir}; - -pub struct Opener { - // we use `fn() -> R` to silence the unused generic error - // while keeping this struct `Send + Sync` without requiring `R` to be - #[cfg(not(mobile))] - _marker: std::marker::PhantomData R>, - #[cfg(mobile)] - mobile_plugin_handle: PluginHandle, - require_literal_leading_dot: Option, -} - -impl Opener { - /// Open a url with a default or specific program. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri_plugin_opener::OpenerExt; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// // open the given URL on the system default browser - /// app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; - /// Ok(()) - /// }); - /// ``` - /// - /// ## Platform-specific: - /// - /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser". - #[cfg(desktop)] - pub fn open_url(&self, url: impl Into, with: Option>) -> Result<()> { - crate::open::open(url.into(), with.map(Into::into)) - } - - /// Open a url with a default or specific program. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri_plugin_opener::OpenerExt; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// // open the given URL on the system default browser - /// app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; - /// Ok(()) - /// }); - /// ``` - /// - /// ## Platform-specific: - /// - /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser". - #[cfg(mobile)] - pub fn open_url(&self, url: impl Into, with: Option>) -> Result<()> { - self.mobile_plugin_handle - .run_mobile_plugin( - "open", - serde_json::json!({ "url": url.into(), "with": with.map(Into::into) }), - ) - .map_err(Into::into) - } - - /// Open a path with a default or specific program. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri_plugin_opener::OpenerExt; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// // open the given path on the system default explorer - /// app.opener().open_path("/path/to/file", None::<&str>)?; - /// Ok(()) - /// }); - /// ``` - /// - /// ## Platform-specific: - /// - /// - **Android / iOS**: Always opens using default program. - #[cfg(desktop)] - pub fn open_path( - &self, - path: impl Into, - with: Option>, - ) -> Result<()> { - crate::open::open(path.into(), with.map(Into::into)) - } - - /// Open a path with a default or specific program. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri_plugin_opener::OpenerExt; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// // open the given path on the system default explorer - /// app.opener().open_path("/path/to/file", None::<&str>)?; - /// Ok(()) - /// }); - /// ``` - /// - /// ## Platform-specific: - /// - /// - **Android / iOS**: Always opens using default program. - #[cfg(mobile)] - pub fn open_path( - &self, - path: impl Into, - _with: Option>, - ) -> Result<()> { - self.mobile_plugin_handle - .run_mobile_plugin("open", path.into()) - .map_err(Into::into) - } - - pub fn reveal_item_in_dir>(&self, p: P) -> Result<()> { - reveal_item_in_dir(p) - } - - pub fn reveal_items_in_dir(&self, paths: I) -> Result<()> - where - I: IntoIterator, - P: AsRef, - { - reveal_items_in_dir(paths) - } -} - -/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the opener APIs. -pub trait OpenerExt { - fn opener(&self) -> &Opener; -} - -impl> OpenerExt for T { - fn opener(&self) -> &Opener { - self.state::>().inner() - } -} - -/// The opener plugin Builder. -pub struct Builder { - open_js_links_on_click: bool, -} - -impl Default for Builder { - fn default() -> Self { - Self { - open_js_links_on_click: true, - } - } -} - -impl Builder { - /// Create a new opener plugin Builder. - pub fn new() -> Self { - Self::default() - } - - /// Whether the plugin should inject a JS script to open URLs in default browser - /// when clicking on `
` elements that has `_blank` target, or when pressing `Ctrl` or `Shift` while clicking it. - /// - /// Enabled by default for `http:`, `https:`, `mailto:`, `tel:` links. - pub fn open_js_links_on_click(mut self, open: bool) -> Self { - self.open_js_links_on_click = open; - self - } - - /// Build and Initializes the plugin. - pub fn build(self) -> TauriPlugin> { - let mut builder = tauri::plugin::Builder::>::new("opener") - .setup(|app, api| { - #[cfg(target_os = "android")] - let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "OpenerPlugin")?; - #[cfg(target_os = "ios")] - let handle = api.register_ios_plugin(init_plugin_opener)?; - - app.manage(Opener { - #[cfg(not(mobile))] - _marker: std::marker::PhantomData:: R>, - #[cfg(mobile)] - mobile_plugin_handle: handle, - require_literal_leading_dot: api - .config() - .as_ref() - .and_then(|c| c.require_literal_leading_dot), - }); - Ok(()) - }) - .invoke_handler(tauri::generate_handler![ - commands::open_url, - commands::open_path, - commands::reveal_item_in_dir, - ]); - - if self.open_js_links_on_click { - builder = builder.js_init_script(include_str!("init-iife.js").to_string()); - } - - builder.build() - } -} - -/// Initializes the plugin. -pub fn init() -> TauriPlugin> { - Builder::default().build() -} diff --git a/vendor/tauri-plugin-opener/src/open.rs b/vendor/tauri-plugin-opener/src/open.rs deleted file mode 100644 index a3d46c508d4..00000000000 --- a/vendor/tauri-plugin-opener/src/open.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Types and functions related to shell. - -use std::{ffi::OsStr, path::Path}; - -pub(crate) fn open, S: AsRef>(path: P, with: Option) -> crate::Result<()> { - match with { - Some(program) => ::open::with_detached(path, program.as_ref()), - None => ::open::that_detached(path), - } - .map_err(Into::into) -} - -/// Opens URL with the program specified in `with`, or system default if `None`. -/// -/// ## Platform-specific: -/// -/// - **Android / iOS**: Always opens using default program. -/// -/// # Examples -/// -/// ```rust,no_run -/// tauri::Builder::default() -/// .setup(|app| { -/// // open the given URL on the system default browser -/// tauri_plugin_opener::open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; -/// Ok(()) -/// }); -/// ``` -pub fn open_url, S: AsRef>(url: P, with: Option) -> crate::Result<()> { - let url = url.as_ref(); - open(url, with) -} - -/// Opens path with the program specified in `with`, or system default if `None`. -/// -/// ## Platform-specific: -/// -/// - **Android / iOS**: Always opens using default program. -/// -/// # Examples -/// -/// ```rust,no_run -/// tauri::Builder::default() -/// .setup(|app| { -/// // open the given URL on the system default explorer -/// tauri_plugin_opener::open_path("/path/to/file", None::<&str>)?; -/// Ok(()) -/// }); -/// ``` -pub fn open_path, S: AsRef>(path: P, with: Option) -> crate::Result<()> { - let path = path.as_ref(); - if with.is_none() { - // Returns an IO error if not exists, and besides `exists()` is a shorthand for `metadata()` - _ = path.metadata()?; - } - open(path, with) -} diff --git a/vendor/tauri-plugin-opener/src/reveal_item_in_dir.rs b/vendor/tauri-plugin-opener/src/reveal_item_in_dir.rs deleted file mode 100644 index 6112fb8b19b..00000000000 --- a/vendor/tauri-plugin-opener/src/reveal_item_in_dir.rs +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::path::Path; - -/// Reveal a path the system's default explorer. -/// -/// ## Platform-specific: -/// -/// - **Android / iOS:** Unsupported. -pub fn reveal_item_in_dir>(path: P) -> crate::Result<()> { - let path = dunce::canonicalize(path.as_ref())?; - - #[cfg(any( - windows, - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - return imp::reveal_items_in_dir(&[path]); - - #[cfg(not(any( - windows, - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - )))] - Err(crate::Error::UnsupportedPlatform) -} - -/// Reveal the paths the system's default explorer. -/// -/// ## Platform-specific: -/// -/// - **Android / iOS:** Unsupported. -pub fn reveal_items_in_dir(paths: I) -> crate::Result<()> -where - I: IntoIterator, - P: AsRef, -{ - let mut canonicalized = vec![]; - - for path in paths { - let path = dunce::canonicalize(path.as_ref())?; - canonicalized.push(path); - } - - #[cfg(any( - windows, - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - return imp::reveal_items_in_dir(&canonicalized); - - #[cfg(not(any( - windows, - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - )))] - Err(crate::Error::UnsupportedPlatform) -} - -#[cfg(windows)] -mod imp { - use std::collections::HashMap; - use std::path::{Path, PathBuf}; - - use windows::Win32::UI::Shell::Common::ITEMIDLIST; - use windows::{ - core::{w, HSTRING, PCWSTR}, - Win32::{ - Foundation::ERROR_FILE_NOT_FOUND, - System::Com::CoInitialize, - UI::{ - Shell::{ - ILCreateFromPathW, ILFree, SHOpenFolderAndSelectItems, ShellExecuteExW, - SHELLEXECUTEINFOW, - }, - WindowsAndMessaging::SW_SHOWNORMAL, - }, - }, - }; - - pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { - if paths.is_empty() { - return Ok(()); - } - - let mut grouped_paths: HashMap<&Path, Vec<&Path>> = HashMap::new(); - for path in paths { - let parent = path - .parent() - .ok_or_else(|| crate::Error::NoParent(path.to_path_buf()))?; - grouped_paths.entry(parent).or_default().push(path); - } - - let _ = unsafe { CoInitialize(None) }; - - for (parent, to_reveals) in grouped_paths { - let parent_item_id_list = OwnedItemIdList::new(parent)?; - let to_reveals_item_id_list = to_reveals - .iter() - .map(|to_reveal| OwnedItemIdList::new(*to_reveal)) - .collect::>>()?; - if let Err(e) = unsafe { - SHOpenFolderAndSelectItems( - parent_item_id_list.item, - Some( - &to_reveals_item_id_list - .iter() - .map(|item| item.item) - .collect::>(), - ), - 0, - ) - } { - // from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302 - // On some systems, the above call mysteriously fails with "file not - // found" even though the file is there. In these cases, ShellExecute() - // seems to work as a fallback (although it won't select the file). - // - // Note: we only handle the first file here if multiple of are present - if e.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 { - let first_path = to_reveals[0]; - let is_dir = first_path.is_dir(); - let mut info = SHELLEXECUTEINFOW { - cbSize: std::mem::size_of::() as _, - nShow: SW_SHOWNORMAL.0, - lpFile: PCWSTR(parent_item_id_list.hstring.as_ptr()), - lpClass: if is_dir { w!("folder") } else { PCWSTR::null() }, - lpVerb: if is_dir { - w!("explore") - } else { - PCWSTR::null() - }, - ..Default::default() - }; - - unsafe { ShellExecuteExW(&mut info) }?; - } - } - } - - Ok(()) - } - - struct OwnedItemIdList { - hstring: HSTRING, - item: *const ITEMIDLIST, - } - - impl OwnedItemIdList { - fn new(path: &Path) -> crate::Result { - let path_hstring = HSTRING::from(path); - let item_id_list = unsafe { ILCreateFromPathW(&path_hstring) }; - if item_id_list.is_null() { - Err(crate::Error::FailedToConvertPathToItemIdList( - path.to_owned(), - )) - } else { - Ok(Self { - hstring: path_hstring, - item: item_id_list, - }) - } - } - } - - impl Drop for OwnedItemIdList { - fn drop(&mut self) { - if !self.item.is_null() { - unsafe { ILFree(Some(self.item)) }; - } - } - } -} - -#[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] -mod imp { - use super::*; - use std::collections::HashMap; - use std::path::PathBuf; - - pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { - let connection = zbus::blocking::Connection::session()?; - - reveal_with_filemanager1(paths, &connection).or_else(|e| { - // Fallback to opening the directory of the first item if revealing multiple items fails. - if let Some(first_path) = paths.first() { - reveal_with_open_uri_portal(first_path, &connection) - } else { - Err(e) - } - }) - } - - fn reveal_with_filemanager1( - paths: &[PathBuf], - connection: &zbus::blocking::Connection, - ) -> crate::Result<()> { - let uris: Result, _> = paths - .iter() - .map(|path| { - url::Url::from_file_path(path) - .map_err(|_| crate::Error::FailedToConvertPathToFileUrl) - }) - .collect(); - let uris = uris?; - let uri_strs: Vec<&str> = uris.iter().map(|uri| uri.as_str()).collect(); - - #[zbus::proxy( - interface = "org.freedesktop.FileManager1", - default_service = "org.freedesktop.FileManager1", - default_path = "/org/freedesktop/FileManager1" - )] - trait FileManager1 { - async fn ShowItems(&self, name: Vec<&str>, arg2: &str) -> crate::Result<()>; - } - - let proxy = FileManager1ProxyBlocking::new(connection)?; - - proxy.ShowItems(uri_strs, "") - } - - fn reveal_with_open_uri_portal( - path: &Path, - connection: &zbus::blocking::Connection, - ) -> crate::Result<()> { - let uri = url::Url::from_file_path(path) - .map_err(|_| crate::Error::FailedToConvertPathToFileUrl)?; - - #[zbus::proxy( - interface = "org.freedesktop.portal.Desktop", - default_service = "org.freedesktop.portal.OpenURI", - default_path = "/org/freedesktop/portal/desktop" - )] - trait PortalDesktop { - async fn OpenDirectory( - &self, - arg1: &str, - name: &str, - arg3: HashMap<&str, &str>, - ) -> crate::Result<()>; - } - - let proxy = PortalDesktopProxyBlocking::new(connection)?; - - proxy.OpenDirectory("", uri.as_str(), HashMap::new()) - } -} - -#[cfg(target_os = "macos")] -mod imp { - use objc2_app_kit::NSWorkspace; - use objc2_foundation::{NSArray, NSString, NSURL}; - use std::path::PathBuf; - - pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { - unsafe { - let mut urls = Vec::new(); - - for path in paths { - let path = path.to_string_lossy(); - let path = NSString::from_str(&path); - let url = NSURL::fileURLWithPath(&path); - - urls.push(url); - } - - let urls = NSArray::from_retained_slice(&urls); - - let workspace = NSWorkspace::new(); - workspace.activateFileViewerSelectingURLs(&urls); - } - - Ok(()) - } -} diff --git a/vendor/tauri-plugin-opener/src/scope.rs b/vendor/tauri-plugin-opener/src/scope.rs deleted file mode 100644 index 275913d1ba5..00000000000 --- a/vendor/tauri-plugin-opener/src/scope.rs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::{ - marker::PhantomData, - path::{Path, PathBuf}, - sync::Arc, -}; - -use tauri::{ipc::ScopeObject, utils::acl::Value, AppHandle, Manager, Runtime}; - -use crate::{scope_entry::EntryRaw, Error}; - -pub use crate::scope_entry::Application; - -#[derive(Debug)] -pub enum Entry { - Url { - url: glob::Pattern, - app: Application, - }, - Path { - path: Option, - app: Application, - }, -} - -impl ScopeObject for Entry { - type Error = Error; - - fn deserialize( - app_handle: &AppHandle, - raw: Value, - ) -> std::result::Result { - serde_json::from_value(raw.into()) - .and_then(|raw| { - let entry = match raw { - EntryRaw::Url { url, app } => Entry::Url { - url: glob::Pattern::new(&url) - .map_err(|e| serde::de::Error::custom(e.to_string()))?, - app, - }, - EntryRaw::Path { path, app } => { - let path = match app_handle.path().parse(path) { - Ok(path) => Some(path), - #[cfg(not(target_os = "android"))] - Err(tauri::Error::UnknownPath) => None, - Err(err) => return Err(serde::de::Error::custom(err.to_string())), - }; - - Entry::Path { path, app } - } - }; - - Ok(entry) - }) - .map_err(Into::into) - } -} - -impl Application { - fn matches(&self, a: Option<&str>) -> bool { - match self { - Self::Default => a.is_none(), - Self::Enable(enable) => *enable, - Self::App(program) => Some(program.as_str()) == a, - } - } -} - -impl Entry { - fn path(&self) -> Option { - match self { - Self::Url { .. } => None, - Self::Path { path, .. } => path.clone(), - } - } - - fn matches_url(&self, u: &str, a: Option<&str>) -> bool { - match self { - Self::Url { url, app } => url.matches(u) && app.matches(a), - Self::Path { .. } => false, - } - } - - fn matches_path_program(&self, a: Option<&str>) -> bool { - match self { - Self::Url { .. } => false, - Self::Path { app, .. } => app.matches(a), - } - } -} - -#[derive(Debug)] -pub struct Scope<'a, R: Runtime, M: Manager> { - allowed: Vec<&'a Arc>, - denied: Vec<&'a Arc>, - manager: &'a M, - _marker: PhantomData, -} - -impl<'a, R: Runtime, M: Manager> Scope<'a, R, M> { - pub(crate) fn new( - manager: &'a M, - allowed: Vec<&'a Arc>, - denied: Vec<&'a Arc>, - ) -> Self { - Self { - manager, - allowed, - denied, - _marker: PhantomData, - } - } - - pub fn is_url_allowed(&self, url: &str, with: Option<&str>) -> bool { - let denied = self.denied.iter().any(|e| e.matches_url(url, with)); - if denied { - false - } else { - self.allowed.iter().any(|e| e.matches_url(url, with)) - } - } - - pub fn is_path_allowed(&self, path: &Path, with: Option<&str>) -> crate::Result { - let fs_scope = tauri::fs::Scope::new( - self.manager, - &tauri::utils::config::FsScope::Scope { - allow: self.allowed.iter().filter_map(|e| e.path()).collect(), - deny: self.denied.iter().filter_map(|e| e.path()).collect(), - require_literal_leading_dot: self - .manager - .state::>() - .require_literal_leading_dot, - }, - )?; - - Ok(fs_scope.is_allowed(path) && self.allowed.iter().any(|e| e.matches_path_program(with))) - } -} diff --git a/vendor/tauri-plugin-opener/src/scope_entry.rs b/vendor/tauri-plugin-opener/src/scope_entry.rs deleted file mode 100644 index cf9004a2b50..00000000000 --- a/vendor/tauri-plugin-opener/src/scope_entry.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::path::PathBuf; - -use serde::Deserialize; - -#[derive(Deserialize, Debug)] -#[serde(untagged)] -pub enum Application { - Default, - Enable(bool), - App(String), -} - -impl Default for Application { - fn default() -> Self { - Self::Default - } -} - -#[derive(Deserialize)] -#[serde(untagged, rename_all = "camelCase")] -pub(crate) enum EntryRaw { - Url { - url: String, - #[serde(default)] - app: Application, - }, - Path { - path: PathBuf, - #[serde(default)] - app: Application, - }, -} From e06f285fde15729a7bcc86aaad9f5b18543aea70 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:12:49 +0800 Subject: [PATCH 4/4] revert(desktop): drop the vendored opener plugin --- .github/workflows/ci.yml | 6 ++---- Cargo.lock | 4 ++-- Cargo.toml | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f432e406f4..dd28b8a808e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,6 @@ jobs: - 'scripts/run-gpui-build*' - 'scripts/sync-desktop-versions.mjs' - 'scripts/verify-gpui-release-inputs.mjs' - - 'vendor/tauri-plugin-opener/**' - 'Cargo.toml' - 'Cargo.lock' - 'bun.lock' @@ -64,7 +63,6 @@ jobs: - 'apps/desktop-gpui/**' - 'crates/**' - 'apps/desktop/src-tauri/**' - - 'vendor/tauri-plugin-opener/**' - 'Cargo.toml' - 'Cargo.lock' tauri-plugins: @@ -225,7 +223,7 @@ jobs: - name: Test desktop installation and storage helpers shell: bash - run: cargo test --locked -p cap-utils -p cap-cli-install -p tauri-plugin-opener --lib + run: cargo test --locked -p cap-utils -p cap-cli-install --lib - name: Build desktop binaries shell: bash @@ -321,7 +319,7 @@ jobs: run: | node --test scripts/linux-bundle-config.test.mjs scripts/finalize-linux-appimage.test.mjs bun run --cwd apps/desktop vitest run scripts/prepare.test.js - cargo test --locked -p cap-utils -p cap-cli-install -p tauri-plugin-opener --lib + cargo test --locked -p cap-utils -p cap-cli-install --lib cargo test --locked -p cap --bin cap record::tests cargo test --locked -p cap-recording --lib -- --test-threads=1 cargo test --locked -p cap-enc-ffmpeg diff --git a/Cargo.lock b/Cargo.lock index 90c88f49856..ffb628ea4f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10802,6 +10802,8 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" dependencies = [ "dunce", "glob", @@ -10813,9 +10815,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "tempfile", "thiserror 2.0.16", - "tokio", "url", "windows 0.61.3", "zbus", diff --git a/Cargo.toml b/Cargo.toml index 2749ec83681..8a9be0ffb64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "apps/desktop/src-tauri", "crates/*", "crates/workspace-hack", - "vendor/tauri-plugin-opener", ] [workspace.dependencies] @@ -133,7 +132,6 @@ cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8" } # wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "cd41a6e32a6239b65d1cecbeccde6a43a100914a" } wgpu-hal = { path = "vendor/wgpu-hal" } tao = { path = "vendor/tao" } -tauri-plugin-opener = { path = "vendor/tauri-plugin-opener" } # https://github.com/CapSoftware/reqwest/commit/9b5ecbd5210a9510fde766015cabb724c1e70d2e reqwest = { git = "https://github.com/CapSoftware/reqwest", rev = "9b5ecbd5210a9510fde766015cabb724c1e70d2e" }