Compare commits

...

154 Commits

Author SHA1 Message Date
Lucas Nogueira 57f6e45b27 Merge branch 'v2' into feat/setup-single-instance-manually 2026-02-05 10:29:41 -03:00
Lucas Nogueira 35aa52f45e fix(single-instance): dbus_path should start with /, can't include - 2026-02-05 10:28:58 -03:00
Lucas Nogueira b65a193e6d feat(single-instance): add setup() function to run flow separately 2026-02-05 10:28:15 -03:00
renovate[bot] bbc177150f chore(deps): update dependency prettier to v3.8.1 (#3235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 23:37:55 +08:00
renovate[bot] e6e2edca11 chore(deps): update dependency typescript-eslint to v8.54.0 (#3232)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 23:08:54 +08:00
renovate[bot] 7ecd19da51 chore(deps): update dependency rollup to v4.57.1 (#3230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 23:08:24 +08:00
dependabot[bot] a0b6c8ff3b chore(deps): bump bytes (#3248)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.7.1 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.7.1...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 23:07:49 +08:00
renovate[bot] c12ea9306a chore(deps): update rust crate bytes to v1.11.1 [security] (#3247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-03 23:53:44 +01:00
Demir Yerli 98e2c11eef fix(single-instance): unconventional dbus names (fixes #3184) (#3194)
Co-authored-by: FabianLars <github@fabianlars.de>
Co-authored-by: Amr Bashir <github@amrbashir.me>
2026-02-03 23:36:48 +01:00
github-actions[bot] 50b159f668 Publish New Versions (v2) (#3211)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-03 12:18:37 +01:00
Fabian-Lars 305c4f6b4e ci: add workflow_dipatch trigger to integration tests 2026-02-03 10:55:10 +01:00
Lucas Fernandes Nogueira 89f3e17952 chore: bump tauri to 2.10 (#3246)
* chore: bump tauri to 2.10

* pnpm i

* audit fix
2026-02-02 21:48:08 -03:00
Ale Sanchez d4613ff002 fix(shell): Make sidecars work in tests (fix #13767) (#3234) 2026-01-26 17:12:42 +01:00
Tony 69a9d5771c chore(updater): use keep from tempfile 3.20 (#3229) 2026-01-22 11:40:43 +08:00
Del 61e9b0ab64 fix(http): ensure body resources are released on stream cancellation (#3228)
Co-authored-by: Amr Bashir <github@amrbashir.me>
2026-01-22 05:20:29 +02:00
dependabot[bot] 25ad21beff chore(deps): bump diff from 5.2.0 to 5.2.2 (#3226)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: FabianLars <github@fabianlars.de>
2026-01-20 20:30:35 +01:00
Madhusudhan 2dc3f3f039 fix(sql): uuid columns returning null in postgres query results (#2039) (#3144)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2026-01-19 17:30:28 +01:00
Lucas Fernandes Nogueira c27af9128d fix(barcode-scanner): check if iOS platform supports scanning (#3222) 2026-01-19 13:16:31 -03:00
Lucas Fernandes Nogueira b60dd88702 fix(barcode-scanner): start capture session on a separate thread (#3223)
fixes the given warning:

Thread Performance Checker: -[AVCaptureSession startRunning] should be called from background thread. Calling it on the main thread can lead to UI unresponsiveness

which might cause a UI freeze
2026-01-19 13:16:16 -03:00
renovate[bot] a97033bcd9 chore(deps): update dependency rollup to v4.55.1 (#3201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 16:44:07 +01:00
miaoshengkun 4375c98bed feat(updater): add no_proxy config to disable system proxy (#3073)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2026-01-19 16:17:37 +01:00
Fabian-Lars 82fbb0c790 fix(barcode-scanner): early return scanner if missing permissions (#3221) 2026-01-19 10:21:33 -03:00
Tony de6bf68585 docs(store): fix outdated fn link (#3220) 2026-01-19 12:21:55 +01:00
renovate[bot] 734c627084 chore(deps): update dependency prettier to v3.8.0 (#3217) 2026-01-18 10:54:16 +08:00
Tony 05c5da072b chore(deps): update reqwest to 0.13 for updater (#3213) 2026-01-16 18:37:20 +08:00
renovate[bot] 0d126ff0ad chore(deps): update dependency typescript-eslint to v8.53.0 (v2) (#3189)
* chore(deps): update dependency typescript-eslint to v8.53.0

* Update api-iife.js
2026-01-16 18:36:41 +08:00
FabianLars f122ee98c6 Revert "temp: delete updater changefiles"
This reverts commit 82c404635b.
2026-01-14 20:48:59 +01:00
github-actions[bot] 2308f2299d publish new versions (#3203)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-14 20:48:22 +01:00
FabianLars 82c404635b temp: delete updater changefiles 2026-01-14 20:30:54 +01:00
Fabian-Lars b1dbee2c55 fix(http): manually set origin header to tauri://localhost (#3210) 2026-01-14 20:26:17 +01:00
Andrew de Waal d7a0bb325d feat(dialog) - Support fileAccessMode for open dialog (#3030) (#3136)
* feat(dialog) - Support fileAccessMode for open dialog (#3030)

On iOS, when trying to access a file that exists outside of the app sandbox, one of 2 things need to happen to be able to perform any operations on said file:

* A copy of the file needs to be made to the internal app sandbox
* The method startAccessingSecurityScopedResource needs to be called.

Previously, a copy of the file was always being made when a file was selected through the picker dialog.

While this did ensure there were no file access exceptions when reading from the file, it does not scale well for large files.

To resolve this, we now support `fileAccessMode`, which allows a file handle to be returned without copying the file to the app sandbox.

This MR only supports this change for iOS; MacOS has a different set of needs for security scoped resources.

See discussion in #3716 for more discussion of the difference between iOS and MacOS.
See MR #3185 to see how these scoped files will be accessible using security scoping.

* fmt, clippy

* use enum

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
2026-01-14 08:35:06 -03:00
FabianLars f3d75f7abb Revert "temp: remove updater changefiles"
This reverts commit 9a2c98f450.
2026-01-08 15:54:33 +01:00
github-actions[bot] 6e2e7e48c3 publish new versions (#3070)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-08 15:34:17 +01:00
FabianLars 9a2c98f450 temp: remove updater changefiles 2026-01-08 15:17:30 +01:00
FabianLars 4a2ecb6287 chore(deps): update rkyv, closes #3196 2026-01-06 11:05:04 +01:00
Tunglies 31415effdf docs(shell): update example to include Encoding usage in Command::spawn (#3183)
* docs(shell): update example to include Encoding usage in `Command::spawn`

* docs(shell): update patch level to minor for Encoding usage in `Command::spawn` documentation

* Update .changes/doc-to-Encoding-usage-in-shell.md

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* docs(shell): update examples to demonstrate manual Encoding usage in command output

* Update plugins/shell/src/process/mod.rs

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* docs(shell): update example to use 'windows-1252' encoding in command output

* Update plugins/shell/src/process/mod.rs

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* docs(shell): update example command in documentation to use 'some-program' with 'some-arg'

* Bump shell-js in change file

* Fix indent
2025-12-29 10:58:55 +08:00
renovate[bot] 04b33ea0b0 chore(deps): update dependency typescript-eslint to v8.50.1 (#3181)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-27 22:30:26 +08:00
renovate[bot] 54e21f142b chore(deps): update dependency rollup to v4.54.0 (#3179)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-23 22:50:36 +08:00
renovate[bot] d528c88b4f chore(deps): update dependency rollup to v4.53.5 (#3172)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 19:16:32 +08:00
renovate[bot] 69146fa852 chore(deps): update dependency rollup to v4.53.4 (#3167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 11:20:50 +08:00
renovate[bot] 9f68f2d827 chore(deps): update dependency typescript-eslint to v8.50.0 (#3170)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 10:58:03 +08:00
Fabian-Lars 3d0d2e041b fix(opener): ignore inAppBrowser on desktop (#3163) 2025-12-16 07:07:15 -03:00
renovate[bot] 1e3d7ef16e chore(deps): update eslint monorepo to v9.39.2 (#3164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 12:36:13 +08:00
renovate[bot] ce6835d50f chore(deps): update dependency @tauri-apps/cli to v2.9.6 (#3162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 22:13:33 +08:00
renovate[bot] 1d9f47c882 chore(deps): update dependency typescript-eslint to v8.49.0 (#3160)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 10:39:11 +08:00
Fabian-Lars e221a04ef4 docs(stronghold): add scrypt opt-level recommendation, closes #2048 2025-12-09 11:50:09 +01:00
Tony eebfd2ed3e fix(store): error instead of panic on invalid rid (#3157) 2025-12-09 18:02:23 +08:00
Elias* 521cd8b372 fix(fs): off by one error in readTextFileLines (#3155)
* fix: off by one error in readTextFileLines (#3154)

Signed-off-by: EliasStar <31409841+EliasStar@users.noreply.github.com>

* Format and regenerate `api-iife.js`

---------

Signed-off-by: EliasStar <31409841+EliasStar@users.noreply.github.com>
Co-authored-by: Tony <legendmastertony@gmail.com>
2025-12-09 11:48:18 +08:00
renovate[bot] e4a40f4423 chore(deps): update tauri monorepo (v2) (#3142)
* chore(deps): update tauri monorepo

* Build

* Bump rust tauri

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tony <legendmastertony@gmail.com>
2025-12-07 00:03:50 +08:00
renovate[bot] fa8b11f19d chore(deps): update dependency prettier to v3.7.4 (#3150)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-06 13:10:40 +08:00
renovate[bot] baefd761e0 chore(deps): update dependency typescript-eslint to v8.48.1 (#3146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 18:35:15 +08:00
Tunglies 2804803949 fix: deep-link clippy warnings (#3143)
* Update build.rs

* fix: address clippy warnings in deep-link module

* fix: add deep-link-js patch to address clippy warnings
2025-12-04 20:04:56 +08:00
renovate[bot] d9d51eb8ea chore(deps): update dependency prettier to v3.7.3 (#3140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 09:58:49 +08:00
renovate[bot] 1483c63101 chore(deps): update dependency prettier to v3.7.2 (#3137)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 10:31:13 +08:00
renovate[bot] 26ed78989a chore(deps): update dependency prettier to v3.7.1 (#3134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-30 20:01:16 +08:00
renovate[bot] f1564e58b6 chore(deps): update dependency prettier to v3.7.0 (#3132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-30 19:53:50 +08:00
Fabian-Lars f2d4abb9e2 fix(dialog): rfd feature flags (#3129) 2025-11-28 15:25:58 +01:00
renovate[bot] 3dcf7522b1 chore(deps): update dependency typescript-eslint to v8.48.0 (#3126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 14:45:59 +08:00
renovate[bot] 02068550e8 chore(deps): update rust crate rfd to 0.16 (#3128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 13:47:22 +08:00
Fabian-Lars 66a75ece27 fix(websocket): install crypto provider if needed (#3124) 2025-11-25 20:02:47 +01:00
Christian Leingang fa601e8754 feat(updater): allow accepting invalid TLS certs/hostnames via config (#3057)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-24 14:56:59 +01:00
Niccolò Gnani d8bfe61f20 fix(barcode-scanner): remove unnecessary permission checks on Android (fix #2312) (#3121) 2025-11-24 14:27:46 +01:00
Amr Bashir e8915f17e4 fix(http): fix aborting a streaming response (#2562) 2025-11-24 13:55:11 +01:00
Lucas Fernandes Nogueira 6de61f854b chore(log): remove iOS simulator workarounds (#3113) 2025-11-24 13:35:19 +01:00
renovate[bot] ad7a8d3e42 chore(deps): update dependency rollup to v4.53.3 (#3119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 16:25:46 +08:00
FabianLars d5509a9ac8 chore: add docs issue template and enable blank issues 2025-11-23 13:55:52 +01:00
FabianLars 06727b19c1 chore: fix issue templates 2025-11-23 13:51:01 +01:00
Fabian-Lars 2f9fa79747 chore: add issue templates (#3122) 2025-11-23 13:42:24 +01:00
renovate[bot] 1db4b0719d chore(deps): update dependency typescript-eslint to v8.47.0 (#3117)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 10:47:11 +08:00
Andrew de Waal dff6fa986a feat(dialog) - Support picker mode for open dialog (#3030) (#3034)
Co-authored-by: Andrew de Waal <andrewldewaal@gmail.com>
2025-11-20 22:28:13 +01:00
Nazar Antoniuk a4aa53ab90 chore(shell): clarify that the code field in ExitStatus is intentionally left private (#3116)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-20 15:33:19 +01:00
liuzhch1 ae278ddf60 fix(log): custom writer to rotate log file when exceeds max_file_size (fix #707) (#3110) 2025-11-20 10:29:17 +01:00
Jacob Bolda e644f38673 remove token for oidc publishing (#3077) 2025-11-19 21:52:57 -06:00
Lucas Fernandes Nogueira 8bfa445023 chore(localhost): disable caching (#3112)
* chore(localhost): disable caching

mostly seeing this cache being a problem on CEF - the response is cached so app updates are not reflected properly

* fmt
2025-11-19 17:03:32 -03:00
Lucas Nogueira 14fb36e347 chore: remove test logs 2025-11-19 17:00:14 -03:00
Fabian-Lars 5767b848fa chore: add PR ref in updater changefile
Added reference to pull request for clarity on changes.
2025-11-18 12:11:12 +01:00
renovate[bot] 277a45f56c chore(deps): update dependency rollup to v4.53.2 (#3103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 00:07:40 +08:00
renovate[bot] 8dbe7e3233 chore(deps): update dependency typescript-eslint to v8.46.4 (v2) (#3105)
* chore(deps): update dependency typescript-eslint to v8.46.4

* Fix audit (bump js-yaml)

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tony <legendmastertony@gmail.com>
2025-11-17 23:44:02 +08:00
Blaine c23fa03f07 feat(dialog): add xdg-portal as an optional feature for rfd (#3098) 2025-11-17 14:56:02 +01:00
Kingsword 631d0e256a fix(barcode-scanner): fix the cameraView is not removed after scanning in iOS (#2440)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-16 22:31:30 +01:00
Ben Clarke b4348cee92 fix(nfc): Resolve boolean in isAvailable to agree with TypeScript API (#3101)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-13 20:38:15 +01:00
renovate[bot] 3019063ae1 chore(deps): update dependency rollup to v4.53.1 (#3093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 22:06:11 +08:00
renovate[bot] 944614f46a chore(deps): update dependency @tauri-apps/cli to v2.9.4 (#3100)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 21:15:09 +08:00
Tony a368cef912 docs(updater): installer args and on_before_exit (#3099)
* docs(updater): installer args and `on_before_exit`

* Update plugins/updater/src/updater.rs

Co-authored-by: Fabian-Lars <github@fabianlars.de>

* Explicitly state there are internal args

* s
2025-11-12 20:49:41 +08:00
Fabian-Lars 27790aa67c chore: fix upload test on macos ci (#3097) 2025-11-11 22:56:09 +01:00
Matthew Richardson ad910b1135 feat(upload): Add HTTP method selection for upload (#2991)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-10 21:31:57 +01:00
Fabian-Lars 6b854421a1 docs(localhost): improve readme example, closes #3095 2025-11-10 21:27:39 +01:00
Fabian-Lars 5438a5cd22 chore: apply clippy fixes (#3092) 2025-11-09 18:43:17 +01:00
Fabian-Lars 5cd7778723 docs(dialog): add/fix doc item links, closes #3089 (#3091) 2025-11-09 17:07:35 +01:00
renovate[bot] 1a03e9761f chore(deps): update dependency @tauri-apps/cli to v2.9.3 (#3088)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-08 13:34:05 +08:00
renovate[bot] 1d4bffadda chore(deps): update eslint monorepo to v9.39.1 (#3086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-08 12:36:37 +08:00
renovate[bot] b8794272ae chore(deps): update dependency typescript-eslint to v8.46.3 (#3083)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-08 12:02:18 +08:00
Michelle Tilley 2a625adff3 feat(log): Allow a log formatter per target (#3065)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-04 15:52:46 +01:00
FabianLars 371cd8227c chore(deps): update pino to 9.14 to remove fast-redact 2025-11-04 10:00:51 +01:00
renovate[bot] 70ef6f8d3e chore(deps): update eslint monorepo to v9.39.0 (#3079)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 09:54:50 +01:00
renovate[bot] 5f0ac1436f chore(deps): update dependency @tauri-apps/cli to v2.9.2 (#3078)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 09:50:14 +08:00
Fabian-Lars ea172bfa3c docs(global-shortcut): import Emitter instead of Manager in example 2025-10-29 11:46:16 +01:00
FabianLars 6aead24047 Revert "chore: temp delete updater changefile"
This reverts commit b5550a3b0d.
2025-10-27 11:43:23 +01:00
github-actions[bot] e7a68fa637 publish new versions (#3068)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-27 11:31:06 +01:00
FabianLars b5550a3b0d chore: temp delete updater changefile 2025-10-27 11:13:33 +01:00
FabianLars 93426f8512 fix: fix docsrs builds 2025-10-27 11:12:56 +01:00
FabianLars 4ee61e055e Revert "chore: temp delete updater changefile"
This reverts commit 6314b004ab.
2025-10-26 22:13:19 +01:00
github-actions[bot] 06124af8d6 publish new versions (#2972)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-26 22:02:33 +01:00
renovate[bot] 060219e597 chore(deps): update dependency @rollup/plugin-typescript to v12.3.0 (#3067)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 20:25:22 +01:00
renovate[bot] c7e9766ff5 chore(deps): update tauri monorepo (v2) (#3058)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FabianLars <github@fabianlars.de>
2025-10-26 17:22:06 +01:00
renovate[bot] d4a8ce962b chore(deps): update rust crate tokio-tungstenite to 0.28 (#3016)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 14:07:12 +01:00
renovate[bot] cdc7eec415 chore(deps): update dependency @rollup/plugin-typescript to v12.2.0 (#3066)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 12:36:06 +01:00
Fabian-Lars 6314b004ab chore: temp delete updater changefile 2025-10-26 12:34:59 +01:00
renovate[bot] fb4c8ae54c chore(deps): update dependency typescript-eslint to v8.46.2 (#3060)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-24 16:46:50 +08:00
renovate[bot] fccc1cfb7e chore(deps): update eslint monorepo to v9.38.0 (#3044)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 17:17:32 +08:00
renovate[bot] 3702308e6f chore(deps): update dependency rollup to v4.52.5 (#3043)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 16:32:14 +08:00
renovate[bot] c9c8b39b56 chore(deps): update dependency typescript-eslint to v8.46.1 (#3025)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 16:31:31 +08:00
renovate[bot] 3c396feedf chore(deps): update dependency typescript to v5.9.3 (#3028)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 15:42:04 +08:00
renovate[bot] b514d482fa chore(deps): update dependency @rollup/plugin-node-resolve to v16.0.3 (#3036)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 15:41:37 +08:00
renovate[bot] 60e5714ceb chore(deps): update dependency vite to v7.1.11 [security] (#3040)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 15:28:07 +08:00
Daniel Mader 654bf4891a feat(barcode-scanner): update androidx.camera from 1.1.0 to 1.5.1 to support 16 KB memory page sizes (#3038) 2025-10-20 17:11:17 +02:00
Fabian-Lars 6c9b61fb65 chore: Update compileSdk to 36 (#3039) 2025-10-20 16:58:29 +02:00
Amr Bashir 6b5b1053ba enhance(fs): improve error message when resolving path in debug builds (#1939)
* enhance(fs): improve error message when resolving path in debug builds

closes #11338

* fix compilation

* deduplicate read_file code

* fix compile

* fix compile again

* fmt

* comma

* add change file

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
2025-10-09 08:58:08 -03:00
Tony 67a7bf80f8 fix(opener): doesn't open same origin links (#3018) 2025-10-04 21:16:08 +08:00
Kevin Schoonover e25a9339f4 fix(deep-link): update the Exec= in handler if the executable path changes (#3019)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-10-01 12:01:09 +02:00
Tony 92f98a93b4 docs(fs): fs:scope usage (#2969)
* docs(fs): `fs:scope` usage

* Use APPDATA for the example instead

* `/**/*`
2025-10-01 17:24:04 +08:00
renovate[bot] c0f82e5a5e chore(deps): update dependency rollup to v4.52.3 (#3021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 11:54:47 +08:00
renovate[bot] 5204145c6a chore(deps): update dependency typescript-eslint to v8.44.1 (#3011)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-27 22:45:12 +08:00
renovate[bot] b4ee33dd01 chore(deps): update dependency rollup to v4.52.2 (#3013)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-27 21:55:53 +08:00
Fabian-Lars e933acb004 fix(geolocation): use timeout for min,max,interval on android (#3010) 2025-09-25 15:41:03 +02:00
FabianLars e4a9154f4a ci(renovate): Group windows crates into one PR 2025-09-25 13:31:39 +02:00
renovate[bot] d44ac9afed chore(deps): update eslint monorepo to v9.36.0 (#3004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-25 12:59:04 +02:00
renovate[bot] 1b197c2d4d chore(deps): update dependency rollup to v4.52.0 (#3003)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-25 12:57:57 +02:00
renovate[bot] 650ff5ff23 chore(deps): update dependency typescript-eslint to v8.44.0 (#2988)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-25 12:21:23 +02:00
Fabian-Lars 2930aa39a4 chore: set minimumReleaseAge to 3 days in renovate and pnpm (#2999) 2025-09-25 11:39:52 +02:00
frieddeeu 3e92ce260b fix(geolocation): remove logging of priority (#3002) 2025-09-25 11:38:49 +02:00
renovate[bot] 12da195fce chore(deps): update dependency rollup to v4.50.1 (#2986)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 17:47:39 +08:00
renovate[bot] 30dd109d9f chore(deps): update eslint monorepo to v9.35.0 (#2975)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 09:03:34 +08:00
renovate[bot] 5e5bc30ad0 chore(deps): update dependency typescript-eslint to v8.42.0 (v2) (#2973)
* chore(deps): update dependency typescript-eslint to v8.42.0

* Switch to `defineConfig`
2025-09-03 10:36:36 +08:00
renovate[bot] f6dca71343 chore(deps): update dependency @tauri-apps/cli to v2.8.4 (#2971)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 10:16:08 +08:00
FabianLars 021e573248 Revert "ci: delete .changes/updater-new-bundle-support.md"
This reverts commit 51b430be98.
2025-09-02 16:49:58 +02:00
Fabian-Lars 51b430be98 ci: delete .changes/updater-new-bundle-support.md 2025-09-02 15:43:40 +02:00
github-actions[bot] fd439b143e Publish New Versions (v2) (#2964)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-09-02 14:04:12 +02:00
Sean Wang 2522b71f6b fix(deep-link): revert the breaking change introduced by #2928 (#2970) 2025-09-02 13:50:10 +02:00
renovate[bot] 9021a73247 chore(deps): update dependency rollup to v4.50.0 (#2966)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 20:53:18 +08:00
Lucas Fernandes Nogueira 625bb1c096 feat(log): re-export the log crate (#2965)
* feat(log): re-export the log crate

* code review

* Move emit_trace

---------

Co-authored-by: Tony <legendmastertony@gmail.com>
2025-08-30 23:44:03 -03:00
renovate[bot] 6215afe023 chore(deps): update dependency rollup to v4.49.0 (#2962)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-29 10:40:39 +08:00
Fabian-Lars 8cf8eeab02 feat(updater): inject bundle_type into endpoint url (#2960)
* feat(updater): inject bundle_type into endpoint url

* Revert schemas

* replace with unknown if none
2025-08-27 15:52:17 -03:00
Amr Bashir 509eba8d44 feat: support message dialogs with 3 buttons (#2641)
* feat: support message dialogs with 3 buttons

* change file

* From<String>

* untagged & YesNoCancel

* revert package.json

* Update plugins/dialog/src/desktop.rs

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* no optional

* Update desktop.rs

* Update plugins/dialog/src/models.rs

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* change to an enum

* convert back into union

* regen

* update @since

* map buttons for linux

* enhance type

* Add examples

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Co-authored-by: Tony <legendmastertony@gmail.com>
2025-08-27 09:40:47 -03:00
kandrelczyk 9ac5fe84e7 feat(updater): support bundle-specific targets (#2624)
* fallback targets

* linux test

* linux ready

* RPM installation

* small error fix

* fix windows build

* windows tests

* add aider files to .gitignore

* get bundle type out of patched variable

* windows tests

* patch windows binary

* format

* fix bundler

* remove local tauri dependency

* remove print

* rever Cargo.lock

* move __TAURI_BUNDLE_TYPE to tauri::utils

* get_current_bundle_type

* update tauri

* fix macos integration test

* fix fallback logic

Signed-off-by: Krzysztof Andrelczyk <cristof@curiana.net>

* amend! fallback targets

fallback targets

* reformat

* fix tests

* reformat

* bump tari versio

* fix fallback logic

* restore Cargo.lock

* Bump tauri and add notes

* Rename some staffs

* move target logic

* Refactor the target fallback to a function

* Format and clippy

* Keep target in `Update` since it's public

* Keep updater/tests/app-updater/src/main.rs lf

* Revert changes in tests/app-updater/src/main.rs

* Clean up

* changefile

* Bump updater-js as well

* update pub fn target docs

* update pub fn target docs

* Update plugins/updater/src/error.rs

Co-authored-by: Fabian-Lars <github@fabianlars.de>

* Update plugins/updater/src/updater.rs

Co-authored-by: Fabian-Lars <github@fabianlars.de>

* Update plugins/updater/src/updater.rs

Co-authored-by: Fabian-Lars <github@fabianlars.de>

* suggestios

* add comment

* restore error

* Revert "Bump tauri and add notes"

This reverts commit 0a495ccc6a.

* Revert "bump tari versio"

This reverts commit 5b4c1c164b.

---------

Signed-off-by: Krzysztof Andrelczyk <cristof@curiana.net>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Co-authored-by: Tony <legendmastertony@gmail.com>
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-08-26 09:33:58 -03:00
renovate[bot] c247410319 chore(deps): update dependency typescript-eslint to v8.41.0 (#2956)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-26 09:37:44 +08:00
Fabian-Lars de45034082 docs(store): tauri-docs compatibility 2025-08-25 20:13:16 +02:00
renovate[bot] 50c6b7c644 chore(deps): update dependency rollup to v4.48.1 (#2952)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 18:55:07 +02:00
renovate[bot] b79d02d896 chore(deps): update dependency @tauri-apps/cli to v2.8.3 (#2955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 18:54:23 +02:00
github-actions[bot] b75f9f5cd3 publish new versions (#2954)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-25 18:15:22 +02:00
Lucas Fernandes Nogueira d865ed4768 fix(shell): run sidecar with dots in filename, closes #2310 (#2950)
* fix(shell): run sidecar with dots in filename, closes #2310

* fix import

* remove dead code

* code review suggestions

* clippy

* clippy
2025-08-25 10:44:47 -03:00
renovate[bot] 1107c46425 chore(deps): update dependency @tauri-apps/cli to v2.8.2 (#2932)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 09:25:07 +08:00
renovate[bot] 23a3705857 chore(deps): update dependency rollup to v4.48.0 (#2948)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 17:43:32 +08:00
renovate[bot] 6f65e68340 chore(deps): update eslint monorepo to v9.34.0 (#2946)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 09:05:42 +08:00
214 changed files with 4403 additions and 2126 deletions
@@ -0,0 +1,9 @@
---
"single-instance": minor:fix
---
**Breaking Change:** On Linux, the DBus ID/name will now be `<bundle-id>.SingleInstance` instead of `org.<bundle_id_underscores>.SingleInstance` to follow DBus specifications.
This will break the single-instance mechanism across different app versions if the app was installed multiple times.
Added `dbus_id` builder method, which can be used to restore previous behavior. For a bundle identifier of `com.tauri.my-example` this would be `dbus_id("org.com_tauri_my_example")`.
@@ -0,0 +1,5 @@
---
"single-instance": patch
---
Add `setup` function to run the single instance initialization manually, without waiting for the tauri setup hook to run.
+63
View File
@@ -0,0 +1,63 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: 🐞 Bug Report
description: Report a bug
labels: ['type: bug']
body:
- type: markdown
attributes:
value: |
## First of all
1. Please search for [existing issues](https://github.com/tauri-apps/plugins-workspace/issues?q=is%3Aissue) about this problem first.
2. Make sure `rustc` and all relevant Tauri packages are up to date.
3. Make sure it's an issue with a tauri plugin and not something else you are using.
4. Remember to follow our community guidelines and be friendly.
- type: textarea
id: description
attributes:
label: Describe the bug
description: A clear description of what the bug is. Include screenshots if applicable.
placeholder: Bug description
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: A link to a reproduction repo or steps to reproduce the behaviour.
placeholder: |
Please provide a minimal reproduction or steps to reproduce, see this guide https://stackoverflow.com/help/minimal-reproducible-example
Why reproduction is required? see this article https://antfu.me/posts/why-reproductions-are-required
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear description of what you expected to happen.
- type: textarea
id: info
attributes:
label: Full `tauri info` output
description: 'Output of `npm run tauri info` or `cargo tauri info`. Issues without this will be closed!'
render: text
validations:
required: true
- type: textarea
id: logs
attributes:
label: Stack trace
description: A stack trace or ANY error messages you may see.
render: text
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here. For example a link to a Discord discussion.
+12
View File
@@ -0,0 +1,12 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
#blank_issues_enabled: false
contact_links:
- name: 💡 Request a new plugin
url: https://github.com/orgs/tauri-apps/discussions/new?category=plugin-requests
about: Propose a new Plugin to the community or the Tauri org.
- name: 💬 Discord Chat
url: https://discord.com/invite/tauri
about: Ask questions and talk to other Tauri users and the maintainers
+5
View File
@@ -0,0 +1,5 @@
---
name: 📚 Docs Report
about: Create a report to help us improve the docs
labels: 'type: documentation'
---
@@ -0,0 +1,46 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: 💡 Feature Request
description: Request a feature or enhancement for an existing plugin
labels: ['type: feature request']
body:
- type: markdown
attributes:
value: |
## First of all
- Please search for [existing issues](https://github.com/tauri-apps/plugins-workspace/issues?q=is%3Aissue) for this request first.
- Only requests for plugins that exist in this repo are allowed.
- You can request new plugins [here](https://github.com/orgs/tauri-apps/discussions/new?category=plugin-requests)
- type: textarea
id: problem
attributes:
label: Describe the problem
description: A clear description of the problem this feature would solve
placeholder: "I'm always frustrated when..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: "Describe the solution you'd like"
description: A clear description of what change you would like
placeholder: 'I would like to...'
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: "Any alternative solutions you've considered"
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here.
@@ -11,7 +11,7 @@ on:
- v2 - v2
permissions: permissions:
# required for npm provenance # required for oidc token
id-token: write id-token: write
# required to create the GitHub Release # required to create the GitHub Release
contents: write contents: write
@@ -62,7 +62,6 @@ jobs:
id: covector id: covector
env: env:
CARGO_TARGET_DIR: /mnt/target CARGO_TARGET_DIR: /mnt/target
NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
command: 'version-or-publish' command: 'version-or-publish'
+1
View File
@@ -5,6 +5,7 @@
name: integration tests name: integration tests
on: on:
workflow_dispatch:
push: push:
branches: branches:
- v1 - v1
+1
View File
@@ -154,4 +154,5 @@ jobs:
run: cargo clippy --package ${{ matrix.package }} --all-targets -- -D warnings run: cargo clippy --package ${{ matrix.package }} --all-targets -- -D warnings
- name: clippy ${{ matrix.package }} --all-features - name: clippy ${{ matrix.package }} --all-features
if: matrix.package != 'tauri-plugin-dialog'
run: cargo clippy --package ${{ matrix.package }} --all-targets --all-features -- -D warnings run: cargo clippy --package ${{ matrix.package }} --all-targets --all-features -- -D warnings
+2 -2
View File
@@ -246,9 +246,9 @@ jobs:
run: cargo +stable install cross --git https://github.com/cross-rs/cross run: cargo +stable install cross --git https://github.com/cross-rs/cross
- name: test ${{ matrix.package }} - name: test ${{ matrix.package }}
if: matrix.package != 'tauri-plugin-http' if: ${{ matrix.package != 'tauri-plugin-http' && matrix.package != 'tauri-plugin-dialog' }}
run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets --all-features run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets --all-features
- name: test ${{ matrix.package }} - name: test ${{ matrix.package }}
if: matrix.package == 'tauri-plugin-http' if: ${{ matrix.package == 'tauri-plugin-http' || matrix.package == 'tauri-plugin-dialog' }}
run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets
+2 -1
View File
@@ -21,6 +21,7 @@ target/
package-lock.json package-lock.json
yarn.lock yarn.lock
bun.lockb bun.lockb
bun.lock
# rust compiled folders # rust compiled folders
target/ target/
@@ -58,4 +59,4 @@ pids
.idea .idea
debug.log debug.log
TODO.md TODO.md
.aider* .aider.*
Generated
+319 -154
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -12,10 +12,10 @@ resolver = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
log = "0.4" log = "0.4"
tauri = { version = "2.8.2", default-features = false } tauri = { version = "2.10", default-features = false }
tauri-build = "2.4" tauri-build = "2.5"
tauri-plugin = "2.4" tauri-plugin = "2.5"
tauri-utils = "2.7" tauri-utils = "2.8"
serde_json = "1" serde_json = "1"
thiserror = "2" thiserror = "2"
url = "2" url = "2"
+2 -1
View File
@@ -3,11 +3,12 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import eslint from '@eslint/js' import eslint from '@eslint/js'
import { defineConfig } from 'eslint/config'
import eslintConfigPrettier from 'eslint-config-prettier' import eslintConfigPrettier from 'eslint-config-prettier'
import eslintPluginSecurity from 'eslint-plugin-security' import eslintPluginSecurity from 'eslint-plugin-security'
import tseslint from 'typescript-eslint' import tseslint from 'typescript-eslint'
export default tseslint.config( export default defineConfig(
{ {
ignores: [ ignores: [
'**/target', '**/target',
+79
View File
@@ -1,5 +1,84 @@
# Changelog # Changelog
## \[2.0.37]
### Dependencies
- Upgraded to `updater-js@2.10.0`
- Upgraded to `barcode-scanner-js@2.4.4`
- Upgraded to `http-js@2.5.7`
- Upgraded to `shell-js@2.3.5`
## \[2.0.36]
### Dependencies
- Upgraded to `http-js@2.5.6`
- Upgraded to `dialog-js@2.6.0`
## \[2.0.35]
### Dependencies
- Upgraded to `dialog-js@2.5.0`
- Upgraded to `log-js@2.8.0`
- Upgraded to `shell-js@2.3.4`
- Upgraded to `barcode-scanner-js@2.4.3`
- Upgraded to `fs-js@2.4.5`
- Upgraded to `http-js@2.5.5`
- Upgraded to `nfc-js@2.3.4`
- Upgraded to `opener-js@2.5.3`
- Upgraded to `store-js@2.4.2`
## \[2.0.34]
### Dependencies
- Upgraded to `barcode-scanner-js@2.4.2`
- Upgraded to `biometric-js@2.3.2`
- Upgraded to `cli-js@2.4.1`
- Upgraded to `clipboard-manager-js@2.3.2`
- Upgraded to `dialog-js@2.4.2`
- Upgraded to `fs-js@2.4.4`
- Upgraded to `global-shortcut-js@2.3.1`
- Upgraded to `http-js@2.5.4`
- Upgraded to `log-js@2.7.1`
- Upgraded to `nfc-js@2.3.3`
- Upgraded to `notification-js@2.3.3`
- Upgraded to `opener-js@2.5.2`
- Upgraded to `os-js@2.3.2`
- Upgraded to `process-js@2.3.1`
- Upgraded to `shell-js@2.3.3`
- Upgraded to `store-js@2.4.1`
## \[2.0.33]
### Dependencies
- Upgraded to `barcode-scanner-js@2.4.1`
- Upgraded to `biometric-js@2.3.1`
- Upgraded to `clipboard-manager-js@2.3.1`
- Upgraded to `dialog-js@2.4.1`
- Upgraded to `fs-js@2.4.3`
- Upgraded to `nfc-js@2.3.2`
- Upgraded to `notification-js@2.3.2`
- Upgraded to `opener-js@2.5.1`
- Upgraded to `shell-js@2.3.2`
- Upgraded to `http-js@2.5.3`
## \[2.0.32]
### Dependencies
- Upgraded to `dialog-js@2.4.0`
- Upgraded to `log-js@2.7.0`
## \[2.0.31]
### Dependencies
- Upgraded to `shell-js@2.3.1`
## \[2.0.30] ## \[2.0.30]
### Dependencies ### Dependencies
+20 -20
View File
@@ -1,7 +1,7 @@
{ {
"name": "api", "name": "api",
"private": true, "private": true,
"version": "2.0.30", "version": "2.0.37",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --clearScreen false", "dev": "vite --clearScreen false",
@@ -10,25 +10,25 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.8.0", "@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-barcode-scanner": "^2.4.0", "@tauri-apps/plugin-barcode-scanner": "^2.4.4",
"@tauri-apps/plugin-biometric": "^2.3.0", "@tauri-apps/plugin-biometric": "^2.3.2",
"@tauri-apps/plugin-cli": "^2.4.0", "@tauri-apps/plugin-cli": "^2.4.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.3.3", "@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.2", "@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-geolocation": "^2.2.0", "@tauri-apps/plugin-geolocation": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.3.0", "@tauri-apps/plugin-global-shortcut": "^2.3.1",
"@tauri-apps/plugin-haptics": "^2.2.0", "@tauri-apps/plugin-haptics": "^2.2.0",
"@tauri-apps/plugin-http": "^2.5.2", "@tauri-apps/plugin-http": "^2.5.7",
"@tauri-apps/plugin-nfc": "^2.3.1", "@tauri-apps/plugin-nfc": "^2.3.4",
"@tauri-apps/plugin-notification": "^2.3.1", "@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-opener": "^2.5.0", "@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-os": "^2.3.1", "@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.0", "@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.0", "@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-store": "^2.4.0", "@tauri-apps/plugin-store": "^2.4.2",
"@tauri-apps/plugin-updater": "^2.9.0", "@tauri-apps/plugin-updater": "^2.10.0",
"@tauri-apps/plugin-upload": "^2.3.0", "@tauri-apps/plugin-upload": "^2.3.0",
"@zerodevx/svelte-json-view": "1.0.11" "@zerodevx/svelte-json-view": "1.0.11"
}, },
@@ -36,10 +36,10 @@
"@iconify-json/codicon": "^1.2.12", "@iconify-json/codicon": "^1.2.12",
"@iconify-json/ph": "^1.2.2", "@iconify-json/ph": "^1.2.2",
"@sveltejs/vite-plugin-svelte": "^6.0.0", "@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tauri-apps/cli": "2.8.1", "@tauri-apps/cli": "2.10.0",
"@unocss/extractor-svelte": "^66.3.3", "@unocss/extractor-svelte": "^66.3.3",
"svelte": "^5.20.4", "svelte": "^5.20.4",
"unocss": "^66.3.3", "unocss": "^66.3.3",
"vite": "^7.0.4" "vite": "^7.0.7"
} }
} }
+83
View File
@@ -1,5 +1,88 @@
# Changelog # Changelog
## \[2.0.41]
### Dependencies
- Upgraded to `updater@2.10.0`
- Upgraded to `barcode-scanner@2.4.4`
- Upgraded to `http@2.5.7`
- Upgraded to `shell@2.3.5`
## \[2.0.40]
### Dependencies
- Upgraded to `http@2.5.6`
- Upgraded to `dialog@2.6.0`
## \[2.0.39]
### Dependencies
- Upgraded to `dialog@2.5.0`
- Upgraded to `log@2.8.0`
- Upgraded to `shell@2.3.4`
- Upgraded to `barcode-scanner@2.4.3`
- Upgraded to `fs@2.4.5`
- Upgraded to `http@2.5.5`
- Upgraded to `nfc@2.3.4`
- Upgraded to `opener@2.5.3`
- Upgraded to `store@2.4.2`
## \[2.0.38]
### Dependencies
- Upgraded to `barcode-scanner@2.4.2`
- Upgraded to `biometric@2.3.2`
- Upgraded to `cli@2.4.1`
- Upgraded to `clipboard-manager@2.3.2`
- Upgraded to `dialog@2.4.2`
- Upgraded to `fs@2.4.4`
- Upgraded to `geolocation@2.3.2`
- Upgraded to `global-shortcut@2.3.1`
- Upgraded to `haptics@2.3.2`
- Upgraded to `http@2.5.4`
- Upgraded to `log@2.7.1`
- Upgraded to `nfc@2.3.3`
- Upgraded to `notification@2.3.3`
- Upgraded to `opener@2.5.2`
- Upgraded to `os@2.3.2`
- Upgraded to `process@2.3.1`
- Upgraded to `shell@2.3.3`
- Upgraded to `store@2.4.1`
## \[2.0.37]
### Dependencies
- Upgraded to `barcode-scanner@2.4.1`
- Upgraded to `biometric@2.3.1`
- Upgraded to `clipboard-manager@2.3.1`
- Upgraded to `dialog@2.4.1`
- Upgraded to `fs@2.4.3`
- Upgraded to `geolocation@2.3.1`
- Upgraded to `haptics@2.3.1`
- Upgraded to `nfc@2.3.2`
- Upgraded to `notification@2.3.2`
- Upgraded to `opener@2.5.1`
- Upgraded to `shell@2.3.2`
- Upgraded to `http@2.5.3`
## \[2.0.36]
### Dependencies
- Upgraded to `dialog@2.4.0`
- Upgraded to `log@2.7.0`
## \[2.0.35]
### Dependencies
- Upgraded to `shell@2.3.1`
## \[2.0.34] ## \[2.0.34]
### Dependencies ### Dependencies
+20 -20
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "api" name = "api"
publish = false publish = false
version = "2.0.34" version = "2.0.41"
description = "An example Tauri Application showcasing the api" description = "An example Tauri Application showcasing the api"
edition = "2021" edition = "2021"
rust-version = { workspace = true } rust-version = { workspace = true }
@@ -20,24 +20,24 @@ serde = { workspace = true }
tiny_http = "0.12" tiny_http = "0.12"
time = "0.3" time = "0.3"
log = { workspace = true } log = { workspace = true }
tauri-plugin-log = { path = "../../../plugins/log", version = "2.6.0" } tauri-plugin-log = { path = "../../../plugins/log", version = "2.8.0" }
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.2", features = [ tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.5", features = [
"watch", "watch",
] } ] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" } tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.2" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.3" } tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.6.0" }
tauri-plugin-http = { path = "../../../plugins/http", features = [ tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart", "multipart",
"cookies", "cookies",
], version = "2.5.2" } ], version = "2.5.7" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.1", features = [ tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.3", features = [
"windows7-compat", "windows7-compat",
] } ] }
tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.1" } tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.2" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" } tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.1" }
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.0" } tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.3" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.0" } tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.5" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.0" } tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.2" }
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" } tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
[dependencies.tauri] [dependencies.tauri]
@@ -55,17 +55,17 @@ features = [
] ]
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.0" } tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.1" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.0" } tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.1" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.9.0" } tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.10.0" }
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" } tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies] [target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.0" } tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.4" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.1" } tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.4" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.0" } tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.2" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.0" } tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.2" }
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.0" } tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.2" }
[features] [features]
prod = ["tauri/custom-protocol"] prod = ["tauri/custom-protocol"]
@@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
} }
android { android {
compileSdk = 34 compileSdk = 36
namespace = "com.tauri.api" namespace = "com.tauri.api"
defaultConfig { defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false" manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "com.tauri.api" applicationId = "com.tauri.api"
minSdk = 24 minSdk = 24
targetSdk = 34 targetSdk = 36
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
} }
@@ -4,7 +4,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:8.5.1") classpath("com.android.tools.build:gradle:8.11.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
} }
} }
@@ -18,6 +18,6 @@ repositories {
dependencies { dependencies {
compileOnly(gradleApi()) compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:8.5.1") implementation("com.android.tools.build:gradle:8.11.0")
} }
+94 -56
View File
@@ -8,6 +8,8 @@
let filter = null; let filter = null;
let multiple = false; let multiple = false;
let directory = false; let directory = false;
let pickerMode = "document";
let fileAccessMode = "scoped";
function arrayBufferToBase64(buffer, callback) { function arrayBufferToBase64(buffer, callback) {
var blob = new Blob([buffer], { var blob = new Blob([buffer], {
@@ -44,55 +46,69 @@
await message("Tauri is awesome!"); await message("Tauri is awesome!");
} }
function openDialog() { async function msgCustom(result) {
open({ const buttons = { yes: "awesome", no: "amazing", cancel: "stunning" };
title: "My wonderful open dialog", await message(`Tauri is: `, { buttons })
defaultPath, .then((res) => onMessage(`Tauri is ${res}`))
filters: filter
? [
{
name: "Tauri Example",
extensions: filter.split(",").map((f) => f.trim()),
},
]
: [],
multiple,
directory,
})
.then(function (res) {
if (Array.isArray(res)) {
onMessage(res);
} else {
var pathToRead = res;
var isFile = pathToRead.match(/\S+\.\S+$/g);
readFile(pathToRead)
.then(function (response) {
if (isFile) {
if (
pathToRead.includes(".png") ||
pathToRead.includes(".jpg") ||
pathToRead.includes(".jpeg")
) {
arrayBufferToBase64(
new Uint8Array(response),
function (base64) {
var src = "data:image/png;base64," + base64;
insecureRenderHtml('<img src="' + src + '"></img>');
}
);
} else {
onMessage(res);
}
} else {
onMessage(res);
}
})
.catch(onMessage(res));
}
})
.catch(onMessage); .catch(onMessage);
} }
async function openDialog() {
try {
var result = await open({
title: "My wonderful open dialog",
defaultPath,
filters: filter
? [
{
name: "Tauri Example",
extensions: filter.split(",").map((f) => f.trim()),
},
]
: [],
multiple,
directory,
pickerMode,
fileAccessMode,
})
if (Array.isArray(result)) {
onMessage(result);
} else {
var pathToRead = result;
var isFile = pathToRead.match(/\S+\.\S+$/g);
await readFile(pathToRead)
.then(function (res) {
if (isFile) {
if (
pathToRead.includes(".png") ||
pathToRead.includes(".jpg") ||
pathToRead.includes(".jpeg")
) {
arrayBufferToBase64(
new Uint8Array(res),
function (base64) {
var src = "data:image/png;base64," + base64;
insecureRenderHtml('<img src="' + src + '"></img>');
}
);
} else {
// Convert byte array to UTF-8 string
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(new Uint8Array(res));
onMessage(text);
}
} else {
onMessage(res);
}
})
}
} catch(exception) {
onMessage(exception)
}
}
function saveDialog() { function saveDialog() {
save({ save({
title: "My wonderful save dialog", title: "My wonderful save dialog",
@@ -105,7 +121,7 @@
}, },
] ]
: [], : [],
}) })
.then(onMessage) .then(onMessage)
.catch(onMessage); .catch(onMessage);
} }
@@ -135,13 +151,35 @@
<input type="checkbox" id="dialog-directory" bind:checked={directory} /> <input type="checkbox" id="dialog-directory" bind:checked={directory} />
<label for="dialog-directory">Directory</label> <label for="dialog-directory">Directory</label>
</div> </div>
<div>
<label for="dialog-picker-mode">Picker Mode:</label>
<select id="dialog-picker-mode" bind:value={pickerMode}>
<option value="">None</option>
<option value="media">Media</option>
<option value="image">Image</option>
<option value="video">Video</option>
<option value="document">Document</option>
</select>
</div>
<div>
<label for="dialog-file-access-mode">File Access Mode:</label>
<select id="dialog-file-access-mode" bind:value={fileAccessMode}>
<option value="copy">Copy</option>
<option value="scoped">Scoped</option>
</select>
</div>
<br /> <br />
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
<button class="btn" id="save-dialog" on:click={saveDialog} <div class="flex flex-wrap flex-col md:flex-row gap-2 children:flex-shrink-0">
>Open save dialog</button <button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
> <button class="btn" id="save-dialog" on:click={saveDialog}
<button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button> >Open save dialog</button
<button class="btn" id="custom-prompt-dialog" on:click={promptCustom} >
>Prompt (custom)</button <button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button>
> <button class="btn" id="custom-prompt-dialog" on:click={promptCustom}
<button class="btn" id="message-dialog" on:click={msg}>Message</button> >Prompt (custom)</button
>
<button class="btn" id="message-dialog" on:click={msg}>Message</button>
<button class="btn" id="message-dialog" on:click={msgCustom}>Message (custom)</button>
</div>
+44 -3
View File
@@ -1,5 +1,5 @@
<script> <script>
import { download, upload } from '@tauri-apps/plugin-upload' import { download, upload, HttpMethod } from '@tauri-apps/plugin-upload'
import { open } from '@tauri-apps/plugin-dialog' import { open } from '@tauri-apps/plugin-dialog'
import { JsonView } from '@zerodevx/svelte-json-view' import { JsonView } from '@zerodevx/svelte-json-view'
import { appDataDir } from '@tauri-apps/api/path' import { appDataDir } from '@tauri-apps/api/path'
@@ -16,6 +16,22 @@
let uploadUrl = 'https://httpbin.org/post' let uploadUrl = 'https://httpbin.org/post'
let uploadFilePath = '' let uploadFilePath = ''
let uploadMethod = HttpMethod.Post
// Update URL when method changes
$: {
switch (uploadMethod) {
case HttpMethod.Post:
uploadUrl = 'https://httpbin.org/post'
break
case HttpMethod.Put:
uploadUrl = 'https://httpbin.org/put'
break
case HttpMethod.Patch:
uploadUrl = 'https://httpbin.org/patch'
break
}
}
let uploadProgress = null let uploadProgress = null
let uploadResult = null let uploadResult = null
let isUploading = false let isUploading = false
@@ -197,7 +213,8 @@
}, },
new Map([ new Map([
['User-Agent', 'Tauri Upload Plugin Demo'] ['User-Agent', 'Tauri Upload Plugin Demo']
]) ]),
uploadMethod
) )
uploadResult = { uploadResult = {
@@ -340,12 +357,36 @@
</div> </div>
</div> </div>
<div>
<label for="upload-method" class="block text-sm font-medium text-gray-700 mb-1">HTTP Method:</label>
<select
id="upload-method"
bind:value={uploadMethod}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
disabled={isUploading}
>
<option value={HttpMethod.Post}>POST</option>
<option value={HttpMethod.Put}>PUT</option>
<option value={HttpMethod.Patch}>PATCH</option>
</select>
<p class="text-xs text-gray-500 mt-1">Choose the HTTP method for the upload request</p>
</div>
<div class="bg-blue-50 border border-blue-200 p-3 rounded-md">
<div class="text-sm text-blue-800">
<strong>Upload Configuration:</strong>
<div class="font-mono text-xs mt-1">
Method: {uploadMethod} | URL: {uploadUrl || 'Not set'}
</div>
</div>
</div>
<button <button
on:click={startUpload} on:click={startUpload}
class="w-full px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed" class="w-full px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isUploading || !uploadUrl || !uploadFilePath} disabled={isUploading || !uploadUrl || !uploadFilePath}
> >
{isUploading ? 'Uploading...' : 'Upload File'} {isUploading ? `Uploading (${uploadMethod})...` : `Upload File (${uploadMethod})`}
</button> </button>
{#if uploadProgress} {#if uploadProgress}
+12 -10
View File
@@ -11,29 +11,31 @@
"example:api:dev": "pnpm run --filter \"api\" tauri dev" "example:api:dev": "pnpm run --filter \"api\" tauri dev"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "9.33.0", "@eslint/js": "9.39.2",
"@rollup/plugin-node-resolve": "16.0.1", "@rollup/plugin-node-resolve": "16.0.3",
"@rollup/plugin-terser": "0.4.4", "@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "12.1.4", "@rollup/plugin-typescript": "12.3.0",
"covector": "^0.12.4", "covector": "^0.12.4",
"eslint": "9.33.0", "eslint": "9.39.2",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-security": "3.0.1", "eslint-plugin-security": "3.0.1",
"prettier": "3.6.2", "prettier": "3.8.1",
"rollup": "4.47.1", "rollup": "4.57.1",
"tslib": "2.8.1", "tslib": "2.8.1",
"typescript": "5.9.2", "typescript": "5.9.3",
"typescript-eslint": "8.40.0" "typescript-eslint": "8.54.0"
}, },
"minimumReleaseAge": 4320,
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"esbuild@<0.25.0": ">=0.25.0" "esbuild@<0.25.0": ">=0.25.0",
"lodash@>=4.0.0 <=4.17.22": ">=4.17.23"
}, },
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"esbuild" "esbuild"
] ]
}, },
"engines": { "engines": {
"pnpm": "^10.0.0" "pnpm": "^10.16.0"
} }
} }
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.5.1]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.5.0] ## \[2.5.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-autostart" name = "tauri-plugin-autostart"
version = "2.5.0" version = "2.5.1"
description = "Automatically launch your application at startup." description = "Automatically launch your application at startup."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -9,10 +9,6 @@ rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-autostart" links = "tauri-plugin-autostart"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
linux = { level = "full", notes = "" } linux = { level = "full", notes = "" }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-autostart", "name": "@tauri-apps/plugin-autostart",
"version": "2.5.0", "version": "2.5.1",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+20
View File
@@ -1,5 +1,25 @@
# Changelog # Changelog
## \[2.4.4]
- [`82fbb0c7`](https://github.com/tauri-apps/plugins-workspace/commit/82fbb0c790288eca72af9ade13828ded7700ff90) ([#3221](https://github.com/tauri-apps/plugins-workspace/pull/3221)) On iOS, fixed an application crash happening when the scanner was started when user denied permission before.
- [`b60dd887`](https://github.com/tauri-apps/plugins-workspace/commit/b60dd88702a2be2af942a3d104d728970a4c42d6) ([#3223](https://github.com/tauri-apps/plugins-workspace/pull/3223)) On iOS, start the scanning session on a separate thread to fix performance issues.
- [`c27af912`](https://github.com/tauri-apps/plugins-workspace/commit/c27af9128d6cc7a2424ec49e98005e68b2d0eb1a) ([#3222](https://github.com/tauri-apps/plugins-workspace/pull/3222)) On iOS, fixed an application crash happening when the scanner was started on the iOS Simulator (no camera available).
## \[2.4.3]
- [`d8bfe61f`](https://github.com/tauri-apps/plugins-workspace/commit/d8bfe61f20f235314bad93a9c50d8b7f3eade734) ([#3121](https://github.com/tauri-apps/plugins-workspace/pull/3121) by [@NKIPSC](https://github.com/tauri-apps/plugins-workspace/../../NKIPSC)) Remove unnecessary checks on Android when requesting camera permission.
- [`631d0e25`](https://github.com/tauri-apps/plugins-workspace/commit/631d0e256a37946b6a9102ca35511abfbebb92c5) ([#2440](https://github.com/tauri-apps/plugins-workspace/pull/2440) by [@kingsword09](https://github.com/tauri-apps/plugins-workspace/../../kingsword09)) Fix the `cameraView` is not removed after scanning in iOS.
## \[2.4.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.4.1]
- [`654bf489`](https://github.com/tauri-apps/plugins-workspace/commit/654bf4891a35769f7e82971641d3ad99974b2dfe) ([#3038](https://github.com/tauri-apps/plugins-workspace/pull/3038) by [@daniel-mader](https://github.com/tauri-apps/plugins-workspace/../../daniel-mader)) Update `androidx.camera` from `1.1.0` to `1.5.1` to support 16 KB memory page sizes.
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
## \[2.4.0] ## \[2.4.0]
- [`aa9140e1`](https://github.com/tauri-apps/plugins-workspace/commit/aa9140e1ac239ab9f015f92b2ed52bbf0eda7c12) ([#2437](https://github.com/tauri-apps/plugins-workspace/pull/2437) by [@enkhjile](https://github.com/tauri-apps/plugins-workspace/../../enkhjile)) Added support for GS1 DataBar on iOS 15.4+ - [`aa9140e1`](https://github.com/tauri-apps/plugins-workspace/commit/aa9140e1ac239ab9f015f92b2ed52bbf0eda7c12) ([#2437](https://github.com/tauri-apps/plugins-workspace/pull/2437) by [@enkhjile](https://github.com/tauri-apps/plugins-workspace/../../enkhjile)) Added support for GS1 DataBar on iOS 15.4+
+1 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-barcode-scanner" name = "tauri-plugin-barcode-scanner"
version = "2.4.0" version = "2.4.4"
description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS" description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -10,8 +10,6 @@ repository = { workspace = true }
links = "tauri-plugin-barcode-scanner" links = "tauri-plugin-barcode-scanner"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
@@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "app.tauri.barcodescanner" namespace = "app.tauri.barcodescanner"
compileSdk = 34 compileSdk = 36
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
@@ -33,16 +33,14 @@ android {
} }
dependencies { dependencies {
val camerax_version = "1.5.1"
implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0") implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0") implementation("com.google.android.material:material:1.7.0")
implementation("androidx.camera:camera-core:1.1.0") implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-view:1.1.0") implementation("androidx.camera:camera-camera2:${camerax_version}")
implementation("androidx.camera:camera-lifecycle:1.1.0") implementation("androidx.camera:camera-lifecycle:${camerax_version}")
implementation("androidx.camera:camera-camera2:1.1.0") implementation("androidx.camera:camera-view:${camerax_version}")
implementation("androidx.camera:camera-lifecycle:1.1.0")
implementation("androidx.camera:camera-view:1.1.0")
implementation("com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0") implementation("com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
@@ -54,7 +54,6 @@ import java.util.concurrent.ExecutionException
private const val PERMISSION_ALIAS_CAMERA = "camera" private const val PERMISSION_ALIAS_CAMERA = "camera"
private const val PERMISSION_NAME = Manifest.permission.CAMERA private const val PERMISSION_NAME = Manifest.permission.CAMERA
private const val PREFS_PERMISSION_FIRST_TIME_ASKING = "PREFS_PERMISSION_FIRST_TIME_ASKING"
@InvokeArg @InvokeArg
class ScanOptions { class ScanOptions {
@@ -354,17 +353,6 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
} }
} }
private fun markFirstPermissionRequest() {
val sharedPreference: SharedPreferences =
activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE)
sharedPreference.edit().putBoolean(PERMISSION_NAME, false).apply()
}
private fun firstPermissionRequest(): Boolean {
return activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE)
.getBoolean(PERMISSION_NAME, true)
}
@SuppressLint("ObsoleteSdkInt") @SuppressLint("ObsoleteSdkInt")
@PermissionCallback @PermissionCallback
fun cameraPermissionCallback(invoke: Invoke) { fun cameraPermissionCallback(invoke: Invoke) {
@@ -380,9 +368,7 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
} else { } else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!activity.shouldShowRequestPermissionRationale(PERMISSION_NAME)) { requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
}
} else { } else {
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
} }
@@ -401,20 +387,12 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
} else { } else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (firstPermissionRequest() || activity.shouldShowRequestPermissionRationale( requestPermissionForAlias(
PERMISSION_NAME PERMISSION_ALIAS_CAMERA,
) invoke,
) { "cameraPermissionCallback"
markFirstPermissionRequest() )
requestPermissionForAlias( return
PERMISSION_ALIAS_CAMERA,
invoke,
"cameraPermissionCallback"
)
return
} else {
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
}
} else { } else {
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
} }
@@ -191,6 +191,7 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
if self.captureSession != nil { if self.captureSession != nil {
self.captureSession!.stopRunning() self.captureSession!.stopRunning()
self.cameraView.removePreviewLayer() self.cameraView.removePreviewLayer()
self.cameraView.removeFromSuperview()
self.captureVideoPreviewLayer = nil self.captureVideoPreviewLayer = nil
self.metaOutput = nil self.metaOutput = nil
self.captureSession = nil self.captureSession = nil
@@ -260,27 +261,34 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
} }
private func runScanner(_ invoke: Invoke, args: ScanOptions) { private func runScanner(_ invoke: Invoke, args: ScanOptions) {
if getPermissionState() != "granted" {
invoke.reject("Camera permission denied or not yet requested")
return
}
scanFormats = [AVMetadataObject.ObjectType]() scanFormats = [AVMetadataObject.ObjectType]()
(args.formats ?? []).forEach { format in (args.formats ?? []).forEach { format in
if let formatValue = format.value { if let formatValue = format.value {
scanFormats.append(formatValue) scanFormats.append(formatValue)
} else { } else {
invoke.reject("Unsupported barcode format on this iOS version: \(format)") invoke.reject("Unsupported barcode format on this iOS version: \(format)")
return return
} }
} }
if scanFormats.isEmpty { if scanFormats.isEmpty {
for supportedFormat in SupportedFormat.allCases { for supportedFormat in SupportedFormat.allCases {
if let formatValue = supportedFormat.value { if let formatValue = supportedFormat.value {
scanFormats.append(formatValue) scanFormats.append(formatValue)
}
} }
}
} }
self.metaOutput!.metadataObjectTypes = self.scanFormats self.metaOutput!.metadataObjectTypes = self.scanFormats
self.captureSession!.startRunning() DispatchQueue.main.async {
self.captureSession!.startRunning()
}
self.isScanning = true self.isScanning = true
} }
@@ -297,6 +305,13 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
return return
} }
// Check if camera is available on this platform (iOS simulator doesn't have cameras)
let availableVideoDevices = discoverCaptureDevices()
if availableVideoDevices.isEmpty {
invoke.reject("No camera available on this device (e.g., iOS Simulator)")
return
}
var iOS14min: Bool = false var iOS14min: Bool = false
if #available(iOS 14.0, *) { iOS14min = true } if #available(iOS 14.0, *) { iOS14min = true }
if !iOS14min && self.getPermissionState() != "granted" { if !iOS14min && self.getPermissionState() != "granted" {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-barcode-scanner", "name": "@tauri-apps/plugin-barcode-scanner",
"version": "2.4.0", "version": "2.4.4",
"description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS", "description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
@@ -25,6 +25,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## \[2.3.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.3.1]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
## \[2.3.0] ## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-biometric" name = "tauri-plugin-biometric"
version = "2.3.0" version = "2.3.2"
description = "Prompt the user for biometric authentication on Android and iOS." description = "Prompt the user for biometric authentication on Android and iOS."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -9,8 +9,6 @@ repository = { workspace = true }
links = "tauri-plugin-biometric" links = "tauri-plugin-biometric"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
+2 -2
View File
@@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "app.tauri.biometric" namespace = "app.tauri.biometric"
compileSdk = 34 compileSdk = 36
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-biometric", "name": "@tauri-apps/plugin-biometric",
"version": "2.3.0", "version": "2.3.2",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -25,6 +25,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.4.1]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.4.0] ## \[2.4.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-cli" name = "tauri-plugin-cli"
version = "2.4.0" version = "2.4.1"
description = "Parse arguments from your Tauri application's command line interface." description = "Parse arguments from your Tauri application's command line interface."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -9,10 +9,6 @@ rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-cli" links = "tauri-plugin-cli"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
linux = { level = "full", notes = "" } linux = { level = "full", notes = "" }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-cli", "name": "@tauri-apps/plugin-cli",
"version": "2.4.0", "version": "2.4.1",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## \[2.3.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.3.1]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
## \[2.3.0] ## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+2 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-clipboard-manager" name = "tauri-plugin-clipboard-manager"
version = "2.3.0" version = "2.3.2"
description = "Read and write to the system clipboard." description = "Read and write to the system clipboard."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -10,8 +10,7 @@ repository = { workspace = true }
links = "tauri-plugin-clipboard-manager" links = "tauri-plugin-clipboard-manager"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"] targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
@@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "app.tauri.clipboard" namespace = "app.tauri.clipboard"
compileSdk = 34 compileSdk = 36
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-clipboard-manager", "name": "@tauri-apps/plugin-clipboard-manager",
"version": "2.3.0", "version": "2.3.2",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+17
View File
@@ -1,5 +1,22 @@
# Changelog # Changelog
## \[2.4.6]
- [`28048039`](https://github.com/tauri-apps/plugins-workspace/commit/28048039496e84b46847c008416d341f1349e30e) ([#3143](https://github.com/tauri-apps/plugins-workspace/pull/3143) by [@Tunglies](https://github.com/tauri-apps/plugins-workspace/../../Tunglies)) Fix clippy warnings. No user facing changes.
## \[2.4.5]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.4.4]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
- [`e25a9339`](https://github.com/tauri-apps/plugins-workspace/commit/e25a9339f46268f70dc9afd0c5ab3decbf79b330) ([#3019](https://github.com/tauri-apps/plugins-workspace/pull/3019) by [@kevinschoonover](https://github.com/tauri-apps/plugins-workspace/../../kevinschoonover)) Fix Exec= field in desktop handler if executable path changes
## \[2.4.3]
- [`2522b71f`](https://github.com/tauri-apps/plugins-workspace/commit/2522b71f6bcae65c03b24415eb9295c9e7c84ffc) ([#2970](https://github.com/tauri-apps/plugins-workspace/pull/2970) by [@WSH032](https://github.com/tauri-apps/plugins-workspace/../../WSH032)) Revert the breaking change introduced by [#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928).
## \[2.4.2] ## \[2.4.2]
- [`21d721a0`](https://github.com/tauri-apps/plugins-workspace/commit/21d721a0c2731fc201872f5b99ea8bbdc61b0b60) ([#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Linux, improved error messages when OS commands fail. - [`21d721a0`](https://github.com/tauri-apps/plugins-workspace/commit/21d721a0c2731fc201872f5b99ea8bbdc61b0b60) ([#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Linux, improved error messages when OS commands fail.
+1 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-deep-link" name = "tauri-plugin-deep-link"
version = "2.4.2" version = "2.4.6"
description = "Set your Tauri application as the default handler for an URL" description = "Set your Tauri application as the default handler for an URL"
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -10,8 +10,6 @@ repository = { workspace = true }
links = "tauri-plugin-deep-link" links = "tauri-plugin-deep-link"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
+2 -2
View File
@@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "app.tauri.deep_link" namespace = "app.tauri.deep_link"
compileSdk = 34 compileSdk = 36
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
+2 -8
View File
@@ -137,13 +137,7 @@ fn main() {
let deep_link_domains = config let deep_link_domains = config
.mobile .mobile
.iter() .iter()
.filter_map(|domain| { .filter(|domain| domain.is_app_link())
if domain.is_app_link() {
return None;
}
Some(domain)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if deep_link_domains.is_empty() { if deep_link_domains.is_empty() {
@@ -177,7 +171,7 @@ fn main() {
); );
dict.insert( dict.insert(
"CFBundleURLName".into(), "CFBundleURLName".into(),
format!("{}", domain.scheme[0]).into(), domain.scheme[0].clone().into(),
); );
plist::Value::Dictionary(dict) plist::Value::Dictionary(dict)
}) })
@@ -1,5 +1,29 @@
# Changelog # Changelog
## \[2.2.9]
### Dependencies
- Upgraded to `deep-link-js@2.4.6`
## \[2.2.8]
### Dependencies
- Upgraded to `deep-link-js@2.4.5`
## \[2.2.7]
### Dependencies
- Upgraded to `deep-link-js@2.4.4`
## \[2.2.6]
### Dependencies
- Upgraded to `deep-link-js@2.4.3`
## \[2.2.5] ## \[2.2.5]
### Dependencies ### Dependencies
+5 -5
View File
@@ -1,7 +1,7 @@
{ {
"name": "deep-link-example", "name": "deep-link-example",
"private": true, "private": true,
"version": "2.2.5", "version": "2.2.9",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -10,12 +10,12 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.8.0", "@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-deep-link": "2.4.2" "@tauri-apps/plugin-deep-link": "2.4.6"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.8.1", "@tauri-apps/cli": "2.10.0",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"vite": "^7.0.4" "vite": "^7.3.1"
} }
} }
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-deep-link", "name": "@tauri-apps/plugin-deep-link",
"version": "2.4.2", "version": "2.4.6",
"description": "Set your Tauri application as the default handler for an URL", "description": "Set your Tauri application as the default handler for an URL",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
@@ -25,9 +25,9 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.8.1" "@tauri-apps/cli": "2.10.0"
} }
} }
+10 -3
View File
@@ -23,14 +23,21 @@ pub enum Error {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
#[error(transparent)] #[error(transparent)]
ParseIni(#[from] ini::ParseError), ParseIni(#[from] ini::ParseError),
#[cfg(target_os = "linux")]
#[error("Failed to run OS command `{0}`: {1}")]
Execute(&'static str, #[source] std::io::Error),
#[cfg(mobile)] #[cfg(mobile)]
#[error(transparent)] #[error(transparent)]
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
} }
// TODO(v3): change this into an error in v3,
// see <https://github.com/tauri-apps/plugins-workspace/pull/2970#issuecomment-3244660138>.
#[inline]
#[cfg(target_os = "linux")]
pub(crate) fn inspect_command_error<'a>(command: &'a str) -> impl Fn(&std::io::Error) + 'a {
move |e| {
tracing::error!("Failed to run OS command `{command}`: {e}");
}
}
impl Serialize for Error { impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where where
+25 -6
View File
@@ -293,6 +293,7 @@ mod imp {
.unwrap_or_else(|| bin.into_os_string()) .unwrap_or_else(|| bin.into_os_string())
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
let qualified_exec = format!("{} %u", exec);
let target = self.app.path().data_dir()?.join("applications"); let target = self.app.path().data_dir()?.join("applications");
@@ -304,12 +305,28 @@ mod imp {
if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) { if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) {
if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) { if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) {
// it's ok to remove it - we only write to the file if it's missing
// and in that case we include old_mimes
let old_mimes = section.remove("MimeType").unwrap_or_default(); let old_mimes = section.remove("MimeType").unwrap_or_default();
let mut change = false;
// if the mime type is not present, append it to the list
if !old_mimes.split(';').any(|mime| mime == mime_type) { if !old_mimes.split(';').any(|mime| mime == mime_type) {
section.append("MimeType", format!("{mime_type};{old_mimes}")); section.append("MimeType", format!("{mime_type};{old_mimes}"));
change = true;
} else {
section.insert("MimeType".to_string(), old_mimes);
}
// if the exec command doesnt match, update to the new one
let old_exec = section.remove("Exec").unwrap_or_default();
if old_exec != qualified_exec {
section.append("Exec", qualified_exec);
change = true;
} else {
section.insert("Exec".to_string(), old_exec.to_string());
}
// if any property has changed, rewrite the .desktop file
if change {
desktop_file.write_to_file(&target_file)?; desktop_file.write_to_file(&target_file)?;
} }
} }
@@ -324,7 +341,7 @@ mod imp {
.product_name .product_name
.clone() .clone()
.unwrap_or_else(|| file_name.clone()), .unwrap_or_else(|| file_name.clone()),
exec = exec, qualified_exec = qualified_exec,
mime_type = mime_type mime_type = mime_type
) )
.as_bytes(), .as_bytes(),
@@ -334,12 +351,14 @@ mod imp {
Command::new("update-desktop-database") Command::new("update-desktop-database")
.arg(target) .arg(target)
.status() .status()
.map_err(|error| crate::Error::Execute("update-desktop-database", error))?; .inspect_err(crate::error::inspect_command_error(
"update-desktop-database",
))?;
Command::new("xdg-mime") Command::new("xdg-mime")
.args(["default", &file_name, mime_type.as_str()]) .args(["default", &file_name, mime_type.as_str()])
.status() .status()
.map_err(|error| crate::Error::Execute("xdg-mime", error))?; .inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
Ok(()) Ok(())
} }
@@ -444,7 +463,7 @@ mod imp {
&format!("x-scheme-handler/{}", _protocol.as_ref()), &format!("x-scheme-handler/{}", _protocol.as_ref()),
]) ])
.output() .output()
.map_err(|error| crate::Error::Execute("xdg-mime", error))?; .inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name)) Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name))
} }
+2 -2
View File
@@ -1,7 +1,7 @@
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Name={name} Name={name}
Exec={exec} %u Exec={qualified_exec}
Terminal=false Terminal=false
MimeType={mime_type} MimeType={mime_type}
NoDisplay=true NoDisplay=true
+36
View File
@@ -1,5 +1,41 @@
# Changelog # Changelog
## \[2.6.0]
- [`d7a0bb32`](https://github.com/tauri-apps/plugins-workspace/commit/d7a0bb325dad919d6cc132eb3898c33540de77c4) ([#3136](https://github.com/tauri-apps/plugins-workspace/pull/3136) by [@onehumandev](https://github.com/tauri-apps/plugins-workspace/../../onehumandev)) Add `fileAccessMode` option to file picker.
## \[2.5.0]
- [`dff6fa98`](https://github.com/tauri-apps/plugins-workspace/commit/dff6fa986a9a05ba98b6ca660fea78ae97251fc2) ([#3034](https://github.com/tauri-apps/plugins-workspace/pull/3034) by [@onehumandev](https://github.com/tauri-apps/plugins-workspace/../../onehumandev)) Add `pickerMode` option to file picker (currently only used on iOS)
### Dependencies
- Upgraded to `fs-js@2.4.5`
### feat
- [`c23fa03f`](https://github.com/tauri-apps/plugins-workspace/commit/c23fa03f07d5c1c220bcf0bca482364513e3f754) ([#3098](https://github.com/tauri-apps/plugins-workspace/pull/3098) by [@Lepidopteran](https://github.com/tauri-apps/plugins-workspace/../../Lepidopteran)) Add `xdg-portal` as an optional feature for `rfd`
## \[2.4.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
### Dependencies
- Upgraded to `fs-js@2.4.4`
## \[2.4.1]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
### Dependencies
- Upgraded to `fs-js@2.4.3`
## \[2.4.0]
- [`509eba8d`](https://github.com/tauri-apps/plugins-workspace/commit/509eba8d441c4f6ecf0af77b572cb2afd69a752d) ([#2641](https://github.com/tauri-apps/plugins-workspace/pull/2641) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add support for showing a message dialog with 3 buttons.
## \[2.3.3] ## \[2.3.3]
### Dependencies ### Dependencies
+9 -7
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.3.3" version = "2.6.0"
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application." description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -9,9 +9,12 @@ rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-dialog" links = "tauri-plugin-dialog"
[features]
default = ["gtk3"]
xdg-portal = ["rfd/xdg-portal", "rfd/tokio", "rfd/wayland"]
gtk3 = ["rfd/gtk3"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"] targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
@@ -34,15 +37,14 @@ tauri = { workspace = true }
log = { workspace = true } log = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
url = { workspace = true } url = { workspace = true }
tauri-plugin-fs = { path = "../fs", version = "2.4.2" } tauri-plugin-fs = { path = "../fs", version = "2.4.5" }
[target.'cfg(target_os = "ios")'.dependencies] [target.'cfg(target_os = "ios")'.dependencies]
tauri = { workspace = true, features = ["wry"] } tauri = { workspace = true, features = ["wry"] }
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
rfd = { version = "0.15", default-features = false, features = [ rfd = { version = "0.16", default-features = false, features = [
"tokio",
"gtk3",
"common-controls-v6", "common-controls-v6",
] } ] }
raw-window-handle = "0.6" raw-window-handle = "0.6"
+18
View File
@@ -31,6 +31,24 @@ tauri-plugin-dialog = "2.0.0"
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
``` ```
### Linux XDG Desktop Portal Support
By default, this plugin uses gtk to show dialogs, however since `v2.5.0` you can switch to using [XDG Desktop Portal](https://flatpak.github.io/xdg-desktop-portal/) by adding the following to your `Cargo.toml` file:
```toml
[dependencies]
tauri-plugin-dialog = { version = "2.5.0", default-features = false, features = ["xdg-portal"] }
# alternatively with Git:
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2", default-features = false, features = ["xdg-portal"] }
```
Do note if you use the `xdg-portal` feature, you need to ensure that [`zenity`](https://gitlab.gnome.org/GNOME/zenity) and an [XDG Desktop Portal backend](https://flatpak.github.io/xdg-desktop-portal#using-portals) is installed with your program.
For more information, see [XDG Desktop Portal documentation](https://flatpak.github.io/xdg-desktop-portal/) and [`rfd` documentation](https://docs.rs/rfd/latest/rfd#xdg-desktop-portal-backend).
### JavaScript
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
```sh ```sh
+2 -2
View File
@@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "app.tauri.dialog" namespace = "app.tauri.dialog"
compileSdk = 34 compileSdk = 36
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
@@ -31,6 +31,7 @@ class Filter {
class FilePickerOptions { class FilePickerOptions {
lateinit var filters: Array<Filter> lateinit var filters: Array<Filter>
var multiple: Boolean? = null var multiple: Boolean? = null
var pickerMode: String? = null
} }
@InvokeArg @InvokeArg
@@ -38,6 +39,7 @@ class MessageOptions {
var title: String? = null var title: String? = null
lateinit var message: String lateinit var message: String
var okButtonLabel: String? = null var okButtonLabel: String? = null
var noButtonLabel: String? = null
var cancelButtonLabel: String? = null var cancelButtonLabel: String? = null
} }
@@ -60,10 +62,19 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
// TODO: ACTION_OPEN_DOCUMENT ?? // TODO: ACTION_OPEN_DOCUMENT ??
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
if (parsedTypes.isNotEmpty()) { if (args.pickerMode == "image") {
intent.type = "image/*"
} else if (args.pickerMode == "video") {
intent.type = "video/*"
} else if (args.pickerMode == "media") {
intent.type = "*/*"
intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("video/*", "image/*"))
} else if (parsedTypes.isNotEmpty()) {
intent.type = "*/*"
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes) intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
} else {
intent.type = "*/*"
} }
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, args.multiple ?: false) intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, args.multiple ?: false)
@@ -139,9 +150,8 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
return return
} }
val handler = { cancelled: Boolean, value: Boolean -> val handler = { value: String ->
val ret = JSObject() val ret = JSObject()
ret.put("cancelled", cancelled)
ret.put("value", value) ret.put("value", value)
invoke.resolve(ret) invoke.resolve(ret)
} }
@@ -153,24 +163,34 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
if (args.title != null) { if (args.title != null) {
builder.setTitle(args.title) builder.setTitle(args.title)
} }
val okButtonLabel = args.okButtonLabel ?: "Ok"
builder builder
.setMessage(args.message) .setMessage(args.message)
.setPositiveButton( .setPositiveButton(okButtonLabel) { dialog, _ ->
args.okButtonLabel ?: "OK"
) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
handler(false, true) handler(okButtonLabel)
} }
.setOnCancelListener { dialog -> .setOnCancelListener { dialog ->
dialog.dismiss() dialog.dismiss()
handler(true, false) handler(args.cancelButtonLabel ?: "Cancel")
} }
if (args.noButtonLabel != null) {
builder.setNeutralButton(args.noButtonLabel) { dialog, _ ->
dialog.dismiss()
handler(args.noButtonLabel!!)
}
}
if (args.cancelButtonLabel != null) { if (args.cancelButtonLabel != null) {
builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ -> builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
handler(false, false) handler(args.cancelButtonLabel!!)
} }
} }
val dialog = builder.create() val dialog = builder.create()
dialog.show() dialog.show()
} }
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,yesButtonLabel:i?.okLabel?.toString(),noButtonLabel:i?.cancelLabel?.toString()})},t.confirm=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),cancelButtonLabel:i?.cancelLabel?.toString()})},t.message=async function(t,e){const i="string"==typeof e?{title:e}:e;await n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString()})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})} if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}function e(t){if(void 0!==t)return"string"==typeof t?t:"ok"in t&&"cancel"in t?{OkCancelCustom:[t.ok,t.cancel]}:"yes"in t&&"no"in t&&"cancel"in t?{YesNoCancelCustom:[t.yes,t.no,t.cancel]}:"ok"in t?{OkCustom:t.ok}:void 0}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,yesButtonLabel:o?.okLabel?.toString(),noButtonLabel:o?.cancelLabel?.toString()})},t.confirm=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,okButtonLabel:o?.okLabel?.toString(),cancelButtonLabel:o?.cancelLabel?.toString()})},t.message=async function(t,o){const i="string"==typeof o?{title:o}:o;return n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),buttons:e(i?.buttons)})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})}
+210 -5
View File
@@ -14,6 +14,16 @@ interface DialogFilter {
name: string name: string
/** /**
* Extensions to filter, without a `.` prefix. * Extensions to filter, without a `.` prefix.
*
* **Note:** Mobile platforms have different APIs for filtering that may not support extensions.
* iOS: Extensions are supported in the document picker, but not in the media picker.
* Android: Extensions are not supported.
*
* For these platforms, MIME types are the primary way to filter files, as opposed to extensions.
* This means the string values here labeled as `extensions` may also be a MIME type.
* This property name of `extensions` is being kept for backwards compatibility, but this may be revisited to
* specify the difference between extension or MIME type filtering.
*
* @example * @example
* ```typescript * ```typescript
* extensions: ['svg', 'png'] * extensions: ['svg', 'png']
@@ -30,7 +40,14 @@ interface DialogFilter {
interface OpenDialogOptions { interface OpenDialogOptions {
/** The title of the dialog window (desktop only). */ /** The title of the dialog window (desktop only). */
title?: string title?: string
/** The filters of the dialog. */ /**
* The filters of the dialog.
* On mobile platforms, if either:
* A) the {@linkcode pickerMode} is set to `media`, `image`, or `video`
* -- or --
* B) the filters include **only** either image or video mime types, the media picker will be displayed.
* Otherwise, the document picker will be displayed.
*/
filters?: DialogFilter[] filters?: DialogFilter[]
/** /**
* Initial directory or file path. * Initial directory or file path.
@@ -52,6 +69,38 @@ interface OpenDialogOptions {
recursive?: boolean recursive?: boolean
/** Whether to allow creating directories in the dialog. Enabled by default. **macOS Only** */ /** Whether to allow creating directories in the dialog. Enabled by default. **macOS Only** */
canCreateDirectories?: boolean canCreateDirectories?: boolean
/**
* The preferred mode of the dialog.
* This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
* If not provided, the dialog will automatically choose the best mode based on the MIME types or extensions of the {@linkcode filters}.
* On desktop, this option is ignored.
*/
pickerMode?: PickerMode
/**
* The file access mode of the dialog.
* If not provided, `copy` is used, which matches the behavior of the {@linkcode open} method before the introduction of this option.
*
* **Usage**
* If a file is opened with {@linkcode fileAccessMode: 'copy'}, it will be copied to the app's sandbox.
* This means the file can be read, edited, deleted, copied, or any other operation without any issues, since the file
* now belongs to the app.
* This also means that the caller has responsibility of deleting the file if this file is not meant to be retained
* in the app sandbox.
*
* If a file is opened with {@linkcode fileAccessMode: 'scoped'}, the file will remain in its original location
* and security-scoped access will be automatically managed by the system.
*
* **Note**
* This is specifically meant for document pickers on iOS or MacOS, in conjunction with [security scoped resources](https://developer.apple.com/documentation/foundation/nsurl/startaccessingsecurityscopedresource()).
*
* Why only document pickers, and not image or video pickers?
* The image and video pickers on iOS behave differently from the document pickers, and return [NSItemProvider](https://developer.apple.com/documentation/foundation/nsitemprovider) objects instead of file URLs.
* These are meant to be ephemeral (only available within the callback of the picker), and are not accessible outside of the callback.
* So for image and video pickers, the only way to access the file is to copy it to the app's sandbox, and this is the URL that is returned from this API.
* This means there is no provision for using `scoped` mode with image or video pickers.
* If an image or video picker is used, `copy` is always used.
*/
fileAccessMode?: FileAccessMode
} }
/** /**
@@ -77,6 +126,100 @@ interface SaveDialogOptions {
canCreateDirectories?: boolean canCreateDirectories?: boolean
} }
/**
* The preferred mode of the dialog.
* This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
* On desktop, this option is ignored.
* If not provided, the dialog will automatically choose the best mode based on the MIME types or extensions of the {@linkcode filters}.
*
* **Note:** This option is only supported on iOS 14 and above. This parameter is ignored on iOS 13 and below.
*/
export type PickerMode = 'document' | 'media' | 'image' | 'video'
/**
* The file access mode of the dialog.
*
* - `copy`: copy/move the picked file to the app sandbox; no scoped access required.
* - `scoped`: keep file in place; security-scoped access is automatically managed.
*
* **Note:** This option is only supported on iOS 14 and above. This parameter is ignored on iOS 13 and below.
*/
export type FileAccessMode = 'copy' | 'scoped'
/**
* Default buttons for a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogDefaultButtons =
| 'Ok'
| 'OkCancel'
| 'YesNo'
| 'YesNoCancel'
/** All possible button keys. */
type ButtonKey = 'ok' | 'cancel' | 'yes' | 'no'
/** Ban everything except a set of keys. */
type BanExcept<Allowed extends ButtonKey> = Partial<
Record<Exclude<ButtonKey, Allowed>, never>
>
/**
* The Yes, No and Cancel buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsYesNoCancel = {
/** The Yes button. */
yes: string
/** The No button. */
no: string
/** The Cancel button. */
cancel: string
} & BanExcept<'yes' | 'no' | 'cancel'>
/**
* The Ok and Cancel buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsOkCancel = {
/** The Ok button. */
ok: string
/** The Cancel button. */
cancel: string
} & BanExcept<'ok' | 'cancel'>
/**
* The Ok button of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsOk = {
/** The Ok button. */
ok: string
} & BanExcept<'ok'>
/**
* Custom buttons for a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogCustomButtons =
| MessageDialogButtonsYesNoCancel
| MessageDialogButtonsOkCancel
| MessageDialogButtonsOk
/**
* The buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtons =
| MessageDialogDefaultButtons
| MessageDialogCustomButtons
/** /**
* @since 2.0.0 * @since 2.0.0
*/ */
@@ -85,8 +228,58 @@ interface MessageDialogOptions {
title?: string title?: string
/** The kind of the dialog. Defaults to `info`. */ /** The kind of the dialog. Defaults to `info`. */
kind?: 'info' | 'warning' | 'error' kind?: 'info' | 'warning' | 'error'
/** The label of the confirm button. */ /**
* The label of the Ok button.
*
* @deprecated Use {@linkcode MessageDialogOptions.buttons} instead.
*/
okLabel?: string okLabel?: string
/**
* The buttons of the dialog.
*
* @example
*
* ```ts
* // Use system default buttons texts
* await message('Hello World!', { buttons: 'Ok' })
* await message('Hello World!', { buttons: 'OkCancel' })
*
* // Or with custom button texts
* await message('Hello World!', { buttons: { ok: 'Yes!' } })
* await message('Take on the task?', {
* buttons: { ok: 'Accept', cancel: 'Cancel' }
* })
* await message('Show the file content?', {
* buttons: { yes: 'Show content', no: 'Show in folder', cancel: 'Cancel' }
* })
* ```
*
* @since 2.4.0
*/
buttons?: MessageDialogButtons
}
/**
* Internal function to convert the buttons to the Rust type.
*/
function buttonsToRust(buttons: MessageDialogButtons | undefined) {
if (buttons === undefined) {
return undefined
}
if (typeof buttons === 'string') {
return buttons
} else if ('ok' in buttons && 'cancel' in buttons) {
return { OkCancelCustom: [buttons.ok, buttons.cancel] }
} else if ('yes' in buttons && 'no' in buttons && 'cancel' in buttons) {
return {
YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel]
}
} else if ('ok' in buttons) {
return { OkCustom: buttons.ok }
}
return undefined
} }
interface ConfirmDialogOptions { interface ConfirmDialogOptions {
@@ -202,6 +395,16 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
return await invoke('plugin:dialog|save', { options }) return await invoke('plugin:dialog|save', { options })
} }
/**
* The result of a message dialog.
*
* The result is a string if the dialog has custom buttons,
* otherwise it is one of the default buttons.
*
* @since 2.4.0
*/
export type MessageDialogResult = 'Yes' | 'No' | 'Ok' | 'Cancel' | (string & {})
/** /**
* Shows a message dialog with an `Ok` button. * Shows a message dialog with an `Ok` button.
* @example * @example
@@ -222,13 +425,15 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
async function message( async function message(
message: string, message: string,
options?: string | MessageDialogOptions options?: string | MessageDialogOptions
): Promise<void> { ): Promise<MessageDialogResult> {
const opts = typeof options === 'string' ? { title: options } : options const opts = typeof options === 'string' ? { title: options } : options
await invoke('plugin:dialog|message', {
return invoke<MessageDialogResult>('plugin:dialog|message', {
message: message.toString(), message: message.toString(),
title: opts?.title?.toString(), title: opts?.title?.toString(),
kind: opts?.kind, kind: opts?.kind,
okButtonLabel: opts?.okLabel?.toString() okButtonLabel: opts?.okLabel?.toString(),
buttons: buttonsToRust(opts?.buttons)
}) })
} }
+138 -66
View File
@@ -8,6 +8,7 @@ import PhotosUI
import SwiftRs import SwiftRs
import Tauri import Tauri
import UIKit import UIKit
import UniformTypeIdentifiers
import WebKit import WebKit
enum FilePickerEvent { enum FilePickerEvent {
@@ -20,6 +21,7 @@ struct MessageDialogOptions: Decodable {
var title: String? var title: String?
let message: String let message: String
var okButtonLabel: String? var okButtonLabel: String?
var noButtonLabel: String?
var cancelButtonLabel: String? var cancelButtonLabel: String?
} }
@@ -31,6 +33,8 @@ struct FilePickerOptions: Decodable {
var multiple: Bool? var multiple: Bool?
var filters: [Filter]? var filters: [Filter]?
var defaultPath: String? var defaultPath: String?
var pickerMode: PickerMode?
var fileAccessMode: FileAccessMode?
} }
struct SaveFileDialogOptions: Decodable { struct SaveFileDialogOptions: Decodable {
@@ -38,6 +42,18 @@ struct SaveFileDialogOptions: Decodable {
var defaultPath: String? var defaultPath: String?
} }
enum FileAccessMode: String, Decodable {
case copy
case scoped
}
enum PickerMode: String, Decodable {
case document
case media
case image
case video
}
class DialogPlugin: Plugin { class DialogPlugin: Plugin {
var filePickerController: FilePickerController! var filePickerController: FilePickerController!
@@ -46,31 +62,12 @@ class DialogPlugin: Plugin {
override init() { override init() {
super.init() super.init()
filePickerController = FilePickerController(self) filePickerController = FilePickerController(self)
} }
@objc public func showFilePicker(_ invoke: Invoke) throws { @objc public func showFilePicker(_ invoke: Invoke) throws {
let args = try invoke.parseArgs(FilePickerOptions.self) let args = try invoke.parseArgs(FilePickerOptions.self)
let parsedTypes = parseFiltersOption(args.filters ?? [])
var isMedia = !parsedTypes.isEmpty
var uniqueMimeType: Bool? = nil
var mimeKind: String? = nil
if !parsedTypes.isEmpty {
uniqueMimeType = true
for mime in parsedTypes {
let kind = mime.components(separatedBy: "/")[0]
if kind != "image" && kind != "video" {
isMedia = false
}
if mimeKind == nil {
mimeKind = kind
} else if mimeKind != kind {
uniqueMimeType = false
}
}
}
onFilePickerResult = { (event: FilePickerEvent) -> Void in onFilePickerResult = { (event: FilePickerEvent) -> Void in
switch event { switch event {
case .selected(let urls): case .selected(let urls):
@@ -82,49 +79,59 @@ class DialogPlugin: Plugin {
} }
} }
if uniqueMimeType == true || isMedia { if #available(iOS 14, *) {
DispatchQueue.main.async { let parsedTypes = parseFiltersOption(args.filters ?? [])
if #available(iOS 14, *) {
let mimeKinds = Set(
parsedTypes.compactMap { $0.preferredMIMEType?.components(separatedBy: "/")[0] })
let filtersIncludeImage = mimeKinds.contains("image")
let filtersIncludeVideo = mimeKinds.contains("video")
let filtersIncludeNonMedia = mimeKinds.contains(where: { $0 != "image" && $0 != "video" })
// If the picker mode is media, images, or videos, we always want to show the media picker regardless of what's in the filters.
// Otherwise, if the filters A) do not include non-media types and B) include either image or video, we want to show the media picker.
if args.pickerMode == .media
|| args.pickerMode == .image
|| args.pickerMode == .video
|| (!filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo))
{
DispatchQueue.main.async {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1 configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1
if uniqueMimeType == true { // If the filters include image or video, use the appropriate filter.
if mimeKind == "image" { // If both are true, don't define a filter, which means we will display all media.
configuration.filter = .images if args.pickerMode == .image || (filtersIncludeImage && !filtersIncludeVideo) {
} else if mimeKind == "video" { configuration.filter = .images
configuration.filter = .videos } else if args.pickerMode == .video || (filtersIncludeVideo && !filtersIncludeImage) {
} configuration.filter = .videos
} }
let picker = PHPickerViewController(configuration: configuration) let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self.filePickerController picker.delegate = self.filePickerController
picker.modalPresentationStyle = .fullScreen picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker) self.presentViewController(picker)
} else { }
let picker = UIImagePickerController() } else {
picker.delegate = self.filePickerController DispatchQueue.main.async {
// The UTType.item is the catch-all, allowing for any file type to be selected.
let contentTypes = parsedTypes.isEmpty ? [UTType.item] : parsedTypes
let picker: UIDocumentPickerViewController = UIDocumentPickerViewController(
forOpeningContentTypes: contentTypes,
asCopy: args.fileAccessMode == .scoped ? false : true)
if uniqueMimeType == true && mimeKind == "image" { if let defaultPath = args.defaultPath {
picker.sourceType = .photoLibrary picker.directoryURL = URL(string: defaultPath)
} }
picker.sourceType = .photoLibrary picker.delegate = self.filePickerController
picker.allowsMultipleSelection = args.multiple ?? false
picker.modalPresentationStyle = .fullScreen picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker) self.presentViewController(picker)
} }
} }
} else { } else {
let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes showFilePickerLegacy(args: args)
DispatchQueue.main.async {
let picker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)
if let defaultPath = args.defaultPath {
picker.directoryURL = URL(string: defaultPath)
}
picker.delegate = self.filePickerController
picker.allowsMultipleSelection = args.multiple ?? false
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
} }
} }
@@ -172,7 +179,68 @@ class DialogPlugin: Plugin {
self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil) self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
} }
private func parseFiltersOption(_ filters: [Filter]) -> [String] { @available(iOS 14, *)
private func parseFiltersOption(_ filters: [Filter]) -> [UTType] {
var parsedTypes: [UTType] = []
for filter in filters {
for ext in filter.extensions ?? [] {
// We need to support extensions as well as MIME types.
if let utType = UTType(mimeType: ext) {
parsedTypes.append(utType)
} else if let utType = UTType(filenameExtension: ext) {
parsedTypes.append(utType)
}
}
}
return parsedTypes
}
/// This function is only used for iOS < 14, and should be removed if/when the deployment target is raised to 14.
private func showFilePickerLegacy(args: FilePickerOptions) {
let parsedTypes = parseFiltersOptionLegacy(args.filters ?? [])
var filtersIncludeImage: Bool = false
var filtersIncludeVideo: Bool = false
var filtersIncludeNonMedia: Bool = false
if !parsedTypes.isEmpty {
let mimeKinds = Set(parsedTypes.map { $0.components(separatedBy: "/")[0] })
filtersIncludeImage = mimeKinds.contains("image")
filtersIncludeVideo = mimeKinds.contains("video")
filtersIncludeNonMedia = mimeKinds.contains(where: { $0 != "image" && $0 != "video" })
}
if !filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo) {
DispatchQueue.main.async {
let picker = UIImagePickerController()
picker.delegate = self.filePickerController
if filtersIncludeImage && !filtersIncludeVideo {
picker.sourceType = .photoLibrary
}
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
} else {
let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes
DispatchQueue.main.async {
let picker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)
if let defaultPath = args.defaultPath {
picker.directoryURL = URL(string: defaultPath)
}
picker.delegate = self.filePickerController
picker.allowsMultipleSelection = args.multiple ?? false
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
}
}
/// This function is only used for iOS < 14, and should be removed if/when the deployment target is raised to 14.
private func parseFiltersOptionLegacy(_ filters: [Filter]) -> [String] {
var parsedTypes: [String] = [] var parsedTypes: [String] = []
for filter in filters { for filter in filters {
for ext in filter.extensions ?? [] { for ext in filter.extensions ?? [] {
@@ -185,6 +253,7 @@ class DialogPlugin: Plugin {
parsedTypes.append(utType) parsedTypes.append(utType)
} }
} }
return parsedTypes return parsedTypes
} }
@@ -200,39 +269,42 @@ class DialogPlugin: Plugin {
let alert = UIAlertController( let alert = UIAlertController(
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert) title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert)
let cancelButtonLabel = args.cancelButtonLabel ?? "" if let cancelButtonLabel = args.cancelButtonLabel {
if !cancelButtonLabel.isEmpty {
alert.addAction( alert.addAction(
UIAlertAction( UIAlertAction(
title: cancelButtonLabel, style: UIAlertAction.Style.default, title: cancelButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in handler: { (_) -> Void in
Logger.error("cancel") invoke.resolve(["value": cancelButtonLabel])
}
invoke.resolve([ )
"value": false, )
"cancelled": false,
])
}))
} }
let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "") if let noButtonLabel = args.noButtonLabel {
if !okButtonLabel.isEmpty {
alert.addAction( alert.addAction(
UIAlertAction( UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default, title: noButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in handler: { (_) -> Void in
Logger.error("ok") invoke.resolve(["value": noButtonLabel])
}
invoke.resolve([ )
"value": true, )
"cancelled": false,
])
}))
} }
let okButtonLabel = args.okButtonLabel ?? "Ok"
alert.addAction(
UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
invoke.resolve(["value": okButtonLabel])
}
)
)
manager.viewController?.present(alert, animated: true, completion: nil) manager.viewController?.present(alert, animated: true, completion: nil)
} }
} }
} }
@_cdecl("init_plugin_dialog") @_cdecl("init_plugin_dialog")
@@ -95,16 +95,33 @@ public class FilePickerController: NSObject {
return nil return nil
} }
} }
/// ## In which cases do we need to save a copy of a file selected by a user to the app sandbox?
/// In short, only when the file is **not** selected using UIDocumentPickerDelegate.
/// For the rest of the cases, we need to write a copy of the file to the app sandbox.
///
/// For PHPicker (used for photos and videos), `NSItemProvider.loadFileRepresentation` returns a temporary file URL that is deleted after the completion handler.
/// The recommendation is to [Persist](https://developer.apple.com/documentation/foundation/nsitemprovider/2888338-loadfilerepresentation) the file by moving/copying
/// it to your apps directory within the completion handler.
///
/// If available, `loadInPlaceFileRepresentation` can open a file in place; Photos assets typically do not support true in-place access,
/// so fall back to persisting a local file.
/// Ref: https://developer.apple.com/documentation/foundation/nsitemprovider/2888335-loadinplacefilerepresentation
///
/// For UIDocumentPicker, prefer "open in place" and avoid copying when possible.
/// Ref: https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller
private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL { private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
var directory = URL(fileURLWithPath: NSTemporaryDirectory()) var directory = URL(fileURLWithPath: NSTemporaryDirectory())
if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first { if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
directory = cachesDirectory directory = cachesDirectory
} }
let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent) let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent)
do { do {
try deleteFile(targetUrl) try deleteFile(targetUrl)
} }
try FileManager.default.copyItem(at: sourceUrl, to: targetUrl) try FileManager.default.copyItem(at: sourceUrl, to: targetUrl)
return targetUrl return targetUrl
} }
@@ -119,8 +136,7 @@ public class FilePickerController: NSObject {
extension FilePickerController: UIDocumentPickerDelegate { extension FilePickerController: UIDocumentPickerDelegate {
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
do { do {
let temporaryUrls = try urls.map { try saveTemporaryFile($0) } self.plugin.onFilePickerEvent(.selected(urls))
self.plugin.onFilePickerEvent(.selected(temporaryUrls))
} catch { } catch {
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file")) self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
} }
@@ -191,6 +207,8 @@ extension FilePickerController: PHPickerViewControllerDelegate {
return return
} }
do { do {
// We have to make a copy of the file to the app sandbox here, as PHPicker returns an NSItemProvider with either an ephemeral file URL or content that is deleted after the completion handler.
// This is a different behavior from UIDocumentPicker, where the file can either be copied to the app sandbox or opened in place, and then accessed with `startAccessingSecurityScopedResource`.
let temporaryUrl = try self.saveTemporaryFile(url) let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl) temporaryUrls.append(temporaryUrl)
} catch { } catch {
@@ -212,6 +230,8 @@ extension FilePickerController: PHPickerViewControllerDelegate {
return return
} }
do { do {
// We have to make a copy of the file to the app sandbox here, as PHPicker returns an NSItemProvider with either an ephemeral file URL or content that is deleted after the completion handler.
// This is a different behavior from UIDocumentPicker, where the file can either be copied to the app sandbox or opened in place, and then accessed with `startAccessingSecurityScopedResource`.
let temporaryUrl = try self.saveTemporaryFile(url) let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl) temporaryUrls.append(temporaryUrl)
} catch { } catch {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-dialog", "name": "@tauri-apps/plugin-dialog",
"version": "2.3.3", "version": "2.6.0",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+39 -21
View File
@@ -9,8 +9,9 @@ use tauri::{command, Manager, Runtime, State, Window};
use tauri_plugin_fs::FsExt; use tauri_plugin_fs::FsExt;
use crate::{ use crate::{
Dialog, FileDialogBuilder, FilePath, MessageDialogButtons, MessageDialogKind, Result, CANCEL, Dialog, FileAccessMode, FileDialogBuilder, FilePath, MessageDialogBuilder,
NO, OK, YES, MessageDialogButtons, MessageDialogKind, MessageDialogResult, PickerMode, Result, CANCEL, NO,
OK, YES,
}; };
#[derive(Serialize)] #[derive(Serialize)]
@@ -56,6 +57,17 @@ pub struct OpenDialogOptions {
recursive: bool, recursive: bool,
/// Whether to allow creating directories in the dialog **macOS Only** /// Whether to allow creating directories in the dialog **macOS Only**
can_create_directories: Option<bool>, can_create_directories: Option<bool>,
/// The preferred mode of the dialog.
/// This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
/// On desktop, this option is ignored.
/// If not provided, the dialog will automatically choose the best mode based on the MIME types of the filters.
#[serde(default)]
#[cfg_attr(mobile, allow(dead_code))]
picker_mode: Option<PickerMode>,
/// The file access mode of the dialog.
#[serde(default)]
#[cfg_attr(mobile, allow(dead_code))]
file_access_mode: Option<FileAccessMode>,
} }
/// The options for the save dialog API. /// The options for the save dialog API.
@@ -127,10 +139,16 @@ pub(crate) async fn open<R: Runtime>(
if let Some(can) = options.can_create_directories { if let Some(can) = options.can_create_directories {
dialog_builder = dialog_builder.set_can_create_directories(can); dialog_builder = dialog_builder.set_can_create_directories(can);
} }
if let Some(picker_mode) = options.picker_mode {
dialog_builder = dialog_builder.set_picker_mode(picker_mode);
}
for filter in options.filters { for filter in options.filters {
let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect(); let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
dialog_builder = dialog_builder.add_filter(filter.name, &extensions); dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
} }
if let Some(file_access_mode) = options.file_access_mode {
dialog_builder = dialog_builder.set_file_access_mode(file_access_mode);
}
let res = if options.directory { let res = if options.directory {
#[cfg(desktop)] #[cfg(desktop)]
@@ -248,7 +266,7 @@ fn message_dialog<R: Runtime>(
message: String, message: String,
kind: Option<MessageDialogKind>, kind: Option<MessageDialogKind>,
buttons: MessageDialogButtons, buttons: MessageDialogButtons,
) -> bool { ) -> MessageDialogBuilder<R> {
let mut builder = dialog.message(message); let mut builder = dialog.message(message);
builder = builder.buttons(buttons); builder = builder.buttons(buttons);
@@ -266,7 +284,7 @@ fn message_dialog<R: Runtime>(
builder = builder.kind(kind); builder = builder.kind(kind);
} }
builder.blocking_show() builder
} }
#[command] #[command]
@@ -277,19 +295,15 @@ pub(crate) async fn message<R: Runtime>(
message: String, message: String,
kind: Option<MessageDialogKind>, kind: Option<MessageDialogKind>,
ok_button_label: Option<String>, ok_button_label: Option<String>,
) -> Result<bool> { buttons: Option<MessageDialogButtons>,
Ok(message_dialog( ) -> Result<MessageDialogResult> {
window, let buttons = buttons.unwrap_or(if let Some(ok_button_label) = ok_button_label {
dialog, MessageDialogButtons::OkCustom(ok_button_label)
title, } else {
message, MessageDialogButtons::Ok
kind, });
if let Some(ok_button_label) = ok_button_label {
MessageDialogButtons::OkCustom(ok_button_label) Ok(message_dialog(window, dialog, title, message, kind, buttons).blocking_show_with_result())
} else {
MessageDialogButtons::Ok
},
))
} }
#[command] #[command]
@@ -302,7 +316,7 @@ pub(crate) async fn ask<R: Runtime>(
yes_button_label: Option<String>, yes_button_label: Option<String>,
no_button_label: Option<String>, no_button_label: Option<String>,
) -> Result<bool> { ) -> Result<bool> {
Ok(message_dialog( let dialog = message_dialog(
window, window,
dialog, dialog,
title, title,
@@ -318,7 +332,9 @@ pub(crate) async fn ask<R: Runtime>(
} else { } else {
MessageDialogButtons::YesNo MessageDialogButtons::YesNo
}, },
)) );
Ok(dialog.blocking_show())
} }
#[command] #[command]
@@ -331,7 +347,7 @@ pub(crate) async fn confirm<R: Runtime>(
ok_button_label: Option<String>, ok_button_label: Option<String>,
cancel_button_label: Option<String>, cancel_button_label: Option<String>,
) -> Result<bool> { ) -> Result<bool> {
Ok(message_dialog( let dialog = message_dialog(
window, window,
dialog, dialog,
title, title,
@@ -347,5 +363,7 @@ pub(crate) async fn confirm<R: Runtime>(
} else { } else {
MessageDialogButtons::OkCancel MessageDialogButtons::OkCancel
}, },
)) );
Ok(dialog.blocking_show())
} }
+40 -18
View File
@@ -13,7 +13,7 @@ use rfd::{AsyncFileDialog, AsyncMessageDialog};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tauri::{plugin::PluginApi, AppHandle, Runtime}; use tauri::{plugin::PluginApi, AppHandle, Runtime};
use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder, OK}; use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder};
pub fn init<R: Runtime, C: DeserializeOwned>( pub fn init<R: Runtime, C: DeserializeOwned>(
app: &AppHandle<R>, app: &AppHandle<R>,
@@ -115,6 +115,10 @@ impl From<MessageDialogButtons> for rfd::MessageButtons {
MessageDialogButtons::YesNo => Self::YesNo, MessageDialogButtons::YesNo => Self::YesNo,
MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok), MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok),
MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel), MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel),
MessageDialogButtons::YesNoCancel => Self::YesNoCancel,
MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => {
Self::YesNoCancelCustom(yes, no, cancel)
}
} }
} }
} }
@@ -208,28 +212,46 @@ pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
} }
/// Shows a message dialog /// Shows a message dialog
pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>( pub fn show_message_dialog<R: Runtime, F: FnOnce(MessageDialogResult) + Send + 'static>(
dialog: MessageDialogBuilder<R>, dialog: MessageDialogBuilder<R>,
f: F, callback: F,
) { ) {
use rfd::MessageDialogResult; let f = move |res: rfd::MessageDialogResult| callback(res.into());
let ok_label = match &dialog.buttons {
MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
_ => None,
};
let f = move |res| {
f(match res {
MessageDialogResult::Ok | MessageDialogResult::Yes => true,
MessageDialogResult::Custom(s) => ok_label.map_or(s == OK, |ok_label| ok_label == s),
_ => false,
});
};
let handle = dialog.dialog.app_handle().to_owned(); let handle = dialog.dialog.app_handle().to_owned();
let _ = handle.run_on_main_thread(move || { let _ = handle.run_on_main_thread(move || {
let buttons = dialog.buttons.clone();
let dialog = AsyncMessageDialog::from(dialog).show(); let dialog = AsyncMessageDialog::from(dialog).show();
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog))); std::thread::spawn(move || {
let result = tauri::async_runtime::block_on(dialog);
// on Linux rfd does not return rfd::MessageDialogResult::Custom, so we must map manually
let result = match (result, buttons) {
(rfd::MessageDialogResult::Ok, MessageDialogButtons::OkCustom(s)) => {
rfd::MessageDialogResult::Custom(s)
}
(
rfd::MessageDialogResult::Ok,
MessageDialogButtons::OkCancelCustom(ok, _cancel),
) => rfd::MessageDialogResult::Custom(ok),
(
rfd::MessageDialogResult::Cancel,
MessageDialogButtons::OkCancelCustom(_ok, cancel),
) => rfd::MessageDialogResult::Custom(cancel),
(
rfd::MessageDialogResult::Yes,
MessageDialogButtons::YesNoCancelCustom(yes, _no, _cancel),
) => rfd::MessageDialogResult::Custom(yes),
(
rfd::MessageDialogResult::No,
MessageDialogButtons::YesNoCancelCustom(_yes, no, _cancel),
) => rfd::MessageDialogResult::Custom(no),
(
rfd::MessageDialogResult::Cancel,
MessageDialogButtons::YesNoCancelCustom(_yes, _no, cancel),
) => rfd::MessageDialogResult::Custom(cancel),
(result, _) => result,
};
f(result);
});
}); });
} }
+127 -14
View File
@@ -9,7 +9,7 @@
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
)] )]
use serde::Serialize; use serde::{Deserialize, Serialize};
use tauri::{ use tauri::{
plugin::{Builder, TauriPlugin}, plugin::{Builder, TauriPlugin},
Manager, Runtime, Manager, Runtime,
@@ -44,6 +44,22 @@ pub use desktop::Dialog;
#[cfg(mobile)] #[cfg(mobile)]
pub use mobile::Dialog; pub use mobile::Dialog;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum PickerMode {
Document,
Media,
Image,
Video,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum FileAccessMode {
Copy,
Scoped,
}
pub(crate) const OK: &str = "Ok"; pub(crate) const OK: &str = "Ok";
pub(crate) const CANCEL: &str = "Cancel"; pub(crate) const CANCEL: &str = "Cancel";
pub(crate) const YES: &str = "Yes"; pub(crate) const YES: &str = "Yes";
@@ -182,7 +198,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
commands::save, commands::save,
commands::message, commands::message,
commands::ask, commands::ask,
commands::confirm commands::confirm,
]) ])
.setup(|app, api| { .setup(|app, api| {
#[cfg(mobile)] #[cfg(mobile)]
@@ -216,6 +232,7 @@ pub(crate) struct MessageDialogPayload<'a> {
message: &'a String, message: &'a String,
kind: &'a MessageDialogKind, kind: &'a MessageDialogKind,
ok_button_label: Option<&'a str>, ok_button_label: Option<&'a str>,
no_button_label: Option<&'a str>,
cancel_button_label: Option<&'a str>, cancel_button_label: Option<&'a str>,
} }
@@ -238,13 +255,17 @@ impl<R: Runtime> MessageDialogBuilder<R> {
#[cfg(mobile)] #[cfg(mobile)]
pub(crate) fn payload(&self) -> MessageDialogPayload<'_> { pub(crate) fn payload(&self) -> MessageDialogPayload<'_> {
let (ok_button_label, cancel_button_label) = match &self.buttons { let (ok_button_label, no_button_label, cancel_button_label) = match &self.buttons {
MessageDialogButtons::Ok => (Some(OK), None), MessageDialogButtons::Ok => (Some(OK), None, None),
MessageDialogButtons::OkCancel => (Some(OK), Some(CANCEL)), MessageDialogButtons::OkCancel => (Some(OK), None, Some(CANCEL)),
MessageDialogButtons::YesNo => (Some(YES), Some(NO)), MessageDialogButtons::YesNo => (Some(YES), Some(NO), None),
MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), Some(CANCEL)), MessageDialogButtons::YesNoCancel => (Some(YES), Some(NO), Some(CANCEL)),
MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), None, None),
MessageDialogButtons::OkCancelCustom(ok, cancel) => { MessageDialogButtons::OkCancelCustom(ok, cancel) => {
(Some(ok.as_str()), Some(cancel.as_str())) (Some(ok.as_str()), None, Some(cancel.as_str()))
}
MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => {
(Some(yes.as_str()), Some(no.as_str()), Some(cancel.as_str()))
} }
}; };
MessageDialogPayload { MessageDialogPayload {
@@ -252,6 +273,7 @@ impl<R: Runtime> MessageDialogBuilder<R> {
message: &self.message, message: &self.message,
kind: &self.kind, kind: &self.kind,
ok_button_label, ok_button_label,
no_button_label,
cancel_button_label, cancel_button_label,
} }
} }
@@ -295,16 +317,55 @@ impl<R: Runtime> MessageDialogBuilder<R> {
} }
/// Shows a message dialog /// Shows a message dialog
///
/// Returns `true` if the user pressed the OK/Yes button,
pub fn show<F: FnOnce(bool) + Send + 'static>(self, f: F) { pub fn show<F: FnOnce(bool) + Send + 'static>(self, f: F) {
let ok_label = match &self.buttons {
MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
MessageDialogButtons::YesNoCancelCustom(yes, _, _) => Some(yes.clone()),
_ => None,
};
show_message_dialog(self, move |res| {
let sucess = match res {
MessageDialogResult::Ok | MessageDialogResult::Yes => true,
MessageDialogResult::Custom(s) => {
ok_label.map_or(s == OK, |ok_label| ok_label == s)
}
_ => false,
};
f(sucess)
})
}
/// Shows a message dialog and returns the button that was pressed.
///
/// Returns a [`MessageDialogResult`] enum that indicates which button was pressed.
pub fn show_with_result<F: FnOnce(MessageDialogResult) + Send + 'static>(self, f: F) {
show_message_dialog(self, f) show_message_dialog(self, f)
} }
/// Shows a message dialog. /// Shows a message dialog.
///
/// Returns `true` if the user pressed the OK/Yes button,
///
/// This is a blocking operation, /// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context. /// and should *NOT* be used when running on the main thread context.
pub fn blocking_show(self) -> bool { pub fn blocking_show(self) -> bool {
blocking_fn!(self, show) blocking_fn!(self, show)
} }
/// Shows a message dialog and returns the button that was pressed.
///
/// Returns a [`MessageDialogResult`] enum that indicates which button was pressed.
///
/// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context.
pub fn blocking_show_with_result(self) -> MessageDialogResult {
blocking_fn!(self, show_with_result)
}
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub(crate) struct Filter { pub(crate) struct Filter {
@@ -324,6 +385,8 @@ pub struct FileDialogBuilder<R: Runtime> {
pub(crate) file_name: Option<String>, pub(crate) file_name: Option<String>,
pub(crate) title: Option<String>, pub(crate) title: Option<String>,
pub(crate) can_create_directories: Option<bool>, pub(crate) can_create_directories: Option<bool>,
pub(crate) picker_mode: Option<PickerMode>,
pub(crate) file_access_mode: Option<FileAccessMode>,
#[cfg(desktop)] #[cfg(desktop)]
pub(crate) parent: Option<crate::desktop::WindowHandle>, pub(crate) parent: Option<crate::desktop::WindowHandle>,
} }
@@ -335,6 +398,8 @@ pub(crate) struct FileDialogPayload<'a> {
file_name: &'a Option<String>, file_name: &'a Option<String>,
filters: &'a Vec<Filter>, filters: &'a Vec<Filter>,
multiple: bool, multiple: bool,
picker_mode: &'a Option<PickerMode>,
file_access_mode: &'a Option<FileAccessMode>,
} }
// raw window handle :( // raw window handle :(
@@ -350,6 +415,8 @@ impl<R: Runtime> FileDialogBuilder<R> {
file_name: None, file_name: None,
title: None, title: None,
can_create_directories: None, can_create_directories: None,
picker_mode: None,
file_access_mode: None,
#[cfg(desktop)] #[cfg(desktop)]
parent: None, parent: None,
} }
@@ -361,6 +428,8 @@ impl<R: Runtime> FileDialogBuilder<R> {
file_name: &self.file_name, file_name: &self.file_name,
filters: &self.filters, filters: &self.filters,
multiple, multiple,
picker_mode: &self.picker_mode,
file_access_mode: &self.file_access_mode,
} }
} }
@@ -421,11 +490,29 @@ impl<R: Runtime> FileDialogBuilder<R> {
self self
} }
/// Set the picker mode of the dialog.
/// This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
/// On desktop, this option is ignored.
/// If not provided, the dialog will automatically choose the best mode based on the MIME types of the filters.
pub fn set_picker_mode(mut self, mode: PickerMode) -> Self {
self.picker_mode.replace(mode);
self
}
/// Set the file access mode of the dialog.
/// This is only used on iOS.
/// On desktop and Android, this option is ignored.
pub fn set_file_access_mode(mut self, mode: FileAccessMode) -> Self {
self.file_access_mode.replace(mode);
self
}
/// Shows the dialog to select a single file. /// Shows the dialog to select a single file.
///
/// This is not a blocking operation, /// This is not a blocking operation,
/// and should be used when running on the main thread to avoid deadlocks with the event loop. /// and should be used when running on the main thread to avoid deadlocks with the event loop.
/// ///
/// For usage in other contexts such as commands, prefer [`Self::pick_file`]. /// See [`Self::blocking_pick_file`] for a blocking version for use in other contexts.
/// ///
/// # Examples /// # Examples
/// ///
@@ -445,9 +532,12 @@ impl<R: Runtime> FileDialogBuilder<R> {
} }
/// Shows the dialog to select multiple files. /// Shows the dialog to select multiple files.
///
/// This is not a blocking operation, /// This is not a blocking operation,
/// and should be used when running on the main thread to avoid deadlocks with the event loop. /// and should be used when running on the main thread to avoid deadlocks with the event loop.
/// ///
/// See [`Self::blocking_pick_files`] for a blocking version for use in other contexts.
///
/// # Reading the files /// # Reading the files
/// ///
/// The file paths cannot be read directly on Android as they are behind a content URI. /// The file paths cannot be read directly on Android as they are behind a content URI.
@@ -490,9 +580,12 @@ impl<R: Runtime> FileDialogBuilder<R> {
} }
/// Shows the dialog to select a single folder. /// Shows the dialog to select a single folder.
///
/// This is not a blocking operation, /// This is not a blocking operation,
/// and should be used when running on the main thread to avoid deadlocks with the event loop. /// and should be used when running on the main thread to avoid deadlocks with the event loop.
/// ///
/// See [`Self::blocking_pick_folder`] for a blocking version for use in other contexts.
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@@ -512,9 +605,12 @@ impl<R: Runtime> FileDialogBuilder<R> {
} }
/// Shows the dialog to select multiple folders. /// Shows the dialog to select multiple folders.
///
/// This is not a blocking operation, /// This is not a blocking operation,
/// and should be used when running on the main thread to avoid deadlocks with the event loop. /// and should be used when running on the main thread to avoid deadlocks with the event loop.
/// ///
/// See [`Self::blocking_pick_folders`] for a blocking version for use in other contexts.
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@@ -538,6 +634,8 @@ impl<R: Runtime> FileDialogBuilder<R> {
/// This is not a blocking operation, /// This is not a blocking operation,
/// and should be used when running on the main thread to avoid deadlocks with the event loop. /// and should be used when running on the main thread to avoid deadlocks with the event loop.
/// ///
/// See [`Self::blocking_save_file`] for a blocking version for use in other contexts.
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@@ -559,8 +657,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
/// Blocking APIs. /// Blocking APIs.
impl<R: Runtime> FileDialogBuilder<R> { impl<R: Runtime> FileDialogBuilder<R> {
/// Shows the dialog to select a single file. /// Shows the dialog to select a single file.
///
/// This is a blocking operation, /// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context. /// and should *NOT* be used when running on the main thread.
///
/// See [`Self::pick_file`] for a non-blocking version for use in main-thread contexts.
/// ///
/// # Examples /// # Examples
/// ///
@@ -578,8 +679,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
} }
/// Shows the dialog to select multiple files. /// Shows the dialog to select multiple files.
///
/// This is a blocking operation, /// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context. /// and should *NOT* be used when running on the main thread.
///
/// See [`Self::pick_files`] for a non-blocking version for use in main-thread contexts.
/// ///
/// # Examples /// # Examples
/// ///
@@ -597,8 +701,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
} }
/// Shows the dialog to select a single folder. /// Shows the dialog to select a single folder.
///
/// This is a blocking operation, /// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context. /// and should *NOT* be used when running on the main thread.
///
/// See [`Self::pick_folder`] for a non-blocking version for use in main-thread contexts.
/// ///
/// # Examples /// # Examples
/// ///
@@ -617,8 +724,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
} }
/// Shows the dialog to select multiple folders. /// Shows the dialog to select multiple folders.
///
/// This is a blocking operation, /// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context. /// and should *NOT* be used when running on the main thread.
///
/// See [`Self::pick_folders`] for a non-blocking version for use in main-thread contexts.
/// ///
/// # Examples /// # Examples
/// ///
@@ -637,8 +747,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
} }
/// Shows the dialog to save a file. /// Shows the dialog to save a file.
///
/// This is a blocking operation, /// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context. /// and should *NOT* be used when running on the main thread.
///
/// See [`Self::save_file`] for a non-blocking version for use in main-thread contexts.
/// ///
/// # Examples /// # Examples
/// ///
+6 -6
View File
@@ -8,7 +8,7 @@ use tauri::{
AppHandle, Runtime, AppHandle, Runtime,
}; };
use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder}; use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogResult};
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog"; const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog";
@@ -107,13 +107,11 @@ pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct ShowMessageDialogResponse { struct ShowMessageDialogResponse {
#[allow(dead_code)] value: String,
cancelled: bool,
value: bool,
} }
/// Shows a message dialog /// Shows a message dialog
pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>( pub fn show_message_dialog<R: Runtime, F: FnOnce(MessageDialogResult) + Send + 'static>(
dialog: MessageDialogBuilder<R>, dialog: MessageDialogBuilder<R>,
f: F, f: F,
) { ) {
@@ -122,6 +120,8 @@ pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>(
.dialog .dialog
.0 .0
.run_mobile_plugin::<ShowMessageDialogResponse>("showMessageDialog", dialog.payload()); .run_mobile_plugin::<ShowMessageDialogResponse>("showMessageDialog", dialog.payload());
f(res.map(|r| r.value).unwrap_or_default())
let res = res.map(|res| res.value.into());
f(res.unwrap_or_default())
}); });
} }
+44 -8
View File
@@ -6,9 +6,10 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Types of message, ask and confirm dialogs. /// Types of message, ask and confirm dialogs.
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
pub enum MessageDialogKind { pub enum MessageDialogKind {
/// Information dialog. /// Information dialog.
#[default]
Info, Info,
/// Warning dialog. /// Warning dialog.
Warning, Warning,
@@ -16,12 +17,6 @@ pub enum MessageDialogKind {
Error, Error,
} }
impl Default for MessageDialogKind {
fn default() -> Self {
Self::Info
}
}
impl<'de> Deserialize<'de> for MessageDialogKind { impl<'de> Deserialize<'de> for MessageDialogKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
@@ -52,7 +47,7 @@ impl Serialize for MessageDialogKind {
/// Set of button that will be displayed on the dialog /// Set of button that will be displayed on the dialog
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub enum MessageDialogButtons { pub enum MessageDialogButtons {
#[default] #[default]
/// A single `Ok` button with OS default dialog text /// A single `Ok` button with OS default dialog text
@@ -61,8 +56,49 @@ pub enum MessageDialogButtons {
OkCancel, OkCancel,
/// 2 buttons `Yes` and `No` with OS default dialog texts /// 2 buttons `Yes` and `No` with OS default dialog texts
YesNo, YesNo,
/// 3 buttons `Yes`, `No` and `Cancel` with OS default dialog texts
YesNoCancel,
/// A single `Ok` button with custom text /// A single `Ok` button with custom text
OkCustom(String), OkCustom(String),
/// 2 buttons `Ok` and `Cancel` with custom texts /// 2 buttons `Ok` and `Cancel` with custom texts
OkCancelCustom(String, String), OkCancelCustom(String, String),
/// 3 buttons `Yes`, `No` and `Cancel` with custom texts
YesNoCancelCustom(String, String, String),
}
/// Result of a message dialog
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum MessageDialogResult {
Yes,
No,
Ok,
#[default]
Cancel,
#[serde(untagged)]
Custom(String),
}
#[cfg(desktop)]
impl From<rfd::MessageDialogResult> for MessageDialogResult {
fn from(result: rfd::MessageDialogResult) -> Self {
match result {
rfd::MessageDialogResult::Yes => Self::Yes,
rfd::MessageDialogResult::No => Self::No,
rfd::MessageDialogResult::Ok => Self::Ok,
rfd::MessageDialogResult::Cancel => Self::Cancel,
rfd::MessageDialogResult::Custom(s) => Self::Custom(s),
}
}
}
impl From<String> for MessageDialogResult {
fn from(value: String) -> Self {
match value.as_str() {
"Yes" => Self::Yes,
"No" => Self::No,
"Ok" => Self::Ok,
"Cancel" => Self::Cancel,
_ => Self::Custom(value),
}
}
} }
+15
View File
@@ -1,5 +1,20 @@
# Changelog # Changelog
## \[2.4.5]
- [`521cd8b3`](https://github.com/tauri-apps/plugins-workspace/commit/521cd8b372c862d96b1637775710e4d7cf2443e2) ([#3155](https://github.com/tauri-apps/plugins-workspace/pull/3155) by [@EliasStar](https://github.com/tauri-apps/plugins-workspace/../../EliasStar)) Fix off by one error in the implementation of readTextFileLines causing all lines to end with an (additional) null byte.
Issue: [#3154](https://github.com/tauri-apps/plugins-workspace/issues/3154)
PR: [#3155](https://github.com/tauri-apps/plugins-workspace/pull/3155)
## \[2.4.4]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.4.3]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
- [`6b5b1053`](https://github.com/tauri-apps/plugins-workspace/commit/6b5b1053ba8aeb789dd5cb5fb05b7e98f3b8de0b) ([#1939](https://github.com/tauri-apps/plugins-workspace/pull/1939) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Enhance error messages.
## \[2.4.2] ## \[2.4.2]
- [`4eb36b0f`](https://github.com/tauri-apps/plugins-workspace/commit/4eb36b0ff57acb0bb1b911c583efa3bf2f56aa32) ([#2907](https://github.com/tauri-apps/plugins-workspace/pull/2907) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument` - [`4eb36b0f`](https://github.com/tauri-apps/plugins-workspace/commit/4eb36b0ff57acb0bb1b911c583efa3bf2f56aa32) ([#2907](https://github.com/tauri-apps/plugins-workspace/pull/2907) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument`
+1 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.4.2" version = "2.4.5"
description = "Access the file system." description = "Access the file system."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -9,10 +9,6 @@ rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-fs" links = "tauri-plugin-fs"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write access in `$RESOURCES` folder" } windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write access in `$RESOURCES` folder" }
linux = { level = "full", notes = "No write access to `$RESOURCES` folder" } linux = { level = "full", notes = "No write access to `$RESOURCES` folder" }
+1 -2
View File
@@ -5,11 +5,10 @@ plugins {
android { android {
namespace = "com.plugin.fs" namespace = "com.plugin.fs"
compileSdk = 34 compileSdk = 36
defaultConfig { defaultConfig {
minSdk = 21 minSdk = 21
targetSdk = 34
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -838,7 +838,9 @@ async function readTextFileLines(
return { value: null, done } return { value: null, done }
} }
const line = new TextDecoder().decode(bytes.slice(0, bytes.byteLength)) const line = new TextDecoder().decode(
bytes.slice(0, bytes.byteLength - 1)
)
return { return {
value: line, value: line,
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-fs", "name": "@tauri-apps/plugin-fs",
"version": "2.4.2", "version": "2.4.5",
"description": "Access the file system.", "description": "Access the file system.",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
@@ -25,6 +25,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
@@ -3748,6 +3748,28 @@ This enables all index or metadata related commands without any pre-configured a
An empty permission you can use to modify the global scope. An empty permission you can use to modify the global scope.
## Example
```json
{
"identifier": "read-documents",
"windows": ["main"],
"permissions": [
"fs:allow-read",
{
"identifier": "fs:scope",
"allow": [
"$APPDATA/documents/**/*"
],
"deny": [
"$APPDATA/documents/secret.txt"
]
}
]
}
```
</td> </td>
</tr> </tr>
+2 -2
View File
@@ -2005,10 +2005,10 @@
"markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths."
}, },
{ {
"description": "An empty permission you can use to modify the global scope.", "description": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n",
"type": "string", "type": "string",
"const": "scope", "const": "scope",
"markdownDescription": "An empty permission you can use to modify the global scope." "markdownDescription": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n"
}, },
{ {
"description": "This enables all write related commands without any pre-configured accessible paths.", "description": "This enables all write related commands without any pre-configured accessible paths.",
+24 -1
View File
@@ -2,4 +2,27 @@
[[permission]] [[permission]]
identifier = "scope" identifier = "scope"
description = "An empty permission you can use to modify the global scope." description = """
An empty permission you can use to modify the global scope.
## Example
```json
{
"identifier": "read-documents",
"windows": ["main"],
"permissions": [
"fs:allow-read",
{
"identifier": "fs:scope",
"allow": [
"$APPDATA/documents/**/*"
],
"deny": [
"$APPDATA/documents/secret.txt"
]
}
]
}
```
"""
+110 -18
View File
@@ -85,6 +85,7 @@ pub fn create<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"create",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -119,6 +120,7 @@ pub fn open<R: Runtime>(
options: Option<OpenOptions>, options: Option<OpenOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
let (file, _path) = resolve_file( let (file, _path) = resolve_file(
"open",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -167,6 +169,7 @@ pub async fn copy_file<R: Runtime>(
options: Option<CopyFileOptions>, options: Option<CopyFileOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_from_path = resolve_path( let resolved_from_path = resolve_path(
"copy-file",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -174,6 +177,7 @@ pub async fn copy_file<R: Runtime>(
options.as_ref().and_then(|o| o.from_path_base_dir), options.as_ref().and_then(|o| o.from_path_base_dir),
)?; )?;
let resolved_to_path = resolve_path( let resolved_to_path = resolve_path(
"copy-file",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -208,6 +212,7 @@ pub fn mkdir<R: Runtime>(
options: Option<MkdirOptions>, options: Option<MkdirOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"mkdir",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -255,6 +260,7 @@ pub async fn read_dir<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<Vec<DirEntry>> { ) -> CommandResult<Vec<DirEntry>> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"read-dir",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -331,8 +337,8 @@ pub async fn read<R: Runtime>(
Ok(tauri::ipc::Response::new(data)) Ok(tauri::ipc::Response::new(data))
} }
#[tauri::command] async fn read_file_inner<R: Runtime>(
pub async fn read_file<R: Runtime>( permission: &str,
webview: Webview<R>, webview: Webview<R>,
global_scope: GlobalScope<Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
@@ -340,6 +346,7 @@ pub async fn read_file<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<tauri::ipc::Response> { ) -> CommandResult<tauri::ipc::Response> {
let (mut file, path) = resolve_file( let (mut file, path) = resolve_file(
permission,
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -367,6 +374,25 @@ pub async fn read_file<R: Runtime>(
Ok(tauri::ipc::Response::new(contents)) Ok(tauri::ipc::Response::new(contents))
} }
#[tauri::command]
pub async fn read_file<R: Runtime>(
webview: Webview<R>,
global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>,
path: SafeFilePath,
options: Option<BaseOptions>,
) -> CommandResult<tauri::ipc::Response> {
read_file_inner(
"read-file",
webview,
global_scope,
command_scope,
path,
options,
)
.await
}
// TODO, remove in v3, rely on `read_file` command instead // TODO, remove in v3, rely on `read_file` command instead
#[tauri::command] #[tauri::command]
pub async fn read_text_file<R: Runtime>( pub async fn read_text_file<R: Runtime>(
@@ -376,7 +402,15 @@ pub async fn read_text_file<R: Runtime>(
path: SafeFilePath, path: SafeFilePath,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<tauri::ipc::Response> { ) -> CommandResult<tauri::ipc::Response> {
read_file(webview, global_scope, command_scope, path, options).await read_file_inner(
"read-text-file",
webview,
global_scope,
command_scope,
path,
options,
)
.await
} }
#[tauri::command] #[tauri::command]
@@ -388,6 +422,7 @@ pub fn read_text_file_lines<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"read-text-file-lines",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -452,6 +487,7 @@ pub fn remove<R: Runtime>(
options: Option<RemoveOptions>, options: Option<RemoveOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"remove",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -521,6 +557,7 @@ pub fn rename<R: Runtime>(
options: Option<RenameOptions>, options: Option<RenameOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_old_path = resolve_path( let resolved_old_path = resolve_path(
"rename",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -528,6 +565,7 @@ pub fn rename<R: Runtime>(
options.as_ref().and_then(|o| o.old_path_base_dir), options.as_ref().and_then(|o| o.old_path_base_dir),
)?; )?;
let resolved_new_path = resolve_path( let resolved_new_path = resolve_path(
"rename",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -575,6 +613,7 @@ pub async fn seek<R: Runtime>(
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>( fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>(
permission: &str,
metadata_fn: F, metadata_fn: F,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
@@ -585,6 +624,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
match path { match path {
SafeFilePath::Url(url) => { SafeFilePath::Url(url) => {
let (file, path) = resolve_file( let (file, path) = resolve_file(
permission,
webview, webview,
global_scope, global_scope,
command_scope, command_scope,
@@ -606,6 +646,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
}) })
} }
SafeFilePath::Path(p) => get_fs_metadata( SafeFilePath::Path(p) => get_fs_metadata(
permission,
metadata_fn, metadata_fn,
webview, webview,
global_scope, global_scope,
@@ -618,6 +659,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>( fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>(
permission: &str,
metadata_fn: F, metadata_fn: F,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
@@ -626,6 +668,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<std::fs::Metadata> { ) -> CommandResult<std::fs::Metadata> {
get_fs_metadata( get_fs_metadata(
permission,
metadata_fn, metadata_fn,
webview, webview,
global_scope, global_scope,
@@ -636,6 +679,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
} }
fn get_fs_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>( fn get_fs_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>(
permission: &str,
metadata_fn: F, metadata_fn: F,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
@@ -644,6 +688,7 @@ fn get_fs_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::M
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<std::fs::Metadata> { ) -> CommandResult<std::fs::Metadata> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
permission,
webview, webview,
global_scope, global_scope,
command_scope, command_scope,
@@ -668,6 +713,7 @@ pub fn stat<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<FileInfo> { ) -> CommandResult<FileInfo> {
let metadata = get_metadata( let metadata = get_metadata(
"stat",
|p| std::fs::metadata(p), |p| std::fs::metadata(p),
&webview, &webview,
&global_scope, &global_scope,
@@ -688,6 +734,7 @@ pub fn lstat<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<FileInfo> { ) -> CommandResult<FileInfo> {
let metadata = get_metadata( let metadata = get_metadata(
"lstat",
|p| std::fs::symlink_metadata(p), |p| std::fs::symlink_metadata(p),
&webview, &webview,
&global_scope, &global_scope,
@@ -716,6 +763,7 @@ pub async fn truncate<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"truncate",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -784,23 +832,13 @@ fn default_create_value() -> bool {
true true
} }
#[tauri::command] async fn write_file_inner<R: Runtime>(
pub async fn write_file<R: Runtime>( permission: &str,
webview: Webview<R>, webview: Webview<R>,
global_scope: GlobalScope<Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
request: tauri::ipc::Request<'_>, request: tauri::ipc::Request<'_>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let data = match request.body() {
tauri::ipc::InvokeBody::Raw(data) => Cow::Borrowed(data),
tauri::ipc::InvokeBody::Json(serde_json::Value::Array(data)) => Cow::Owned(
data.iter()
.flat_map(|v| v.as_number().and_then(|v| v.as_u64().map(|v| v as u8)))
.collect(),
),
_ => return Err(anyhow::anyhow!("unexpected invoke body").into()),
};
let path = request let path = request
.headers() .headers()
.get("path") .get("path")
@@ -811,6 +849,7 @@ pub async fn write_file<R: Runtime>(
.map_err(|_| anyhow::anyhow!("path is not a valid UTF-8").into()) .map_err(|_| anyhow::anyhow!("path is not a valid UTF-8").into())
}) })
.and_then(|p| SafeFilePath::from_str(&p).map_err(CommandError::from))?; .and_then(|p| SafeFilePath::from_str(&p).map_err(CommandError::from))?;
let options: Option<WriteFileOptions> = request let options: Option<WriteFileOptions> = request
.headers() .headers()
.get("options") .get("options")
@@ -818,6 +857,7 @@ pub async fn write_file<R: Runtime>(
.and_then(|opts| serde_json::from_str(opts).ok()); .and_then(|opts| serde_json::from_str(opts).ok());
let (mut file, path) = resolve_file( let (mut file, path) = resolve_file(
permission,
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -853,6 +893,16 @@ pub async fn write_file<R: Runtime>(
}, },
)?; )?;
let data = match request.body() {
tauri::ipc::InvokeBody::Raw(data) => Cow::Borrowed(data),
tauri::ipc::InvokeBody::Json(serde_json::Value::Array(data)) => Cow::Owned(
data.iter()
.flat_map(|v| v.as_number().and_then(|v| v.as_u64().map(|v| v as u8)))
.collect(),
),
_ => return Err(anyhow::anyhow!("unexpected invoke body").into()),
};
file.write_all(&data) file.write_all(&data)
.map_err(|e| { .map_err(|e| {
format!( format!(
@@ -863,6 +913,16 @@ pub async fn write_file<R: Runtime>(
.map_err(Into::into) .map_err(Into::into)
} }
#[tauri::command]
pub async fn write_file<R: Runtime>(
webview: Webview<R>,
global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>,
request: tauri::ipc::Request<'_>,
) -> CommandResult<()> {
write_file_inner("write-file", webview, global_scope, command_scope, request).await
}
// TODO, remove in v3, rely on `write_file` command instead // TODO, remove in v3, rely on `write_file` command instead
#[tauri::command] #[tauri::command]
pub async fn write_text_file<R: Runtime>( pub async fn write_text_file<R: Runtime>(
@@ -871,7 +931,14 @@ pub async fn write_text_file<R: Runtime>(
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
request: tauri::ipc::Request<'_>, request: tauri::ipc::Request<'_>,
) -> CommandResult<()> { ) -> CommandResult<()> {
write_file(webview, global_scope, command_scope, request).await write_file_inner(
"write-text-file",
webview,
global_scope,
command_scope,
request,
)
.await
} }
#[tauri::command] #[tauri::command]
@@ -883,6 +950,7 @@ pub fn exists<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<bool> { ) -> CommandResult<bool> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"exists",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -901,6 +969,7 @@ pub async fn size<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<u64> { ) -> CommandResult<u64> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"size",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -943,16 +1012,25 @@ fn get_dir_size(path: &PathBuf) -> CommandResult<u64> {
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
pub fn resolve_file<R: Runtime>( pub fn resolve_file<R: Runtime>(
permission: &str,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: &CommandScope<Entry>,
path: SafeFilePath, path: SafeFilePath,
open_options: OpenOptions, open_options: OpenOptions,
) -> CommandResult<(File, PathBuf)> { ) -> CommandResult<(File, PathBuf)> {
resolve_file_in_fs(webview, global_scope, command_scope, path, open_options) resolve_file_in_fs(
permission,
webview,
global_scope,
command_scope,
path,
open_options,
)
} }
fn resolve_file_in_fs<R: Runtime>( fn resolve_file_in_fs<R: Runtime>(
permission: &str,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: &CommandScope<Entry>,
@@ -960,6 +1038,7 @@ fn resolve_file_in_fs<R: Runtime>(
open_options: OpenOptions, open_options: OpenOptions,
) -> CommandResult<(File, PathBuf)> { ) -> CommandResult<(File, PathBuf)> {
let path = resolve_path( let path = resolve_path(
permission,
webview, webview,
global_scope, global_scope,
command_scope, command_scope,
@@ -980,6 +1059,7 @@ fn resolve_file_in_fs<R: Runtime>(
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub fn resolve_file<R: Runtime>( pub fn resolve_file<R: Runtime>(
permission: &str,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: &CommandScope<Entry>,
@@ -997,6 +1077,7 @@ pub fn resolve_file<R: Runtime>(
Ok((file, path)) Ok((file, path))
} }
SafeFilePath::Path(path) => resolve_file_in_fs( SafeFilePath::Path(path) => resolve_file_in_fs(
permission,
webview, webview,
global_scope, global_scope,
command_scope, command_scope,
@@ -1007,6 +1088,7 @@ pub fn resolve_file<R: Runtime>(
} }
pub fn resolve_path<R: Runtime>( pub fn resolve_path<R: Runtime>(
permission: &str,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: &CommandScope<Entry>,
@@ -1052,7 +1134,17 @@ pub fn resolve_path<R: Runtime>(
if fs_scope.scope.is_allowed(&path) || scope.is_allowed(&path) { if fs_scope.scope.is_allowed(&path) || scope.is_allowed(&path) {
Ok(path) Ok(path)
} else { } else {
Err(CommandError::Plugin(Error::PathForbidden(path))) #[cfg(not(debug_assertions))]
return Err(CommandError::Plugin(Error::PathForbidden(path)));
#[cfg(debug_assertions)]
Err(
anyhow::anyhow!(
"forbidden path: {}, maybe it is not allowed on the scope for `allow-{permission}` permission in your capability file",
path.display()
)
)
.map_err(Into::into)
} }
} }
+1 -1
View File
@@ -89,7 +89,7 @@ impl<R: Runtime> Fs<R> {
std::fs::File::from_raw_fd(fd) std::fs::File::from_raw_fd(fd)
}) })
} else { } else {
todo!() unimplemented!()
} }
} }
} }
+1
View File
@@ -49,6 +49,7 @@ pub fn watch<R: Runtime>(
.into_iter() .into_iter()
.map(|path| { .map(|path| {
resolve_path( resolve_path(
"watch",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
+9
View File
@@ -1,5 +1,14 @@
# Changelog # Changelog
## \[2.3.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.3.1]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
- [`e933acb0`](https://github.com/tauri-apps/plugins-workspace/commit/e933acb0044b4d49053eae6492b542351160e66a) ([#3010](https://github.com/tauri-apps/plugins-workspace/pull/3010) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, use the `timeout` value for `setMinUpdateIntervalMillis`, `setMaxUpdateDelayMillis` and `setIntervalMillis` instead of just `minUpdateInterval`.
## \[2.3.0] ## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -3
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "tauri-plugin-geolocation" name = "tauri-plugin-geolocation"
description = "Get and track the device's current position" description = "Get and track the device's current position"
version = "2.3.0" version = "2.3.2"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -9,8 +9,6 @@ repository = { workspace = true }
links = "tauri-plugin-geolocation" links = "tauri-plugin-geolocation"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
+2 -2
View File
@@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "app.tauri.geolocation" namespace = "app.tauri.geolocation"
compileSdk = 34 compileSdk = 36
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
@@ -49,8 +49,6 @@ public class Geolocation(private val context: Context) {
val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER
val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio
Logger.error(prio.toString())
LocationServices LocationServices
.getFusedLocationProviderClient(context) .getFusedLocationProviderClient(context)
.getCurrentLocation(prio, null) .getCurrentLocation(prio, null)
@@ -91,11 +89,9 @@ public class Geolocation(private val context: Context) {
val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER
val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio
Logger.error(prio.toString()) val locationRequest = LocationRequest.Builder(timeout)
val locationRequest = LocationRequest.Builder(10000)
.setMaxUpdateDelayMillis(timeout) .setMaxUpdateDelayMillis(timeout)
.setMinUpdateIntervalMillis(5000) .setMinUpdateIntervalMillis(timeout)
.setPriority(prio) .setPriority(prio)
.build() .build()
@@ -145,4 +141,4 @@ public class Geolocation(private val context: Context) {
return lastLoc return lastLoc
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-geolocation", "name": "@tauri-apps/plugin-geolocation",
"version": "2.3.0", "version": "2.3.2",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.3.1]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.3.0] ## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-global-shortcut" name = "tauri-plugin-global-shortcut"
version = "2.3.0" version = "2.3.1"
description = "Register global hotkeys listeners on your Tauri application." description = "Register global hotkeys listeners on your Tauri application."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -9,10 +9,6 @@ rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-global-shortcut" links = "tauri-plugin-global-shortcut"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
linux = { level = "full", notes = "" } linux = { level = "full", notes = "" }
+1 -1
View File
@@ -54,7 +54,7 @@ fn main() {
.setup(|app| { .setup(|app| {
#[cfg(desktop)] #[cfg(desktop)]
{ {
use tauri::Manager; use tauri::Emitter;
use tauri_plugin_global_shortcut::{Code, Modifiers, ShortcutState}; use tauri_plugin_global_shortcut::{Code, Modifiers, ShortcutState};
app.handle().plugin( app.handle().plugin(
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-global-shortcut", "name": "@tauri-apps/plugin-global-shortcut",
"version": "2.3.0", "version": "2.3.1",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## \[2.3.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.3.1]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
## \[2.3.0] ## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -3
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "tauri-plugin-haptics" name = "tauri-plugin-haptics"
description = "Haptic feedback and vibrations on Android and iOS" description = "Haptic feedback and vibrations on Android and iOS"
version = "2.3.0" version = "2.3.2"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -9,8 +9,6 @@ repository = { workspace = true }
links = "tauri-plugin-haptics" links = "tauri-plugin-haptics"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
+2 -2
View File
@@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "app.tauri.haptics" namespace = "app.tauri.haptics"
compileSdk = 34 compileSdk = 36
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
@@ -2,12 +2,12 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import AudioToolbox
import CoreHaptics
import SwiftRs import SwiftRs
import Tauri import Tauri
import UIKit import UIKit
import WebKit import WebKit
import CoreHaptics
import AudioToolbox
class ImpactFeedbackOptions: Decodable { class ImpactFeedbackOptions: Decodable {
let style: ImpactFeedbackStyle let style: ImpactFeedbackStyle
@@ -69,19 +69,21 @@ class HapticsPlugin: Plugin {
try engine.start() try engine.start()
engine.resetHandler = { [] in engine.resetHandler = { [] in
do { do {
try engine.start() try engine.start()
} catch { } catch {
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
} }
} }
// TODO: Make some of this (or all) configurable? // TODO: Make some of this (or all) configurable?
let intensity: CHHapticEventParameter = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0) let intensity: CHHapticEventParameter = CHHapticEventParameter(
let sharpness: CHHapticEventParameter = CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) parameterID: .hapticIntensity, value: 1.0)
let sharpness: CHHapticEventParameter = CHHapticEventParameter(
parameterID: .hapticSharpness, value: 1.0)
let continuousEvent = CHHapticEvent( let continuousEvent = CHHapticEvent(
eventType: .hapticContinuous, eventType: .hapticContinuous,
parameters: [intensity, sharpness], parameters: [intensity, sharpness],
relativeTime: 0.0, relativeTime: 0.0,
duration: args.duration/1000 duration: args.duration / 1000
) )
let pattern = try CHHapticPattern(events: [continuousEvent], parameters: []) let pattern = try CHHapticPattern(events: [continuousEvent], parameters: [])
let player = try engine.makePlayer(with: pattern) let player = try engine.makePlayer(with: pattern)
@@ -94,8 +96,6 @@ class HapticsPlugin: Plugin {
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
} }
Logger.error("VIBRATE END")
invoke.resolve() invoke.resolve()
} }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-haptics", "name": "@tauri-apps/plugin-haptics",
"version": "2.3.0", "version": "2.3.2",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.8.0" "@tauri-apps/api": "^2.10.1"
} }
} }
+30
View File
@@ -1,5 +1,35 @@
# Changelog # Changelog
## \[2.5.7]
- [`61e9b0ab`](https://github.com/tauri-apps/plugins-workspace/commit/61e9b0ab64c56cc5f7a5cb3b92b386671da6e0a2) ([#3228](https://github.com/tauri-apps/plugins-workspace/pull/3228)) Cleanup resource when the returned `ReadableStream.cancel` is called to avoid memory leaks
## \[2.5.6]
- [`b1dbee2c`](https://github.com/tauri-apps/plugins-workspace/commit/b1dbee2c55c1aa7b64732c4cc9f8cb20520b8666) ([#3210](https://github.com/tauri-apps/plugins-workspace/pull/3210) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the Origin header to always be `null` on macOS, iOS and Linux.
## \[2.5.5]
- [`e8915f17`](https://github.com/tauri-apps/plugins-workspace/commit/e8915f17e418138f0776870353cd6ce7254b0473) ([#2562](https://github.com/tauri-apps/plugins-workspace/pull/2562) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix aborting a request in the middle of a streaming response.
### Dependencies
- Upgraded to `fs-js@2.4.5`
## \[2.5.4]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
### Dependencies
- Upgraded to `fs-js@2.4.4`
## \[2.5.3]
### Dependencies
- Upgraded to `fs-js@2.4.3`
## \[2.5.2] ## \[2.5.2]
### Dependencies ### Dependencies
+2 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.5.2" version = "2.5.7"
description = "Access an HTTP client written in Rust." description = "Access an HTTP client written in Rust."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -9,10 +9,6 @@ rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-http" links = "tauri-plugin-http"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
linux = { level = "full", notes = "" } linux = { level = "full", notes = "" }
@@ -34,7 +30,7 @@ serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { version = "1", features = ["sync", "macros"] } tokio = { version = "1", features = ["sync", "macros"] }
tauri-plugin-fs = { path = "../fs", version = "2.4.2" } tauri-plugin-fs = { path = "../fs", version = "2.4.5" }
urlpattern = "0.3" urlpattern = "0.3"
regex = "1" regex = "1"
http = "1" http = "1"
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,i,a;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class c{constructor(e){n.set(this,void 0),s.set(this,0),i.set(this,[]),a.set(this,void 0),r(this,n,e||(()=>{})),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((e=>{const o=e.index;if("end"in e)return void(o==t(this,s,"f")?this.cleanupCallback():r(this,a,o));const c=e.message;if(o==t(this,s,"f")){for(t(this,n,"f").call(this,c),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,i,"f");){const e=t(this,i,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,i,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}t(this,s,"f")===t(this,a,"f")&&this.cleanupCallback()}else t(this,i,"f")[o]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,i=new WeakMap,a=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function d(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const h="Request cancelled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(h);const n=t?.maxRedirections,s=t?.connectTimeout,i=t?.proxy,a=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const o=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,f=new Request(e,t),l=await f.arrayBuffer(),u=0!==l.byteLength?Array.from(new Uint8Array(l)):null;for(const[e,t]of f.headers)o.get(e)||o.set(e,t);const _=(o instanceof Headers?Array.from(o.entries()):Array.isArray(o)?o:Object.entries(o)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(h);const w=await d("plugin:http|fetch",{clientConfig:{method:f.method,url:f.url,headers:_,data:u,maxRedirections:n,connectTimeout:s,proxy:i,danger:a}}),p=()=>d("plugin:http|fetch_cancel",{rid:w});if(r?.aborted)throw p(),new Error(h);r?.addEventListener("abort",(()=>{p()}));const{status:y,statusText:m,url:b,headers:T,rid:g}=await d("plugin:http|fetch_send",{rid:w}),A=[101,103,204,205,304].includes(y)?null:new ReadableStream({start:e=>{const t=new c;t.onmessage=t=>{if(r?.aborted)return void e.error(h);const n=new Uint8Array(t),s=n[n.byteLength-1],i=n.slice(0,n.byteLength-1);1!=s?e.enqueue(i):e.close()},d("plugin:http|fetch_read_body",{rid:g,streamChannel:t}).catch((t=>{e.error(t)}))}}),R=new Response(A,{status:y,statusText:m});return Object.defineProperty(R,"url",{value:b}),Object.defineProperty(R,"headers",{value:new Headers(T)}),R},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";async function t(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;const r="Request cancelled";return e.fetch=async function(e,n){const a=n?.signal;if(a?.aborted)throw new Error(r);const o=n?.maxRedirections,s=n?.connectTimeout,i=n?.proxy,d=n?.danger;n&&(delete n.maxRedirections,delete n.connectTimeout,delete n.proxy,delete n.danger);const c=n?.headers?n.headers instanceof Headers?n.headers:new Headers(n.headers):new Headers,u=new Request(e,n),l=await u.arrayBuffer(),_=0!==l.byteLength?Array.from(new Uint8Array(l)):null;for(const[e,t]of u.headers)c.get(e)||c.set(e,t);const h=(c instanceof Headers?Array.from(c.entries()):Array.isArray(c)?c:Object.entries(c)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(a?.aborted)throw new Error(r);const f=await t("plugin:http|fetch",{clientConfig:{method:u.method,url:u.url,headers:h,data:_,maxRedirections:o,connectTimeout:s,proxy:i,danger:d}}),p=()=>t("plugin:http|fetch_cancel",{rid:f});if(a?.aborted)throw p(),new Error(r);a?.addEventListener("abort",(()=>{p()}));const{status:w,statusText:y,url:g,headers:b,rid:T}=await t("plugin:http|fetch_send",{rid:f}),R=()=>t("plugin:http|fetch_cancel_body",{rid:T}),m=[101,103,204,205,304].includes(w)?null:new ReadableStream({start:e=>{a?.addEventListener("abort",(()=>{e.error(r),R()}))},pull:e=>(async e=>{let r;try{r=await t("plugin:http|fetch_read_body",{rid:T})}catch(t){return e.error(t),void R()}const n=new Uint8Array(r),a=n[n.byteLength-1],o=n.slice(0,n.byteLength-1);1!==a?e.enqueue(o):e.close()})(e),cancel:()=>{R()}}),A=new Response(m,{status:w,statusText:y});return Object.defineProperty(A,"url",{value:g}),Object.defineProperty(A,"headers",{value:new Headers(b)}),A},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})}
+7 -1
View File
@@ -6,7 +6,13 @@
#[allow(dead_code)] #[allow(dead_code)]
mod scope; mod scope;
const COMMANDS: &[&str] = &["fetch", "fetch_cancel", "fetch_send", "fetch_read_body"]; const COMMANDS: &[&str] = &[
"fetch",
"fetch_cancel",
"fetch_send",
"fetch_read_body",
"fetch_cancel_body",
];
/// HTTP scope entry. /// HTTP scope entry.
#[derive(schemars::JsonSchema)] #[derive(schemars::JsonSchema)]
+48 -32
View File
@@ -26,7 +26,7 @@
* @module * @module
*/ */
import { Channel, invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
/** /**
* Configuration of a proxy that a Client should pass requests to. * Configuration of a proxy that a Client should pass requests to.
@@ -126,7 +126,7 @@ export async function fetch(
input: URL | Request | string, input: URL | Request | string,
init?: RequestInit & ClientOptions init?: RequestInit & ClientOptions
): Promise<Response> { ): Promise<Response> {
// abort early here if needed // Optimistically check for abort signal and avoid doing any work
const signal = init?.signal const signal = init?.signal
if (signal?.aborted) { if (signal?.aborted) {
throw new Error(ERROR_REQUEST_CANCELLED) throw new Error(ERROR_REQUEST_CANCELLED)
@@ -181,7 +181,7 @@ export async function fetch(
] ]
) )
// abort early here if needed // Optimistically check for abort signal and avoid doing any work on the Rust side
if (signal?.aborted) { if (signal?.aborted) {
throw new Error(ERROR_REQUEST_CANCELLED) throw new Error(ERROR_REQUEST_CANCELLED)
} }
@@ -201,7 +201,8 @@ export async function fetch(
const abort = () => invoke('plugin:http|fetch_cancel', { rid }) const abort = () => invoke('plugin:http|fetch_cancel', { rid })
// abort early here if needed // Optimistically check for abort signal
// and avoid doing any work after doing intial work on the Rust side
if (signal?.aborted) { if (signal?.aborted) {
// we don't care about the result of this proimse // we don't care about the result of this proimse
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -229,40 +230,55 @@ export async function fetch(
rid rid
}) })
const dropBody = () => {
return invoke('plugin:http|fetch_cancel_body', { rid: responseRid })
}
const readChunk = async (
controller: ReadableStreamDefaultController<Uint8Array>
) => {
let data: ArrayBuffer
try {
data = await invoke('plugin:http|fetch_read_body', {
rid: responseRid
})
} catch (e) {
// close the stream if an error occurs
// and drop the body on Rust side
controller.error(e)
void dropBody()
return
}
const dataUint8 = new Uint8Array(data)
const lastByte = dataUint8[dataUint8.byteLength - 1]
const actualData = dataUint8.slice(0, dataUint8.byteLength - 1)
// close when the signal to close (last byte is 1) is sent from the IPC.
if (lastByte === 1) {
controller.close()
return
}
controller.enqueue(actualData)
}
// no body for 101, 103, 204, 205 and 304 // no body for 101, 103, 204, 205 and 304
// see https://fetch.spec.whatwg.org/#null-body-status // see https://fetch.spec.whatwg.org/#null-body-status
const body = [101, 103, 204, 205, 304].includes(status) const body = [101, 103, 204, 205, 304].includes(status)
? null ? null
: new ReadableStream({ : new ReadableStream<Uint8Array>({
start: (controller) => { start: (controller) => {
const streamChannel = new Channel<ArrayBuffer | number[]>() // listen for abort events to cancel reading
streamChannel.onmessage = (res: ArrayBuffer | number[]) => { signal?.addEventListener('abort', () => {
// close early if aborted controller.error(ERROR_REQUEST_CANCELLED)
if (signal?.aborted) { void dropBody()
controller.error(ERROR_REQUEST_CANCELLED)
return
}
const resUint8 = new Uint8Array(res)
const lastByte = resUint8[resUint8.byteLength - 1]
const actualRes = resUint8.slice(0, resUint8.byteLength - 1)
// close when the signal to close (last byte is 1) is sent from the IPC.
if (lastByte == 1) {
controller.close()
return
}
controller.enqueue(actualRes)
}
// run a non-blocking body stream fetch
invoke('plugin:http|fetch_read_body', {
rid: responseRid,
streamChannel
}).catch((e) => {
controller.error(e)
}) })
},
pull: (controller) => readChunk(controller),
cancel: () => {
// Ensure body resources are released on stream cancellation
void dropBody()
} }
}) })

Some files were not shown because too many files have changed in this diff Show More