├── .github ├── FUNDING.yml └── workflows │ ├── rustdoc.yml │ ├── spelling.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .nojekyll ├── Cargo.lock ├── Cargo.toml ├── LICENSE_APACHE-2.0 ├── LICENSE_MIT ├── README.md ├── examples └── test │ ├── .gitignore │ ├── .taurignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Trunk.toml │ ├── index.html │ ├── src-tauri │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ ├── ci.tauri.conf.json │ ├── icons │ │ ├── 128x128.png │ │ ├── 128x128@2x.png │ │ ├── 32x32.png │ │ ├── Square107x107Logo.png │ │ ├── Square142x142Logo.png │ │ ├── Square150x150Logo.png │ │ ├── Square284x284Logo.png │ │ ├── Square30x30Logo.png │ │ ├── Square310x310Logo.png │ │ ├── Square44x44Logo.png │ │ ├── Square71x71Logo.png │ │ ├── Square89x89Logo.png │ │ ├── StoreLogo.png │ │ ├── icon.icns │ │ ├── icon.ico │ │ └── icon.png │ ├── src │ │ └── main.rs │ └── tauri.conf.json │ ├── src │ ├── app.rs │ ├── clipboard.rs │ ├── dialog.rs │ ├── event.rs │ ├── global_shortcut.rs │ ├── main.rs │ ├── notification.rs │ ├── os.rs │ ├── tauri_log.rs │ └── window.rs │ └── styles.css ├── package.json ├── pnpm-lock.yaml ├── renovate.json5 ├── src ├── app.js ├── app.rs ├── cli.js ├── clipboard.js ├── clipboard.rs ├── dialog.js ├── dialog.rs ├── error.rs ├── event.js ├── event.rs ├── fs.js ├── fs.rs ├── globalShortcut.js ├── global_shortcut.rs ├── http.js ├── index.js ├── lib.rs ├── mocks.js ├── mocks.rs ├── notification.js ├── notification.rs ├── os.js ├── os.rs ├── path.js ├── path.rs ├── process.js ├── process.rs ├── shell.js ├── tauri.js ├── tauri.rs ├── updater.js ├── updater.rs ├── window.js └── window.rs ├── tests └── web.rs └── typos.toml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: JonasKruckenberg 4 | -------------------------------------------------------------------------------- /.github/workflows/rustdoc.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | 34 | - uses: Swatinem/rust-cache@v1 35 | 36 | - name: Install Rust toolchain 37 | uses: actions-rs/toolchain@v1 38 | with: 39 | toolchain: stable 40 | profile: minimal 41 | override: true 42 | components: rustfmt, rust-src 43 | 44 | - name: Build Documentation 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: doc 48 | args: -p tauri-sys --all-features --no-deps 49 | 50 | - name: Create index.html 51 | run: echo '' > ./target/doc/index.html 52 | 53 | - name: Setup Pages 54 | uses: actions/configure-pages@v4 55 | 56 | - name: Upload artifact 57 | uses: actions/upload-pages-artifact@v1 58 | with: 59 | # Upload entire repository 60 | path: './target/doc' 61 | 62 | - name: Deploy to GitHub Pages 63 | id: deployment 64 | uses: actions/deploy-pages@v3 65 | -------------------------------------------------------------------------------- /.github/workflows/spelling.yml: -------------------------------------------------------------------------------- 1 | name: Spelling 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: [pull_request] 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | CARGO_TERM_COLOR: always 11 | CLICOLOR: 1 12 | 13 | jobs: 14 | spelling: 15 | name: Spell Check with Typos 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout Actions Repository 19 | uses: actions/checkout@v3 20 | - name: Spell Check Repo 21 | uses: crate-ci/typos@master -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/test.yml' 9 | - 'src/**' 10 | - 'examples/test/**' 11 | pull_request: 12 | branches: 13 | - main 14 | paths: 15 | - '.github/workflows/test.yml' 16 | - 'src/**' 17 | - 'examples/test/**' 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | build-and-test: 25 | runs-on: ubuntu-latest 26 | timeout-minutes: 15 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Install stable toolchain 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | override: true 34 | target: wasm32-unknown-unknown 35 | - uses: Swatinem/rust-cache@v1 36 | - name: Install native deps 37 | run: | 38 | sudo apt-get update 39 | sudo apt-get install -y webkit2gtk-4.0 at-spi2-core 40 | - name: Install Tauri CLI 41 | run: | 42 | cd examples/test 43 | wget -qO- https://github.com/tauri-apps/tauri/releases/download/cli.rs-v1.5.8/cargo-tauri-x86_64-unknown-linux-gnu.tgz | tar -xzf- -C ~/.cargo/bin 44 | - name: Install Trunk 45 | run: | 46 | cd examples/test 47 | wget -qO- https://github.com/thedodd/trunk/releases/download/v0.18.0/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- -C ~/.cargo/bin 48 | - name: Run test app 49 | run: | 50 | cd examples/test 51 | export CARGO_UNSTABLE_SPARSE_REGISTRY=true 52 | xvfb-run cargo tauri dev --exit-on-panic --config ./src-tauri/ci.tauri.conf.json 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | node_modules 3 | src/*.mjs 4 | .DS_Store 5 | .idea -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tauri"] 2 | path = tauri 3 | url = https://github.com/tauri-apps/tauri 4 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "tauri-sys" 4 | version = "0.1.0" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | futures = { version = "0.3.29", optional = true } 10 | js-sys = "0.3.66" 11 | log = "0.4.20" 12 | semver = {version = "1.0.20", optional = true, features = ["serde"]} 13 | serde = {version = "1.0.193", features = ["derive"]} 14 | serde-wasm-bindgen = "0.6.3" 15 | serde_repr = "0.1.17" 16 | thiserror = "1.0.50" 17 | url = {version = "2.5.0", optional = true, features = ["serde"]} 18 | wasm-bindgen = "0.2.89" 19 | wasm-bindgen-futures = "0.4.39" 20 | 21 | [dev-dependencies] 22 | tauri-sys = {path = ".", features = ["all"]} 23 | wasm-bindgen-test = "0.3.42" 24 | 25 | [package.metadata.docs.rs] 26 | all-features = true 27 | 28 | [features] 29 | all = ["app", "clipboard", "event", "fs", "mocks", "tauri", "window", "process", "dialog", "os", "notification", "path", "updater", "global_shortcut"] 30 | app = ["dep:semver"] 31 | clipboard = [] 32 | dialog = [] 33 | event = ["dep:futures"] 34 | fs = [] 35 | global_shortcut = [] 36 | mocks = [] 37 | notification = [] 38 | os = [] 39 | path = [] 40 | process = [] 41 | tauri = ["dep:url"] 42 | updater = ["dep:futures", "event"] 43 | window = ["dep:futures", "event"] 44 | 45 | [workspace] 46 | members = ["examples/test", "examples/test/src-tauri"] 47 | -------------------------------------------------------------------------------- /LICENSE_APACHE-2.0: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jonas Kruckenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | tauri-sys 4 |

5 |

6 | Raw bindings to the Tauri API 7 | for projects using wasm-bindgen 8 |

