├── .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 |
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"]
--------------------------------------------------------------------------------