9 |
10 | 11 | [![Documentation master][docs-badge]][docs-url] 12 | [![MIT licensed][mit-badge]][mit-url] 13 | 14 | [docs-badge]: https://img.shields.io/badge/docs-main-blue 15 | [docs-url]: https://jonaskruckenberg.github.io/tauri-sys/tauri_sys 16 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 17 | [mit-url]: LICENSE 18 | 19 | ## Installation 20 | 21 | This crate is not yet published to crates.io, so you need to use it from git. You also need a global installation of [`esbuild`]. 22 | 23 | ```toml 24 | tauri-sys = { git = "https://github.com/JonasKruckenberg/tauri-sys" } // Tauri v1 25 | // OR 26 | tauri-sys = { git = "https://github.com/JonasKruckenberg/tauri-sys", branch = "v2" } // Tauri v2 27 | ``` 28 | 29 | ## Usage 30 | 31 | ```rust 32 | use serde::{Deserialize, Serialize}; 33 | use tauri_sys::tauri; 34 | 35 | #[derive(Serialize, Deserialize)] 36 | struct GreetArgs<'a> { 37 | name: &'a str, 38 | } 39 | 40 | fn main() { 41 | wasm_bindgen_futures::spawn_local(async move { 42 | let new_msg: String = tauri::invoke("greet", &GreetArgs { name: &name.get() }).await.unwrap(); 43 | 44 | println!("{}", new_msg); 45 | }); 46 | } 47 | ``` 48 | 49 | ## Features 50 | 51 | All modules are gated by accordingly named Cargo features. It is recommended you keep this synced with the features enabled in your [Tauri Allowlist] but no automated tool for this exists (yet). 52 | 53 | - **all**: Enables all modules. 54 | - **app**: Enables the `app` module. 55 | - **clipboard**: Enables the `clipboard` module. 56 | - **dialog**: Enables the `dialog` module. 57 | - **event**: Enables the `event` module. 58 | - **fs**: Enables the `fs` module. 59 | - **mocks**: Enables the `mocks` module. 60 | - **tauri**: Enables the `tauri` module. 61 | 62 | ## Are we Tauri yet? 63 | 64 | These API bindings are not completely on-par with `@tauri-apps/api` yet, but here is the current status-quo: 65 | 66 | - [x] `app` 67 | - [ ] `cli` 68 | - [x] `clipboard` 69 | - [x] `dialog` 70 | - [x] `event` 71 | - [x] `fs` 72 | - [x] `global_shortcut` 73 | - [ ] `http` 74 | - [x] `mocks` 75 | - [x] `notification` 76 | - [x] `os` 77 | - [x] `path` 78 | - [x] `process` 79 | - [ ] `shell` 80 | - [x] `tauri` 81 | - [ ] `updater` 82 | - [x] `window` 83 | 84 | The current API also very closely mirrors the JS API even though that might not be the most ergonomic choice, ideas for improving the API with quality-of-life features beyond the regular JS API interface are very welcome. 85 | 86 | [wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen 87 | [tauri allowlist]: https://tauri.app/v1/api/config#allowlistconfig 88 | [`esbuild`]: https://esbuild.github.io/getting-started/#install-esbuild 89 | -------------------------------------------------------------------------------- /examples/test/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | dist -------------------------------------------------------------------------------- /examples/test/.taurignore: -------------------------------------------------------------------------------- 1 | /src 2 | /public 3 | /Cargo.toml -------------------------------------------------------------------------------- /examples/test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tauri-sys-test-ui" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [dependencies] 8 | tauri-sys = { path = "../../", features = ["all"] } 9 | sycamore = { git = "https://github.com/sycamore-rs/sycamore", rev = "abd556cbc02047042dad2ebd04405e455a9b11b2", features = ["suspense"] } 10 | anyhow = "1.0.75" 11 | console_error_panic_hook = "0.1.7" 12 | wasm-bindgen-futures = "0.4.39" 13 | serde = { version = "1.0.193", features = ["derive"] } 14 | log = { version = "0.4.20", features = ["serde"] } 15 | futures = "0.3.29" 16 | gloo-timers = { version = "0.3", features = ["futures"] } 17 | 18 | [features] 19 | ci = [] 20 | -------------------------------------------------------------------------------- /examples/test/Trunk.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "./index.html" 3 | 4 | [watch] 5 | ignore = ["./src-tauri"] 6 | 7 | [serve] 8 | address = "127.0.0.1" 9 | port = 1420 10 | open = false 11 | 12 | [tools] 13 | # Default wasm-bindgen version to download. 14 | wasm_bindgen = "0.2.89" -------------------------------------------------------------------------------- /examples/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tauri + Yew App 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/test/src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | -------------------------------------------------------------------------------- /examples/test/src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tauri-sys-test" 3 | version = "0.0.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | license = "" 7 | repository = "" 8 | edition = "2021" 9 | rust-version = "1.57" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [build-dependencies] 14 | tauri-build = { version = "1.5.0", features = [] } 15 | 16 | [dependencies] 17 | serde_json = "1.0" 18 | serde = { version = "1.0", features = ["derive"] } 19 | tauri-plugin-log = {git = "https://github.com/tauri-apps/tauri-plugin-log", features = ["colored"] } 20 | tauri = { version = "1.5.3", features = ["api-all", "updater"] } 21 | 22 | [features] 23 | # by default Tauri runs in production mode 24 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 25 | default = [ "custom-protocol" ] 26 | # this feature is used for production builds where `devPath` points to the filesystem 27 | # DO NOT remove this 28 | custom-protocol = [ "tauri/custom-protocol" ] 29 | -------------------------------------------------------------------------------- /examples/test/src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /examples/test/src-tauri/ci.tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeDevCommand": "trunk serve --features ci", 4 | "beforeBuildCommand": "trunk build --release --features ci" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /examples/test/src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonasKruckenberg/tauri-sys/e953a9c912212d43fa42add71bc84c40c30140e0/examples/test/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /examples/test/src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | use std::sync::atomic::{AtomicBool, Ordering}; 7 | use tauri::{Manager, Runtime, State, Window}; 8 | use tauri_plugin_log::{LogTarget, LoggerBuilder}; 9 | 10 | struct Received(AtomicBool); 11 | // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command 12 | #[tauri::command] 13 | fn verify_receive(emitted: State) -> bool { 14 | emitted.0.load(Ordering::Relaxed) 15 | } 16 | 17 | #[tauri::command] 18 | async fn emit_event(win: Window) -> Result<(), ()> { 19 | let _ = win.emit("rust-event-once", "Hello World from Rust!"); 20 | 21 | Ok(()) 22 | } 23 | 24 | #[tauri::command] 25 | async fn emit_event_5_times(win: Window) -> Result<(), ()> { 26 | for i in 0..5 { 27 | let _ = win.emit("rust-event-listen", i); 28 | } 29 | 30 | Ok(()) 31 | } 32 | 33 | #[tauri::command] 34 | fn exit_with_error(e: &str) -> bool { 35 | eprintln!("{}", e); 36 | std::process::exit(1); 37 | } 38 | 39 | fn main() { 40 | let log_plugin = { 41 | let targets = [ 42 | LogTarget::LogDir, 43 | #[cfg(debug_assertions)] 44 | LogTarget::Stdout, 45 | #[cfg(debug_assertions)] 46 | LogTarget::Webview, 47 | ]; 48 | 49 | LoggerBuilder::new().targets(targets).build() 50 | }; 51 | 52 | tauri::Builder::default() 53 | .plugin(log_plugin) 54 | .invoke_handler(tauri::generate_handler![verify_receive, emit_event, emit_event_5_times, exit_with_error]) 55 | .setup(|app| { 56 | app.manage(Received(AtomicBool::new(false))); 57 | 58 | let app_handle = app.handle(); 59 | app.listen_global("javascript-event", move |_| { 60 | app_handle 61 | .state::() 62 | .0 63 | .store(true, Ordering::Relaxed); 64 | }); 65 | 66 | Ok(()) 67 | }) 68 | .run(tauri::generate_context!()) 69 | .expect("error while running tauri application"); 70 | } 71 | -------------------------------------------------------------------------------- /examples/test/src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeDevCommand": "trunk serve", 4 | "beforeBuildCommand": "trunk build --release", 5 | "devPath": "http://localhost:1420", 6 | "distDir": "../dist", 7 | "withGlobalTauri": true 8 | }, 9 | "package": { 10 | "productName": "tauri-sys-test", 11 | "version": "0.0.0" 12 | }, 13 | "tauri": { 14 | "allowlist": { 15 | "all": true 16 | }, 17 | "bundle": { 18 | "active": true, 19 | "category": "DeveloperTool", 20 | "copyright": "", 21 | "deb": { 22 | "depends": [] 23 | }, 24 | "externalBin": [], 25 | "icon": [ 26 | "icons/32x32.png", 27 | "icons/128x128.png", 28 | "icons/128x128@2x.png", 29 | "icons/icon.icns", 30 | "icons/icon.ico" 31 | ], 32 | "identifier": "com.tauri.dev", 33 | "longDescription": "", 34 | "macOS": { 35 | "entitlements": null, 36 | "exceptionDomain": "", 37 | "frameworks": [], 38 | "providerShortName": null, 39 | "signingIdentity": null 40 | }, 41 | "resources": [], 42 | "shortDescription": "", 43 | "targets": "all", 44 | "windows": { 45 | "certificateThumbprint": null, 46 | "digestAlgorithm": "sha256", 47 | "timestampUrl": "" 48 | } 49 | }, 50 | "security": { 51 | "csp": null 52 | }, 53 | "updater": { 54 | "active": true, 55 | "endpoints": [ 56 | "https://releases.myapp.com/{{target}}/{{current_version}}" 57 | ], 58 | "dialog": true, 59 | "pubkey": "YOUR_UPDATER_SIGNATURE_PUBKEY_HERE" 60 | }, 61 | "windows": [ 62 | { 63 | "fullscreen": false, 64 | "height": 600, 65 | "resizable": true, 66 | "title": "tauri-sys testing suite", 67 | "width": 800 68 | } 69 | ] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/test/src/app.rs: -------------------------------------------------------------------------------- 1 | use anyhow::ensure; 2 | use tauri_sys::app; 3 | 4 | pub async fn get_name() -> anyhow::Result<()> { 5 | let name = app::get_name().await?; 6 | 7 | ensure!(name == "tauri-sys-test"); 8 | 9 | Ok(()) 10 | } 11 | 12 | pub async fn get_version() -> anyhow::Result<()> { 13 | let version = app::get_version().await?; 14 | 15 | ensure!(version.major == 0); 16 | ensure!(version.minor == 0); 17 | ensure!(version.patch == 0); 18 | ensure!(version.build.is_empty()); 19 | ensure!(version.pre.is_empty()); 20 | 21 | Ok(()) 22 | } 23 | 24 | pub async fn get_tauri_version() -> anyhow::Result<()> { 25 | let version = app::get_tauri_version().await?; 26 | 27 | ensure!(version.major == 1); 28 | ensure!(version.minor == 5); 29 | ensure!(version.patch == 3); 30 | ensure!(version.build.is_empty()); 31 | ensure!(version.pre.is_empty()); 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /examples/test/src/clipboard.rs: -------------------------------------------------------------------------------- 1 | use anyhow::ensure; 2 | use tauri_sys::clipboard; 3 | 4 | pub async fn test() -> anyhow::Result<()> { 5 | clipboard::write_text("foobar").await?; 6 | 7 | let text = clipboard::read_text().await?; 8 | 9 | ensure!(text == "foobar".to_string()); 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /examples/test/src/dialog.rs: -------------------------------------------------------------------------------- 1 | use anyhow::ensure; 2 | use tauri_sys::dialog::{FileDialogBuilder, MessageDialogBuilder, MessageDialogKind}; 3 | 4 | pub async fn ask() -> anyhow::Result<()> { 5 | let works = MessageDialogBuilder::new() 6 | .set_title("Tauri") 7 | .set_kind(MessageDialogKind::Warning) 8 | .ask("Does this work? \n Click Yes to mark this test as passing") 9 | .await?; 10 | 11 | ensure!(works); 12 | 13 | Ok(()) 14 | } 15 | 16 | pub async fn confirm() -> anyhow::Result<()> { 17 | let works = MessageDialogBuilder::new() 18 | .set_title("Tauri") 19 | .set_kind(MessageDialogKind::Warning) 20 | .confirm("Does this work? \n Click Ok to mark this test as passing") 21 | .await?; 22 | 23 | ensure!(works); 24 | 25 | Ok(()) 26 | } 27 | 28 | pub async fn message() -> anyhow::Result<()> { 29 | MessageDialogBuilder::new() 30 | .set_title("Tauri") 31 | .set_kind(MessageDialogKind::Warning) 32 | .message("This is a message just for you!") 33 | .await?; 34 | 35 | Ok(()) 36 | } 37 | 38 | pub async fn pick_file() -> anyhow::Result<()> { 39 | let file = FileDialogBuilder::new() 40 | .set_title("Select a file to mark this test as passing") 41 | .pick_file() 42 | .await?; 43 | 44 | ensure!(file.is_some()); 45 | 46 | Ok(()) 47 | } 48 | 49 | pub async fn pick_files() -> anyhow::Result<()> { 50 | let file = FileDialogBuilder::new() 51 | .set_title("Select a multiple files to mark this test as passing") 52 | .pick_files() 53 | .await?; 54 | 55 | ensure!(file.is_some()); 56 | ensure!(file.unwrap().count() > 1); 57 | 58 | Ok(()) 59 | } 60 | 61 | pub async fn pick_folder() -> anyhow::Result<()> { 62 | let file = FileDialogBuilder::new() 63 | .set_title("Select a folder to mark this test as passing") 64 | .pick_folder() 65 | .await?; 66 | 67 | ensure!(file.is_some()); 68 | 69 | Ok(()) 70 | } 71 | 72 | pub async fn pick_folders() -> anyhow::Result<()> { 73 | let file = FileDialogBuilder::new() 74 | .set_title("Select a multiple folders to mark this test as passing") 75 | .pick_folders() 76 | .await?; 77 | 78 | ensure!(file.is_some()); 79 | ensure!(file.unwrap().count() > 1); 80 | 81 | Ok(()) 82 | } 83 | 84 | pub async fn save() -> anyhow::Result<()> { 85 | let file = FileDialogBuilder::new() 86 | .set_title("Select a file to mark this test as passing") 87 | .save() 88 | .await?; 89 | 90 | ensure!(file.is_some()); 91 | 92 | Ok(()) 93 | } 94 | -------------------------------------------------------------------------------- /examples/test/src/event.rs: -------------------------------------------------------------------------------- 1 | use anyhow::ensure; 2 | use futures::StreamExt; 3 | use tauri_sys::{event, tauri}; 4 | 5 | pub async fn emit() -> anyhow::Result<()> { 6 | event::emit("javascript-event", &"bar").await?; 7 | 8 | ensure!(tauri::invoke::<_, bool>("verify_receive", &()).await?); 9 | 10 | Ok(()) 11 | } 12 | 13 | pub async fn listen() -> anyhow::Result<()> { 14 | let events = event::listen::("rust-event-listen").await?; 15 | tauri::invoke::<_, ()>("emit_event_5_times", &()).await?; 16 | 17 | let events: Vec = events 18 | .take(5) 19 | .map(|e| e.payload) 20 | .collect() 21 | .await; 22 | 23 | ensure!(events == vec![0, 1, 2, 3, 4]); 24 | 25 | Ok(()) 26 | } 27 | 28 | pub async fn once() -> anyhow::Result<()> { 29 | // this causes enough delay for `once` to register it's event listener before the event gets triggered 30 | wasm_bindgen_futures::spawn_local(async { 31 | tauri::invoke::<_, ()>("emit_event", &()).await.unwrap(); 32 | }); 33 | let event = event::once::("rust-event-once").await?; 34 | 35 | ensure!(event.payload == "Hello World from Rust!"); 36 | 37 | Ok(()) 38 | } -------------------------------------------------------------------------------- /examples/test/src/global_shortcut.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use futures::StreamExt; 4 | use tauri_sys::global_shortcut; 5 | 6 | pub async fn register_all() -> anyhow::Result<()> { 7 | let task = async { 8 | let shortcuts = ["CommandOrControl+Shift+C", "Ctrl+Alt+F12"]; 9 | 10 | let streams = futures::future::try_join_all(shortcuts.map(|s| async move { 11 | let stream = global_shortcut::register(s).await?; 12 | 13 | anyhow::Ok(stream.map(move |_| s)) 14 | })) 15 | .await?; 16 | 17 | let mut events = futures::stream::select_all(streams); 18 | 19 | while let Some(shortcut) = events.next().await { 20 | log::debug!("Shortcut {} triggered", shortcut) 21 | } 22 | 23 | anyhow::Ok(()) 24 | }; 25 | 26 | let timeout = gloo_timers::future::sleep(Duration::from_secs(20)); 27 | 28 | futures::future::select(Box::pin(task), timeout).await; 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /examples/test/src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod clipboard; 3 | mod dialog; 4 | mod event; 5 | mod notification; 6 | mod os; 7 | mod tauri_log; 8 | mod window; 9 | mod global_shortcut; 10 | 11 | extern crate console_error_panic_hook; 12 | use log::LevelFilter; 13 | use std::future::Future; 14 | use std::panic; 15 | use sycamore::prelude::*; 16 | use sycamore::suspense::Suspense; 17 | use tauri_log::TauriLogger; 18 | 19 | #[cfg(feature = "ci")] 20 | async fn exit_with_error(e: String) { 21 | use serde::Serialize; 22 | 23 | #[derive(Serialize)] 24 | struct Args { 25 | e: String, 26 | } 27 | 28 | tauri_sys::tauri::invoke::<_, ()>("exit_with_error", &Args { e }) 29 | .await 30 | .unwrap(); 31 | } 32 | 33 | #[derive(Props)] 34 | pub struct TestProps<'a, F> 35 | where 36 | F: Future> + 'a, 37 | { 38 | name: &'a str, 39 | test: F, 40 | } 41 | 42 | #[component] 43 | pub async fn TestInner<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View 44 | where 45 | F: Future> + 'a, 46 | { 47 | let res = props.test.await; 48 | 49 | view! { cx, 50 | tr { 51 | td { code { (props.name.to_string()) } } 52 | td { (if let Err(e) = &res { 53 | #[cfg(feature = "ci")] 54 | { 55 | wasm_bindgen_futures::spawn_local(exit_with_error(e.to_string())); 56 | unreachable!() 57 | } 58 | #[cfg(not(feature = "ci"))] 59 | format!("❌ {:?}", e) 60 | } else { 61 | format!("✅") 62 | }) 63 | } 64 | } 65 | } 66 | } 67 | 68 | #[component] 69 | pub fn Test<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View 70 | where 71 | F: Future> + 'a, 72 | { 73 | let fallback = view! { cx, 74 | tr { 75 | td { code { (props.name.to_string()) } } 76 | td { 77 | span(class="loader") { "⏳" } 78 | } 79 | } 80 | }; 81 | 82 | view! { cx, 83 | Suspense(fallback=fallback) { 84 | TestInner(name=props.name, test=props.test) 85 | } 86 | } 87 | } 88 | 89 | #[cfg(not(feature = "ci"))] 90 | #[component] 91 | pub fn InteractiveTest<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View 92 | where 93 | F: Future> + 'a, 94 | { 95 | let mut test = Some(props.test); 96 | let render_test = create_signal(cx, false); 97 | 98 | let run_test = |_| { 99 | render_test.set(true); 100 | }; 101 | 102 | view! { cx, 103 | (if *render_test.get() { 104 | let test = test.take().unwrap(); 105 | 106 | view! { cx, 107 | Test(name=props.name, test=test) 108 | } 109 | } else { 110 | view! { cx, 111 | tr { 112 | td { code { (props.name.to_string()) } } 113 | td { 114 | button(on:click=run_test) { "Run Interactive Test"} 115 | } 116 | } 117 | } 118 | }) 119 | } 120 | } 121 | 122 | #[cfg(feature = "ci")] 123 | #[component] 124 | pub async fn InteractiveTest<'a, G: Html, F>(cx: Scope<'a>, _props: TestProps<'a, F>) -> View 125 | where 126 | F: Future> + 'a, 127 | { 128 | view! { cx, "Interactive tests are not run in CI mode" } 129 | } 130 | 131 | #[component] 132 | pub async fn Terminate<'a, G: Html>(cx: Scope<'a>) -> View { 133 | #[cfg(feature = "ci")] 134 | sycamore::suspense::await_suspense(cx, async { 135 | tauri_sys::process::exit(0).await; 136 | }) 137 | .await; 138 | 139 | view! { 140 | cx, 141 | } 142 | } 143 | 144 | static LOGGER: TauriLogger = TauriLogger; 145 | 146 | fn main() { 147 | log::set_logger(&LOGGER) 148 | .map(|()| log::set_max_level(LevelFilter::Trace)) 149 | .unwrap(); 150 | 151 | panic::set_hook(Box::new(|info| { 152 | console_error_panic_hook::hook(info); 153 | 154 | #[cfg(feature = "ci")] 155 | wasm_bindgen_futures::spawn_local(exit_with_error(format!("{}", info))); 156 | })); 157 | 158 | sycamore::render(|cx| { 159 | view! { cx, 160 | table { 161 | tbody { 162 | // Suspense(fallback=view!{ cx, "Running Tests..." }) { 163 | Test(name="app::get_name",test=app::get_name()) 164 | Test(name="app::get_version",test=app::get_version()) 165 | Test(name="app::get_tauri_version",test=app::get_tauri_version()) 166 | Test(name="clipboard::read_text | clipboard::write_text",test=clipboard::test()) 167 | Test(name="event::emit",test=event::emit()) 168 | Test(name="event::listen",test=event::listen()) 169 | Test(name="event::once",test=event::once()) 170 | InteractiveTest(name="dialog::message",test=dialog::message()) 171 | InteractiveTest(name="dialog::ask",test=dialog::ask()) 172 | InteractiveTest(name="dialog::confirm",test=dialog::confirm()) 173 | InteractiveTest(name="dialog::pick_file",test=dialog::pick_file()) 174 | InteractiveTest(name="dialog::pick_files",test=dialog::pick_files()) 175 | InteractiveTest(name="dialog::pick_folder",test=dialog::pick_folder()) 176 | InteractiveTest(name="dialog::pick_folders",test=dialog::pick_folders()) 177 | InteractiveTest(name="dialog::save",test=dialog::save()) 178 | Test(name="os::arch",test=os::arch()) 179 | Test(name="os::platform",test=os::platform()) 180 | Test(name="os::tempdir",test=os::tempdir()) 181 | Test(name="os::kind",test=os::kind()) 182 | Test(name="os::version",test=os::version()) 183 | Test(name="notification::is_permission_granted",test=notification::is_permission_granted()) 184 | Test(name="notification::request_permission",test=notification::request_permission()) 185 | InteractiveTest(name="notification::show_notification",test=notification::show_notification()) 186 | InteractiveTest(name="global_shortcut::register_all",test=global_shortcut::register_all()) 187 | 188 | Test(name="window::WebviewWindow::new",test=window::create_window()) 189 | 190 | Terminate 191 | // } 192 | } 193 | } 194 | } 195 | }); 196 | } 197 | -------------------------------------------------------------------------------- /examples/test/src/notification.rs: -------------------------------------------------------------------------------- 1 | use anyhow::ensure; 2 | use tauri_sys::notification::{self, Permission}; 3 | 4 | pub async fn is_permission_granted() -> anyhow::Result<()> { 5 | let granted = notification::is_permission_granted().await?; 6 | 7 | ensure!(granted); 8 | 9 | Ok(()) 10 | } 11 | 12 | pub async fn request_permission() -> anyhow::Result<()> { 13 | let permission = notification::request_permission().await?; 14 | 15 | ensure!(permission == Permission::Granted); 16 | 17 | Ok(()) 18 | } 19 | 20 | pub async fn show_notification() -> anyhow::Result<()> { 21 | let mut n = notification::Notification::default(); 22 | n.set_title("TAURI"); 23 | n.set_body("Tauri is awesome!"); 24 | 25 | n.show()?; 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/test/src/os.rs: -------------------------------------------------------------------------------- 1 | use tauri_sys::os; 2 | 3 | pub async fn arch() -> anyhow::Result<()> { 4 | let arch = os::arch().await?; 5 | 6 | log::debug!("{:?}", arch); 7 | 8 | Ok(()) 9 | } 10 | 11 | pub async fn platform() -> anyhow::Result<()> { 12 | let platform = os::platform().await?; 13 | 14 | log::debug!("{:?}", platform); 15 | 16 | Ok(()) 17 | } 18 | 19 | pub async fn tempdir() -> anyhow::Result<()> { 20 | let tempdir = os::tempdir().await?; 21 | 22 | log::info!("{:?}", tempdir); 23 | 24 | Ok(()) 25 | } 26 | 27 | pub async fn kind() -> anyhow::Result<()> { 28 | let kind = os::kind().await?; 29 | 30 | log::debug!("{:?}", kind); 31 | 32 | Ok(()) 33 | } 34 | 35 | pub async fn version() -> anyhow::Result<()> { 36 | let version = os::version().await?; 37 | 38 | log::debug!("{:?}", version); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /examples/test/src/tauri_log.rs: -------------------------------------------------------------------------------- 1 | use log::{Metadata, Record}; 2 | use serde::Serialize; 3 | use tauri_sys::tauri; 4 | 5 | #[derive(Debug, Serialize)] 6 | struct LogArgs { 7 | level: Level, 8 | message: String, 9 | location: String, 10 | file: Option, 11 | line: Option, 12 | } 13 | 14 | #[derive(Debug)] 15 | enum Level { 16 | Trace, 17 | Debug, 18 | Info, 19 | Warn, 20 | Error, 21 | } 22 | 23 | impl From for Level { 24 | fn from(l: log::Level) -> Self { 25 | match l { 26 | log::Level::Error => Level::Error, 27 | log::Level::Warn => Level::Warn, 28 | log::Level::Info => Level::Info, 29 | log::Level::Debug => Level::Debug, 30 | log::Level::Trace => Level::Trace, 31 | } 32 | } 33 | } 34 | 35 | impl Serialize for Level { 36 | fn serialize(&self, serializer: S) -> Result 37 | where 38 | S: serde::Serializer, 39 | { 40 | serializer.serialize_u8(match self { 41 | Level::Trace => 1, 42 | Level::Debug => 2, 43 | Level::Info => 3, 44 | Level::Warn => 4, 45 | Level::Error => 5, 46 | }) 47 | } 48 | } 49 | 50 | pub struct TauriLogger; 51 | impl log::Log for TauriLogger { 52 | fn enabled(&self, metadata: &Metadata) -> bool { 53 | metadata.level() <= log::Level::Trace 54 | } 55 | 56 | fn log(&self, record: &Record) { 57 | if self.enabled(record.metadata()) { 58 | let args = LogArgs { 59 | level: record.level().into(), 60 | location: record.target().to_string(), 61 | message: format!("{}", record.args()), 62 | file: record.file().map(ToString::to_string), 63 | line: record.line(), 64 | }; 65 | 66 | wasm_bindgen_futures::spawn_local(async move { 67 | tauri::invoke::<_, ()>("plugin:log|log", &args) 68 | .await 69 | .unwrap(); 70 | }); 71 | } 72 | } 73 | 74 | fn flush(&self) {} 75 | } 76 | -------------------------------------------------------------------------------- /examples/test/src/window.rs: -------------------------------------------------------------------------------- 1 | use anyhow::ensure; 2 | use tauri_sys::window; 3 | 4 | pub async fn create_window() -> anyhow::Result<()> { 5 | let win = window::WebviewWindowBuilder::new("foo-win") 6 | .set_url("/") 7 | .build() 8 | .await?; 9 | 10 | ensure!(win.is_visible().await?); 11 | 12 | win.close().await?; 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /examples/test/styles.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | transform-origin: baseline; 3 | display: inline-block; 4 | box-sizing: border-box; 5 | animation: rotation 1.3s linear infinite; 6 | } 7 | 8 | @keyframes rotation { 9 | 0% { 10 | transform: rotate(0deg); 11 | } 12 | 100% { 13 | transform: rotate(360deg); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tauri-sys", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "esbuild --outdir=src --format=esm --bundle tauri/tooling/api/src/*.ts" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "esbuild": "^0.19.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | devDependencies: 8 | esbuild: 9 | specifier: ^0.19.0 10 | version: 0.19.9 11 | 12 | packages: 13 | 14 | /@esbuild/android-arm64@0.19.9: 15 | resolution: {integrity: sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==} 16 | engines: {node: '>=12'} 17 | cpu: [arm64] 18 | os: [android] 19 | requiresBuild: true 20 | dev: true 21 | optional: true 22 | 23 | /@esbuild/android-arm@0.19.9: 24 | resolution: {integrity: sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==} 25 | engines: {node: '>=12'} 26 | cpu: [arm] 27 | os: [android] 28 | requiresBuild: true 29 | dev: true 30 | optional: true 31 | 32 | /@esbuild/android-x64@0.19.9: 33 | resolution: {integrity: sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==} 34 | engines: {node: '>=12'} 35 | cpu: [x64] 36 | os: [android] 37 | requiresBuild: true 38 | dev: true 39 | optional: true 40 | 41 | /@esbuild/darwin-arm64@0.19.9: 42 | resolution: {integrity: sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==} 43 | engines: {node: '>=12'} 44 | cpu: [arm64] 45 | os: [darwin] 46 | requiresBuild: true 47 | dev: true 48 | optional: true 49 | 50 | /@esbuild/darwin-x64@0.19.9: 51 | resolution: {integrity: sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==} 52 | engines: {node: '>=12'} 53 | cpu: [x64] 54 | os: [darwin] 55 | requiresBuild: true 56 | dev: true 57 | optional: true 58 | 59 | /@esbuild/freebsd-arm64@0.19.9: 60 | resolution: {integrity: sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==} 61 | engines: {node: '>=12'} 62 | cpu: [arm64] 63 | os: [freebsd] 64 | requiresBuild: true 65 | dev: true 66 | optional: true 67 | 68 | /@esbuild/freebsd-x64@0.19.9: 69 | resolution: {integrity: sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==} 70 | engines: {node: '>=12'} 71 | cpu: [x64] 72 | os: [freebsd] 73 | requiresBuild: true 74 | dev: true 75 | optional: true 76 | 77 | /@esbuild/linux-arm64@0.19.9: 78 | resolution: {integrity: sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==} 79 | engines: {node: '>=12'} 80 | cpu: [arm64] 81 | os: [linux] 82 | requiresBuild: true 83 | dev: true 84 | optional: true 85 | 86 | /@esbuild/linux-arm@0.19.9: 87 | resolution: {integrity: sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==} 88 | engines: {node: '>=12'} 89 | cpu: [arm] 90 | os: [linux] 91 | requiresBuild: true 92 | dev: true 93 | optional: true 94 | 95 | /@esbuild/linux-ia32@0.19.9: 96 | resolution: {integrity: sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==} 97 | engines: {node: '>=12'} 98 | cpu: [ia32] 99 | os: [linux] 100 | requiresBuild: true 101 | dev: true 102 | optional: true 103 | 104 | /@esbuild/linux-loong64@0.19.9: 105 | resolution: {integrity: sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==} 106 | engines: {node: '>=12'} 107 | cpu: [loong64] 108 | os: [linux] 109 | requiresBuild: true 110 | dev: true 111 | optional: true 112 | 113 | /@esbuild/linux-mips64el@0.19.9: 114 | resolution: {integrity: sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==} 115 | engines: {node: '>=12'} 116 | cpu: [mips64el] 117 | os: [linux] 118 | requiresBuild: true 119 | dev: true 120 | optional: true 121 | 122 | /@esbuild/linux-ppc64@0.19.9: 123 | resolution: {integrity: sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==} 124 | engines: {node: '>=12'} 125 | cpu: [ppc64] 126 | os: [linux] 127 | requiresBuild: true 128 | dev: true 129 | optional: true 130 | 131 | /@esbuild/linux-riscv64@0.19.9: 132 | resolution: {integrity: sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==} 133 | engines: {node: '>=12'} 134 | cpu: [riscv64] 135 | os: [linux] 136 | requiresBuild: true 137 | dev: true 138 | optional: true 139 | 140 | /@esbuild/linux-s390x@0.19.9: 141 | resolution: {integrity: sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==} 142 | engines: {node: '>=12'} 143 | cpu: [s390x] 144 | os: [linux] 145 | requiresBuild: true 146 | dev: true 147 | optional: true 148 | 149 | /@esbuild/linux-x64@0.19.9: 150 | resolution: {integrity: sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==} 151 | engines: {node: '>=12'} 152 | cpu: [x64] 153 | os: [linux] 154 | requiresBuild: true 155 | dev: true 156 | optional: true 157 | 158 | /@esbuild/netbsd-x64@0.19.9: 159 | resolution: {integrity: sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==} 160 | engines: {node: '>=12'} 161 | cpu: [x64] 162 | os: [netbsd] 163 | requiresBuild: true 164 | dev: true 165 | optional: true 166 | 167 | /@esbuild/openbsd-x64@0.19.9: 168 | resolution: {integrity: sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==} 169 | engines: {node: '>=12'} 170 | cpu: [x64] 171 | os: [openbsd] 172 | requiresBuild: true 173 | dev: true 174 | optional: true 175 | 176 | /@esbuild/sunos-x64@0.19.9: 177 | resolution: {integrity: sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==} 178 | engines: {node: '>=12'} 179 | cpu: [x64] 180 | os: [sunos] 181 | requiresBuild: true 182 | dev: true 183 | optional: true 184 | 185 | /@esbuild/win32-arm64@0.19.9: 186 | resolution: {integrity: sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==} 187 | engines: {node: '>=12'} 188 | cpu: [arm64] 189 | os: [win32] 190 | requiresBuild: true 191 | dev: true 192 | optional: true 193 | 194 | /@esbuild/win32-ia32@0.19.9: 195 | resolution: {integrity: sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==} 196 | engines: {node: '>=12'} 197 | cpu: [ia32] 198 | os: [win32] 199 | requiresBuild: true 200 | dev: true 201 | optional: true 202 | 203 | /@esbuild/win32-x64@0.19.9: 204 | resolution: {integrity: sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==} 205 | engines: {node: '>=12'} 206 | cpu: [x64] 207 | os: [win32] 208 | requiresBuild: true 209 | dev: true 210 | optional: true 211 | 212 | /esbuild@0.19.9: 213 | resolution: {integrity: sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==} 214 | engines: {node: '>=12'} 215 | hasBin: true 216 | requiresBuild: true 217 | optionalDependencies: 218 | '@esbuild/android-arm': 0.19.9 219 | '@esbuild/android-arm64': 0.19.9 220 | '@esbuild/android-x64': 0.19.9 221 | '@esbuild/darwin-arm64': 0.19.9 222 | '@esbuild/darwin-x64': 0.19.9 223 | '@esbuild/freebsd-arm64': 0.19.9 224 | '@esbuild/freebsd-x64': 0.19.9 225 | '@esbuild/linux-arm': 0.19.9 226 | '@esbuild/linux-arm64': 0.19.9 227 | '@esbuild/linux-ia32': 0.19.9 228 | '@esbuild/linux-loong64': 0.19.9 229 | '@esbuild/linux-mips64el': 0.19.9 230 | '@esbuild/linux-ppc64': 0.19.9 231 | '@esbuild/linux-riscv64': 0.19.9 232 | '@esbuild/linux-s390x': 0.19.9 233 | '@esbuild/linux-x64': 0.19.9 234 | '@esbuild/netbsd-x64': 0.19.9 235 | '@esbuild/openbsd-x64': 0.19.9 236 | '@esbuild/sunos-x64': 0.19.9 237 | '@esbuild/win32-arm64': 0.19.9 238 | '@esbuild/win32-ia32': 0.19.9 239 | '@esbuild/win32-x64': 0.19.9 240 | dev: true 241 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | // schedule: [ 3 | // 'before 5am on the first day of the month', 4 | //], 5 | semanticCommits: 'enabled', 6 | configMigration: true, 7 | dependencyDashboard: true, 8 | customManagers: [ 9 | { 10 | customType: 'regex', 11 | fileMatch: [ 12 | '^rust-toolchain\\.toml$', 13 | 'Cargo.toml$', 14 | 'clippy.toml$', 15 | '\\.clippy.toml$', 16 | '^\\.github/workflows/ci.yml$', 17 | '^\\.github/workflows/rust-next.yml$', 18 | ], 19 | matchStrings: [ 20 | 'MSRV.*?(?\\d+\\.\\d+(\\.\\d+)?)', 21 | '(?\\d+\\.\\d+(\\.\\d+)?).*?MSRV', 22 | ], 23 | depNameTemplate: 'rust', 24 | packageNameTemplate: 'rust-lang/rust', 25 | datasourceTemplate: 'github-releases', 26 | }, 27 | ], 28 | packageRules: [ 29 | { 30 | commitMessageTopic: 'MSRV', 31 | matchManagers: [ 32 | 'custom.regex', 33 | ], 34 | matchPackageNames: [ 35 | 'rust', 36 | ], 37 | minimumReleaseAge: '252 days', // 6 releases * 6 weeks per release * 7 days per week 38 | internalChecksFilter: 'strict', 39 | }, 40 | // Goals: 41 | // - Keep version reqs low, ignoring compatible normal/build dependencies 42 | // - Take advantage of latest dev-dependencies 43 | // - Rollup safe upgrades to reduce CI runner load 44 | // - Help keep number of versions down by always using latest breaking change 45 | // - Have lockfile and manifest in-sync 46 | { 47 | matchManagers: [ 48 | 'cargo', 49 | ], 50 | matchDepTypes: [ 51 | 'build-dependencies', 52 | 'dependencies', 53 | ], 54 | matchCurrentVersion: '>=0.1.0', 55 | matchUpdateTypes: [ 56 | 'patch', 57 | ], 58 | enabled: false, 59 | }, 60 | { 61 | matchManagers: [ 62 | 'cargo', 63 | ], 64 | matchDepTypes: [ 65 | 'build-dependencies', 66 | 'dependencies', 67 | ], 68 | matchCurrentVersion: '>=1.0.0', 69 | matchUpdateTypes: [ 70 | 'minor', 71 | ], 72 | enabled: false, 73 | }, 74 | { 75 | matchManagers: [ 76 | 'cargo', 77 | ], 78 | matchDepTypes: [ 79 | 'dev-dependencies', 80 | ], 81 | matchCurrentVersion: '>=0.1.0', 82 | matchUpdateTypes: [ 83 | 'patch', 84 | ], 85 | automerge: true, 86 | groupName: 'compatible (dev)', 87 | }, 88 | { 89 | matchManagers: [ 90 | 'cargo', 91 | ], 92 | matchDepTypes: [ 93 | 'dev-dependencies', 94 | ], 95 | matchCurrentVersion: '>=1.0.0', 96 | matchUpdateTypes: [ 97 | 'minor', 98 | ], 99 | automerge: true, 100 | groupName: 'compatible (dev)', 101 | }, 102 | ], 103 | } -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/app.ts 45 | async function getVersion() { 46 | return invokeTauriCommand({ 47 | __tauriModule: "App", 48 | message: { 49 | cmd: "getAppVersion" 50 | } 51 | }); 52 | } 53 | async function getName() { 54 | return invokeTauriCommand({ 55 | __tauriModule: "App", 56 | message: { 57 | cmd: "getAppName" 58 | } 59 | }); 60 | } 61 | async function getTauriVersion() { 62 | return invokeTauriCommand({ 63 | __tauriModule: "App", 64 | message: { 65 | cmd: "getTauriVersion" 66 | } 67 | }); 68 | } 69 | async function show() { 70 | return invokeTauriCommand({ 71 | __tauriModule: "App", 72 | message: { 73 | cmd: "show" 74 | } 75 | }); 76 | } 77 | async function hide() { 78 | return invokeTauriCommand({ 79 | __tauriModule: "App", 80 | message: { 81 | cmd: "hide" 82 | } 83 | }); 84 | } 85 | export { 86 | getName, 87 | getTauriVersion, 88 | getVersion, 89 | hide, 90 | show 91 | }; 92 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | //! Get application metadata. 2 | //! 3 | //! he APIs must be added to tauri.allowlist.app in tauri.conf.json: 4 | //! ```json 5 | //! { 6 | //! "tauri": { 7 | //! "allowlist": { 8 | //! "app": { 9 | //! "all": true, // enable all app APIs 10 | //! "show": true, 11 | //! "hide": true 12 | //! } 13 | //! } 14 | //! } 15 | //! } 16 | //! ``` 17 | //! It is recommended to allowlist only the APIs you use for optimal bundle size and security. 18 | 19 | use semver::Version; 20 | 21 | /// Gets the application name. 22 | /// 23 | /// # Example 24 | /// 25 | /// ```typescript 26 | /// import { getName } from '@tauri-apps/api/app'; 27 | /// const appName = await getName(); 28 | /// ``` 29 | #[inline(always)] 30 | pub async fn get_name() -> crate::Result { 31 | let js_val = inner::getName().await?; 32 | 33 | Ok(serde_wasm_bindgen::from_value(js_val)?) 34 | } 35 | 36 | /// Gets the application version. 37 | /// 38 | /// # Example 39 | /// 40 | /// ```rust,no_run 41 | /// use tauri_api::app::get_version; 42 | /// 43 | /// let version = get_version().await; 44 | /// ``` 45 | #[inline(always)] 46 | pub async fn get_version() -> crate::Result { 47 | let js_val = inner::getVersion().await?; 48 | 49 | Ok(serde_wasm_bindgen::from_value(js_val)?) 50 | } 51 | 52 | /// Gets the Tauri version. 53 | /// 54 | /// # Example 55 | /// 56 | /// ```rust,no_run 57 | /// use tauri_app::app:get_tauri_version; 58 | /// 59 | /// let version = get_tauri_version().await; 60 | /// ``` 61 | #[inline(always)] 62 | pub async fn get_tauri_version() -> crate::Result { 63 | let js_val = inner::getTauriVersion().await?; 64 | 65 | Ok(serde_wasm_bindgen::from_value(js_val)?) 66 | } 67 | 68 | /// Shows the application on macOS. This function does not automatically focus the apps windows. 69 | /// 70 | /// # Example 71 | /// 72 | /// ```rust,no_run 73 | /// use tauri_api::app::show; 74 | /// 75 | /// show().await; 76 | /// ``` 77 | /// 78 | /// Requires [`allowlist > app > show`](https://tauri.app/v1/api/config#appallowlistconfig.show) to be enabled. 79 | #[inline(always)] 80 | pub async fn show() -> crate::Result<()> { 81 | Ok(inner::show().await?) 82 | } 83 | 84 | /// Hides the application on macOS. 85 | /// 86 | /// # Example 87 | /// 88 | /// ```rust,no_run 89 | /// use tauri_api::app::hide; 90 | /// 91 | /// hide().await; 92 | /// ``` 93 | /// 94 | /// Requires [`allowlist > app > hide`](https://tauri.app/v1/api/config#appallowlistconfig.hide) to be enabled. 95 | #[inline(always)] 96 | pub async fn hide() -> crate::Result<()> { 97 | Ok(inner::hide().await?) 98 | } 99 | 100 | mod inner { 101 | use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; 102 | 103 | #[wasm_bindgen(module = "/src/app.js")] 104 | extern "C" { 105 | #[wasm_bindgen(catch)] 106 | pub async fn getName() -> Result; 107 | #[wasm_bindgen(catch)] 108 | pub async fn getTauriVersion() -> Result; 109 | #[wasm_bindgen(catch)] 110 | pub async fn getVersion() -> Result; 111 | #[wasm_bindgen(catch)] 112 | pub async fn hide() -> Result<(), JsValue>; 113 | #[wasm_bindgen(catch)] 114 | pub async fn show() -> Result<(), JsValue>; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/cli.ts 45 | async function getMatches() { 46 | return invokeTauriCommand({ 47 | __tauriModule: "Cli", 48 | message: { 49 | cmd: "cliMatches" 50 | } 51 | }); 52 | } 53 | export { 54 | getMatches 55 | }; 56 | -------------------------------------------------------------------------------- /src/clipboard.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/clipboard.ts 45 | async function writeText(text) { 46 | return invokeTauriCommand({ 47 | __tauriModule: "Clipboard", 48 | message: { 49 | cmd: "writeText", 50 | data: text 51 | } 52 | }); 53 | } 54 | async function readText() { 55 | return invokeTauriCommand({ 56 | __tauriModule: "Clipboard", 57 | message: { 58 | cmd: "readText", 59 | data: null 60 | } 61 | }); 62 | } 63 | export { 64 | readText, 65 | writeText 66 | }; 67 | -------------------------------------------------------------------------------- /src/clipboard.rs: -------------------------------------------------------------------------------- 1 | //! Read and write to the system clipboard. 2 | //! 3 | //! The APIs must be added to tauri.allowlist.clipboard in tauri.conf.json: 4 | //! ```json 5 | //! { 6 | //! "tauri": { 7 | //! "allowlist": { 8 | //! "clipboard": { 9 | //! "all": true, // enable all Clipboard APIs 10 | //! "writeText": true, 11 | //! "readText": true 12 | //! } 13 | //! } 14 | //! } 15 | //! } 16 | //! ``` 17 | //! It is recommended to allowlist only the APIs you use for optimal bundle size and security. 18 | 19 | /// Gets the clipboard content as plain text. 20 | /// 21 | /// # Example 22 | /// 23 | /// ```rust,no_run 24 | /// use tauri_api::clipboard::read_text; 25 | /// 26 | /// let clipboard_text = read_text().await; 27 | /// ``` 28 | /// 29 | /// Requires [`allowlist > clipboard > readText`](https://tauri.app/v1/api/config#clipboardallowlistconfig.readtext) to be enabled. 30 | #[inline(always)] 31 | pub async fn read_text() -> crate::Result { 32 | let js_val = inner::readText().await?; 33 | 34 | Ok(serde_wasm_bindgen::from_value(js_val)?) 35 | } 36 | 37 | /// Writes plain text to the clipboard. 38 | /// 39 | /// # Example 40 | /// 41 | /// ```rust,no_run 42 | /// use tauri_api::clipboard::{write_text, read_text}; 43 | /// 44 | /// write_text("Tauri is awesome!").await; 45 | /// assert_eq!(read_text().await, "Tauri is awesome!"); 46 | /// ``` 47 | /// 48 | /// Requires [`allowlist > clipboard > writeText`](https://tauri.app/v1/api/config#clipboardallowlistconfig.writetext) to be enabled. 49 | #[inline(always)] 50 | pub async fn write_text(text: &str) -> crate::Result<()> { 51 | Ok(inner::writeText(text).await?) 52 | } 53 | 54 | mod inner { 55 | use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; 56 | 57 | #[wasm_bindgen(module = "/src/clipboard.js")] 58 | extern "C" { 59 | #[wasm_bindgen(catch)] 60 | pub async fn readText() -> Result; 61 | #[wasm_bindgen(catch)] 62 | pub async fn writeText(text: &str) -> Result<(), JsValue>; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/dialog.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/dialog.ts 45 | async function open(options = {}) { 46 | if (typeof options === "object") { 47 | Object.freeze(options); 48 | } 49 | return invokeTauriCommand({ 50 | __tauriModule: "Dialog", 51 | message: { 52 | cmd: "openDialog", 53 | options 54 | } 55 | }); 56 | } 57 | async function save(options = {}) { 58 | if (typeof options === "object") { 59 | Object.freeze(options); 60 | } 61 | return invokeTauriCommand({ 62 | __tauriModule: "Dialog", 63 | message: { 64 | cmd: "saveDialog", 65 | options 66 | } 67 | }); 68 | } 69 | async function message(message2, options) { 70 | const opts = typeof options === "string" ? { title: options } : options; 71 | return invokeTauriCommand({ 72 | __tauriModule: "Dialog", 73 | message: { 74 | cmd: "messageDialog", 75 | message: message2.toString(), 76 | title: opts?.title?.toString(), 77 | type: opts?.type, 78 | buttonLabel: opts?.okLabel?.toString() 79 | } 80 | }); 81 | } 82 | async function ask(message2, options) { 83 | const opts = typeof options === "string" ? { title: options } : options; 84 | return invokeTauriCommand({ 85 | __tauriModule: "Dialog", 86 | message: { 87 | cmd: "askDialog", 88 | message: message2.toString(), 89 | title: opts?.title?.toString(), 90 | type: opts?.type, 91 | buttonLabels: [ 92 | opts?.okLabel?.toString() ?? "Yes", 93 | opts?.cancelLabel?.toString() ?? "No" 94 | ] 95 | } 96 | }); 97 | } 98 | async function confirm(message2, options) { 99 | const opts = typeof options === "string" ? { title: options } : options; 100 | return invokeTauriCommand({ 101 | __tauriModule: "Dialog", 102 | message: { 103 | cmd: "confirmDialog", 104 | message: message2.toString(), 105 | title: opts?.title?.toString(), 106 | type: opts?.type, 107 | buttonLabels: [ 108 | opts?.okLabel?.toString() ?? "Ok", 109 | opts?.cancelLabel?.toString() ?? "Cancel" 110 | ] 111 | } 112 | }); 113 | } 114 | export { 115 | ask, 116 | confirm, 117 | message, 118 | open, 119 | save 120 | }; 121 | -------------------------------------------------------------------------------- /src/dialog.rs: -------------------------------------------------------------------------------- 1 | //! Native system dialogs for opening and saving files. 2 | //! 3 | //! The APIs must be added to `tauri.allowlist.dialog` in `tauri.conf.json`: 4 | //! ```json 5 | //! { 6 | //! "tauri": { 7 | //! "allowlist": { 8 | //! "dialog": { 9 | //! "all": true, // enable all dialog APIs 10 | //! "open": true, // enable file open API 11 | //! "save": true // enable file save API 12 | //! "message": true, 13 | //! "ask": true, 14 | //! "confirm": true 15 | //! } 16 | //! } 17 | //! } 18 | //! } 19 | //! ``` 20 | //! It is recommended to allowlist only the APIs you use for optimal bundle size and security. 21 | 22 | use js_sys::Array; 23 | use serde::Serialize; 24 | use std::path::{Path, PathBuf}; 25 | 26 | use crate::utils::ArrayIterator; 27 | #[derive(Debug, Clone, Copy, Hash, Serialize)] 28 | struct DialogFilter<'a> { 29 | extensions: &'a [&'a str], 30 | name: &'a str, 31 | } 32 | 33 | /// The file dialog builder. 34 | /// 35 | /// Constructs file picker dialogs that can select single/multiple files or directories. 36 | #[derive(Debug, Default, Clone, Hash, Serialize)] 37 | #[serde(rename_all = "camelCase")] 38 | pub struct FileDialogBuilder<'a> { 39 | default_path: Option<&'a Path>, 40 | filters: Vec>, 41 | title: Option<&'a str>, 42 | directory: bool, 43 | multiple: bool, 44 | recursive: bool, 45 | } 46 | 47 | impl<'a> FileDialogBuilder<'a> { 48 | /// Gets the default file dialog builder. 49 | pub fn new() -> Self { 50 | Self::default() 51 | } 52 | 53 | /// Set starting file name or directory of the dialog. 54 | pub fn set_default_path(&mut self, default_path: &'a Path) -> &mut Self { 55 | self.default_path = Some(default_path); 56 | self 57 | } 58 | 59 | /// If directory is true, indicates that it will be read recursively later. 60 | /// Defines whether subdirectories will be allowed on the scope or not. 61 | /// 62 | /// # Example 63 | /// 64 | /// ```rust 65 | /// use tauri_sys::dialog::FileDialogBuilder; 66 | /// 67 | /// # fn main() -> Result<(), Box> { 68 | /// let _builder = FileDialogBuilder::new().set_recursive(true); 69 | /// # Ok(()) 70 | /// # } 71 | /// ``` 72 | pub fn set_recursive(&mut self, recursive: bool) -> &mut Self { 73 | self.recursive = recursive; 74 | self 75 | } 76 | 77 | /// Set the title of the dialog. 78 | /// 79 | /// # Example 80 | /// 81 | /// ```rust 82 | /// use tauri_sys::dialog::FileDialogBuilder; 83 | /// 84 | /// # fn main() -> Result<(), Box> { 85 | /// let _builder = FileDialogBuilder::new().set_title("Test Title"); 86 | /// # Ok(()) 87 | /// # } 88 | /// ``` 89 | pub fn set_title(&mut self, title: &'a str) -> &mut Self { 90 | self.title = Some(title); 91 | self 92 | } 93 | 94 | /// Add file extension filter. Takes in the name of the filter, and list of extensions 95 | /// 96 | /// # Example 97 | /// 98 | /// ```rust 99 | /// use tauri_sys::dialog::FileDialogBuilder; 100 | /// 101 | /// # fn main() -> Result<(), Box> { 102 | /// let _builder = FileDialogBuilder::new().add_filter("Image", &["png", "jpeg"]); 103 | /// # Ok(()) 104 | /// # } 105 | /// ``` 106 | pub fn add_filter(&mut self, name: &'a str, extensions: &'a [&'a str]) -> &mut Self { 107 | self.filters.push(DialogFilter { name, extensions }); 108 | self 109 | } 110 | 111 | /// Add many file extension filters. 112 | /// 113 | /// # Example 114 | /// 115 | /// ```rust 116 | /// use tauri_sys::dialog::FileDialogBuilder; 117 | /// 118 | /// # fn main() -> Result<(), Box> { 119 | /// let _builder = FileDialogBuilder::new().add_filters(&[("Image", &["png", "jpeg"]),("Video", &["mp4"])]); 120 | /// # Ok(()) 121 | /// # } 122 | /// ``` 123 | pub fn add_filters( 124 | &mut self, 125 | filters: impl IntoIterator, 126 | ) -> &mut Self { 127 | for (name, extensions) in filters.into_iter() { 128 | self.filters.push(DialogFilter { 129 | name: name.as_ref(), 130 | extensions, 131 | }); 132 | } 133 | self 134 | } 135 | 136 | /// Shows the dialog to select a single file. 137 | /// 138 | /// # Example 139 | /// 140 | /// ```rust,no_run 141 | /// use tauri_sys::dialog::FileDialogBuilder; 142 | /// 143 | /// # async fn main() -> Result<(), Box> { 144 | /// let file = FileDialogBuilder::new().pick_file().await?; 145 | /// # Ok(()) 146 | /// # } 147 | /// ``` 148 | /// 149 | /// Requires [`allowlist > dialog > open`](https://tauri.app/v1/api/config#dialogallowlistconfig.open) to be enabled. 150 | pub async fn pick_file(&self) -> crate::Result> { 151 | let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?; 152 | 153 | Ok(serde_wasm_bindgen::from_value(raw)?) 154 | } 155 | 156 | /// Shows the dialog to select multiple files. 157 | /// 158 | /// # Example 159 | /// 160 | /// ```rust,no_run 161 | /// use tauri_sys::dialog::FileDialogBuilder; 162 | /// 163 | /// # async fn main() -> Result<(), Box> { 164 | /// let files = FileDialogBuilder::new().pick_files().await?; 165 | /// # Ok(()) 166 | /// # } 167 | /// ``` 168 | /// 169 | /// Requires [`allowlist > dialog > open`](https://tauri.app/v1/api/config#dialogallowlistconfig.open) to be enabled. 170 | pub async fn pick_files(&mut self) -> crate::Result>> { 171 | self.multiple = true; 172 | 173 | let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?; 174 | 175 | if let Ok(files) = Array::try_from(raw) { 176 | let files = 177 | ArrayIterator::new(files).map(|raw| serde_wasm_bindgen::from_value(raw).unwrap()); 178 | 179 | Ok(Some(files)) 180 | } else { 181 | Ok(None) 182 | } 183 | } 184 | 185 | /// Shows the dialog to select a single folder. 186 | /// 187 | /// # Example 188 | /// 189 | /// ```rust,no_run 190 | /// use tauri_sys::dialog::FileDialogBuilder; 191 | /// 192 | /// # async fn main() -> Result<(), Box> { 193 | /// let files = FileDialogBuilder::new().pick_folder().await?; 194 | /// # Ok(()) 195 | /// # } 196 | /// ``` 197 | /// 198 | /// Requires [`allowlist > dialog > open`](https://tauri.app/v1/api/config#dialogallowlistconfig.open) to be enabled. 199 | pub async fn pick_folder(&mut self) -> crate::Result> { 200 | self.directory = true; 201 | 202 | let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?; 203 | 204 | Ok(serde_wasm_bindgen::from_value(raw)?) 205 | } 206 | 207 | /// Shows the dialog to select multiple folders. 208 | /// 209 | /// # Example 210 | /// 211 | /// ```rust,no_run 212 | /// use tauri_sys::dialog::FileDialogBuilder; 213 | /// 214 | /// # async fn main() -> Result<(), Box> { 215 | /// let files = FileDialogBuilder::new().pick_folders().await?; 216 | /// # Ok(()) 217 | /// # } 218 | /// ``` 219 | /// 220 | /// Requires [`allowlist > dialog > open`](https://tauri.app/v1/api/config#dialogallowlistconfig.open) to be enabled. 221 | pub async fn pick_folders(&mut self) -> crate::Result>> { 222 | self.directory = true; 223 | self.multiple = true; 224 | 225 | let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?; 226 | 227 | if let Ok(files) = Array::try_from(raw) { 228 | let files = 229 | ArrayIterator::new(files).map(|raw| serde_wasm_bindgen::from_value(raw).unwrap()); 230 | 231 | Ok(Some(files)) 232 | } else { 233 | Ok(None) 234 | } 235 | } 236 | 237 | /// Open a file/directory save dialog. 238 | /// 239 | /// The selected path is added to the filesystem and asset protocol allowlist scopes. 240 | /// When security is more important than the easy of use of this API, prefer writing a dedicated command instead. 241 | /// 242 | /// Note that the allowlist scope change is not persisted, so the values are cleared when the application is restarted. 243 | /// You can save it to the filesystem using tauri-plugin-persisted-scope. 244 | /// 245 | /// # Example 246 | /// 247 | /// ```rust,no_run 248 | /// use tauri_sys::dialog::FileDialogBuilder; 249 | /// 250 | /// # async fn main() -> Result<(), Box> { 251 | /// let file = FileDialogBuilder::new().save().await?; 252 | /// # Ok(()) 253 | /// # } 254 | /// ``` 255 | /// 256 | /// Requires [`allowlist > dialog > save`](https://tauri.app/v1/api/config#dialogallowlistconfig.save) to be enabled. 257 | pub async fn save(&self) -> crate::Result> { 258 | let raw = inner::save(serde_wasm_bindgen::to_value(&self)?).await?; 259 | 260 | Ok(serde_wasm_bindgen::from_value(raw)?) 261 | } 262 | } 263 | 264 | /// Types of message, ask and confirm dialogs. 265 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] 266 | pub enum MessageDialogKind { 267 | #[default] 268 | #[serde(rename = "info")] 269 | Info, 270 | #[serde(rename = "warning")] 271 | Warning, 272 | #[serde(rename = "error")] 273 | Error, 274 | } 275 | 276 | /// A builder for message dialogs. 277 | #[derive(Debug, Default, Clone, Copy, Hash, Serialize)] 278 | pub struct MessageDialogBuilder<'a> { 279 | title: Option<&'a str>, 280 | #[serde(rename = "type")] 281 | kind: MessageDialogKind, 282 | } 283 | 284 | impl<'a> MessageDialogBuilder<'a> { 285 | pub fn new() -> Self { 286 | Self::default() 287 | } 288 | 289 | /// Set the title of the dialog. 290 | /// 291 | /// # Example 292 | /// 293 | /// ```rust 294 | /// use tauri_sys::dialog::MessageDialogBuilder; 295 | /// 296 | /// # fn main() -> Result<(), Box> { 297 | /// let _builder = MessageDialogBuilder::new().set_title("Test Title"); 298 | /// # Ok(()) 299 | /// # } 300 | /// ``` 301 | pub fn set_title(&mut self, title: &'a str) -> &mut Self { 302 | self.title = Some(title); 303 | self 304 | } 305 | 306 | /// Set the type of the dialog. 307 | /// 308 | /// # Example 309 | /// 310 | /// ```rust 311 | /// use tauri_sys::dialog::{MessageDialogBuilder,MessageDialogKind}; 312 | /// 313 | /// # fn main() -> Result<(), Box> { 314 | /// let _builder = MessageDialogBuilder::new().set_kind(MessageDialogKind::Error); 315 | /// # Ok(()) 316 | /// # } 317 | /// ``` 318 | pub fn set_kind(&mut self, kind: MessageDialogKind) -> &mut Self { 319 | self.kind = kind; 320 | self 321 | } 322 | 323 | /// Shows a message dialog with an `Ok` button. 324 | /// 325 | /// # Example 326 | /// 327 | /// ```rust,no_run 328 | /// use tauri_sys::dialog::MessageDialogBuilder; 329 | /// 330 | /// # async fn main() -> Result<(), Box> { 331 | /// let file = MessageDialogBuilder::new().message("Tauri is awesome").await?; 332 | /// # Ok(()) 333 | /// # } 334 | /// ``` 335 | /// 336 | /// Requires [`allowlist > dialog > message`](https://tauri.app/v1/api/config#dialogallowlistconfig.message) to be enabled. 337 | pub async fn message(&self, message: &str) -> crate::Result<()> { 338 | Ok(inner::message(message, serde_wasm_bindgen::to_value(&self)?).await?) 339 | } 340 | 341 | /// Shows a question dialog with `Yes` and `No` buttons. 342 | /// 343 | /// # Example 344 | /// 345 | /// ```rust,no_run 346 | /// use tauri_sys::dialog::MessageDialogBuilder; 347 | /// 348 | /// # async fn main() -> Result<(), Box> { 349 | /// let confirmation = MessageDialogBuilder::new().ask("Are you sure?").await?; 350 | /// # Ok(()) 351 | /// # } 352 | /// ``` 353 | /// 354 | /// Requires [`allowlist > dialog > ask`](https://tauri.app/v1/api/config#dialogallowlistconfig.ask) to be enabled. 355 | pub async fn ask(&self, message: &str) -> crate::Result { 356 | let raw = inner::ask(message, serde_wasm_bindgen::to_value(&self)?).await?; 357 | 358 | Ok(serde_wasm_bindgen::from_value(raw)?) 359 | } 360 | 361 | /// Shows a question dialog with `Ok` and `Cancel` buttons. 362 | /// 363 | /// # Example 364 | /// 365 | /// ```rust,no_run 366 | /// use tauri_sys::dialog::MessageDialogBuilder; 367 | /// 368 | /// # async fn main() -> Result<(), Box> { 369 | /// let confirmation = MessageDialogBuilder::new().confirm("Are you sure?").await?; 370 | /// # Ok(()) 371 | /// # } 372 | /// ``` 373 | /// 374 | /// Requires [`allowlist > dialog > confirm`](https://tauri.app/v1/api/config#dialogallowlistconfig.confirm) to be enabled. 375 | pub async fn confirm(&self, message: &str) -> crate::Result { 376 | let raw = inner::confirm(message, serde_wasm_bindgen::to_value(&self)?).await?; 377 | 378 | Ok(serde_wasm_bindgen::from_value(raw)?) 379 | } 380 | } 381 | 382 | mod inner { 383 | use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; 384 | 385 | #[wasm_bindgen(module = "/src/dialog.js")] 386 | extern "C" { 387 | #[wasm_bindgen(catch)] 388 | pub async fn ask(message: &str, options: JsValue) -> Result; 389 | #[wasm_bindgen(catch)] 390 | pub async fn confirm(message: &str, options: JsValue) -> Result; 391 | #[wasm_bindgen(catch)] 392 | pub async fn open(options: JsValue) -> Result; 393 | #[wasm_bindgen(catch)] 394 | pub async fn message(message: &str, option: JsValue) -> Result<(), JsValue>; 395 | #[wasm_bindgen(catch)] 396 | pub async fn save(options: JsValue) -> Result; 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use wasm_bindgen::JsValue; 3 | 4 | #[derive(Clone, Eq, PartialEq, Debug, thiserror::Error)] 5 | pub enum Error { 6 | #[error("Command returned Error: {0}")] 7 | Command(String), 8 | #[error("Failed to parse JSON: {0}")] 9 | Serde(String), 10 | #[cfg(any(feature = "event", feature = "window"))] 11 | #[error("Oneshot cancelled: {0}")] 12 | OneshotCanceled(#[from] futures::channel::oneshot::Canceled), 13 | #[cfg(feature = "fs")] 14 | #[error("Could not convert path to string")] 15 | Utf8(PathBuf), 16 | } 17 | 18 | impl From for Error { 19 | fn from(e: serde_wasm_bindgen::Error) -> Self { 20 | Self::Serde(e.to_string()) 21 | } 22 | } 23 | 24 | impl From for Error { 25 | fn from(e: JsValue) -> Self { 26 | Self::Command(format!("{:?}", e)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once3 = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once3) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/helpers/event.ts 45 | async function _unlisten(event, eventId) { 46 | return invokeTauriCommand({ 47 | __tauriModule: "Event", 48 | message: { 49 | cmd: "unlisten", 50 | event, 51 | eventId 52 | } 53 | }); 54 | } 55 | async function emit(event, windowLabel, payload) { 56 | await invokeTauriCommand({ 57 | __tauriModule: "Event", 58 | message: { 59 | cmd: "emit", 60 | event, 61 | windowLabel, 62 | payload 63 | } 64 | }); 65 | } 66 | async function listen(event, windowLabel, handler) { 67 | return invokeTauriCommand({ 68 | __tauriModule: "Event", 69 | message: { 70 | cmd: "listen", 71 | event, 72 | windowLabel, 73 | handler: transformCallback(handler) 74 | } 75 | }).then((eventId) => { 76 | return async () => _unlisten(event, eventId); 77 | }); 78 | } 79 | async function once(event, windowLabel, handler) { 80 | return listen(event, windowLabel, (eventData) => { 81 | handler(eventData); 82 | _unlisten(event, eventData.id).catch(() => { 83 | }); 84 | }); 85 | } 86 | 87 | // tauri/tooling/api/src/event.ts 88 | var TauriEvent = /* @__PURE__ */ ((TauriEvent2) => { 89 | TauriEvent2["WINDOW_RESIZED"] = "tauri://resize"; 90 | TauriEvent2["WINDOW_MOVED"] = "tauri://move"; 91 | TauriEvent2["WINDOW_CLOSE_REQUESTED"] = "tauri://close-requested"; 92 | TauriEvent2["WINDOW_CREATED"] = "tauri://window-created"; 93 | TauriEvent2["WINDOW_DESTROYED"] = "tauri://destroyed"; 94 | TauriEvent2["WINDOW_FOCUS"] = "tauri://focus"; 95 | TauriEvent2["WINDOW_BLUR"] = "tauri://blur"; 96 | TauriEvent2["WINDOW_SCALE_FACTOR_CHANGED"] = "tauri://scale-change"; 97 | TauriEvent2["WINDOW_THEME_CHANGED"] = "tauri://theme-changed"; 98 | TauriEvent2["WINDOW_FILE_DROP"] = "tauri://file-drop"; 99 | TauriEvent2["WINDOW_FILE_DROP_HOVER"] = "tauri://file-drop-hover"; 100 | TauriEvent2["WINDOW_FILE_DROP_CANCELLED"] = "tauri://file-drop-cancelled"; 101 | TauriEvent2["MENU"] = "tauri://menu"; 102 | TauriEvent2["CHECK_UPDATE"] = "tauri://update"; 103 | TauriEvent2["UPDATE_AVAILABLE"] = "tauri://update-available"; 104 | TauriEvent2["INSTALL_UPDATE"] = "tauri://update-install"; 105 | TauriEvent2["STATUS_UPDATE"] = "tauri://update-status"; 106 | TauriEvent2["DOWNLOAD_PROGRESS"] = "tauri://update-download-progress"; 107 | return TauriEvent2; 108 | })(TauriEvent || {}); 109 | async function listen2(event, handler) { 110 | return listen(event, null, handler); 111 | } 112 | async function once2(event, handler) { 113 | return once(event, null, handler); 114 | } 115 | async function emit2(event, payload) { 116 | return emit(event, void 0, payload); 117 | } 118 | export { 119 | TauriEvent, 120 | emit2 as emit, 121 | listen2 as listen, 122 | once2 as once 123 | }; 124 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | //! The event system allows you to emit events to the backend and listen to events from it. 2 | 3 | use futures::{ 4 | channel::{mpsc, oneshot}, 5 | Future, FutureExt, Stream, StreamExt, 6 | }; 7 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 8 | use std::fmt::Debug; 9 | use wasm_bindgen::{prelude::Closure, JsValue}; 10 | 11 | #[derive(Debug, Clone, PartialEq, Deserialize)] 12 | #[serde(rename_all = "camelCase")] 13 | pub struct Event { 14 | /// Event name 15 | pub event: String, 16 | /// Event identifier used to unlisten 17 | pub id: f32, 18 | /// Event payload 19 | pub payload: T, 20 | /// The label of the window that emitted this event 21 | pub window_label: Option, 22 | } 23 | 24 | /// Emits an event to the backend. 25 | /// 26 | /// # Example 27 | /// 28 | /// ```rust,no_run 29 | /// use tauri_api::event::emit; 30 | /// use serde::Serialize; 31 | /// 32 | /// #[derive(Serialize)] 33 | /// struct Payload { 34 | /// logged_in: bool, 35 | /// token: String 36 | /// } 37 | /// 38 | /// emit("frontend-loaded", &Payload { logged_in: true, token: "authToken" }).await; 39 | /// ``` 40 | /// 41 | /// @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`. 42 | #[inline(always)] 43 | pub async fn emit(event: &str, payload: &T) -> crate::Result<()> { 44 | inner::emit(event, serde_wasm_bindgen::to_value(payload)?).await?; 45 | 46 | Ok(()) 47 | } 48 | 49 | /// Listen to an event from the backend. 50 | /// 51 | /// The returned Future will automatically clean up it's underlying event listener when dropped, so no manual unlisten function needs to be called. 52 | /// See [Differences to the JavaScript API](../index.html#differences-to-the-javascript-api) for details. 53 | /// 54 | /// # Example 55 | /// 56 | /// ```rust,no_run 57 | /// use tauri_api::event::listen; 58 | /// use web_sys::console; 59 | /// 60 | /// let events = listen::("error"); 61 | /// 62 | /// while let Some(event) = events.next().await { 63 | /// console::log_1(&format!("Got error in window {}, payload: {}", event.window_label, event.payload).into()); 64 | /// } 65 | /// ``` 66 | #[inline(always)] 67 | pub async fn listen(event: &str) -> crate::Result>> 68 | where 69 | T: DeserializeOwned + 'static, 70 | { 71 | let (tx, rx) = mpsc::unbounded::>(); 72 | 73 | let closure = Closure::::new(move |raw| { 74 | let _ = tx.unbounded_send(serde_wasm_bindgen::from_value(raw).unwrap()); 75 | }); 76 | let unlisten = inner::listen(event, &closure).await?; 77 | closure.forget(); 78 | 79 | Ok(Listen { 80 | rx, 81 | unlisten: js_sys::Function::from(unlisten), 82 | }) 83 | } 84 | 85 | pub(crate) struct Listen { 86 | pub rx: mpsc::UnboundedReceiver, 87 | pub unlisten: js_sys::Function, 88 | } 89 | 90 | impl Drop for Listen { 91 | fn drop(&mut self) { 92 | log::debug!("Calling unlisten for listen callback"); 93 | self.unlisten.call0(&wasm_bindgen::JsValue::NULL).unwrap(); 94 | } 95 | } 96 | 97 | impl Stream for Listen { 98 | type Item = T; 99 | 100 | fn poll_next( 101 | mut self: std::pin::Pin<&mut Self>, 102 | cx: &mut std::task::Context<'_>, 103 | ) -> std::task::Poll> { 104 | self.rx.poll_next_unpin(cx) 105 | } 106 | } 107 | 108 | /// Listen to an one-off event from the backend. 109 | /// 110 | /// The returned Future will automatically clean up it's underlying event listener when dropped, so no manual unlisten function needs to be called. 111 | /// See [Differences to the JavaScript API](../index.html#differences-to-the-javascript-api) for details. 112 | /// 113 | /// # Example 114 | /// 115 | /// ```rust,no_run 116 | /// use tauri_api::event::once; 117 | /// use serde::Deserialize; 118 | /// use web_sys::console; 119 | /// 120 | /// #[derive(Deserialize)] 121 | /// interface LoadedPayload { 122 | /// logged_in: bool, 123 | /// token: String 124 | /// } 125 | /// 126 | /// # async fn main() -> Result<(), Box> { 127 | /// const event = once::("loaded").await?; 128 | /// 129 | /// console::log_1!(&format!("App is loaded, loggedIn: {}, token: {}", event.payload.logged_in, event.payload.token).into()); 130 | /// # Ok(()) 131 | /// # } 132 | /// ``` 133 | #[inline(always)] 134 | pub async fn once(event: &str) -> crate::Result> 135 | where 136 | T: DeserializeOwned + 'static, 137 | { 138 | let (tx, rx) = oneshot::channel::>(); 139 | 140 | let closure: Closure = Closure::once(move |raw| { 141 | let _ = tx.send(serde_wasm_bindgen::from_value(raw).unwrap()); 142 | }); 143 | let unlisten = inner::once(event, &closure).await?; 144 | closure.forget(); 145 | 146 | let fut = Once { 147 | rx, 148 | unlisten: js_sys::Function::from(unlisten), 149 | }; 150 | 151 | fut.await 152 | } 153 | 154 | pub(crate) struct Once { 155 | pub rx: oneshot::Receiver>, 156 | pub unlisten: js_sys::Function, 157 | } 158 | 159 | impl Drop for Once { 160 | fn drop(&mut self) { 161 | self.rx.close(); 162 | log::debug!("Calling unlisten for once callback"); 163 | self.unlisten.call0(&wasm_bindgen::JsValue::NULL).unwrap(); 164 | } 165 | } 166 | 167 | impl Future for Once { 168 | type Output = crate::Result>; 169 | 170 | fn poll( 171 | mut self: std::pin::Pin<&mut Self>, 172 | cx: &mut std::task::Context<'_>, 173 | ) -> std::task::Poll { 174 | self.rx.poll_unpin(cx).map_err(Into::into) 175 | } 176 | } 177 | 178 | mod inner { 179 | use wasm_bindgen::{ 180 | prelude::{wasm_bindgen, Closure}, 181 | JsValue, 182 | }; 183 | 184 | #[wasm_bindgen(module = "/src/event.js")] 185 | extern "C" { 186 | #[wasm_bindgen(catch)] 187 | pub async fn emit(event: &str, payload: JsValue) -> Result<(), JsValue>; 188 | #[wasm_bindgen(catch)] 189 | pub async fn listen( 190 | event: &str, 191 | handler: &Closure, 192 | ) -> Result; 193 | #[wasm_bindgen(catch)] 194 | pub async fn once( 195 | event: &str, 196 | handler: &Closure, 197 | ) -> Result; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/fs.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/fs.ts 45 | var BaseDirectory = /* @__PURE__ */ ((BaseDirectory2) => { 46 | BaseDirectory2[BaseDirectory2["Audio"] = 1] = "Audio"; 47 | BaseDirectory2[BaseDirectory2["Cache"] = 2] = "Cache"; 48 | BaseDirectory2[BaseDirectory2["Config"] = 3] = "Config"; 49 | BaseDirectory2[BaseDirectory2["Data"] = 4] = "Data"; 50 | BaseDirectory2[BaseDirectory2["LocalData"] = 5] = "LocalData"; 51 | BaseDirectory2[BaseDirectory2["Desktop"] = 6] = "Desktop"; 52 | BaseDirectory2[BaseDirectory2["Document"] = 7] = "Document"; 53 | BaseDirectory2[BaseDirectory2["Download"] = 8] = "Download"; 54 | BaseDirectory2[BaseDirectory2["Executable"] = 9] = "Executable"; 55 | BaseDirectory2[BaseDirectory2["Font"] = 10] = "Font"; 56 | BaseDirectory2[BaseDirectory2["Home"] = 11] = "Home"; 57 | BaseDirectory2[BaseDirectory2["Picture"] = 12] = "Picture"; 58 | BaseDirectory2[BaseDirectory2["Public"] = 13] = "Public"; 59 | BaseDirectory2[BaseDirectory2["Runtime"] = 14] = "Runtime"; 60 | BaseDirectory2[BaseDirectory2["Template"] = 15] = "Template"; 61 | BaseDirectory2[BaseDirectory2["Video"] = 16] = "Video"; 62 | BaseDirectory2[BaseDirectory2["Resource"] = 17] = "Resource"; 63 | BaseDirectory2[BaseDirectory2["App"] = 18] = "App"; 64 | BaseDirectory2[BaseDirectory2["Log"] = 19] = "Log"; 65 | BaseDirectory2[BaseDirectory2["Temp"] = 20] = "Temp"; 66 | BaseDirectory2[BaseDirectory2["AppConfig"] = 21] = "AppConfig"; 67 | BaseDirectory2[BaseDirectory2["AppData"] = 22] = "AppData"; 68 | BaseDirectory2[BaseDirectory2["AppLocalData"] = 23] = "AppLocalData"; 69 | BaseDirectory2[BaseDirectory2["AppCache"] = 24] = "AppCache"; 70 | BaseDirectory2[BaseDirectory2["AppLog"] = 25] = "AppLog"; 71 | return BaseDirectory2; 72 | })(BaseDirectory || {}); 73 | async function readTextFile(filePath, options = {}) { 74 | return invokeTauriCommand({ 75 | __tauriModule: "Fs", 76 | message: { 77 | cmd: "readTextFile", 78 | path: filePath, 79 | options 80 | } 81 | }); 82 | } 83 | async function readBinaryFile(filePath, options = {}) { 84 | const arr = await invokeTauriCommand({ 85 | __tauriModule: "Fs", 86 | message: { 87 | cmd: "readFile", 88 | path: filePath, 89 | options 90 | } 91 | }); 92 | return Uint8Array.from(arr); 93 | } 94 | async function writeTextFile(path, contents, options) { 95 | if (typeof options === "object") { 96 | Object.freeze(options); 97 | } 98 | if (typeof path === "object") { 99 | Object.freeze(path); 100 | } 101 | const file = { path: "", contents: "" }; 102 | let fileOptions = options; 103 | if (typeof path === "string") { 104 | file.path = path; 105 | } else { 106 | file.path = path.path; 107 | file.contents = path.contents; 108 | } 109 | if (typeof contents === "string") { 110 | file.contents = contents ?? ""; 111 | } else { 112 | fileOptions = contents; 113 | } 114 | return invokeTauriCommand({ 115 | __tauriModule: "Fs", 116 | message: { 117 | cmd: "writeFile", 118 | path: file.path, 119 | contents: Array.from(new TextEncoder().encode(file.contents)), 120 | options: fileOptions 121 | } 122 | }); 123 | } 124 | async function writeBinaryFile(path, contents, options) { 125 | if (typeof options === "object") { 126 | Object.freeze(options); 127 | } 128 | if (typeof path === "object") { 129 | Object.freeze(path); 130 | } 131 | const file = { path: "", contents: [] }; 132 | let fileOptions = options; 133 | if (typeof path === "string") { 134 | file.path = path; 135 | } else { 136 | file.path = path.path; 137 | file.contents = path.contents; 138 | } 139 | if (contents && "dir" in contents) { 140 | fileOptions = contents; 141 | } else if (typeof path === "string") { 142 | file.contents = contents ?? []; 143 | } 144 | return invokeTauriCommand({ 145 | __tauriModule: "Fs", 146 | message: { 147 | cmd: "writeFile", 148 | path: file.path, 149 | contents: Array.from( 150 | file.contents instanceof ArrayBuffer ? new Uint8Array(file.contents) : file.contents 151 | ), 152 | options: fileOptions 153 | } 154 | }); 155 | } 156 | async function readDir(dir, options = {}) { 157 | return invokeTauriCommand({ 158 | __tauriModule: "Fs", 159 | message: { 160 | cmd: "readDir", 161 | path: dir, 162 | options 163 | } 164 | }); 165 | } 166 | async function createDir(dir, options = {}) { 167 | return invokeTauriCommand({ 168 | __tauriModule: "Fs", 169 | message: { 170 | cmd: "createDir", 171 | path: dir, 172 | options 173 | } 174 | }); 175 | } 176 | async function removeDir(dir, options = {}) { 177 | return invokeTauriCommand({ 178 | __tauriModule: "Fs", 179 | message: { 180 | cmd: "removeDir", 181 | path: dir, 182 | options 183 | } 184 | }); 185 | } 186 | async function copyFile(source, destination, options = {}) { 187 | return invokeTauriCommand({ 188 | __tauriModule: "Fs", 189 | message: { 190 | cmd: "copyFile", 191 | source, 192 | destination, 193 | options 194 | } 195 | }); 196 | } 197 | async function removeFile(file, options = {}) { 198 | return invokeTauriCommand({ 199 | __tauriModule: "Fs", 200 | message: { 201 | cmd: "removeFile", 202 | path: file, 203 | options 204 | } 205 | }); 206 | } 207 | async function renameFile(oldPath, newPath, options = {}) { 208 | return invokeTauriCommand({ 209 | __tauriModule: "Fs", 210 | message: { 211 | cmd: "renameFile", 212 | oldPath, 213 | newPath, 214 | options 215 | } 216 | }); 217 | } 218 | async function exists(path, options = {}) { 219 | return invokeTauriCommand({ 220 | __tauriModule: "Fs", 221 | message: { 222 | cmd: "exists", 223 | path, 224 | options 225 | } 226 | }); 227 | } 228 | export { 229 | BaseDirectory, 230 | BaseDirectory as Dir, 231 | copyFile, 232 | createDir, 233 | exists, 234 | readBinaryFile, 235 | readDir, 236 | readTextFile, 237 | removeDir, 238 | removeFile, 239 | renameFile, 240 | writeBinaryFile, 241 | writeTextFile as writeFile, 242 | writeTextFile 243 | }; 244 | -------------------------------------------------------------------------------- /src/fs.rs: -------------------------------------------------------------------------------- 1 | //! Access the file system. 2 | //! 3 | //! The APIs must be added to `tauri.allowlist.fs` in `tauri.conf.json`: 4 | //! ```json 5 | //! { 6 | //! "tauri": { 7 | //! "allowlist": { 8 | //! "fs": { 9 | //! "all": true, // enable all FS APIs 10 | //! "readFile": true, 11 | //! "writeFile": true, 12 | //! "readDir": true, 13 | //! "copyFile": true, 14 | //! "createDir": true, 15 | //! "removeDir": true, 16 | //! "removeFile": true, 17 | //! "renameFile": true, 18 | //! "exists": true 19 | //! } 20 | //! } 21 | //! } 22 | //! } 23 | //! ``` 24 | //! It is recommended to allowlist only the APIs you use for optimal bundle size and security. 25 | use crate::Error; 26 | use js_sys::ArrayBuffer; 27 | use serde::{Deserialize, Serialize}; 28 | use serde_repr::*; 29 | use std::path::{Path, PathBuf}; 30 | use std::str; 31 | 32 | #[derive(Serialize_repr, Clone, PartialEq, Eq, Debug)] 33 | #[repr(u16)] 34 | pub enum BaseDirectory { 35 | Audio = 1, 36 | Cache = 2, 37 | Config = 3, 38 | Data = 4, 39 | LocalData = 5, 40 | Desktop = 6, 41 | Document = 7, 42 | Download = 8, 43 | Executable = 9, 44 | Font = 10, 45 | Home = 11, 46 | Picture = 12, 47 | Public = 13, 48 | Runtime = 14, 49 | Template = 15, 50 | Video = 16, 51 | Resource = 17, 52 | App = 18, 53 | Log = 19, 54 | Temp = 20, 55 | AppConfig = 21, 56 | AppData = 22, 57 | AppLocalData = 23, 58 | AppCache = 24, 59 | AppLog = 25, 60 | } 61 | 62 | #[derive(Deserialize, Clone, PartialEq, Debug)] 63 | pub struct FileEntry { 64 | pub path: PathBuf, 65 | pub name: Option, 66 | pub children: Option>, 67 | } 68 | 69 | #[derive(Serialize, Clone, PartialEq, Debug)] 70 | struct FsDirOptions { 71 | pub dir: Option, 72 | pub recursive: Option, 73 | } 74 | 75 | #[derive(Serialize, Clone, PartialEq, Debug)] 76 | struct FsOptions { 77 | pub dir: Option, 78 | } 79 | 80 | #[derive(Serialize, Clone, PartialEq, Debug)] 81 | struct FsTextFileOption { 82 | pub contents: String, 83 | path: PathBuf, 84 | } 85 | 86 | /// Copies a file to a destination. 87 | /// 88 | /// # Example 89 | /// 90 | /// ```rust,no_run 91 | /// use tauri_sys::fs; 92 | /// 93 | /// fs::copy_file(source, destination, BaseDirectory::Download).expect("could not copy file"); 94 | /// ``` 95 | /// 96 | /// Requires [`allowlist > fs > copyFile`](https://tauri.app/v1/api/js/fs) to be enabled. 97 | pub async fn copy_file(source: &Path, destination: &Path, dir: BaseDirectory) -> crate::Result<()> { 98 | let Some(source) = source.to_str() else { 99 | return Err(Error::Utf8(source.to_path_buf())); 100 | }; 101 | 102 | let Some(destination) = destination.to_str() else { 103 | return Err(Error::Utf8(destination.to_path_buf())); 104 | }; 105 | 106 | let raw = inner::copyFile( 107 | source, 108 | destination, 109 | serde_wasm_bindgen::to_value(&FsOptions { dir: Some(dir) })?, 110 | ) 111 | .await?; 112 | 113 | Ok(serde_wasm_bindgen::from_value(raw)?) 114 | } 115 | 116 | /// Creates a directory. 117 | /// If one of the path's parent components doesn't exist the promise will be rejected. 118 | /// 119 | /// # Example 120 | /// 121 | /// ```rust,no_run 122 | /// use tauri_sys::fs; 123 | /// 124 | /// fs::create_dir(dir, BaseDirectory::Download).expect("could not create directory"); 125 | /// ``` 126 | /// 127 | /// Requires [`allowlist > fs > createDir`](https://tauri.app/v1/api/js/fs) to be enabled. 128 | pub async fn create_dir(dir: &Path, base_dir: BaseDirectory) -> crate::Result<()> { 129 | let recursive = Some(false); 130 | 131 | let Some(dir) = dir.to_str() else { 132 | return Err(Error::Utf8(dir.to_path_buf())); 133 | }; 134 | 135 | Ok(inner::createDir( 136 | dir, 137 | serde_wasm_bindgen::to_value(&FsDirOptions { 138 | dir: Some(base_dir), 139 | recursive, 140 | })?, 141 | ) 142 | .await?) 143 | } 144 | 145 | /// Creates a directory recursively. 146 | /// 147 | /// # Example 148 | /// 149 | /// ```rust,no_run 150 | /// use tauri_sys::fs; 151 | /// 152 | /// fs::create_dir_all(dir, BaseDirectory::Download).expect("could not create directory"); 153 | /// ``` 154 | /// 155 | /// Requires [`allowlist > fs > createDir`](https://tauri.app/v1/api/js/fs) to be enabled. 156 | pub async fn create_dir_all(dir: &Path, base_dir: BaseDirectory) -> crate::Result<()> { 157 | let recursive = Some(true); 158 | 159 | let Some(dir) = dir.to_str() else { 160 | return Err(Error::Utf8(dir.to_path_buf())); 161 | }; 162 | 163 | Ok(inner::createDir( 164 | dir, 165 | serde_wasm_bindgen::to_value(&FsDirOptions { 166 | dir: Some(base_dir), 167 | recursive, 168 | })?, 169 | ) 170 | .await?) 171 | } 172 | 173 | /// Checks if a path exists. 174 | /// 175 | /// # Example 176 | /// 177 | /// ```rust,no_run 178 | /// use tauri_sys::fs; 179 | /// 180 | /// let file_exists = fs::exists(path, BaseDirectory::Download).expect("could not check if path exists"); 181 | /// ``` 182 | /// 183 | /// Requires [`allowlist > fs > exists`](https://tauri.app/v1/api/js/fs) to be enabled. 184 | pub async fn exists(path: &Path, dir: BaseDirectory) -> crate::Result { 185 | let Some(path) = path.to_str() else { 186 | return Err(Error::Utf8(path.to_path_buf())); 187 | }; 188 | 189 | let raw = inner::exists( 190 | path, 191 | serde_wasm_bindgen::to_value(&FsOptions { dir: Some(dir) })?, 192 | ) 193 | .await?; 194 | 195 | Ok(serde_wasm_bindgen::from_value(raw)?) 196 | } 197 | 198 | /// Reads a file as a byte array. 199 | /// 200 | /// # Example 201 | /// 202 | /// ```rust,no_run 203 | /// use tauri_sys::fs; 204 | /// 205 | /// let contents = fs::read_binary_file(filePath, BaseDirectory::Download).expect("could not read file contents"); 206 | /// ``` 207 | /// 208 | /// Requires [`allowlist > fs > readBinaryFile`](https://tauri.app/v1/api/js/fs) to be enabled. 209 | pub async fn read_binary_file(path: &Path, dir: BaseDirectory) -> crate::Result> { 210 | let Some(path) = path.to_str() else { 211 | return Err(Error::Utf8(path.to_path_buf())); 212 | }; 213 | 214 | let raw = inner::readBinaryFile( 215 | path, 216 | serde_wasm_bindgen::to_value(&FsOptions { dir: Some(dir) })?, 217 | ) 218 | .await?; 219 | 220 | Ok(serde_wasm_bindgen::from_value(raw)?) 221 | } 222 | 223 | /// List directory files. 224 | /// 225 | /// # Example 226 | /// 227 | /// ```rust,no_run 228 | /// use tauri_sys::fs; 229 | /// 230 | /// let files = fs::read_dir(path, BaseDirectory::Download).expect("could not read directory"); 231 | /// ``` 232 | /// 233 | /// Requires [`allowlist > fs > readDir`](https://tauri.app/v1/api/js/fs) to be enabled. 234 | pub async fn read_dir(path: &Path, dir: BaseDirectory) -> crate::Result> { 235 | let recursive = Some(false); 236 | let Some(path) = path.to_str() else { 237 | return Err(Error::Utf8(path.to_path_buf())); 238 | }; 239 | 240 | let raw = inner::readDir( 241 | path, 242 | serde_wasm_bindgen::to_value(&FsDirOptions { 243 | dir: Some(dir), 244 | recursive, 245 | })?, 246 | ) 247 | .await?; 248 | 249 | Ok(serde_wasm_bindgen::from_value(raw)?) 250 | } 251 | 252 | /// List directory files recursively. 253 | /// 254 | /// # Example 255 | /// 256 | /// ```rust,no_run 257 | /// use tauri_sys::fs; 258 | /// 259 | /// let files = fs::read_dir_all(path, BaseDirectory::Download).expect("could not read directory"); 260 | /// ``` 261 | /// 262 | /// Requires [`allowlist > fs > readDir`](https://tauri.app/v1/api/js/fs) to be enabled. 263 | pub async fn read_dir_all(path: &Path, dir: BaseDirectory) -> crate::Result> { 264 | let recursive = Some(true); 265 | let Some(path) = path.to_str() else { 266 | return Err(Error::Utf8(path.to_path_buf())); 267 | }; 268 | 269 | let raw = inner::readDir( 270 | path, 271 | serde_wasm_bindgen::to_value(&FsDirOptions { 272 | dir: Some(dir), 273 | recursive, 274 | })?, 275 | ) 276 | .await?; 277 | 278 | Ok(serde_wasm_bindgen::from_value(raw)?) 279 | } 280 | 281 | /// Read a file as an UTF-8 encoded string. 282 | /// 283 | /// # Example 284 | /// 285 | /// ```rust,no_run 286 | /// use tauri_sys::fs; 287 | /// 288 | /// let contents = fs::readTextFile(path, BaseDirectory::Download).expect("could not read file as text"); 289 | /// ``` 290 | /// 291 | /// Requires [`allowlist > fs > readTextFile`](https://tauri.app/v1/api/js/fs) to be enabled. 292 | pub async fn read_text_file(path: &Path, dir: BaseDirectory) -> crate::Result { 293 | let Some(path) = path.to_str() else { 294 | return Err(Error::Utf8(path.to_path_buf())); 295 | }; 296 | 297 | let raw = inner::readTextFile( 298 | path, 299 | serde_wasm_bindgen::to_value(&FsOptions { dir: Some(dir) })?, 300 | ) 301 | .await?; 302 | 303 | Ok(serde_wasm_bindgen::from_value(raw)?) 304 | } 305 | 306 | /// Removes a directory. 307 | /// If the directory is not empty the promise will be rejected. 308 | /// 309 | /// # Example 310 | /// 311 | /// ```rust,no_run 312 | /// use tauri_sys::fs; 313 | /// 314 | /// fs::remove_dir(path, BaseDirectory::Download).expect("could not remove directory"); 315 | /// ``` 316 | /// 317 | /// Requires [`allowlist > fs > removeDir`](https://tauri.app/v1/api/js/fs) to be enabled. 318 | pub async fn remove_dir(dir: &Path, base_dir: BaseDirectory) -> crate::Result<()> { 319 | let recursive = Some(false); 320 | let Some(dir) = dir.to_str() else { 321 | return Err(Error::Utf8(dir.to_path_buf())); 322 | }; 323 | 324 | Ok(inner::removeDir( 325 | dir, 326 | serde_wasm_bindgen::to_value(&FsDirOptions { 327 | dir: Some(base_dir), 328 | recursive, 329 | })?, 330 | ) 331 | .await?) 332 | } 333 | 334 | /// Removes a directory and its contents. 335 | /// 336 | /// # Example 337 | /// 338 | /// ```rust,no_run 339 | /// use tauri_sys::fs; 340 | /// 341 | /// fs::remove_dir_all(path, BaseDirectory::Download).expect("could not remove directory"); 342 | /// ``` 343 | /// 344 | /// Requires [`allowlist > fs > removeDir`](https://tauri.app/v1/api/js/fs) to be enabled. 345 | pub async fn remove_dir_all(dir: &Path, base_dir: BaseDirectory) -> crate::Result<()> { 346 | let recursive = Some(true); 347 | let Some(dir) = dir.to_str() else { 348 | return Err(Error::Utf8(dir.to_path_buf())); 349 | }; 350 | 351 | Ok(inner::removeDir( 352 | dir, 353 | serde_wasm_bindgen::to_value(&FsDirOptions { 354 | dir: Some(base_dir), 355 | recursive, 356 | })?, 357 | ) 358 | .await?) 359 | } 360 | 361 | /// Removes a file. 362 | /// 363 | /// # Example 364 | /// 365 | /// ```rust,no_run 366 | /// use tauri_sys::fs; 367 | /// 368 | /// fs::remove_file(path, BaseDirectory::Download).expect("could not remove file"); 369 | /// ``` 370 | /// 371 | /// Requires [`allowlist > fs > removeFile`](https://tauri.app/v1/api/js/fs) to be enabled. 372 | pub async fn remove_file(file: &Path, dir: BaseDirectory) -> crate::Result<()> { 373 | let Some(file) = file.to_str() else { 374 | return Err(Error::Utf8(file.to_path_buf())); 375 | }; 376 | 377 | Ok(inner::removeFile( 378 | file, 379 | serde_wasm_bindgen::to_value(&FsOptions { dir: Some(dir) })?, 380 | ) 381 | .await?) 382 | } 383 | 384 | /// Renames a file. 385 | /// 386 | /// # Example 387 | /// 388 | /// ```rust,no_run 389 | /// use tauri_sys::fs; 390 | /// 391 | /// fs::rename_file(old_path, new_path, BaseDirectory::Download).expect("could not rename file"); 392 | /// ``` 393 | /// 394 | /// Requires [`allowlist > fs > renameFile`](https://tauri.app/v1/api/js/fs) to be enabled. 395 | pub async fn rename_file( 396 | old_path: &Path, 397 | new_path: &Path, 398 | dir: BaseDirectory, 399 | ) -> crate::Result<()> { 400 | let Some(old_path) = old_path.to_str() else { 401 | return Err(Error::Utf8(old_path.to_path_buf())); 402 | }; 403 | 404 | let Some(new_path) = new_path.to_str() else { 405 | return Err(Error::Utf8(new_path.to_path_buf())); 406 | }; 407 | 408 | Ok(inner::renameFile( 409 | old_path, 410 | new_path, 411 | serde_wasm_bindgen::to_value(&FsOptions { dir: Some(dir) })?, 412 | ) 413 | .await?) 414 | } 415 | 416 | /// Writes a byte array content to a file. 417 | /// 418 | /// # Example 419 | /// 420 | /// ```rust,no_run 421 | /// use tauri_sys::fs; 422 | /// 423 | /// fs::write_binary_file(path, contents, BaseDirectory::Download).expect("could not writes binary file"); 424 | /// ``` 425 | /// 426 | /// Requires [`allowlist > fs > writeBinaryFile`](https://tauri.app/v1/api/js/fs) to be enabled. 427 | pub async fn write_binary_file( 428 | path: &Path, 429 | contents: ArrayBuffer, 430 | dir: BaseDirectory, 431 | ) -> crate::Result<()> { 432 | let Some(path) = path.to_str() else { 433 | return Err(Error::Utf8(path.to_path_buf())); 434 | }; 435 | 436 | Ok(inner::writeBinaryFile( 437 | path, 438 | contents, 439 | serde_wasm_bindgen::to_value(&FsOptions { dir: Some(dir) })?, 440 | ) 441 | .await?) 442 | } 443 | 444 | /// Writes a UTF-8 text file. 445 | /// 446 | /// # Example 447 | /// 448 | /// ```rust,no_run 449 | /// use tauri_sys::fs; 450 | /// 451 | /// fs::write_text_file(path, contents, BaseDirectory::Download).expect("could not writes binary file"); 452 | /// ``` 453 | /// 454 | /// Requires [`allowlist > fs > writeTextFile`](https://tauri.app/v1/api/js/fs) to be enabled. 455 | pub async fn write_text_file(path: &Path, contents: &str, dir: BaseDirectory) -> crate::Result<()> { 456 | let Some(path) = path.to_str() else { 457 | return Err(Error::Utf8(path.to_path_buf())); 458 | }; 459 | 460 | Ok(inner::writeTextFile( 461 | path, 462 | &contents, 463 | serde_wasm_bindgen::to_value(&FsOptions { dir: Some(dir) })?, 464 | ) 465 | .await?) 466 | } 467 | 468 | mod inner { 469 | use super::ArrayBuffer; 470 | use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; 471 | 472 | #[wasm_bindgen(module = "/src/fs.js")] 473 | extern "C" { 474 | #[wasm_bindgen(catch)] 475 | pub async fn copyFile( 476 | source: &str, 477 | destination: &str, 478 | options: JsValue, 479 | ) -> Result; 480 | #[wasm_bindgen(catch)] 481 | pub async fn createDir(dir: &str, options: JsValue) -> Result<(), JsValue>; 482 | #[wasm_bindgen(catch)] 483 | pub async fn exists(path: &str, options: JsValue) -> Result; 484 | #[wasm_bindgen(catch)] 485 | pub async fn readBinaryFile(filePath: &str, options: JsValue) -> Result; 486 | #[wasm_bindgen(catch)] 487 | pub async fn readTextFile(filePath: &str, options: JsValue) -> Result; 488 | #[wasm_bindgen(catch)] 489 | pub async fn readDir(dir: &str, options: JsValue) -> Result; 490 | #[wasm_bindgen(catch)] 491 | pub async fn removeDir(dir: &str, options: JsValue) -> Result<(), JsValue>; 492 | #[wasm_bindgen(catch)] 493 | pub async fn removeFile(source: &str, options: JsValue) -> Result<(), JsValue>; 494 | #[wasm_bindgen(catch)] 495 | pub async fn renameFile( 496 | oldPath: &str, 497 | newPath: &str, 498 | options: JsValue, 499 | ) -> Result<(), JsValue>; 500 | #[wasm_bindgen(catch)] 501 | pub async fn writeBinaryFile( 502 | filePath: &str, 503 | contents: ArrayBuffer, 504 | options: JsValue, 505 | ) -> Result<(), JsValue>; 506 | #[wasm_bindgen(catch)] 507 | pub async fn writeTextFile( 508 | filePath: &str, 509 | contents: &str, 510 | options: JsValue, 511 | ) -> Result<(), JsValue>; 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /src/globalShortcut.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/globalShortcut.ts 45 | async function register(shortcut, handler) { 46 | return invokeTauriCommand({ 47 | __tauriModule: "GlobalShortcut", 48 | message: { 49 | cmd: "register", 50 | shortcut, 51 | handler: transformCallback(handler) 52 | } 53 | }); 54 | } 55 | async function registerAll(shortcuts, handler) { 56 | return invokeTauriCommand({ 57 | __tauriModule: "GlobalShortcut", 58 | message: { 59 | cmd: "registerAll", 60 | shortcuts, 61 | handler: transformCallback(handler) 62 | } 63 | }); 64 | } 65 | async function isRegistered(shortcut) { 66 | return invokeTauriCommand({ 67 | __tauriModule: "GlobalShortcut", 68 | message: { 69 | cmd: "isRegistered", 70 | shortcut 71 | } 72 | }); 73 | } 74 | async function unregister(shortcut) { 75 | return invokeTauriCommand({ 76 | __tauriModule: "GlobalShortcut", 77 | message: { 78 | cmd: "unregister", 79 | shortcut 80 | } 81 | }); 82 | } 83 | async function unregisterAll() { 84 | return invokeTauriCommand({ 85 | __tauriModule: "GlobalShortcut", 86 | message: { 87 | cmd: "unregisterAll" 88 | } 89 | }); 90 | } 91 | export { 92 | isRegistered, 93 | register, 94 | registerAll, 95 | unregister, 96 | unregisterAll 97 | }; 98 | -------------------------------------------------------------------------------- /src/global_shortcut.rs: -------------------------------------------------------------------------------- 1 | //! Register global shortcuts. 2 | //! 3 | //! ## Differences to the JavaScript API 4 | //! 5 | //! ## `registerAll` 6 | //! 7 | //! ```rust,no_run 8 | //! # async fn main() -> Result<(), Box> { 9 | //! let shortcuts = ["CommandOrControl+Shift+C", "Ctrl+Alt+F12"]; 10 | //! 11 | //! let streams = futures::future::try_join_all(shortcuts.map(|s| async move { 12 | //! let stream = global_shortcut::register(s).await?; 13 | //! 14 | //! anyhow::Ok(stream.map(move |_| s)) 15 | //! })) 16 | //! .await?; 17 | //! 18 | //! let mut events = futures::stream::select_all(streams); 19 | //! 20 | //! while let Some(shortcut) = events.next().await { 21 | //! log::debug!("Shortcut {} triggered", shortcut) 22 | //! } 23 | //! # Ok(()) 24 | //! # } 25 | //! ``` 26 | //! 27 | //! The APIs must be added to tauri.allowlist.globalShortcut in tauri.conf.json: 28 | //! 29 | //! ```json 30 | //! { 31 | //! "tauri": { 32 | //! "allowlist": { 33 | //! "globalShortcut": { 34 | //! "all": true // enable all global shortcut APIs 35 | //! } 36 | //! } 37 | //! } 38 | //! } 39 | //! ``` 40 | //! It is recommended to allowlist only the APIs you use for optimal bundle size and security. 41 | 42 | use futures::{channel::mpsc, Stream, StreamExt}; 43 | use wasm_bindgen::{prelude::Closure, JsValue}; 44 | 45 | /// Determines whether the given shortcut is registered by this application or not. 46 | /// 47 | /// # Example 48 | /// 49 | /// ```rust,no_run 50 | /// use tauri_sys::global_shortcut::is_registered; 51 | /// 52 | /// # async fn main() -> Result<(), Box> { 53 | /// let registered = is_registered("CommandOrControl+P").await?; 54 | /// # Ok(()) 55 | /// # } 56 | /// ``` 57 | pub async fn is_registered(shortcut: &str) -> crate::Result { 58 | let raw = inner::isRegistered(shortcut).await?; 59 | 60 | Ok(serde_wasm_bindgen::from_value(raw)?) 61 | } 62 | 63 | /// Register a global shortcut. 64 | /// 65 | /// The returned Future will automatically clean up it's underlying event listener when dropped, so no manual unlisten function needs to be called. 66 | /// See [Differences to the JavaScript API](../index.html#differences-to-the-javascript-api) for details. 67 | /// 68 | /// # Examples 69 | /// 70 | /// ```rust,no_run 71 | /// use tauri_sys::global_shortcut::register; 72 | /// use web_sys::console; 73 | /// 74 | /// # async fn main() -> Result<(), Box> { 75 | /// let events = register("CommandOrControl+Shift+C").await?; 76 | /// 77 | /// while let Some(_) in events.next().await { 78 | /// console::log_1(&"Shortcut triggered".into()); 79 | /// } 80 | /// # Ok(()) 81 | /// # } 82 | /// ``` 83 | pub async fn register(shortcut: &str) -> crate::Result> { 84 | let (tx, rx) = mpsc::unbounded(); 85 | 86 | let closure = Closure::::new(move |_| { 87 | let _ = tx.unbounded_send(()); 88 | }); 89 | inner::register(shortcut, &closure).await?; 90 | closure.forget(); 91 | 92 | Ok(Listen { 93 | shortcut: JsValue::from_str(shortcut), 94 | rx, 95 | }) 96 | } 97 | 98 | struct Listen { 99 | pub shortcut: JsValue, 100 | pub rx: mpsc::UnboundedReceiver, 101 | } 102 | 103 | impl Drop for Listen { 104 | fn drop(&mut self) { 105 | log::debug!("Unregistering shortcut {:?}", self.shortcut); 106 | inner::unregister(self.shortcut.clone()); 107 | } 108 | } 109 | 110 | impl Stream for Listen { 111 | type Item = T; 112 | 113 | fn poll_next( 114 | mut self: std::pin::Pin<&mut Self>, 115 | cx: &mut std::task::Context<'_>, 116 | ) -> std::task::Poll> { 117 | self.rx.poll_next_unpin(cx) 118 | } 119 | } 120 | 121 | /// Register a collection of global shortcuts. 122 | /// 123 | /// # Example 124 | /// 125 | /// ```rust,no_run 126 | /// use tauri_sys::global_shortcut::register; 127 | /// use web_sys::console; 128 | /// 129 | /// # async fn main() -> Result<(), Box> { 130 | /// let events = register_all(["CommandOrControl+Shift+C", "Ctrl+Alt+F12"]).await?; 131 | /// 132 | /// while let Some(shortcut) in events.next().await { 133 | /// console::log_1(&format!("Shortcut {} triggered", shortcut).into()); 134 | /// } 135 | /// # Ok(()) 136 | /// # } 137 | /// ``` 138 | // pub async fn register_all(shortcuts: impl IntoIterator) -> crate::Result> 139 | // { 140 | // let shortcuts: Array = shortcuts.into_iter().map(JsValue::from_str).collect(); 141 | // let (tx, rx) = mpsc::unbounded::(); 142 | 143 | // let closure = Closure::::new(move |raw| { 144 | // let _ = tx.unbounded_send(serde_wasm_bindgen::from_value(raw).unwrap()); 145 | // }); 146 | // inner::registerAll(shortcuts.clone(), &closure).await?; 147 | // closure.forget(); 148 | 149 | // Ok(ListenAll { shortcuts, rx }) 150 | // } 151 | 152 | // struct ListenAll { 153 | // pub shortcuts: js_sys::Array, 154 | // pub rx: mpsc::UnboundedReceiver, 155 | // } 156 | 157 | // impl Drop for ListenAll { 158 | // fn drop(&mut self) { 159 | // for shortcut in self.shortcuts.iter() { 160 | // inner::unregister(shortcut); 161 | // } 162 | // } 163 | // } 164 | 165 | // impl Stream for ListenAll { 166 | // type Item = T; 167 | 168 | // fn poll_next( 169 | // mut self: std::pin::Pin<&mut Self>, 170 | // cx: &mut std::task::Context<'_>, 171 | // ) -> std::task::Poll> { 172 | // self.rx.poll_next_unpin(cx) 173 | // } 174 | // } 175 | 176 | mod inner { 177 | // use js_sys::Array; 178 | use wasm_bindgen::{ 179 | prelude::{wasm_bindgen, Closure}, 180 | JsValue, 181 | }; 182 | 183 | #[wasm_bindgen(module = "/src/globalShortcut.js")] 184 | extern "C" { 185 | #[wasm_bindgen(catch)] 186 | pub async fn isRegistered(shortcut: &str) -> Result; 187 | #[wasm_bindgen(catch)] 188 | pub async fn register( 189 | shortcut: &str, 190 | handler: &Closure, 191 | ) -> Result<(), JsValue>; 192 | // #[wasm_bindgen(catch)] 193 | // pub async fn registerAll( 194 | // shortcuts: Array, 195 | // handler: &Closure, 196 | // ) -> Result<(), JsValue>; 197 | pub fn unregister(shortcut: JsValue); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/http.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/http.ts 45 | var ResponseType = /* @__PURE__ */ ((ResponseType2) => { 46 | ResponseType2[ResponseType2["JSON"] = 1] = "JSON"; 47 | ResponseType2[ResponseType2["Text"] = 2] = "Text"; 48 | ResponseType2[ResponseType2["Binary"] = 3] = "Binary"; 49 | return ResponseType2; 50 | })(ResponseType || {}); 51 | async function formBody(data) { 52 | const form = {}; 53 | const append = async (key, v) => { 54 | if (v !== null) { 55 | let r; 56 | if (typeof v === "string") { 57 | r = v; 58 | } else if (v instanceof Uint8Array || Array.isArray(v)) { 59 | r = Array.from(v); 60 | } else if (v instanceof File) { 61 | r = { 62 | file: Array.from(new Uint8Array(await v.arrayBuffer())), 63 | mime: v.type, 64 | fileName: v.name 65 | }; 66 | } else if (typeof v.file === "string") { 67 | r = { file: v.file, mime: v.mime, fileName: v.fileName }; 68 | } else { 69 | r = { file: Array.from(v.file), mime: v.mime, fileName: v.fileName }; 70 | } 71 | form[String(key)] = r; 72 | } 73 | }; 74 | if (data instanceof FormData) { 75 | for (const [key, value] of data) { 76 | await append(key, value); 77 | } 78 | } else { 79 | for (const [key, value] of Object.entries(data)) { 80 | await append(key, value); 81 | } 82 | } 83 | return form; 84 | } 85 | var Body = class { 86 | constructor(type, payload) { 87 | this.type = type; 88 | this.payload = payload; 89 | } 90 | static form(data) { 91 | return new Body("Form", data); 92 | } 93 | static json(data) { 94 | return new Body("Json", data); 95 | } 96 | static text(value) { 97 | return new Body("Text", value); 98 | } 99 | static bytes(bytes) { 100 | return new Body( 101 | "Bytes", 102 | Array.from(bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes) 103 | ); 104 | } 105 | }; 106 | var Response = class { 107 | constructor(response) { 108 | this.url = response.url; 109 | this.status = response.status; 110 | this.ok = this.status >= 200 && this.status < 300; 111 | this.headers = response.headers; 112 | this.rawHeaders = response.rawHeaders; 113 | this.data = response.data; 114 | } 115 | }; 116 | var Client = class { 117 | constructor(id) { 118 | this.id = id; 119 | } 120 | async drop() { 121 | return invokeTauriCommand({ 122 | __tauriModule: "Http", 123 | message: { 124 | cmd: "dropClient", 125 | client: this.id 126 | } 127 | }); 128 | } 129 | async request(options) { 130 | const jsonResponse = !options.responseType || options.responseType === 1 /* JSON */; 131 | if (jsonResponse) { 132 | options.responseType = 2 /* Text */; 133 | } 134 | if (options.body?.type === "Form") { 135 | options.body.payload = await formBody(options.body.payload); 136 | } 137 | return invokeTauriCommand({ 138 | __tauriModule: "Http", 139 | message: { 140 | cmd: "httpRequest", 141 | client: this.id, 142 | options 143 | } 144 | }).then((res) => { 145 | const response = new Response(res); 146 | if (jsonResponse) { 147 | try { 148 | response.data = JSON.parse(response.data); 149 | } catch (e) { 150 | if (response.ok && response.data === "") { 151 | response.data = {}; 152 | } else if (response.ok) { 153 | throw Error( 154 | `Failed to parse response \`${response.data}\` as JSON: ${e}; 155 | try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.` 156 | ); 157 | } 158 | } 159 | return response; 160 | } 161 | return response; 162 | }); 163 | } 164 | async get(url, options) { 165 | return this.request({ 166 | method: "GET", 167 | url, 168 | ...options 169 | }); 170 | } 171 | async post(url, body, options) { 172 | return this.request({ 173 | method: "POST", 174 | url, 175 | body, 176 | ...options 177 | }); 178 | } 179 | async put(url, body, options) { 180 | return this.request({ 181 | method: "PUT", 182 | url, 183 | body, 184 | ...options 185 | }); 186 | } 187 | async patch(url, options) { 188 | return this.request({ 189 | method: "PATCH", 190 | url, 191 | ...options 192 | }); 193 | } 194 | async delete(url, options) { 195 | return this.request({ 196 | method: "DELETE", 197 | url, 198 | ...options 199 | }); 200 | } 201 | }; 202 | async function getClient(options) { 203 | return invokeTauriCommand({ 204 | __tauriModule: "Http", 205 | message: { 206 | cmd: "createClient", 207 | options 208 | } 209 | }).then((id) => new Client(id)); 210 | } 211 | var defaultClient = null; 212 | async function fetch(url, options) { 213 | if (defaultClient === null) { 214 | defaultClient = await getClient(); 215 | } 216 | return defaultClient.request({ 217 | url, 218 | method: options?.method ?? "GET", 219 | ...options 220 | }); 221 | } 222 | export { 223 | Body, 224 | Client, 225 | Response, 226 | ResponseType, 227 | fetch, 228 | getClient 229 | }; 230 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to the [`Tauri API`](https://tauri.app/v1/api/js/) for projects using [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) 2 | //! 3 | //! Tauri is a framework for building tiny, blazing fast, and secure cross-platform applications. 4 | //! Developers can integrate any front-end framework that compiles to HTML, JS and CSS for building their user interface. 5 | //! The backend of the application is a rust binary, leveraging the [`tauri`] crate. 6 | //! 7 | //! This crate contains idiomatic rust bindings to the backend, for usage within Rust projects that target wasm32-unknown-unknown, 8 | //! for example Rust frontend frameworks such as [`yew`](https://yew.rs), [`sycamore`](https://sycamore-rs.netlify.app) or [`dominator`](https://github.com/Pauan/rust-dominator). 9 | //! 10 | //! The wasmtime crate has similar concepts to the the JS WebAssembly API as well as the proposed C API, but the Rust API is designed for efficiency, ergonomics, and expressivity in Rust. As with all other Rust code you’re guaranteed that programs will be safe (not have undefined behavior or segfault) so long as you don’t use unsafe in your own program. 11 | //! 12 | //! # Differences to the JavaScript API 13 | //! 14 | //! ## Event Listeners 15 | //! 16 | //! Event Listeners, such as [`event::listen`] module or [`window::WebviewWindow::listen`], 17 | //! are modeled as async streams of data using the [`futures::Stream`] trait instead of using callbacks. 18 | //! Streams have multiple advantages over callbacks: 19 | //! 20 | //! #### Stream Combinators 21 | //! 22 | //! Streams are essentially the async equivalent of the standard [`Iterator`] and therefore expose a very similar set of combinator functions. 23 | //! This means streams are much more versatile and ergonomic than simple callbacks. 24 | //! 25 | //! For example, we can use Stream combinators and various utility functions 26 | //! to replicate the `registerAll` function that unregisters the shortcuts after 20 seconds: 27 | //! 28 | //! ```rust 29 | //! use futures::{future, stream, Stream, StreamExt}; 30 | //! use std::time::Duration; 31 | //! use tauri_sys::global_shortcut; 32 | //! 33 | //! async fn register_with_shortcut<'a>( 34 | //! shortcut: &'a str, 35 | //! ) -> anyhow::Result> { 36 | //! let stream = global_shortcut::register(shortcut).await?; 37 | //! 38 | //! Ok(stream.map(move |_| shortcut)) 39 | //! } 40 | //! 41 | //! async fn register_all() { 42 | //! let shortcuts = ["CommandOrControl+Shift+C", "Ctrl+Alt+F12"]; 43 | //! 44 | //! let timeout = gloo_timers::future::sleep(Duration::from_secs(20)); 45 | //! 46 | //! // await the futures that creates the streams, exiting early if any future resolves with an error 47 | //! let streams = future::try_join_all(shortcuts.map(register_with_shortcut)).await?; 48 | //! 49 | //! // combine all streams into one 50 | //! let mut events = stream::select_all(streams).take_until(timeout); 51 | //! 52 | //! while let Some(shortcut) = events.next().await { 53 | //! log::debug!("Shortcut {} triggered", shortcut); 54 | //! } 55 | //! } 56 | //! ``` 57 | //! 58 | //! #### Automatic cleanup 59 | //! 60 | //! Streams follow Rust's RAII idiom as they automatically clean up after themselves when being dropped. 61 | //! No need to manually call `unlisten` like in the JS API to avoid memory leaks or double-listens. 62 | //! 63 | //! ```rust 64 | //! async fn process_some_errors() { 65 | //! let win = WebviewWindow::get_by_label("main").unwrap(); 66 | //! 67 | //! let errors = win.listen("tauri://error").await? 68 | //! .take(3); 69 | //! 70 | //! while let Some(err) = errors.next().await { 71 | //! log::error!("Something bad happened! {}", err) 72 | //! } 73 | //! 74 | //! // the stream is dropped here and the underlying listener automatically detached. 75 | //! } 76 | //! ``` 77 | //! 78 | //! #### Streams are buffered 79 | //! 80 | //! Streams, much like iterators, are poll-based meaning the caller is responsible for advancing it. 81 | //! This allows greater flexibility as you can freely decide *when* to process events. 82 | //! Event streams are internally backed by an unbounded queue so events are buffered until read, 83 | //! so no events are getting lost even if you temporarily pause processing. 84 | //! 85 | //! Being unbounded means the memory consumption will grow if the stream is kept around, but not read from. 86 | //! This is rarely a concern in practice, but if you need to suspend processing of events for a long time, 87 | //! you should rather drop the entire stream and re-create it as needed later. 88 | //! 89 | //! ### Cancelling Streams 90 | //! 91 | //! One usecase of the `unlisten` function might intuitively not map well to streams: Cancellation. 92 | //! In JavaScript you can do this when you want to detach an event listener: 93 | //! 94 | //! ```js 95 | //! import { listen } from '@tauri-apps/api/event' 96 | //! 97 | //! const unlisten = await listen('rust-event', (ev) => console.log(ev)) 98 | //! 99 | //! // Some time later. We are no longer interested in listening to the event 100 | //! unlisten() 101 | //! ``` 102 | //! 103 | //! But if the Rust event stream only gets detached when the stream get's dropped, how can we cancel the stream at will? 104 | //! We can make use of the combinators and utility functions offered by the [`futures`] crate again, namely the [`futures::stream::Abortable`] type: 105 | //! 106 | //! ```rust 107 | //! use tauri_sys::event::listen; 108 | //! 109 | //! let events = listen::<()>("rust-event").await? 110 | //! // abort handle behaves identical to the JavaScript `unlisten` function 111 | //! let (events, abort_handle) = futures::stream::abortable(events); 112 | //! 113 | //! while let Some(_) = events.next().await { 114 | //! log::debug!("Received event!"); 115 | //! } 116 | //! 117 | //! // in some other task, when we're done with listening to the events 118 | //! abort_handle.abort(); 119 | //! ``` 120 | 121 | #[cfg(feature = "app")] 122 | pub mod app; 123 | #[cfg(feature = "clipboard")] 124 | pub mod clipboard; 125 | #[cfg(feature = "dialog")] 126 | pub mod dialog; 127 | mod error; 128 | #[cfg(feature = "event")] 129 | pub mod event; 130 | #[cfg(feature = "fs")] 131 | pub mod fs; 132 | #[cfg(feature = "global_shortcut")] 133 | pub mod global_shortcut; 134 | #[cfg(feature = "mocks")] 135 | pub mod mocks; 136 | #[cfg(feature = "notification")] 137 | pub mod notification; 138 | #[cfg(feature = "os")] 139 | pub mod os; 140 | #[cfg(feature = "path")] 141 | pub mod path; 142 | #[cfg(feature = "process")] 143 | pub mod process; 144 | #[cfg(feature = "tauri")] 145 | pub mod tauri; 146 | #[cfg(feature = "updater")] 147 | pub mod updater; 148 | #[cfg(feature = "window")] 149 | pub mod window; 150 | 151 | pub use error::Error; 152 | pub(crate) type Result = core::result::Result; 153 | 154 | #[cfg(any(feature = "dialog", feature = "window"))] 155 | pub(crate) mod utils { 156 | pub struct ArrayIterator { 157 | pos: u32, 158 | arr: js_sys::Array, 159 | } 160 | 161 | impl ArrayIterator { 162 | pub fn new(arr: js_sys::Array) -> Self { 163 | Self { pos: 0, arr } 164 | } 165 | } 166 | 167 | impl Iterator for ArrayIterator { 168 | type Item = wasm_bindgen::JsValue; 169 | 170 | fn next(&mut self) -> Option { 171 | let raw = self.arr.get(self.pos); 172 | 173 | if raw.is_undefined() { 174 | None 175 | } else { 176 | self.pos += 1; 177 | 178 | Some(raw) 179 | } 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/mocks.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/mocks.ts 2 | function mockIPC(cb) { 3 | window.__TAURI_IPC__ = async ({ 4 | cmd, 5 | callback, 6 | error, 7 | ...args 8 | }) => { 9 | try { 10 | window[`_${callback}`](await cb(cmd, args)); 11 | } catch (err) { 12 | window[`_${error}`](err); 13 | } 14 | }; 15 | } 16 | function mockWindows(current, ...additionalWindows) { 17 | window.__TAURI_METADATA__ = { 18 | __windows: [current, ...additionalWindows].map((label) => ({ label })), 19 | __currentWindow: { label: current } 20 | }; 21 | } 22 | function mockConvertFileSrc(osName, windowsProtocolScheme = "https") { 23 | window.__TAURI__ = window.__TAURI__ ?? {}; 24 | window.__TAURI__.convertFileSrc = function(filePath, protocol = "asset") { 25 | const path = encodeURIComponent(filePath); 26 | return osName === "windows" ? `${windowsProtocolScheme}://${protocol}.localhost/${path}` : `${protocol}://localhost/${path}`; 27 | }; 28 | } 29 | function clearMocks() { 30 | if (window.__TAURI__?.convertFileSrc) 31 | delete window.__TAURI__.convertFileSrc; 32 | if (window.__TAURI_IPC__) 33 | delete window.__TAURI_IPC__; 34 | if (window.__TAURI_METADATA__) 35 | delete window.__TAURI_METADATA__; 36 | } 37 | export { 38 | clearMocks, 39 | mockConvertFileSrc, 40 | mockIPC, 41 | mockWindows 42 | }; 43 | -------------------------------------------------------------------------------- /src/mocks.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use js_sys::Array; 4 | use wasm_bindgen::{prelude::Closure, JsValue}; 5 | 6 | /// Mocks the current window label 7 | /// In non-tauri context it is required to call this function///before* using the `@tauri-apps/api/window` module. 8 | /// 9 | /// This function only mocks the *presence* of a window, 10 | /// window properties (e.g. width and height) can be mocked like regular IPC calls using the `mockIPC` function. 11 | pub fn mock_window(current: &str) { 12 | inner::mockWindows(current, JsValue::UNDEFINED) 13 | } 14 | 15 | /// Mocks many window labels. 16 | /// In non-tauri context it is required to call this function///before* using the `@tauri-apps/api/window` module. 17 | /// 18 | /// This function only mocks the *presence* of windows, 19 | /// window properties (e.g. width and height) can be mocked like regular IPC calls using the `mockIPC` function. 20 | /// 21 | /// @param current Label of window this JavaScript context is running in. 22 | /// @param additionalWindows Label of additional windows the app has. 23 | pub fn mock_windows(current: &str, additional_windows: &[&str]) { 24 | inner::mockWindows( 25 | current, 26 | Array::from_iter(additional_windows.iter().map(|str| JsValue::from_str(str))).into(), 27 | ) 28 | } 29 | 30 | /// Intercepts all IPC requests with the given mock handler. 31 | /// 32 | /// This function can be used when testing tauri frontend applications or when running the frontend in a Node.js context during static site generation. 33 | pub fn mock_ipc(mut handler: H) 34 | where 35 | H: FnMut(String, JsValue) -> Result + 'static, 36 | R: Into, 37 | E: Into, 38 | { 39 | let closure = Closure:: Result>::new( 40 | move |cmd, payload| (handler)(cmd, payload).map(Into::into).map_err(Into::into), 41 | ); 42 | 43 | inner::mockIPC(&closure); 44 | 45 | closure.forget(); 46 | } 47 | 48 | /// Clears mocked functions/data injected by the other functions in this module. 49 | /// When using a test runner that doesn't provide a fresh window object for each test, calling this function will reset tauri specific properties. 50 | pub fn clear_mocks() { 51 | inner::clearMocks() 52 | } 53 | 54 | mod inner { 55 | use wasm_bindgen::{ 56 | prelude::{wasm_bindgen, Closure}, 57 | JsValue, 58 | }; 59 | 60 | #[wasm_bindgen(module = "/src/mocks.js")] 61 | extern "C" { 62 | #[wasm_bindgen(variadic)] 63 | pub fn mockWindows(current: &str, rest: JsValue); 64 | pub fn mockIPC(handler: &Closure Result>); 65 | pub fn clearMocks(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/notification.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/notification.ts 45 | async function isPermissionGranted() { 46 | if (window.Notification.permission !== "default") { 47 | return Promise.resolve(window.Notification.permission === "granted"); 48 | } 49 | return invokeTauriCommand({ 50 | __tauriModule: "Notification", 51 | message: { 52 | cmd: "isNotificationPermissionGranted" 53 | } 54 | }); 55 | } 56 | async function requestPermission() { 57 | return window.Notification.requestPermission(); 58 | } 59 | function sendNotification(options) { 60 | if (typeof options === "string") { 61 | new window.Notification(options); 62 | } else { 63 | new window.Notification(options.title, options); 64 | } 65 | } 66 | export { 67 | isPermissionGranted, 68 | requestPermission, 69 | sendNotification 70 | }; 71 | -------------------------------------------------------------------------------- /src/notification.rs: -------------------------------------------------------------------------------- 1 | //! Send toast notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. 2 | //! 3 | //! The APIs must be added to tauri.allowlist.notification in tauri.conf.json: 4 | //! 5 | //! ```json 6 | //! { 7 | //! "tauri": { 8 | //! "allowlist": { 9 | //! "notification": { 10 | //! "all": true // enable all notification APIs 11 | //! } 12 | //! } 13 | //! } 14 | //! } 15 | //! ``` 16 | //! It is recommended to allowlist only the APIs you use for optimal bundle size and security. 17 | 18 | use serde::{Deserialize, Serialize}; 19 | 20 | /// Checks if the permission to send notifications is granted. 21 | /// 22 | /// # Example 23 | /// 24 | /// ```rust,no_run 25 | /// use tauri_sys::notification; 26 | /// 27 | /// # async fn main() -> Result<(), Box> { 28 | /// let is_granted = notification::is_permission_granted().await?; 29 | /// # Ok(()) 30 | /// # } 31 | /// ``` 32 | #[inline(always)] 33 | pub async fn is_permission_granted() -> crate::Result { 34 | let raw = inner::isPermissionGranted().await?; 35 | 36 | Ok(serde_wasm_bindgen::from_value(raw)?) 37 | } 38 | 39 | /// Requests the permission to send notifications. 40 | /// 41 | /// # Example 42 | /// 43 | /// ```rust,no_run 44 | /// use tauri_sys::notification; 45 | /// 46 | /// # async fn main() -> Result<(), Box> { 47 | /// let perm = notification::request_permission().await?; 48 | /// # Ok(()) 49 | /// # } 50 | /// ``` 51 | #[inline(always)] 52 | pub async fn request_permission() -> crate::Result { 53 | let raw = inner::requestPermission().await?; 54 | 55 | Ok(serde_wasm_bindgen::from_value(raw)?) 56 | } 57 | 58 | /// Possible permission values. 59 | #[derive(Debug, Deserialize, Default, Clone, Copy, PartialEq, Eq)] 60 | pub enum Permission { 61 | #[default] 62 | #[serde(rename = "default")] 63 | Default, 64 | #[serde(rename = "granted")] 65 | Granted, 66 | #[serde(rename = "denied")] 67 | Denied, 68 | } 69 | 70 | /// The desktop notification definition. 71 | /// 72 | /// Allows you to construct a Notification data and send it. 73 | #[derive(Debug, Default, Serialize)] 74 | pub struct Notification<'a> { 75 | body: Option<&'a str>, 76 | title: Option<&'a str>, 77 | icon: Option<&'a str>, 78 | } 79 | 80 | impl<'a> Notification<'a> { 81 | pub fn new() -> Self { 82 | Self::default() 83 | } 84 | 85 | /// Sets the notification title. 86 | pub fn set_title(&mut self, title: &'a str) { 87 | self.title = Some(title); 88 | } 89 | 90 | /// Sets the notification body. 91 | pub fn set_body(&mut self, body: &'a str) { 92 | self.body = Some(body); 93 | } 94 | 95 | /// Sets the notification icon. 96 | pub fn set_icon(&mut self, icon: &'a str) { 97 | self.icon = Some(icon); 98 | } 99 | 100 | /// Shows the notification. 101 | /// 102 | /// # Example 103 | /// 104 | /// ```rust,no_run 105 | /// use tauri_sys::notification::Notification; 106 | /// 107 | /// # fn main() -> Result<(), Box> { 108 | /// Notification::new() 109 | /// .set_title("Tauri") 110 | /// .set_body("Tauri is awesome!") 111 | /// .show()?; 112 | /// # Ok(()) 113 | /// # } 114 | /// ``` 115 | #[inline(always)] 116 | pub fn show(&self) -> crate::Result<()> { 117 | inner::sendNotification(serde_wasm_bindgen::to_value(&self)?)?; 118 | 119 | Ok(()) 120 | } 121 | } 122 | 123 | mod inner { 124 | use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; 125 | 126 | #[wasm_bindgen(module = "/src/notification.js")] 127 | extern "C" { 128 | #[wasm_bindgen(catch)] 129 | pub async fn isPermissionGranted() -> Result; 130 | #[wasm_bindgen(catch)] 131 | pub async fn requestPermission() -> Result; 132 | #[wasm_bindgen(catch)] 133 | pub fn sendNotification(notification: JsValue) -> Result<(), JsValue>; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/os.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/helpers/os-check.ts 2 | function isWindows() { 3 | return navigator.appVersion.includes("Win"); 4 | } 5 | 6 | // tauri/tooling/api/src/tauri.ts 7 | function uid() { 8 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 9 | } 10 | function transformCallback(callback, once = false) { 11 | const identifier = uid(); 12 | const prop = `_${identifier}`; 13 | Object.defineProperty(window, prop, { 14 | value: (result) => { 15 | if (once) { 16 | Reflect.deleteProperty(window, prop); 17 | } 18 | return callback?.(result); 19 | }, 20 | writable: false, 21 | configurable: true 22 | }); 23 | return identifier; 24 | } 25 | async function invoke(cmd, args = {}) { 26 | return new Promise((resolve, reject) => { 27 | const callback = transformCallback((e) => { 28 | resolve(e); 29 | Reflect.deleteProperty(window, `_${error}`); 30 | }, true); 31 | const error = transformCallback((e) => { 32 | reject(e); 33 | Reflect.deleteProperty(window, `_${callback}`); 34 | }, true); 35 | window.__TAURI_IPC__({ 36 | cmd, 37 | callback, 38 | error, 39 | ...args 40 | }); 41 | }); 42 | } 43 | 44 | // tauri/tooling/api/src/helpers/tauri.ts 45 | async function invokeTauriCommand(command) { 46 | return invoke("tauri", command); 47 | } 48 | 49 | // tauri/tooling/api/src/os.ts 50 | var EOL = isWindows() ? "\r\n" : "\n"; 51 | async function platform() { 52 | return invokeTauriCommand({ 53 | __tauriModule: "Os", 54 | message: { 55 | cmd: "platform" 56 | } 57 | }); 58 | } 59 | async function version() { 60 | return invokeTauriCommand({ 61 | __tauriModule: "Os", 62 | message: { 63 | cmd: "version" 64 | } 65 | }); 66 | } 67 | async function type() { 68 | return invokeTauriCommand({ 69 | __tauriModule: "Os", 70 | message: { 71 | cmd: "osType" 72 | } 73 | }); 74 | } 75 | async function arch() { 76 | return invokeTauriCommand({ 77 | __tauriModule: "Os", 78 | message: { 79 | cmd: "arch" 80 | } 81 | }); 82 | } 83 | async function tempdir() { 84 | return invokeTauriCommand({ 85 | __tauriModule: "Os", 86 | message: { 87 | cmd: "tempdir" 88 | } 89 | }); 90 | } 91 | async function locale() { 92 | return invokeTauriCommand({ 93 | __tauriModule: "Os", 94 | message: { 95 | cmd: "locale" 96 | } 97 | }); 98 | } 99 | export { 100 | EOL, 101 | arch, 102 | locale, 103 | platform, 104 | tempdir, 105 | type, 106 | version 107 | }; 108 | -------------------------------------------------------------------------------- /src/os.rs: -------------------------------------------------------------------------------- 1 | //! Provides operating system-related utility methods and properties. 2 | //! 3 | //! The APIs must be added to tauri.allowlist.os in tauri.conf.json: 4 | //! ```json 5 | //! { 6 | //! "tauri": { 7 | //! "allowlist": { 8 | //! "os": { 9 | //! "all": true, // enable all Os APIs 10 | //! } 11 | //! } 12 | //! } 13 | //! } 14 | //! ``` 15 | //! It is recommended to allowlist only the APIs you use for optimal bundle size and security. 16 | 17 | use serde::{Deserialize, Serialize}; 18 | use std::path::PathBuf; 19 | 20 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 21 | pub enum Arch { 22 | #[serde(rename = "x86")] 23 | X86, 24 | #[serde(rename = "x86_64")] 25 | X86_64, 26 | #[serde(rename = "arm")] 27 | Arm, 28 | #[serde(rename = "aarch64")] 29 | Aarch64, 30 | #[serde(rename = "mips")] 31 | Mips, 32 | #[serde(rename = "mips64")] 33 | Mips64, 34 | #[serde(rename = "powerpc")] 35 | Powerpc, 36 | #[serde(rename = "powerpc64")] 37 | Powerpc64, 38 | #[serde(rename = "riscv64")] 39 | Riscv64, 40 | #[serde(rename = "s390x")] 41 | S390x, 42 | #[serde(rename = "sparc64")] 43 | Sparc64, 44 | } 45 | 46 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 47 | pub enum Platform { 48 | #[serde(rename = "linux")] 49 | Linux, 50 | #[serde(rename = "darwin")] 51 | Darwin, 52 | #[serde(rename = "ios")] 53 | Ios, 54 | #[serde(rename = "freebsd")] 55 | Freebsd, 56 | #[serde(rename = "dragonfly")] 57 | Dragonfly, 58 | #[serde(rename = "netbsd")] 59 | Netbsd, 60 | #[serde(rename = "openbsd")] 61 | Openbsd, 62 | #[serde(rename = "solaris")] 63 | Solaris, 64 | #[serde(rename = "android")] 65 | Android, 66 | #[serde(rename = "win32")] 67 | Win32, 68 | } 69 | 70 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 71 | pub enum OsKind { 72 | #[serde(rename = "Linux")] 73 | Linux, 74 | #[serde(rename = "Darwin")] 75 | Darwin, 76 | #[serde(rename = "Windows_NT")] 77 | WindowsNT, 78 | } 79 | 80 | /// Returns the operating system CPU architecture for which the tauri app was compiled. 81 | #[inline(always)] 82 | pub async fn arch() -> crate::Result { 83 | let raw = inner::arch().await?; 84 | 85 | Ok(serde_wasm_bindgen::from_value(raw)?) 86 | } 87 | 88 | /// Returns a string identifying the operating system platform. The value is set at compile time. 89 | #[inline(always)] 90 | pub async fn platform() -> crate::Result { 91 | let raw = inner::platform().await?; 92 | 93 | Ok(serde_wasm_bindgen::from_value(raw)?) 94 | } 95 | 96 | /// Returns the operating system's default directory for temporary files. 97 | #[inline(always)] 98 | pub async fn tempdir() -> crate::Result { 99 | let raw = inner::tempdir().await?; 100 | 101 | Ok(serde_wasm_bindgen::from_value(raw)?) 102 | } 103 | 104 | /// Returns [`OsKind::Linux`] on Linux, [`OsKind::Darwin`] on macOS, and [`OsKind::WindowsNT`] on Windows. 105 | #[inline(always)] 106 | pub async fn kind() -> crate::Result { 107 | let raw = inner::kind().await?; 108 | 109 | Ok(serde_wasm_bindgen::from_value(raw)?) 110 | } 111 | 112 | /// Returns a string identifying the kernel version. 113 | #[inline(always)] 114 | pub async fn version() -> crate::Result { 115 | let raw = inner::version().await?; 116 | 117 | Ok(serde_wasm_bindgen::from_value(raw)?) 118 | } 119 | 120 | mod inner { 121 | use wasm_bindgen::prelude::*; 122 | 123 | #[wasm_bindgen(module = "/src/os.js")] 124 | extern "C" { 125 | #[wasm_bindgen(catch)] 126 | pub async fn arch() -> Result; 127 | #[wasm_bindgen(catch)] 128 | pub async fn platform() -> Result; 129 | #[wasm_bindgen(catch)] 130 | pub async fn tempdir() -> Result; 131 | #[wasm_bindgen(catch, js_name = "type")] 132 | pub async fn kind() -> Result; 133 | #[wasm_bindgen(catch)] 134 | pub async fn version() -> Result; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/path.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve2, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve2(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/fs.ts 45 | var BaseDirectory = /* @__PURE__ */ ((BaseDirectory2) => { 46 | BaseDirectory2[BaseDirectory2["Audio"] = 1] = "Audio"; 47 | BaseDirectory2[BaseDirectory2["Cache"] = 2] = "Cache"; 48 | BaseDirectory2[BaseDirectory2["Config"] = 3] = "Config"; 49 | BaseDirectory2[BaseDirectory2["Data"] = 4] = "Data"; 50 | BaseDirectory2[BaseDirectory2["LocalData"] = 5] = "LocalData"; 51 | BaseDirectory2[BaseDirectory2["Desktop"] = 6] = "Desktop"; 52 | BaseDirectory2[BaseDirectory2["Document"] = 7] = "Document"; 53 | BaseDirectory2[BaseDirectory2["Download"] = 8] = "Download"; 54 | BaseDirectory2[BaseDirectory2["Executable"] = 9] = "Executable"; 55 | BaseDirectory2[BaseDirectory2["Font"] = 10] = "Font"; 56 | BaseDirectory2[BaseDirectory2["Home"] = 11] = "Home"; 57 | BaseDirectory2[BaseDirectory2["Picture"] = 12] = "Picture"; 58 | BaseDirectory2[BaseDirectory2["Public"] = 13] = "Public"; 59 | BaseDirectory2[BaseDirectory2["Runtime"] = 14] = "Runtime"; 60 | BaseDirectory2[BaseDirectory2["Template"] = 15] = "Template"; 61 | BaseDirectory2[BaseDirectory2["Video"] = 16] = "Video"; 62 | BaseDirectory2[BaseDirectory2["Resource"] = 17] = "Resource"; 63 | BaseDirectory2[BaseDirectory2["App"] = 18] = "App"; 64 | BaseDirectory2[BaseDirectory2["Log"] = 19] = "Log"; 65 | BaseDirectory2[BaseDirectory2["Temp"] = 20] = "Temp"; 66 | BaseDirectory2[BaseDirectory2["AppConfig"] = 21] = "AppConfig"; 67 | BaseDirectory2[BaseDirectory2["AppData"] = 22] = "AppData"; 68 | BaseDirectory2[BaseDirectory2["AppLocalData"] = 23] = "AppLocalData"; 69 | BaseDirectory2[BaseDirectory2["AppCache"] = 24] = "AppCache"; 70 | BaseDirectory2[BaseDirectory2["AppLog"] = 25] = "AppLog"; 71 | return BaseDirectory2; 72 | })(BaseDirectory || {}); 73 | 74 | // tauri/tooling/api/src/helpers/os-check.ts 75 | function isWindows() { 76 | return navigator.appVersion.includes("Win"); 77 | } 78 | 79 | // tauri/tooling/api/src/path.ts 80 | async function appDir() { 81 | return appConfigDir(); 82 | } 83 | async function appConfigDir() { 84 | return invokeTauriCommand({ 85 | __tauriModule: "Path", 86 | message: { 87 | cmd: "resolvePath", 88 | path: "", 89 | directory: 21 /* AppConfig */ 90 | } 91 | }); 92 | } 93 | async function appDataDir() { 94 | return invokeTauriCommand({ 95 | __tauriModule: "Path", 96 | message: { 97 | cmd: "resolvePath", 98 | path: "", 99 | directory: 22 /* AppData */ 100 | } 101 | }); 102 | } 103 | async function appLocalDataDir() { 104 | return invokeTauriCommand({ 105 | __tauriModule: "Path", 106 | message: { 107 | cmd: "resolvePath", 108 | path: "", 109 | directory: 23 /* AppLocalData */ 110 | } 111 | }); 112 | } 113 | async function appCacheDir() { 114 | return invokeTauriCommand({ 115 | __tauriModule: "Path", 116 | message: { 117 | cmd: "resolvePath", 118 | path: "", 119 | directory: 24 /* AppCache */ 120 | } 121 | }); 122 | } 123 | async function audioDir() { 124 | return invokeTauriCommand({ 125 | __tauriModule: "Path", 126 | message: { 127 | cmd: "resolvePath", 128 | path: "", 129 | directory: 1 /* Audio */ 130 | } 131 | }); 132 | } 133 | async function cacheDir() { 134 | return invokeTauriCommand({ 135 | __tauriModule: "Path", 136 | message: { 137 | cmd: "resolvePath", 138 | path: "", 139 | directory: 2 /* Cache */ 140 | } 141 | }); 142 | } 143 | async function configDir() { 144 | return invokeTauriCommand({ 145 | __tauriModule: "Path", 146 | message: { 147 | cmd: "resolvePath", 148 | path: "", 149 | directory: 3 /* Config */ 150 | } 151 | }); 152 | } 153 | async function dataDir() { 154 | return invokeTauriCommand({ 155 | __tauriModule: "Path", 156 | message: { 157 | cmd: "resolvePath", 158 | path: "", 159 | directory: 4 /* Data */ 160 | } 161 | }); 162 | } 163 | async function desktopDir() { 164 | return invokeTauriCommand({ 165 | __tauriModule: "Path", 166 | message: { 167 | cmd: "resolvePath", 168 | path: "", 169 | directory: 6 /* Desktop */ 170 | } 171 | }); 172 | } 173 | async function documentDir() { 174 | return invokeTauriCommand({ 175 | __tauriModule: "Path", 176 | message: { 177 | cmd: "resolvePath", 178 | path: "", 179 | directory: 7 /* Document */ 180 | } 181 | }); 182 | } 183 | async function downloadDir() { 184 | return invokeTauriCommand({ 185 | __tauriModule: "Path", 186 | message: { 187 | cmd: "resolvePath", 188 | path: "", 189 | directory: 8 /* Download */ 190 | } 191 | }); 192 | } 193 | async function executableDir() { 194 | return invokeTauriCommand({ 195 | __tauriModule: "Path", 196 | message: { 197 | cmd: "resolvePath", 198 | path: "", 199 | directory: 9 /* Executable */ 200 | } 201 | }); 202 | } 203 | async function fontDir() { 204 | return invokeTauriCommand({ 205 | __tauriModule: "Path", 206 | message: { 207 | cmd: "resolvePath", 208 | path: "", 209 | directory: 10 /* Font */ 210 | } 211 | }); 212 | } 213 | async function homeDir() { 214 | return invokeTauriCommand({ 215 | __tauriModule: "Path", 216 | message: { 217 | cmd: "resolvePath", 218 | path: "", 219 | directory: 11 /* Home */ 220 | } 221 | }); 222 | } 223 | async function localDataDir() { 224 | return invokeTauriCommand({ 225 | __tauriModule: "Path", 226 | message: { 227 | cmd: "resolvePath", 228 | path: "", 229 | directory: 5 /* LocalData */ 230 | } 231 | }); 232 | } 233 | async function pictureDir() { 234 | return invokeTauriCommand({ 235 | __tauriModule: "Path", 236 | message: { 237 | cmd: "resolvePath", 238 | path: "", 239 | directory: 12 /* Picture */ 240 | } 241 | }); 242 | } 243 | async function publicDir() { 244 | return invokeTauriCommand({ 245 | __tauriModule: "Path", 246 | message: { 247 | cmd: "resolvePath", 248 | path: "", 249 | directory: 13 /* Public */ 250 | } 251 | }); 252 | } 253 | async function resourceDir() { 254 | return invokeTauriCommand({ 255 | __tauriModule: "Path", 256 | message: { 257 | cmd: "resolvePath", 258 | path: "", 259 | directory: 17 /* Resource */ 260 | } 261 | }); 262 | } 263 | async function resolveResource(resourcePath) { 264 | return invokeTauriCommand({ 265 | __tauriModule: "Path", 266 | message: { 267 | cmd: "resolvePath", 268 | path: resourcePath, 269 | directory: 17 /* Resource */ 270 | } 271 | }); 272 | } 273 | async function runtimeDir() { 274 | return invokeTauriCommand({ 275 | __tauriModule: "Path", 276 | message: { 277 | cmd: "resolvePath", 278 | path: "", 279 | directory: 14 /* Runtime */ 280 | } 281 | }); 282 | } 283 | async function templateDir() { 284 | return invokeTauriCommand({ 285 | __tauriModule: "Path", 286 | message: { 287 | cmd: "resolvePath", 288 | path: "", 289 | directory: 15 /* Template */ 290 | } 291 | }); 292 | } 293 | async function videoDir() { 294 | return invokeTauriCommand({ 295 | __tauriModule: "Path", 296 | message: { 297 | cmd: "resolvePath", 298 | path: "", 299 | directory: 16 /* Video */ 300 | } 301 | }); 302 | } 303 | async function logDir() { 304 | return appLogDir(); 305 | } 306 | async function appLogDir() { 307 | return invokeTauriCommand({ 308 | __tauriModule: "Path", 309 | message: { 310 | cmd: "resolvePath", 311 | path: "", 312 | directory: 25 /* AppLog */ 313 | } 314 | }); 315 | } 316 | var sep = isWindows() ? "\\" : "/"; 317 | var delimiter = isWindows() ? ";" : ":"; 318 | async function resolve(...paths) { 319 | return invokeTauriCommand({ 320 | __tauriModule: "Path", 321 | message: { 322 | cmd: "resolve", 323 | paths 324 | } 325 | }); 326 | } 327 | async function normalize(path) { 328 | return invokeTauriCommand({ 329 | __tauriModule: "Path", 330 | message: { 331 | cmd: "normalize", 332 | path 333 | } 334 | }); 335 | } 336 | async function join(...paths) { 337 | return invokeTauriCommand({ 338 | __tauriModule: "Path", 339 | message: { 340 | cmd: "join", 341 | paths 342 | } 343 | }); 344 | } 345 | async function dirname(path) { 346 | return invokeTauriCommand({ 347 | __tauriModule: "Path", 348 | message: { 349 | cmd: "dirname", 350 | path 351 | } 352 | }); 353 | } 354 | async function extname(path) { 355 | return invokeTauriCommand({ 356 | __tauriModule: "Path", 357 | message: { 358 | cmd: "extname", 359 | path 360 | } 361 | }); 362 | } 363 | async function basename(path, ext) { 364 | return invokeTauriCommand({ 365 | __tauriModule: "Path", 366 | message: { 367 | cmd: "basename", 368 | path, 369 | ext 370 | } 371 | }); 372 | } 373 | async function isAbsolute(path) { 374 | return invokeTauriCommand({ 375 | __tauriModule: "Path", 376 | message: { 377 | cmd: "isAbsolute", 378 | path 379 | } 380 | }); 381 | } 382 | export { 383 | BaseDirectory, 384 | appCacheDir, 385 | appConfigDir, 386 | appDataDir, 387 | appDir, 388 | appLocalDataDir, 389 | appLogDir, 390 | audioDir, 391 | basename, 392 | cacheDir, 393 | configDir, 394 | dataDir, 395 | delimiter, 396 | desktopDir, 397 | dirname, 398 | documentDir, 399 | downloadDir, 400 | executableDir, 401 | extname, 402 | fontDir, 403 | homeDir, 404 | isAbsolute, 405 | join, 406 | localDataDir, 407 | logDir, 408 | normalize, 409 | pictureDir, 410 | publicDir, 411 | resolve, 412 | resolveResource, 413 | resourceDir, 414 | runtimeDir, 415 | sep, 416 | templateDir, 417 | videoDir 418 | }; 419 | -------------------------------------------------------------------------------- /src/process.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/process.ts 45 | async function exit(exitCode = 0) { 46 | return invokeTauriCommand({ 47 | __tauriModule: "Process", 48 | message: { 49 | cmd: "exit", 50 | exitCode 51 | } 52 | }); 53 | } 54 | async function relaunch() { 55 | return invokeTauriCommand({ 56 | __tauriModule: "Process", 57 | message: { 58 | cmd: "relaunch" 59 | } 60 | }); 61 | } 62 | export { 63 | exit, 64 | relaunch 65 | }; 66 | -------------------------------------------------------------------------------- /src/process.rs: -------------------------------------------------------------------------------- 1 | //! Perform operations on the current process. 2 | 3 | /// Exits immediately with the given `exit_code`. 4 | #[inline(always)] 5 | pub async fn exit(exit_code: i32) -> ! { 6 | inner::exit(exit_code).await; 7 | unreachable!() 8 | } 9 | 10 | /// Exits the current instance of the app then relaunches it. 11 | #[inline(always)] 12 | pub fn relaunch() { 13 | inner::relaunch(); 14 | } 15 | 16 | mod inner { 17 | use wasm_bindgen::prelude::*; 18 | 19 | #[wasm_bindgen(module = "/src/process.js")] 20 | extern "C" { 21 | pub async fn exit(exitCode: i32); 22 | pub fn relaunch(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/shell.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/shell.ts 45 | async function execute(onEvent, program, args = [], options) { 46 | if (typeof args === "object") { 47 | Object.freeze(args); 48 | } 49 | return invokeTauriCommand({ 50 | __tauriModule: "Shell", 51 | message: { 52 | cmd: "execute", 53 | program, 54 | args, 55 | options, 56 | onEventFn: transformCallback(onEvent) 57 | } 58 | }); 59 | } 60 | var EventEmitter = class { 61 | constructor() { 62 | this.eventListeners = /* @__PURE__ */ Object.create(null); 63 | } 64 | addListener(eventName, listener) { 65 | return this.on(eventName, listener); 66 | } 67 | removeListener(eventName, listener) { 68 | return this.off(eventName, listener); 69 | } 70 | on(eventName, listener) { 71 | if (eventName in this.eventListeners) { 72 | this.eventListeners[eventName].push(listener); 73 | } else { 74 | this.eventListeners[eventName] = [listener]; 75 | } 76 | return this; 77 | } 78 | once(eventName, listener) { 79 | const wrapper = (...args) => { 80 | this.removeListener(eventName, wrapper); 81 | listener(...args); 82 | }; 83 | return this.addListener(eventName, wrapper); 84 | } 85 | off(eventName, listener) { 86 | if (eventName in this.eventListeners) { 87 | this.eventListeners[eventName] = this.eventListeners[eventName].filter( 88 | (l) => l !== listener 89 | ); 90 | } 91 | return this; 92 | } 93 | removeAllListeners(event) { 94 | if (event) { 95 | delete this.eventListeners[event]; 96 | } else { 97 | this.eventListeners = /* @__PURE__ */ Object.create(null); 98 | } 99 | return this; 100 | } 101 | emit(eventName, ...args) { 102 | if (eventName in this.eventListeners) { 103 | const listeners = this.eventListeners[eventName]; 104 | for (const listener of listeners) 105 | listener(...args); 106 | return true; 107 | } 108 | return false; 109 | } 110 | listenerCount(eventName) { 111 | if (eventName in this.eventListeners) 112 | return this.eventListeners[eventName].length; 113 | return 0; 114 | } 115 | prependListener(eventName, listener) { 116 | if (eventName in this.eventListeners) { 117 | this.eventListeners[eventName].unshift(listener); 118 | } else { 119 | this.eventListeners[eventName] = [listener]; 120 | } 121 | return this; 122 | } 123 | prependOnceListener(eventName, listener) { 124 | const wrapper = (...args) => { 125 | this.removeListener(eventName, wrapper); 126 | listener(...args); 127 | }; 128 | return this.prependListener(eventName, wrapper); 129 | } 130 | }; 131 | var Child = class { 132 | constructor(pid) { 133 | this.pid = pid; 134 | } 135 | async write(data) { 136 | return invokeTauriCommand({ 137 | __tauriModule: "Shell", 138 | message: { 139 | cmd: "stdinWrite", 140 | pid: this.pid, 141 | buffer: typeof data === "string" ? data : Array.from(data) 142 | } 143 | }); 144 | } 145 | async kill() { 146 | return invokeTauriCommand({ 147 | __tauriModule: "Shell", 148 | message: { 149 | cmd: "killChild", 150 | pid: this.pid 151 | } 152 | }); 153 | } 154 | }; 155 | var Command = class extends EventEmitter { 156 | constructor(program, args = [], options) { 157 | super(); 158 | this.stdout = new EventEmitter(); 159 | this.stderr = new EventEmitter(); 160 | this.program = program; 161 | this.args = typeof args === "string" ? [args] : args; 162 | this.options = options ?? {}; 163 | } 164 | static sidecar(program, args = [], options) { 165 | const instance = new Command(program, args, options); 166 | instance.options.sidecar = true; 167 | return instance; 168 | } 169 | async spawn() { 170 | return execute( 171 | (event) => { 172 | switch (event.event) { 173 | case "Error": 174 | this.emit("error", event.payload); 175 | break; 176 | case "Terminated": 177 | this.emit("close", event.payload); 178 | break; 179 | case "Stdout": 180 | this.stdout.emit("data", event.payload); 181 | break; 182 | case "Stderr": 183 | this.stderr.emit("data", event.payload); 184 | break; 185 | } 186 | }, 187 | this.program, 188 | this.args, 189 | this.options 190 | ).then((pid) => new Child(pid)); 191 | } 192 | async execute() { 193 | return new Promise((resolve, reject) => { 194 | this.on("error", reject); 195 | const stdout = []; 196 | const stderr = []; 197 | this.stdout.on("data", (line) => { 198 | stdout.push(line); 199 | }); 200 | this.stderr.on("data", (line) => { 201 | stderr.push(line); 202 | }); 203 | this.on("close", (payload) => { 204 | resolve({ 205 | code: payload.code, 206 | signal: payload.signal, 207 | stdout: stdout.join("\n"), 208 | stderr: stderr.join("\n") 209 | }); 210 | }); 211 | this.spawn().catch(reject); 212 | }); 213 | } 214 | }; 215 | async function open(path, openWith) { 216 | return invokeTauriCommand({ 217 | __tauriModule: "Shell", 218 | message: { 219 | cmd: "open", 220 | path, 221 | with: openWith 222 | } 223 | }); 224 | } 225 | export { 226 | Child, 227 | Command, 228 | EventEmitter, 229 | open 230 | }; 231 | -------------------------------------------------------------------------------- /src/tauri.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | function convertFileSrc(filePath, protocol = "asset") { 39 | return window.__TAURI__.convertFileSrc(filePath, protocol); 40 | } 41 | export { 42 | convertFileSrc, 43 | invoke, 44 | transformCallback 45 | }; 46 | -------------------------------------------------------------------------------- /src/tauri.rs: -------------------------------------------------------------------------------- 1 | //! Invoke your custom commands. 2 | 3 | use serde::{de::DeserializeOwned, Serialize}; 4 | use url::Url; 5 | 6 | /// Convert a device file path to an URL that can be loaded by the webview. 7 | /// 8 | /// Note that `asset:` and `https://asset.localhost` must be added to [`tauri.security.csp`](https://tauri.app/v1/api/config/#securityconfig.csp) in `tauri.conf.json`. 9 | /// Example CSP value: `"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost"` to use the asset protocol on image sources. 10 | /// 11 | /// Additionally, `asset` must be added to [`tauri.allowlist.protocol`](https://tauri.app/v1/api/config/#allowlistconfig.protocol) 12 | /// in `tauri.conf.json` and its access scope must be defined on the `assetScope` array on the same `protocol` object. 13 | /// 14 | /// @param filePath The file path. 15 | /// @param protocol The protocol to use. Defaults to `asset`. You only need to set this when using a custom protocol. 16 | /// 17 | /// # Example 18 | /// 19 | /// ```rust,no_run 20 | /// use tauri_api::path::{app_data_dir, join}; 21 | /// use tauri_api::tauri::convert_file_src; 22 | /// 23 | /// const app_data_dir_path = app_data_dir().await; 24 | /// const file_path = join(app_data_dir_path, "assets/video.mp4").await; 25 | /// const asset_url = convert_file_src(file_path); 26 | /// 27 | /// let window = web_sys::window().expect("no global `window` exists"); 28 | /// let document = window.document().expect("should have a document on window"); 29 | /// 30 | /// // Manufacture the element we're gonna append 31 | /// let video = document.get_element_by_id("my-video")?; 32 | /// let source = document.create_element("source")?; 33 | /// 34 | /// source.set_attribute("type", "video/mp4")?; 35 | /// source.set_attribute("src", asset_url.as_str())?; 36 | /// 37 | /// video.append_child(&val)?; 38 | /// ``` 39 | /// 40 | /// @return the URL that can be used as source on the webview. 41 | #[inline(always)] 42 | pub async fn convert_file_src(file_path: &str, protocol: Option<&str>) -> crate::Result { 43 | let js_val = inner::convertFileSrc(file_path, protocol).await?; 44 | 45 | Ok(serde_wasm_bindgen::from_value(js_val)?) 46 | } 47 | 48 | /// Sends a message to the backend. 49 | /// 50 | /// # Example 51 | /// 52 | /// ```rust,no_run 53 | /// use tauri_api::tauri::invoke; 54 | /// 55 | /// struct User<'a> { 56 | /// user: &'a str, 57 | /// password: &'a str 58 | /// } 59 | /// 60 | /// invoke("login", &User { user: "tauri", password: "poiwe3h4r5ip3yrhtew9ty" }).await; 61 | /// ``` 62 | /// 63 | /// @param cmd The command name. 64 | /// @param args The optional arguments to pass to the command. 65 | /// @return A promise resolving or rejecting to the backend response. 66 | #[inline(always)] 67 | pub async fn invoke(cmd: &str, args: &A) -> crate::Result { 68 | let raw = inner::invoke(cmd, serde_wasm_bindgen::to_value(args)?).await?; 69 | 70 | serde_wasm_bindgen::from_value(raw).map_err(Into::into) 71 | } 72 | 73 | /// Transforms a callback function to a string identifier that can be passed to the backend. 74 | /// 75 | /// The backend uses the identifier to `eval()` the callback. 76 | /// 77 | /// @return A unique identifier associated with the callback function. 78 | #[inline(always)] 79 | pub async fn transform_callback( 80 | callback: &dyn Fn(T), 81 | once: bool, 82 | ) -> crate::Result { 83 | let js_val = inner::transformCallback( 84 | &|raw| callback(serde_wasm_bindgen::from_value(raw).unwrap()), 85 | once, 86 | ) 87 | .await?; 88 | 89 | Ok(serde_wasm_bindgen::from_value(js_val)?) 90 | } 91 | 92 | mod inner { 93 | use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; 94 | 95 | #[wasm_bindgen(module = "/src/tauri.js")] 96 | extern "C" { 97 | #[wasm_bindgen(catch)] 98 | pub async fn convertFileSrc( 99 | filePath: &str, 100 | protocol: Option<&str>, 101 | ) -> Result; 102 | #[wasm_bindgen(catch)] 103 | pub async fn invoke(cmd: &str, args: JsValue) -> Result; 104 | #[wasm_bindgen(catch)] 105 | pub async fn transformCallback( 106 | callback: &dyn Fn(JsValue), 107 | once: bool, 108 | ) -> Result; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/updater.js: -------------------------------------------------------------------------------- 1 | // tauri/tooling/api/src/tauri.ts 2 | function uid() { 3 | return window.crypto.getRandomValues(new Uint32Array(1))[0]; 4 | } 5 | function transformCallback(callback, once3 = false) { 6 | const identifier = uid(); 7 | const prop = `_${identifier}`; 8 | Object.defineProperty(window, prop, { 9 | value: (result) => { 10 | if (once3) { 11 | Reflect.deleteProperty(window, prop); 12 | } 13 | return callback?.(result); 14 | }, 15 | writable: false, 16 | configurable: true 17 | }); 18 | return identifier; 19 | } 20 | async function invoke(cmd, args = {}) { 21 | return new Promise((resolve, reject) => { 22 | const callback = transformCallback((e) => { 23 | resolve(e); 24 | Reflect.deleteProperty(window, `_${error}`); 25 | }, true); 26 | const error = transformCallback((e) => { 27 | reject(e); 28 | Reflect.deleteProperty(window, `_${callback}`); 29 | }, true); 30 | window.__TAURI_IPC__({ 31 | cmd, 32 | callback, 33 | error, 34 | ...args 35 | }); 36 | }); 37 | } 38 | 39 | // tauri/tooling/api/src/helpers/tauri.ts 40 | async function invokeTauriCommand(command) { 41 | return invoke("tauri", command); 42 | } 43 | 44 | // tauri/tooling/api/src/helpers/event.ts 45 | async function _unlisten(event, eventId) { 46 | return invokeTauriCommand({ 47 | __tauriModule: "Event", 48 | message: { 49 | cmd: "unlisten", 50 | event, 51 | eventId 52 | } 53 | }); 54 | } 55 | async function emit(event, windowLabel, payload) { 56 | await invokeTauriCommand({ 57 | __tauriModule: "Event", 58 | message: { 59 | cmd: "emit", 60 | event, 61 | windowLabel, 62 | payload 63 | } 64 | }); 65 | } 66 | async function listen(event, windowLabel, handler) { 67 | return invokeTauriCommand({ 68 | __tauriModule: "Event", 69 | message: { 70 | cmd: "listen", 71 | event, 72 | windowLabel, 73 | handler: transformCallback(handler) 74 | } 75 | }).then((eventId) => { 76 | return async () => _unlisten(event, eventId); 77 | }); 78 | } 79 | async function once(event, windowLabel, handler) { 80 | return listen(event, windowLabel, (eventData) => { 81 | handler(eventData); 82 | _unlisten(event, eventData.id).catch(() => { 83 | }); 84 | }); 85 | } 86 | 87 | // tauri/tooling/api/src/event.ts 88 | async function listen2(event, handler) { 89 | return listen(event, null, handler); 90 | } 91 | async function once2(event, handler) { 92 | return once(event, null, handler); 93 | } 94 | async function emit2(event, payload) { 95 | return emit(event, void 0, payload); 96 | } 97 | 98 | // tauri/tooling/api/src/updater.ts 99 | async function onUpdaterEvent(handler) { 100 | return listen2("tauri://update-status" /* STATUS_UPDATE */, (data) => { 101 | handler(data?.payload); 102 | }); 103 | } 104 | async function installUpdate() { 105 | let unlistenerFn; 106 | function cleanListener() { 107 | if (unlistenerFn) { 108 | unlistenerFn(); 109 | } 110 | unlistenerFn = void 0; 111 | } 112 | return new Promise((resolve, reject) => { 113 | function onStatusChange(statusResult) { 114 | if (statusResult.error) { 115 | cleanListener(); 116 | reject(statusResult.error); 117 | return; 118 | } 119 | if (statusResult.status === "DONE") { 120 | cleanListener(); 121 | resolve(); 122 | } 123 | } 124 | onUpdaterEvent(onStatusChange).then((fn) => { 125 | unlistenerFn = fn; 126 | }).catch((e) => { 127 | cleanListener(); 128 | throw e; 129 | }); 130 | emit2("tauri://update-install" /* INSTALL_UPDATE */).catch((e) => { 131 | cleanListener(); 132 | throw e; 133 | }); 134 | }); 135 | } 136 | async function checkUpdate() { 137 | let unlistenerFn; 138 | function cleanListener() { 139 | if (unlistenerFn) { 140 | unlistenerFn(); 141 | } 142 | unlistenerFn = void 0; 143 | } 144 | return new Promise((resolve, reject) => { 145 | function onUpdateAvailable(manifest) { 146 | cleanListener(); 147 | resolve({ 148 | manifest, 149 | shouldUpdate: true 150 | }); 151 | } 152 | function onStatusChange(statusResult) { 153 | if (statusResult.error) { 154 | cleanListener(); 155 | reject(statusResult.error); 156 | return; 157 | } 158 | if (statusResult.status === "UPTODATE") { 159 | cleanListener(); 160 | resolve({ 161 | shouldUpdate: false 162 | }); 163 | } 164 | } 165 | once2("tauri://update-available" /* UPDATE_AVAILABLE */, (data) => { 166 | onUpdateAvailable(data?.payload); 167 | }).catch((e) => { 168 | cleanListener(); 169 | throw e; 170 | }); 171 | onUpdaterEvent(onStatusChange).then((fn) => { 172 | unlistenerFn = fn; 173 | }).catch((e) => { 174 | cleanListener(); 175 | throw e; 176 | }); 177 | emit2("tauri://update" /* CHECK_UPDATE */).catch((e) => { 178 | cleanListener(); 179 | throw e; 180 | }); 181 | }); 182 | } 183 | export { 184 | checkUpdate, 185 | installUpdate, 186 | onUpdaterEvent 187 | }; 188 | -------------------------------------------------------------------------------- /src/updater.rs: -------------------------------------------------------------------------------- 1 | //! Customize the auto updater flow. 2 | 3 | use futures::{Stream, channel::mpsc}; 4 | use serde::Deserialize; 5 | use wasm_bindgen::{prelude::Closure, JsValue}; 6 | use crate::event::Listen; 7 | 8 | #[derive(Deserialize, Debug, Clone)] 9 | pub struct UpdateManifest { 10 | pub body: String, 11 | pub date: String, 12 | pub version: String, 13 | } 14 | 15 | #[derive(Deserialize, Debug, Clone)] 16 | #[serde(rename_all = "camelCase")] 17 | pub struct UpdateResult { 18 | pub manifest: Option, 19 | pub should_update: bool, 20 | } 21 | 22 | #[derive(Deserialize)] 23 | struct UpdateStatusResult { 24 | error: Option, 25 | status: UpdateStatus, 26 | } 27 | 28 | #[derive(Deserialize)] 29 | pub enum UpdateStatus { 30 | #[serde(rename = "PENDING")] 31 | Pending, 32 | #[serde(rename = "DONE")] 33 | Done, 34 | #[serde(rename = "UPTODATE")] 35 | UpToDate, 36 | } 37 | 38 | /// Checks if an update is available. 39 | /// 40 | /// # Example 41 | /// 42 | /// ```rust,no_run 43 | /// use tauri_sys::updater::check_update; 44 | /// 45 | /// # async fn main() -> Result<(), Box> { 46 | /// let update = check_update().await?; 47 | /// // now run installUpdate() if needed 48 | /// # Ok(()) 49 | /// # } 50 | /// ``` 51 | #[inline(always)] 52 | pub async fn check_update() -> crate::Result { 53 | let raw = inner::checkUpdate().await?; 54 | 55 | Ok(serde_wasm_bindgen::from_value(raw)?) 56 | } 57 | 58 | /// Install the update if there's one available. 59 | /// 60 | /// # Example 61 | /// 62 | /// ```rust,no_run 63 | /// use tauri_sys::updater::{check_update, install_update}; 64 | /// 65 | /// # async fn main() -> Result<(), Box> { 66 | /// let update = check_update().await?; 67 | /// 68 | /// if update.should_update { 69 | /// log::info("Installing update {:?}", update.manifest); 70 | /// install_update().await?; 71 | /// } 72 | /// # Ok(()) 73 | /// # } 74 | /// ``` 75 | #[inline(always)] 76 | pub async fn install_update() -> crate::Result<()> { 77 | inner::installUpdate().await?; 78 | Ok(()) 79 | } 80 | 81 | /// Listen to an updater event. 82 | /// 83 | /// The returned Future will automatically clean up it's underlying event listener when dropped, so no manual unlisten function needs to be called. 84 | /// See [Differences to the JavaScript API](../index.html#differences-to-the-javascript-api) for details. 85 | /// 86 | /// # Example 87 | /// 88 | /// ```rust,no_run 89 | /// use tauri_sys::updater::updater_events; 90 | /// use web_sys::console; 91 | /// 92 | /// # async fn main() -> Result<(), Box> { 93 | /// let events = updater_events(); 94 | /// 95 | /// while let Some(event) = events.next().await { 96 | /// console::log_1(&format!("Updater event {:?}", event).into()); 97 | /// } 98 | /// # Ok(()) 99 | /// # } 100 | /// ``` 101 | #[inline(always)] 102 | pub async fn updater_events() -> crate::Result>> { 103 | let (tx, rx) = mpsc::unbounded::>(); 104 | 105 | let closure = Closure::::new(move |raw| { 106 | let raw: UpdateStatusResult = serde_wasm_bindgen::from_value(raw).unwrap(); 107 | 108 | let msg = if let Some(error) = raw.error { 109 | Err(error) 110 | } else { 111 | Ok(raw.status) 112 | }; 113 | 114 | let _ = tx.unbounded_send(msg); 115 | }); 116 | let unlisten = inner::onUpdaterEvent(&closure).await?; 117 | closure.forget(); 118 | 119 | Ok(Listen { 120 | rx, 121 | unlisten: js_sys::Function::from(unlisten), 122 | }) 123 | } 124 | 125 | mod inner { 126 | use wasm_bindgen::prelude::*; 127 | 128 | #[wasm_bindgen(module = "/src/updater.js")] 129 | extern "C" { 130 | #[wasm_bindgen(catch)] 131 | pub async fn checkUpdate() -> Result; 132 | #[wasm_bindgen(catch)] 133 | pub async fn installUpdate() -> Result; 134 | #[wasm_bindgen(catch)] 135 | pub async fn onUpdaterEvent( 136 | handler: &Closure, 137 | ) -> Result; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/web.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde::Serialize; 3 | use tauri_sys::{mocks::mock_ipc, tauri}; 4 | use wasm_bindgen::JsError; 5 | use wasm_bindgen_test::wasm_bindgen_test; 6 | use wasm_bindgen_test::wasm_bindgen_test_configure; 7 | 8 | macro_rules! bail { 9 | ($e:expr) => { 10 | return Err(JsError::new($e)); 11 | }; 12 | ($fmt:expr, $($arg:tt)*) => { 13 | return Err(JsError::new(&format!($fmt, $($arg)*))); 14 | }; 15 | } 16 | 17 | macro_rules! ensure { 18 | ($cond:expr) => { 19 | if !($cond) { 20 | bail!("{}", _failure__stringify!($cond)); 21 | } 22 | }; 23 | ($cond:expr, $e:expr) => { 24 | if !($cond) { 25 | bail!($e); 26 | } 27 | }; 28 | ($cond:expr, $fmt:expr, $($arg:tt)*) => { 29 | if !($cond) { 30 | bail!($fmt, $($arg)*); 31 | } 32 | }; 33 | } 34 | 35 | macro_rules! _failure__stringify { 36 | ($($inner:tt)*) => { 37 | stringify! { $($inner)* } 38 | } 39 | } 40 | 41 | #[derive(Deserialize)] 42 | struct ApiRequestInner { 43 | cmd: String, 44 | } 45 | 46 | #[derive(Deserialize)] 47 | struct ApiRequest { 48 | #[serde(rename = "__tauriModule")] 49 | __tauri_module: String, 50 | message: ApiRequestInner, 51 | } 52 | 53 | fn main() { 54 | wasm_bindgen_test_configure!(run_in_browser); 55 | } 56 | 57 | /** 58 | * App module 59 | */ 60 | 61 | #[wasm_bindgen_test] 62 | async fn test_get_version() { 63 | use tauri_sys::app::get_version; 64 | 65 | mock_ipc(|cmd, payload| { 66 | ensure!(cmd.as_str() == "tauri", "unknown command"); 67 | 68 | let payload: ApiRequest = serde_wasm_bindgen::from_value(payload).unwrap(); 69 | 70 | ensure!(payload.__tauri_module == "App"); 71 | ensure!(payload.message.cmd == "getAppVersion"); 72 | 73 | Ok("1.0.0") 74 | }); 75 | 76 | let version = get_version().await.unwrap(); 77 | 78 | assert_eq!(version.major, 1); 79 | assert_eq!(version.minor, 0); 80 | assert_eq!(version.patch, 0) 81 | } 82 | 83 | /** 84 | * Tauri module 85 | */ 86 | 87 | #[wasm_bindgen_test] 88 | async fn test_invoke() -> Result<(), Box> { 89 | #[derive(Serialize, Deserialize)] 90 | struct AddPayload { 91 | a: u32, 92 | b: u32, 93 | } 94 | 95 | mock_ipc(|cmd, payload| match cmd.as_str() { 96 | "add" => { 97 | let args: AddPayload = serde_wasm_bindgen::from_value(payload).unwrap(); 98 | 99 | Ok(args.a + args.b) 100 | } 101 | _ => Err(JsError::new("Unknown command")), 102 | }); 103 | 104 | let out = tauri::invoke::<_, u32>("add", &AddPayload { a: 12, b: 15 }).await?; 105 | 106 | assert_eq!(out, 27); 107 | 108 | Ok(()) 109 | } 110 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = ["tauri", "target"] --------------------------------------------------------------------------------