Compare commits

...

103 Commits

Author SHA1 Message Date
fabianlars 0fd529ae22 revert 2024-12-02 22:31:52 +01:00
fabianlars f71be86d47 temp 2024-12-02 22:28:41 +01:00
FabianLars 7d9dfaeb9e lint 2024-11-26 15:40:14 +01:00
FabianLars 2c00f7ddad shell 2024-11-26 15:04:53 +01:00
FabianLars 0f310e75ed fix 2024-11-26 14:57:41 +01:00
FabianLars bf3e8a0b8a fix(opener): Try /usr/bin/xdg-open first 2024-11-26 14:47:35 +01:00
Roman Steiner 3fa0fc09bb fix(clipboard): alt_text in write_html command not being passed with correct argument name (#2099) 2024-11-26 14:32:24 +02:00
ayang fef76bd504 feat(fs): add the size method to get the size of a file or directory (#2095) 2024-11-26 14:09:42 +02:00
jLynx f8f2eefe03 feat(updater): Add .deb Package Support to Linux Updater (#1991) 2024-11-26 13:48:37 +02:00
renovate[bot] c665818395 chore(deps): update dependency prettier to v3.4.0 (#2097)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 09:20:21 +01:00
renovate[bot] 2ba68760b9 chore(deps): update dependency typescript-eslint to v8.16.0 (v2) (#2096)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 21:49:38 +01:00
SoSweetHam 5070476816 docs(store): Fix file reference (#2094) 2024-11-25 15:21:36 +01:00
Fabian-Lars 51cd283a5f docs(deep-link): Fix js inline docs (#2093) 2024-11-25 13:46:08 +01:00
renovate[bot] d44f0ee7a7 chore(deps): update dependency typescript to v5.7.2 (v2) (#2084)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 10:37:42 +01:00
renovate[bot] b1b0565d12 chore(deps): update dependency rollup to v4.27.4 (v2) (#2086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 10:27:15 +01:00
renovate[bot] db526a1c97 chore(deps): lock file maintenance (#2090)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 10:05:40 +08:00
Amr Bashir a3b553ddb4 feat(http): add request and response tracing behind feature flag (#2079) 2024-11-21 17:08:34 +02:00
Fabian-Lars fecfd5533a fix(fs)!: use tauri::scope::fs::Scope (#2070) 2024-11-21 16:57:25 +02:00
Amr Bashir ed981027dd feat(fs): improve readTextFile and readTextFileLines performance (#1962) 2024-11-21 15:43:51 +02:00
Amr Bashir 5092ea5e89 feat(fs): support ReadableStream<Unit8Array> for writeFile API (#1964) 2024-11-21 14:49:40 +02:00
Fabian-Lars ac2edc2159 docs(notification): Add note about installation requirement on windows 2024-11-20 21:42:27 +01:00
amrbashir 59dd5f105a chore: fmt toml 2024-11-20 03:35:19 +02:00
Jakob Westhoff 4db626354c feat(positioner): add moveWindowConstrained for tray-icon positions (#2076) 2024-11-20 02:05:37 +02:00
Amr Bashir 383e636a8e feat: add tauri-plugin-opener (#2019) 2024-11-20 00:50:02 +02:00
Vinicius Cestari 1051db406a docs(clipboard-manager): update readImage doc to use async rgba() instead of bytes (#2077) 2024-11-19 21:06:29 +01:00
renovate[bot] ee3fb1dba6 chore(deps): update eslint monorepo to v9.15.0 (#2058)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 21:37:34 +01:00
renovate[bot] c34b2ea824 chore(deps): update dependency typescript-eslint to v8.15.0 (#2072)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 21:33:20 +01:00
renovate[bot] 8a33595bbe chore(deps): update dependency rollup to v4.27.3 (#2071)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 21:30:34 +01:00
renovate[bot] ff05a59e60 chore(deps): update rust crate mockito to 1.6.1 (#2068)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 10:39:43 +01:00
renovate[bot] bea474c550 chore(deps): lock file maintenance (#2065)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 11:35:21 +08:00
renovate[bot] e5476aac94 chore(deps): update dependency rollup to v4.27.2 (v2) (#2056)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-16 15:10:38 +01:00
renovate[bot] 7f025e5240 chore(deps): update dependency rollup to v4.27.0 (v2) (#2053)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 12:08:10 +01:00
Jérôme Bousquié 5700bd2213 docs: document permissions example for geolocation plugin (#2044) 2024-11-14 17:58:11 +02:00
Fabian-Lars d402c3865a fix(sql): more explicit error if no driver is enabled (#2047) 2024-11-13 23:43:58 +01:00
John Carmack 90ef77c872 fix(sql) Allow tauri-plugin-sql to work when Tauri is running async (#2038) 2024-11-13 16:58:50 +01:00
Fabian-Lars 51856e9e0a docs: Remove header image from inline docs (#2045) 2024-11-13 16:07:29 +01:00
Jérôme Bousquié 9741b97e8c docs(geolocation): Fix js import example (#2043) 2024-11-13 14:54:15 +01:00
renovate[bot] e421b9a2c0 chore(deps): update dependency rollup to v4.26.0 (v2) (#2041)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 11:58:26 +01:00
Tony 371a2f7361 fix(log): inconsistent webview log target (#2021)
* Fix very inconsistent webview log target

* Add change file

* It's log-plugin not log

* Lower rust version requirement

* Use the third line instead of second
2024-11-12 10:10:38 +08:00
Fabian-Lars 52c093ac9d docs(fs): Fix scope example in js inline docs (#2034) 2024-11-12 00:20:36 +02:00
renovate[bot] 6d6508f18e chore(deps): update dependency typescript-eslint to v8.14.0 (v2) (#2035)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 19:01:45 +01:00
Fabian-Lars 3fa814d1f0 docs(websocket): fix short description (server -> client) 2024-11-11 18:52:54 +01:00
renovate[bot] 1fe3dab64c fix(deps): update rust crate thiserror to v2 (v2) (#2012)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FabianLars <fabianlars@fabianlars.de>
2024-11-11 18:33:58 +01:00
sid 5dadd205f5 feat(upload): add progressTotal to event payload (#2033)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2024-11-11 17:54:59 +01:00
renovate[bot] 3e15acea9a fix(deps): update tauri monorepo (v2) (#2026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2024-11-11 17:28:35 +01:00
Amr Bashir 3e78173df9 fix(fs): ignore metadata error while reading dir entries (#2018)
* fix(fs):  ignore metadata error while reading dir entries

closes #2014

* remove package manager
2024-11-11 18:02:03 +02:00
renovate[bot] 64fac08bfb chore(deps): update dependency rollup to v4.25.0 (v2) (#2024)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 16:56:04 +01:00
Fabian-Lars fdc382dff0 chore(example): Add Haptics page (#2032) 2024-11-11 16:53:56 +01:00
Fabian-Lars b2aea04567 fix(deep-link): Remove getCurrent call in onOpenUrl (#2008) 2024-11-09 10:05:46 -03:00
Ari 3449dd5a8f feat(localhost): add custom host binding to allow external access (#1982)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2024-11-06 11:52:49 +01:00
github-actions[bot] 57f69c6615 Publish New Versions (v2) (#1966)
Co-authored-by: FabianLars <FabianLars@users.noreply.github.com>
2024-11-05 17:32:55 +01:00
Fabian-Lars cfb3ec0e21 fix(window-state): Ignore is_maximized state in resize events on macos (#2007) 2024-11-05 17:01:35 +01:00
renovate[bot] b8bf4ad360 chore(deps): update unocss monorepo to ^0.64.0 (v2) (#2006)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-05 13:23:46 +01:00
Artaza Sameen 87cc58527d feat(upload): Add transfer_speed for downloading and uploading files (#1797)
Co-authored-by: Victor Aremu <me@victorare.mu>
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2024-11-04 22:41:48 +01:00
renovate[bot] e0d2e2c53f chore(deps): update dependency typescript-eslint to v8.13.0 (#2004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 19:36:33 +01:00
renovate[bot] 9a7092ca44 fix(deps): update rust crate notify to v7 (v2) (#1999)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FabianLars <fabianlars@fabianlars.de>
2024-11-04 15:56:31 +01:00
renovate[bot] 606fa08dae fix(deps): update rust crate fern to 0.7 (v2) (#1997)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de>
2024-11-04 15:00:01 +01:00
renovate[bot] 62f1e40682 chore(deps): update eslint monorepo to v9.14.0 (v2) (#1996)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 13:23:13 +01:00
renovate[bot] c8e5614063 chore(deps): update dependency svelte to v5 (v2) (#1956)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FabianLars <fabianlars@fabianlars.de>
2024-11-04 13:16:36 +01:00
Fabian-Lars 03c255ae20 chore(readme): Add new plugins and update support table (#1993) 2024-11-04 13:57:38 +02:00
renovate[bot] a706748dd3 fix(deps): update tauri monorepo (v2) (#1994)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 12:29:34 +01:00
renovate[bot] 7c1046d239 chore(deps): update dependency rollup to v4.24.4 (v2) (#1844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 12:28:44 +01:00
renovate[bot] a90f36b07e chore(deps): update dependency typescript-eslint to v8.12.2 (v2) (#1995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 12:25:26 +01:00
renovate[bot] e5249cff0b chore(deps): update dependency tslib to v2.8.1 (v2) (#1943)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 12:06:26 +01:00
Rika d57df4debe fix(clipboard-manager): read_image wrongly set the image rgba data with binary PNG data. (#1986)
* fix(clipboard-manager): `read_image` wrongly set the image rgba data with binary PNG data.

* remove depencency of `image` crate; add patch file
2024-10-30 20:29:14 +03:00
Amr Bashir 1f649c7f1f chore: print a warning in development for forbidden headers (#1981)
* chore: print a warning in development for forbidden headers

closes #1979

* Update plugins/http/src/commands.rs
2024-10-30 15:26:46 +03:00
YoggieS 0ca4cc914c docs(sql): docs of frontend Database select method (#1963) 2024-10-30 10:53:04 +01:00
Amr Bashir 9dcad78f44 docs(fs): fix default permission documentation (#1980) 2024-10-30 10:50:00 +01:00
阿良仔 4341d7f500 fix(sql): replace Mutex with RwLock to enable concurrent SQL execution (#1972) 2024-10-26 13:19:59 +02:00
Amr Bashir 77149dc432 fix(fs): fix writeFile converting UTF-8 characters in path into replacement character (#1965) 2024-10-22 08:17:37 +03:00
github-actions[bot] 525abc4be5 Publish New Versions (v2) (#1961)
Co-authored-by: amrbashir <amrbashir@users.noreply.github.com>
2024-10-21 15:13:27 +03:00
bWanShiTong 14cee64c82 fix(fs): fix compilation on targets with pointer width 16 and 32 (#1958) 2024-10-21 15:07:55 +03:00
github-actions[bot] 3fd283121f publish new versions (#1909)
Co-authored-by: lucasfernog <lucasfernog@users.noreply.github.com>
2024-10-20 08:54:26 -03:00
Amr Bashir ae8024565f perf(fs): improve FileHandle.read performance (#1950)
* perf(fs): improve `FileHandle.read` performance

* handle different target pointer width

* improve `writeTextFile` performance

* revert packageManager field

* change file

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
2024-10-20 08:49:01 -03:00
Tony 2302c2db1c fix(dialog): ask and confirm not using system button texts (#1910)
* Fix `ask`'s button texts being ok and cancel

* Update change file
2024-10-20 08:48:45 -03:00
renovate[bot] 44c50c1275 chore(deps): update rust crate tauri to 2.0.4 (#1952)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-20 17:28:39 +08:00
renovate[bot] 415bf2abc3 chore(deps): update eslint monorepo to v9.13.0 (#1951)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-20 16:54:34 +08:00
renovate[bot] 36207a93f3 chore(deps): update dependency @tauri-apps/cli to v2.0.3 (#1925)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-19 11:14:14 +08:00
renovate[bot] 854754e10b chore(deps): update dependency typescript-eslint to v8.10.0 (#1949)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-19 11:13:06 +08:00
renovate[bot] 1c2f137a8a chore(deps): lock file maintenance (#1929)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-19 11:12:38 +08:00
Tony 8c67d44aef refactor(store)!: more reworks (#1860)
* refactor(store): more reworks

* Enable auto save by default

* Store to resource table by default

* Remove share store

* Clean up

* Add close store

* Add store function

* Add lazy store

* Add init to lazy store

* refresh cache in example

* Add get-or-create-store

* Revert "Add get-or-create-store"

This reverts commit 7ffd769240.

* try get first

* Docs

* Use absolute path for store

* more docs

* Allow js to use pre-stored (de)serialize functions

* Fix js get and close store

* Show case how to use pretty json

* Update readme

* Use store instead of `store_builder` in example

* Build

* Fix example

* More docs for StoreBuilder::build

* Add default (de)serialize fn

* Use pretty json by default

* Use `undefined` for empty value in get

* Change files

* Differentiate json null from no value for events

* Add create or existing

* Build

* Rename inner store's inset method to set

* Update readme

* Apply suggestions from code review

* Use close instead

* Update breaking changes

* Return result in resolve_store_path

* Change to close_resource and take &self

* Clean up

* Apply suggestions from code review

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>

* Remove unused pub(crate)

* Update change file

* Expose resolve_store_path

* Remove with_store

* Remove StoreInner from pub and expose is_empty

* Fix wrong jsdoc param

* Update readme

* rename createOrExistingStore to createOrLoad

* make api consistent with the JS implementation, add examples

* fmt

* reintroduce "get existing store" behavior for create_or_load

* rename createOrLoad to newOrExisting

* keep store lock throughout whole new_or_existing_inner

* Remove load

* Missed if load

* Don't make StoreState public

* Remove R: Runtime from Builder

* rename newOrExisting to load, load to reload

* update docs

* rename missing reload fn

* rename builder fn to build()

* fix default permission

* Fix description and create_new logic

* Clippy

* Update docs

* Update docs

* remove create_store command

* remove close_store command since we extend from Resource

* Revert "remove close_store command since we extend from Resource"

This reverts commit 4a29fc8990.

* Reapply "remove close_store command since we extend from Resource"

This reverts commit 70a1830e7d.

---------

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
2024-10-17 19:14:41 +08:00
Niko Korvenlaita cfd48b3b2e feat: allow http calls without origin header (#1941) 2024-10-16 15:56:36 +03:00
renovate[bot] 558e14bb4c chore(deps): update dependency typescript-eslint to v8.9.0 (#1937)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 09:52:18 +08:00
凊弦凝绝 aef5dd6157 fix(sql): don't panic on missing migration when preload is enabled (#1932)
Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de>
2024-10-14 20:32:49 +02:00
Fabian-Lars 74ee7edc43 ci: Run workflows on ubuntu 22.04 (#1936) 2024-10-14 18:40:56 +02:00
Fabian-Lars 7e1c17a635 ci(renovate): Remove tauri pre-release workaround 2024-10-09 14:44:38 +02:00
Lucas Fernandes Nogueira 3c1f3874f4 fix(positioner): handleIconState missing permission and wrong impl (#1911) 2024-10-09 09:37:16 -03:00
Amr Bashir 51ddf6a715 fix(shell): use async command for open JS API (#1881)
closes tauri-apps/tauri#11212
2024-10-09 08:47:25 -03:00
renovate[bot] 62082b7086 chore(deps): lock file maintenance (#1895)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-09 08:46:38 -03:00
nashaofu aee14ed426 fix(dialog): Fix the issue of Android saving files with a .txt extension (#1892)
* fix: Fix the issue of Android saving files with a .txt extension

* fix(dialog): pull intent type from extensions filters

* fix covector

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
2024-10-09 08:45:05 -03:00
Amr Bashir 9b2840db94 fix(http): retain headers order (#1884)
* fix(http): retain headers order

closes #1882

* simplify if

* actually set headers

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
2024-10-09 08:07:46 -03:00
renovate[bot] 60064fce52 chore(deps): update dependency typescript to v5.6.3 (#1905)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-09 09:28:06 +08:00
renovate[bot] 0058583583 fix(deps): update tauri monorepo to v2.0.2 (#1874)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-08 15:29:28 +08:00
renovate[bot] fb676626f1 chore(deps): update dependency typescript-eslint to v8.8.1 (#1899)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-08 10:01:07 +08:00
renovate[bot] f445c704a1 chore(deps): update eslint monorepo to v9.12.0 (#1887)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-05 07:29:30 +08:00
github-actions[bot] fd785ab5de publish new versions (#1878)
Co-authored-by: lucasfernog <lucasfernog@users.noreply.github.com>
2024-10-03 10:58:13 -03:00
Lucas Fernandes Nogueira a1a82208ed chore: change MSRV to 1.77.2 to support Windows 7 (#1873)
* chore: change MSRV to 1.77.2 to support Windows 7

* fmt
2024-10-03 10:31:13 -03:00
renovate[bot] dc49de5341 chore(deps): update dependency typescript-eslint to v8.8.0 (#1854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-03 20:52:00 +08:00
Tillmann 4c2454d410 sync readme with metadata (#1872) 2024-10-03 07:23:40 -03:00
renovate[bot] fe6dd0ff9e fix(deps): update dependency @tauri-apps/api to v2.0.1 (#1862)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-03 10:54:25 +08:00
Horu ab8d089b2b chore(barcode-scanner): fix banner link in readme (#1870)
* chore: fix banner link in readme

* chore: raw
2024-10-03 05:04:38 +03:00
github-actions[bot] ee02ea7746 publish new versions (#1869)
Co-authored-by: lucasfernog <lucasfernog@users.noreply.github.com>
2024-10-02 16:40:18 -03:00
Lucas Fernandes Nogueira 9501cfa5f5 fix(updater): validate endpoint scheme before printing warning (#1868)
regression from #1814
2024-10-02 16:28:16 -03:00
278 changed files with 7716 additions and 3478 deletions
+5
View File
@@ -0,0 +1,5 @@
---
fs: minor
---
Add the `size` method to get the size of a file or directory.
+6
View File
@@ -0,0 +1,6 @@
---
"upload": "minor"
"upload-js": "minor"
---
Added a new field `progressTotal` to track the total amount of data transferred during the upload/download process.
+10
View File
@@ -62,6 +62,7 @@
"dialog", "dialog",
"fs", "fs",
"global-shortcut", "global-shortcut",
"opener",
"http", "http",
"nfc", "nfc",
"notification", "notification",
@@ -87,6 +88,7 @@
"dialog-js", "dialog-js",
"fs-js", "fs-js",
"global-shortcut-js", "global-shortcut-js",
"opener-js",
"http-js", "http-js",
"nfc-js", "nfc-js",
"notification-js", "notification-js",
@@ -186,6 +188,14 @@
"path": "./plugins/global-shortcut", "path": "./plugins/global-shortcut",
"manager": "javascript" "manager": "javascript"
}, },
"opener": {
"path": "./plugins/opener",
"manager": "rust"
},
"opener-js": {
"path": "./plugins/opener",
"manager": "javascript"
},
"haptics": { "haptics": {
"path": "./plugins/haptics", "path": "./plugins/haptics",
"manager": "rust" "manager": "rust"
+5
View File
@@ -0,0 +1,5 @@
---
"updater": "minor"
---
Added support for `.deb` package updates on Linux systems.
+5
View File
@@ -0,0 +1,5 @@
---
clipboard-manager-js: patch
---
Fix clipboard manager client side api not copying fallback alternative text when calling `writeHtml`.
@@ -0,0 +1,6 @@
---
deep-link: patch
deep-link-js: patch
---
`onOpenUrl()` will now not call `getCurrent()` anymore, matching the documented behavior.
+6
View File
@@ -0,0 +1,6 @@
---
fs: minor
persisted-scope: minor
---
**Breaking Change:** Replaced the custom `tauri_plugin_fs::Scope` struct with `tauri::fs::Scope`.
@@ -0,0 +1,6 @@
---
'log-plugin': 'patch'
'log-js': 'patch'
---
Make webview log target more consistent that it always starts with `webview`
+6
View File
@@ -0,0 +1,6 @@
---
"fs": "patch"
"fs-js": "patch"
---
Improve performance of `readTextFile` and `readTextFileLines` APIs
+7
View File
@@ -0,0 +1,7 @@
---
"fs": "patch"
"fs-js": "patch"
---
Fix `readDir` function failing to read directories that contain broken symlinks.
+7
View File
@@ -0,0 +1,7 @@
---
"fs": "patch"
"fs-js": "patch"
---
Add support for using `ReadableStream<Unit8Array>` with `writeFile` API.
+5
View File
@@ -0,0 +1,5 @@
---
"http": "patch"
---
Add tracing logs for requestes and responses behind `tracing` feature flag.
@@ -0,0 +1,5 @@
---
'localhost': 'minor'
---
Add custom host binding to allow external access
+6
View File
@@ -0,0 +1,6 @@
---
"opener": "major"
"opener-js": "major"
---
Initial Release
@@ -0,0 +1,6 @@
---
"positioner-js": minor
---
Add `moveWindowConstrained` function that is similar to `moveWindow` but constrains the window to the screen dimensions in case of tray icon positions.
@@ -0,0 +1,6 @@
---
"positioner": minor
---
Add `WindowExt::move_window_constrained` method that is similar to `WindowExt::move_window` but constrains the window to the screen dimensions in case of tray icon positions.
+5
View File
@@ -0,0 +1,5 @@
---
shell: patch
---
shell.open will now try to execute `/usr/bin/xdg-open` before using `xdg-open` from `PATH`.
@@ -0,0 +1,5 @@
---
"sql": "patch"
---
Allow blocking on async code without creating a nested runtime.
@@ -53,6 +53,10 @@ jobs:
- .github/workflows/check-generated-files.yml - .github/workflows/check-generated-files.yml
- plugins/global-shortcut/guest-js/** - plugins/global-shortcut/guest-js/**
- plugins/global-shortcut/src/api-iife.js - plugins/global-shortcut/src/api-iife.js
opener:
- .github/workflows/check-generated-files.yml
- plugins/opener/guest-js/**
- plugins/opener/src/api-iife.js
haptics: haptics:
- .github/workflows/check-generated-files.yml - .github/workflows/check-generated-files.yml
- plugins/haptics/guest-js/** - plugins/haptics/guest-js/**
+4 -1
View File
@@ -66,6 +66,9 @@ jobs:
tauri-plugin-global-shortcut: tauri-plugin-global-shortcut:
- .github/workflows/lint-rust.yml - .github/workflows/lint-rust.yml
- plugins/global-shortcut/** - plugins/global-shortcut/**
tauri-plugin-opener:
- .github/workflows/lint-rust.yml
- plugins/opener/**
tauri-plugin-haptics: tauri-plugin-haptics:
- .github/workflows/lint-rust.yml - .github/workflows/lint-rust.yml
- plugins/haptics/** - plugins/haptics/**
@@ -126,7 +129,7 @@ jobs:
clippy: clippy:
needs: changes needs: changes
if: ${{ needs.changes.outputs.packages != '[]' && needs.changes.outputs.packages != '' }} if: ${{ needs.changes.outputs.packages != '[]' && needs.changes.outputs.packages != '' }}
runs-on: ubuntu-latest runs-on: ubuntu-22.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
+6 -2
View File
@@ -77,6 +77,10 @@ jobs:
- .github/workflows/test-rust.yml - .github/workflows/test-rust.yml
- Cargo.toml - Cargo.toml
- plugins/global-shortcut/** - plugins/global-shortcut/**
tauri-plugin-opener:
- .github/workflows/test-rust.yml
- Cargo.toml
- plugins/opener/**
tauri-plugin-haptics: tauri-plugin-haptics:
- .github/workflows/test-rust.yml - .github/workflows/test-rust.yml
- Cargo.toml - Cargo.toml
@@ -168,7 +172,7 @@ jobs:
} }
- { - {
target: x86_64-unknown-linux-gnu, target: x86_64-unknown-linux-gnu,
os: ubuntu-latest, os: ubuntu-22.04,
runner: 'cargo', runner: 'cargo',
command: 'test' command: 'test'
} }
@@ -202,7 +206,7 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev
- uses: dtolnay/rust-toolchain@1.78.0 - uses: dtolnay/rust-toolchain@1.77.2
with: with:
targets: ${{ matrix.platform.target }} targets: ${{ matrix.platform.target }}
Generated
+1100 -1035
View File
File diff suppressed because it is too large Load Diff
+9 -6
View File
@@ -10,24 +10,27 @@ resolver = "2"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
tracing = "0.1"
log = "0.4" log = "0.4"
tauri = { version = "2.0.0", default-features = false } tauri = { version = "2", default-features = false }
tauri-build = "2.0.0" tauri-build = "2"
tauri-plugin = "2.0.0" tauri-plugin = "2"
tauri-utils = "2.0.0" tauri-utils = "2"
serde_json = "1" serde_json = "1"
thiserror = "1" thiserror = "2"
url = "2" url = "2"
schemars = "0.8" schemars = "0.8"
dunce = "1" dunce = "1"
specta = "=2.0.0-rc.20" specta = "=2.0.0-rc.20"
glob = "0.3"
zbus = "4"
#tauri-specta = "=2.0.0-rc.11" #tauri-specta = "=2.0.0-rc.11"
[workspace.package] [workspace.package]
edition = "2021" edition = "2021"
authors = ["Tauri Programme within The Commons Conservancy"] authors = ["Tauri Programme within The Commons Conservancy"]
license = "Apache-2.0 OR MIT" license = "Apache-2.0 OR MIT"
rust-version = "1.78" rust-version = "1.77.2"
repository = "https://github.com/tauri-apps/plugins-workspace" repository = "https://github.com/tauri-apps/plugins-workspace"
# default to small, optimized release binaries # default to small, optimized release binaries
+17 -8
View File
@@ -1,36 +1,45 @@
# Official Tauri Plugins
This repo and all plugins require a Rust version of at least **1.77.2**
## Plugins Found Here ## Plugins Found Here
| | | Win | Mac | Lin | iOS | And | | | | Win | Mac | Lin | iOS | And |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | --- | --- | --- | | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | --- | --- | --- |
| [autostart](plugins/autostart) | Automatically launch your app at system startup. | ✅ | ✅ | ✅ | ? | ? | | [autostart](plugins/autostart) | Automatically launch your app at system startup. | ✅ | ✅ | ✅ | | |
| [barcode-scanner](plugins/barcode-scanner) | Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes. | ? | ? | ? | ✅ | ✅ | | [barcode-scanner](plugins/barcode-scanner) | Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes. | ? | ? | ? | ✅ | ✅ |
| [biometric](plugins/biometric) | Prompt the user for biometric authentication on Android and iOS. | ? | ? | ? | ✅ | ✅ | | [biometric](plugins/biometric) | Prompt the user for biometric authentication on Android and iOS. | ? | ? | ? | ✅ | ✅ |
| [cli](plugins/cli) | Parse arguments from your Command Line Interface | ✅ | ✅ | ✅ | ? | ? | | [cli](plugins/cli) | Parse arguments from your Command Line Interface | ✅ | ✅ | ✅ | | |
| [clipboard-manager](plugins/clipboard-manager) | Read and write to the system clipboard. | ✅ | ✅ | ✅ | ✅ | ✅ | | [clipboard-manager](plugins/clipboard-manager) | Read and write to the system clipboard. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [deep-link](plugins/deep-link) | Set your Tauri application as the default handler for an URL. | ✅ | ✅ | ✅ | ✅ | ✅ | | [deep-link](plugins/deep-link) | Set your Tauri application as the default handler for an URL. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [dialog](plugins/dialog) | Native system dialogs for opening and saving files along with message dialogs. | ✅ | ✅ | ✅ | ✅ | ✅ | | [dialog](plugins/dialog) | Native system dialogs for opening and saving files along with message dialogs. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [fs](plugins/fs) | Access the file system. | ✅ | ✅ | ✅ | ? | ? | | [fs](plugins/fs) | Access the file system. | ✅ | ✅ | ✅ | ? | ? |
| [geolocation](plugins/geolocation) | Get and track current device position. | ? | ? | ? | ✅ | ✅ |
| [global-shortcut](plugins/global-shortcut) | Register global shortcuts. | ✅ | ✅ | ✅ | ? | ? | | [global-shortcut](plugins/global-shortcut) | Register global shortcuts. | ✅ | ✅ | ✅ | ? | ? |
| [haptics](plugins/haptics) | Haptic feedback and vibrations. | ? | ? | ? | ✅ | ✅ |
| [http](plugins/http) | Access the HTTP client written in Rust. | ✅ | ✅ | ✅ | ✅ | ✅ | | [http](plugins/http) | Access the HTTP client written in Rust. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [localhost](plugins/localhost) | Use a localhost server in production apps. | ✅ | ✅ | ✅ | ? | ? | | [localhost](plugins/localhost) | Use a localhost server in production apps. | ✅ | ✅ | ✅ | ? | ? |
| [log](plugins/log) | Configurable logging. | ✅ | ✅ | ✅ | ✅ | ✅ | | [log](plugins/log) | Configurable logging. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [nfc](plugins/nfc) | Read and write NFC tags on Android and iOS. | ? | ? | ? | ✅ | ✅ | | [nfc](plugins/nfc) | Read and write NFC tags on Android and iOS. | ? | ? | ? | ✅ | ✅ |
| [notification](plugins/notification) | Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. | ✅ | ✅ | ✅ | ✅ | ✅ | | [notification](plugins/notification) | Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [opener](plugins/opener) | Open files and URLs using their default application. | ✅ | ✅ | ✅ | ? | ? |
| [os](plugins/os) | Read information about the operating system. | ✅ | ✅ | ✅ | ✅ | ✅ | | [os](plugins/os) | Read information about the operating system. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [persisted-scope](plugins/persisted-scope) | Persist runtime scope changes on the filesystem. | ✅ | ✅ | ✅ | ? | ? | | [persisted-scope](plugins/persisted-scope) | Persist runtime scope changes on the filesystem. | ✅ | ✅ | ✅ | ? | ? |
| [positioner](plugins/positioner) | Move windows to common locations. | ✅ | ✅ | ✅ | ? | ? | | [positioner](plugins/positioner) | Move windows to common locations. | ✅ | ✅ | ✅ | | |
| [process](plugins/process) | This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin. | ✅ | ✅ | ✅ | ? | ? | | [process](plugins/process) | This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin. | ✅ | ✅ | ✅ | ? | ? |
| [shell](plugins/shell) | Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application. | ✅ | ✅ | ✅ | ? | ? | | [shell](plugins/shell) | Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application. | ✅ | ✅ | ✅ | ? | ? |
| [single-instance](plugins/single-instance) | Ensure a single instance of your tauri app is running. | ✅ | ? | ✅ | ? | ? | | [single-instance](plugins/single-instance) | Ensure a single instance of your tauri app is running. | ✅ | | ✅ | | |
| [sql](plugins/sql) | Interface with SQL databases. | ✅ | ✅ | ✅ | ? | ? | | [sql](plugins/sql) | Interface with SQL databases. | ✅ | ✅ | ✅ | ? | |
| [store](plugins/store) | Persistent key value storage. | ✅ | ✅ | ✅ | ✅ | ✅ | | [store](plugins/store) | Persistent key value storage. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [stronghold](plugins/stronghold) | Encrypted, secure database. | ✅ | ✅ | ✅ | ? | ? | | [stronghold](plugins/stronghold) | Encrypted, secure database. | ✅ | ✅ | ✅ | ? | ? |
| [updater](plugins/updater) | In-app updates for Tauri applications. | ✅ | ✅ | ✅ | ? | ? | | [updater](plugins/updater) | In-app updates for Tauri applications. | ✅ | ✅ | ✅ | | |
| [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ? | ? | | [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ? | ? |
| [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? | | [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? |
| [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ? | ? | | [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | | |
_This repo and all plugins require a Rust version of at least **1.78**_ - ✅: (Partially) Supported
- ❌: Not supported
- `?` : Unknown/Untested or Planned
## Contributing ## Contributing
+16
View File
@@ -1,5 +1,21 @@
# Changelog # Changelog
## \[2.0.2]
### Dependencies
- Upgraded to `fs-js@2.0.2`
## \[2.0.1]
### Dependencies
- Upgraded to `dialog-js@2.0.1`
- Upgraded to `fs-js@2.0.1`
- Upgraded to `http-js@2.0.1`
- Upgraded to `shell-js@2.0.1`
- Upgraded to `store-js@2.1.0`
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+16 -14
View File
@@ -1,42 +1,44 @@
{ {
"name": "svelte-app", "name": "api",
"private": true, "private": true,
"version": "2.0.0", "version": "2.0.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --clearScreen false", "dev": "vite --clearScreen false",
"build": "vite build", "build": "vite build",
"serve": "vite preview" "serve": "vite preview",
"tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0", "@tauri-apps/api": "2.1.1",
"@tauri-apps/plugin-barcode-scanner": "2.0.0", "@tauri-apps/plugin-barcode-scanner": "2.0.0",
"@tauri-apps/plugin-biometric": "2.0.0", "@tauri-apps/plugin-biometric": "2.0.0",
"@tauri-apps/plugin-cli": "2.0.0", "@tauri-apps/plugin-cli": "2.0.0",
"@tauri-apps/plugin-clipboard-manager": "2.0.0", "@tauri-apps/plugin-clipboard-manager": "2.0.0",
"@tauri-apps/plugin-dialog": "2.0.0", "@tauri-apps/plugin-dialog": "2.0.1",
"@tauri-apps/plugin-fs": "2.0.0", "@tauri-apps/plugin-fs": "2.0.2",
"@tauri-apps/plugin-geolocation": "2.0.0", "@tauri-apps/plugin-geolocation": "2.0.0",
"@tauri-apps/plugin-global-shortcut": "2.0.0", "@tauri-apps/plugin-global-shortcut": "2.0.0",
"@tauri-apps/plugin-opener": "1.0.0",
"@tauri-apps/plugin-haptics": "2.0.0", "@tauri-apps/plugin-haptics": "2.0.0",
"@tauri-apps/plugin-http": "2.0.0", "@tauri-apps/plugin-http": "2.0.1",
"@tauri-apps/plugin-nfc": "2.0.0", "@tauri-apps/plugin-nfc": "2.0.0",
"@tauri-apps/plugin-notification": "2.0.0", "@tauri-apps/plugin-notification": "2.0.0",
"@tauri-apps/plugin-os": "2.0.0", "@tauri-apps/plugin-os": "2.0.0",
"@tauri-apps/plugin-process": "2.0.0", "@tauri-apps/plugin-process": "2.0.0",
"@tauri-apps/plugin-shell": "2.0.0", "@tauri-apps/plugin-shell": "2.0.1",
"@tauri-apps/plugin-store": "2.0.0", "@tauri-apps/plugin-store": "2.1.0",
"@tauri-apps/plugin-updater": "2.0.0", "@tauri-apps/plugin-updater": "2.0.0",
"@zerodevx/svelte-json-view": "1.0.11" "@zerodevx/svelte-json-view": "1.0.11"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/codicon": "^1.1.37", "@iconify-json/codicon": "^1.1.37",
"@iconify-json/ph": "^1.1.8", "@iconify-json/ph": "^1.1.8",
"@sveltejs/vite-plugin-svelte": "^3.0.1", "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tauri-apps/cli": "2.0.0", "@tauri-apps/cli": "2.1.0",
"@unocss/extractor-svelte": "^0.63.0", "@unocss/extractor-svelte": "^0.64.0",
"svelte": "^4.2.19", "svelte": "^5.0.0",
"unocss": "^0.63.0", "unocss": "^0.64.0",
"vite": "^5.4.7" "vite": "^5.4.7"
} }
} }
+56
View File
@@ -1,5 +1,61 @@
# Changelog # Changelog
## \[2.0.5]
### Dependencies
- Upgraded to `clipboard-manager@2.0.2`
- Upgraded to `log-plugin@2.0.2`
## \[2.0.4]
### Dependencies
- Upgraded to `fs@2.0.3`
- Upgraded to `dialog@2.0.3`
- Upgraded to `http@2.0.3`
## \[2.0.3]
### Dependencies
- Upgraded to `dialog@2.0.2`
- Upgraded to `fs@2.0.2`
- Upgraded to `http@2.0.2`
- Upgraded to `shell@2.0.2`
- Upgraded to `store@2.1.0`
## \[2.0.2]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
### Dependencies
- Upgraded to `barcode-scanner@2.0.1`
- Upgraded to `biometric@2.0.1`
- Upgraded to `cli@2.0.1`
- Upgraded to `clipboard-manager@2.0.1`
- Upgraded to `fs@2.0.1`
- Upgraded to `dialog@2.0.1`
- Upgraded to `geolocation@2.0.1`
- Upgraded to `global-shortcut@2.0.1`
- Upgraded to `haptics@2.0.1`
- Upgraded to `http@2.0.1`
- Upgraded to `log-plugin@2.0.1`
- Upgraded to `nfc@2.0.1`
- Upgraded to `notification@2.0.1`
- Upgraded to `os@2.0.1`
- Upgraded to `process@2.0.1`
- Upgraded to `shell@2.0.1`
- Upgraded to `store@2.0.1`
- Upgraded to `updater@2.0.2`
## \[2.0.1]
### Dependencies
- Upgraded to `updater@2.0.1`
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+20 -19
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "api" name = "api"
publish = false publish = false
version = "2.0.0" version = "2.0.5"
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 }
@@ -19,22 +19,23 @@ serde_json = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
tiny_http = "0.12" tiny_http = "0.12"
log = { workspace = true } log = { workspace = true }
tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.0" } tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.2" }
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.0", features = [ tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.3", features = [
"watch", "watch",
] } ] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.0.0" } tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.0.2" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.0" } tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.3" }
tauri-plugin-http = { path = "../../../plugins/http", features = [ tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart", "multipart",
], version = "2.0.0" } ], version = "2.0.3" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.0.0", features = [ tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.0.1", features = [
"windows7-compat", "windows7-compat",
] } ] }
tauri-plugin-os = { path = "../../../plugins/os", version = "2.0.0" } tauri-plugin-os = { path = "../../../plugins/os", version = "2.0.1" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.0.0" } tauri-plugin-process = { path = "../../../plugins/process", version = "2.0.1" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.0.0" } tauri-plugin-opener = { path = "../../../plugins/opener", version = "1.0.0" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.0.0" } tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.0.2" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.1.0" }
[dependencies.tauri] [dependencies.tauri]
workspace = true workspace = true
@@ -50,17 +51,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.0.0" } tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.0.1" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.0.0" } tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.0.1" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.0.0" } tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.0.2" }
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.0.0" } tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.0.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.0.0" } tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.0.1" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.0.0" } tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.0.1" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.0.0" } tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.0.1" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.0.0" } tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.0.1" }
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.0.0" } tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.0.1" }
[features] [features]
prod = ["tauri/custom-protocol"] prod = ["tauri/custom-protocol"]
@@ -53,6 +53,7 @@
} }
] ]
}, },
"shell:allow-open",
"shell:allow-kill", "shell:allow-kill",
"shell:allow-stdin-write", "shell:allow-stdin-write",
"process:allow-exit", "process:allow-exit",
@@ -79,10 +80,11 @@
], ],
"deny": ["$APPDATA/db/*.stronghold"] "deny": ["$APPDATA/db/*.stronghold"]
}, },
"store:allow-entries", "store:default",
"store:allow-get", "opener:default",
"store:allow-set", {
"store:allow-save", "identifier": "opener:allow-open-path",
"store:allow-load" "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }]
}
] ]
} }
@@ -9,6 +9,8 @@
"updater:default", "updater:default",
"global-shortcut:allow-unregister", "global-shortcut:allow-unregister",
"global-shortcut:allow-register", "global-shortcut:allow-register",
"global-shortcut:allow-unregister-all" "global-shortcut:allow-unregister-all",
{ "identifier": "fs:allow-watch", "allow": ["*", "**/*"] },
"fs:allow-unwatch"
] ]
} }
@@ -15,6 +15,10 @@
"geolocation:allow-check-permissions", "geolocation:allow-check-permissions",
"geolocation:allow-request-permissions", "geolocation:allow-request-permissions",
"geolocation:allow-watch-position", "geolocation:allow-watch-position",
"geolocation:allow-get-current-position" "geolocation:allow-get-current-position",
"haptics:allow-impact-feedback",
"haptics:allow-notification-feedback",
"haptics:allow-selection-feedback",
"haptics:allow-vibrate"
] ]
} }
+4 -1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
@@ -18,7 +19,7 @@
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$USER_HOME$/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tauri-2.0.0-rc.7/mobile/android" /> <option value="$USER_HOME$/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tauri-2.0.2/mobile/android" />
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/buildSrc" /> <option value="$PROJECT_DIR$/buildSrc" />
@@ -27,6 +28,8 @@
<option value="$PROJECT_DIR$/../../../../../plugins/clipboard-manager/android" /> <option value="$PROJECT_DIR$/../../../../../plugins/clipboard-manager/android" />
<option value="$PROJECT_DIR$/../../../../../plugins/dialog/android" /> <option value="$PROJECT_DIR$/../../../../../plugins/dialog/android" />
<option value="$PROJECT_DIR$/../../../../../plugins/fs/android" /> <option value="$PROJECT_DIR$/../../../../../plugins/fs/android" />
<option value="$PROJECT_DIR$/../../../../../plugins/geolocation/android" />
<option value="$PROJECT_DIR$/../../../../../plugins/haptics/android" />
<option value="$PROJECT_DIR$/../../../../../plugins/nfc/android" /> <option value="$PROJECT_DIR$/../../../../../plugins/nfc/android" />
<option value="$PROJECT_DIR$/../../../../../plugins/notification/android" /> <option value="$PROJECT_DIR$/../../../../../plugins/notification/android" />
<option value="$PROJECT_DIR$/../../../../../plugins/shell/android" /> <option value="$PROJECT_DIR$/../../../../../plugins/shell/android" />
+3 -1
View File
@@ -36,6 +36,7 @@ pub fn run() {
.plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_store::Builder::default().build())
.setup(move |app| { .setup(move |app| {
@@ -67,7 +68,8 @@ pub fn run() {
.user_agent(&format!("Tauri API - {}", std::env::consts::OS)) .user_agent(&format!("Tauri API - {}", std::env::consts::OS))
.title("Tauri API Validation") .title("Tauri API Validation")
.inner_size(1000., 800.) .inner_size(1000., 800.)
.min_inner_size(600., 400.); .min_inner_size(600., 400.)
.visible(false);
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
+56 -42
View File
@@ -1,6 +1,5 @@
<script> <script>
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
import { open } from '@tauri-apps/plugin-shell'
import { getCurrentWindow } from '@tauri-apps/api/window' import { getCurrentWindow } from '@tauri-apps/api/window'
import { getCurrentWebview } from '@tauri-apps/api/webview' import { getCurrentWebview } from '@tauri-apps/api/webview'
import * as os from '@tauri-apps/plugin-os' import * as os from '@tauri-apps/plugin-os'
@@ -14,6 +13,7 @@
import Notifications from './views/Notifications.svelte' import Notifications from './views/Notifications.svelte'
import Shortcuts from './views/Shortcuts.svelte' import Shortcuts from './views/Shortcuts.svelte'
import Shell from './views/Shell.svelte' import Shell from './views/Shell.svelte'
import Opener from './views/Opener.svelte'
import Store from './views/Store.svelte' import Store from './views/Store.svelte'
import Updater from './views/Updater.svelte' import Updater from './views/Updater.svelte'
import Clipboard from './views/Clipboard.svelte' import Clipboard from './views/Clipboard.svelte'
@@ -21,6 +21,7 @@
import Scanner from './views/Scanner.svelte' import Scanner from './views/Scanner.svelte'
import Biometric from './views/Biometric.svelte' import Biometric from './views/Biometric.svelte'
import Geolocation from './views/Geolocation.svelte' import Geolocation from './views/Geolocation.svelte'
import Haptics from './views/Haptics.svelte'
import { onMount, tick } from 'svelte' import { onMount, tick } from 'svelte'
import { ask } from '@tauri-apps/plugin-dialog' import { ask } from '@tauri-apps/plugin-dialog'
@@ -91,6 +92,11 @@
component: Shell, component: Shell,
icon: 'i-codicon-terminal-bash' icon: 'i-codicon-terminal-bash'
}, },
{
label: 'Opener',
component: Opener,
icon: 'i-codicon-link-external'
},
{ {
label: 'Store', label: 'Store',
component: Store, component: Store,
@@ -130,6 +136,11 @@
label: 'Geolocation', label: 'Geolocation',
component: Geolocation, component: Geolocation,
icon: 'i-ph-map-pin' icon: 'i-ph-map-pin'
},
isMobile && {
label: 'Haptics',
component: Haptics,
icon: 'i-ph-vibrate'
} }
] ]
@@ -205,7 +216,7 @@
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
} }
// this function is renders HTML without sanitizing it so it's insecure // this function renders HTML without sanitizing it so it's insecure
// we only use it with our own input data // we only use it with our own input data
async function insecureRenderHtml(html) { async function insecureRenderHtml(html) {
messages.update((r) => [ messages.update((r) => [
@@ -334,42 +345,46 @@
children:h-100% children:w-12 children:inline-flex children:h-100% children:w-12 children:inline-flex
children:items-center children:justify-center" children:items-center children:justify-center"
> >
<span <button
aria-label="Toggle dark mode"
title={isDark ? 'Switch to Light mode' : 'Switch to Dark mode'} title={isDark ? 'Switch to Light mode' : 'Switch to Dark mode'}
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker" class="bg-inherit border-none hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={toggleDark} on:click={toggleDark}
> >
{#if isDark} {#if isDark}
<div class="i-ph-sun" /> <div class="i-ph-sun"></div>
{:else} {:else}
<div class="i-ph-moon" /> <div class="i-ph-moon"></div>
{/if} {/if}
</span> </button>
<span <button
aria-label="Minimize window"
title="Minimize" title="Minimize"
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker" class="bg-inherit border-none hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={minimize} on:click={minimize}
> >
<div class="i-codicon-chrome-minimize" /> <div class="i-codicon-chrome-minimize"></div>
</span> </button>
<span <button
aria-label="Maximize window"
title={isWindowMaximized ? 'Restore' : 'Maximize'} title={isWindowMaximized ? 'Restore' : 'Maximize'}
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker" class="bg-inherit border-none hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={toggleMaximize} on:click={toggleMaximize}
> >
{#if isWindowMaximized} {#if isWindowMaximized}
<div class="i-codicon-chrome-restore" /> <div class="i-codicon-chrome-restore"></div>
{:else} {:else}
<div class="i-codicon-chrome-maximize" /> <div class="i-codicon-chrome-maximize"></div>
{/if} {/if}
</span> </button>
<span <button
aria-label="Close window"
title="Close" title="Close"
class="hover:bg-red-700 dark:hover:bg-red-700 hover:text-darkPrimaryText active:bg-red-700/90 dark:active:bg-red-700/90 active:text-darkPrimaryText" class="bg-inherit border-none hover:bg-red-700 dark:hover:bg-red-700 hover:text-darkPrimaryText active:bg-red-700/90 dark:active:bg-red-700/90 active:text-darkPrimaryText"
on:click={close} on:click={close}
> >
<div class="i-codicon-chrome-close" /> <div class="i-codicon-chrome-close"></div>
</span> </button>
</span> </span>
</div> </div>
{/if} {/if}
@@ -377,13 +392,13 @@
<!-- Sidebar toggle, only visible on small screens --> <!-- Sidebar toggle, only visible on small screens -->
<div <div
id="sidebarToggle" id="sidebarToggle"
class="z-2000 sidebar-toggle display-none lt-sm:flex justify-center absolute items-center w-8 h-8 rd-8 class="z-2000 sidebar-toggle hidden lt-sm:flex justify-center absolute items-center w-8 h-8 rd-8
bg-accent dark:bg-darkAccent active:bg-accentDark dark:active:bg-darkAccentDark" bg-accent dark:bg-darkAccent active:bg-accentDark dark:active:bg-darkAccentDark"
> >
{#if isSideBarOpen} {#if isSideBarOpen}
<span class="i-codicon-close animate-duration-300ms animate-fade-in" /> <span class="i-codicon-close animate-duration-300ms animate-fade-in"></span>
{:else} {:else}
<span class="i-codicon-menu animate-duration-300ms animate-fade-in" /> <span class="i-codicon-menu animate-duration-300ms animate-fade-in"></span>
{/if} {/if}
</div> </div>
@@ -395,24 +410,21 @@
class="lt-sm:h-screen lt-sm:shadow-lg lt-sm:shadow lt-sm:transition-transform lt-sm:absolute lt-sm:z-1999 class="lt-sm:h-screen lt-sm:shadow-lg lt-sm:shadow lt-sm:transition-transform lt-sm:absolute lt-sm:z-1999
bg-darkPrimaryLighter transition-colors-250 overflow-hidden grid select-none px-2" bg-darkPrimaryLighter transition-colors-250 overflow-hidden grid select-none px-2"
> >
<img <a href="https://tauri.app" target="_blank">
on:click={() => open('https://tauri.app/')} <img class="p-7" src="tauri_logo.png" alt="Tauri logo" />
class="self-center p-7 cursor-pointer" </a>
src="tauri_logo.png"
alt="Tauri logo"
/>
{#if !isWindows} {#if !isWindows}
<a href="##" class="nv justify-between h-8" on:click={toggleDark}> <a href="##" class="nv justify-between h-8" on:click={toggleDark}>
{#if isDark} {#if isDark}
Switch to Light mode Switch to Light mode
<div class="i-ph-sun" /> <div class="i-ph-sun"></div>
{:else} {:else}
Switch to Dark mode Switch to Dark mode
<div class="i-ph-moon" /> <div class="i-ph-moon"></div>
{/if} {/if}
</a> </a>
<br /> <br />
<div class="bg-white/5 h-2px" /> <div class="bg-white/5 h-2px"></div>
<br /> <br />
{/if} {/if}
@@ -422,7 +434,7 @@
href="https://tauri.app/v1/guides/" href="https://tauri.app/v1/guides/"
> >
Documentation Documentation
<span class="i-codicon-link-external" /> <span class="i-codicon-link-external"></span>
</a> </a>
<a <a
class="nv justify-between h-8" class="nv justify-between h-8"
@@ -430,7 +442,7 @@
href="https://github.com/tauri-apps/tauri" href="https://github.com/tauri-apps/tauri"
> >
GitHub GitHub
<span class="i-codicon-link-external" /> <span class="i-codicon-link-external"></span>
</a> </a>
<a <a
class="nv justify-between h-8" class="nv justify-between h-8"
@@ -438,10 +450,10 @@
href="https://github.com/tauri-apps/tauri/tree/dev/examples/api" href="https://github.com/tauri-apps/tauri/tree/dev/examples/api"
> >
Source Source
<span class="i-codicon-link-external" /> <span class="i-codicon-link-external"></span>
</a> </a>
<br /> <br />
<div class="bg-white/5 h-2px" /> <div class="bg-white/5 h-2px"></div>
<br /> <br />
<div <div
class="flex flex-col overflow-y-auto children-h-10 children-flex-none gap-1" class="flex flex-col overflow-y-auto children-h-10 children-flex-none gap-1"
@@ -456,7 +468,7 @@
isSideBarOpen = false isSideBarOpen = false
}} }}
> >
<div class="{view.icon} mr-2" /> <div class="{view.icon} mr-2"></div>
<p>{view.label}</p></a <p>{view.label}</p></a
> >
{/if} {/if}
@@ -485,21 +497,23 @@
id="console" id="console"
class="select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden" class="select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden"
> >
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div <div
on:mousedown={startResizingConsole} on:mousedown={startResizingConsole}
class="bg-black/20 h-2px cursor-ns-resize" class="bg-black/20 h-2px cursor-ns-resize"
/> ></div>
<div class="flex justify-between items-center px-2"> <div class="flex justify-between items-center px-2">
<p class="font-semibold">Console</p> <p class="font-semibold">Console</p>
<div <button
class="cursor-pointer h-85% rd-1 p-1 flex justify-center items-center aria-label="Clear Console"
class="cursor-pointer h-85% rd-1 p-1 flex justify-center items-center border-none bg-inherit
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25 active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
" "
on:click={clear} on:click={clear}
> >
<div class="i-codicon-clear-all" /> <div class="i-codicon-clear-all"></div>
</div> </button>
</div> </div>
<div <div
bind:this={consoleTextEl} bind:this={consoleTextEl}
+2 -1
View File
@@ -5,8 +5,9 @@
import 'uno.css' import 'uno.css'
import './app.css' import './app.css'
import App from './App.svelte' import App from './App.svelte'
import { mount } from 'svelte'
const app = new App({ const app = mount(App, {
target: document.querySelector('#app') target: document.querySelector('#app')
}) })
+5 -5
View File
@@ -1,14 +1,14 @@
<script> <script>
import { getMatches } from "@tauri-apps/plugin-cli"; import { getMatches } from '@tauri-apps/plugin-cli'
export let onMessage; export let onMessage
function cliMatches() { function cliMatches() {
getMatches().then(onMessage).catch(onMessage); getMatches().then(onMessage).catch(onMessage)
} }
</script> </script>
<p> <div>
This binary can be run from the terminal and takes the following arguments: This binary can be run from the terminal and takes the following arguments:
<code class="code-block flex flex-wrap my-2"> <code class="code-block flex flex-wrap my-2">
<pre> <pre>
@@ -17,7 +17,7 @@
--verbose</pre> --verbose</pre>
</code> </code>
Additionally, it has a <code>update --background</code> subcommand. Additionally, it has a <code>update --background</code> subcommand.
</p> </div>
<br /> <br />
<div class="note"> <div class="note">
Note that the arguments are only parsed, not implemented. Note that the arguments are only parsed, not implemented.
+46
View File
@@ -0,0 +1,46 @@
<script>
import {
vibrate,
impactFeedback,
notificationFeedback,
selectionFeedback
} from '@tauri-apps/plugin-haptics'
export let onMessage
</script>
<div>
<button
class="btn"
on:click={() => vibrate(300).then(onMessage).catch(onMessage)}
>vibrate short</button
>
<button
class="btn"
on:click={() => vibrate(1500).then(onMessage).catch(onMessage)}
>vibrate long</button
>
<button
class="btn"
on:click={() => impactFeedback('medium').then(onMessage).catch(onMessage)}
>impact medium</button
>
<button
class="btn"
on:click={() =>
notificationFeedback('warning').then(onMessage).catch(onMessage)}
>notification warning</button
>
<button
class="btn"
on:click={() => selectionFeedback().then(onMessage).catch(onMessage)}
>selection</button
>
</div>
<br />
<p>
Depending on your device settings for haptic feedback some of the buttons may
not work.
</p>
+35 -35
View File
@@ -1,69 +1,69 @@
<script> <script>
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"; import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
import { JsonView } from "@zerodevx/svelte-json-view"; import { JsonView } from '@zerodevx/svelte-json-view'
let httpMethod = "GET"; let httpMethod = 'GET'
let httpBody = ""; let httpBody = ''
export let onMessage; export let onMessage
async function makeHttpRequest() { async function makeHttpRequest() {
let method = httpMethod || "GET"; let method = httpMethod || 'GET'
const options = { const options = {
method: method || "GET", method: method || 'GET',
headers: {}, headers: {}
}; }
let bodyType; let bodyType
if (method !== "GET") { if (method !== 'GET') {
options.body = httpBody; options.body = httpBody
if ( if (
(httpBody.startsWith("{") && httpBody.endsWith("}")) || (httpBody.startsWith('{') && httpBody.endsWith('}')) ||
(httpBody.startsWith("[") && httpBody.endsWith("]")) (httpBody.startsWith('[') && httpBody.endsWith(']'))
) { ) {
options.headers["Content-Type"] = "application/json"; options.headers['Content-Type'] = 'application/json'
bodyType = "json"; bodyType = 'json'
} else if (httpBody !== "") { } else if (httpBody !== '') {
bodyType = "text"; bodyType = 'text'
} }
} }
const response = await tauriFetch("http://localhost:3003", options); const response = await tauriFetch('http://localhost:3003', options)
const body = const body =
bodyType === "json" ? await response.json() : await response.text(); bodyType === 'json' ? await response.json() : await response.text()
onMessage({ onMessage({
url: response.url, url: response.url,
status: response.status, status: response.status,
ok: response.ok, ok: response.ok,
headers: Object.fromEntries(response.headers.entries()), headers: Object.fromEntries(response.headers.entries()),
body, body
}); })
} }
/// http form /// http form
let foo = "baz"; let foo = 'baz'
let bar = "qux"; let bar = 'qux'
let result = null; let result = null
async function doPost() { async function doPost() {
const form = new FormData(); const form = new FormData()
form.append("foo", foo); form.append('foo', foo)
form.append("bar", bar); form.append('bar', bar)
const response = await tauriFetch("http://localhost:3003/tauri", { const response = await tauriFetch('http://localhost:3003/tauri', {
method: "POST", method: 'POST',
body: form, body: form
}); })
result = { result = {
url: response.url, url: response.url,
status: response.status, status: response.status,
ok: response.ok, ok: response.ok,
headers: Object.fromEntries(response.headers.entries()), headers: Object.fromEntries(response.headers.entries()),
body: await response.text(), body: await response.text()
}; }
} }
</script> </script>
@@ -82,7 +82,7 @@
placeholder="Request body" placeholder="Request body"
rows="5" rows="5"
bind:value={httpBody} bind:value={httpBody}
/> ></textarea>
<br /> <br />
<button class="btn" id="make-request"> Make request </button> <button class="btn" id="make-request"> Make request </button>
</form> </form>
+66
View File
@@ -0,0 +1,66 @@
<script>
import * as opener from '@tauri-apps/plugin-opener'
export let onMessage
let url = ''
let urlProgram = ''
function openUrl() {
opener.openUrl(url, urlProgram ? urlProgram : undefined).catch(onMessage)
}
let path = ''
let pathProgram = ''
function openPath() {
opener
.openPath(path, pathProgram ? pathProgram : undefined)
.catch(onMessage)
}
let revealPath = ''
function revealItemInDir() {
opener.revealItemInDir(revealPath).catch(onMessage)
}
</script>
<div class="flex flex-col gap-2">
<form
class="flex flex-row gap-2 items-center"
on:submit|preventDefault={openUrl}
>
<button class="btn" type="submit">Open URL</button>
<input
class="input grow"
placeholder="Type the URL to open..."
bind:value={url}
/>
<span> with </span>
<input class="input" bind:value={urlProgram} />
</form>
<form
class="flex flex-row gap-2 items-center"
on:submit|preventDefault={openPath}
>
<button class="btn" type="submit">Open Path</button>
<input
class="input grow"
placeholder="Type the path to open..."
bind:value={path}
/>
<span> with </span>
<input class="input" bind:value={pathProgram} />
</form>
<form
class="flex flex-row gap-2 items-center"
on:submit|preventDefault={revealItemInDir}
>
<button class="btn" type="submit">Reveal</button>
<input
class="input grow"
placeholder="Type the path to reveal..."
bind:value={revealPath}
/>
</form>
</div>
+29 -21
View File
@@ -1,38 +1,44 @@
<script> <script>
import { scan, checkPermissions, requestPermissions, Format, cancel } from "@tauri-apps/plugin-barcode-scanner"; import {
scan,
checkPermissions,
requestPermissions,
Format,
cancel
} from '@tauri-apps/plugin-barcode-scanner'
export let onMessage; export let onMessage
let scanning = false; let scanning = false
let windowed = true; let windowed = true
let formats = [Format.QRCode]; let formats = [Format.QRCode]
const supportedFormats = [Format.QRCode, Format.EAN13]; const supportedFormats = [Format.QRCode, Format.EAN13]
async function startScan() { async function startScan() {
let permission = await checkPermissions(); let permission = await checkPermissions()
if (permission === 'prompt') { if (permission === 'prompt') {
permission = await requestPermissions(); permission = await requestPermissions()
} }
if (permission === 'granted') { if (permission === 'granted') {
scanning = true; scanning = true
scan({ windowed, formats }) scan({ windowed, formats })
.then((res) => { .then((res) => {
scanning = false; scanning = false
onMessage(res); onMessage(res)
}) })
.catch((error) => { .catch((error) => {
scanning = false; scanning = false
onMessage(error); onMessage(error)
}); })
} else { } else {
onMessage('Permission denied') onMessage('Permission denied')
} }
} }
async function cancelScan() { async function cancelScan() {
await cancel(); await cancel()
scanning = false; scanning = false
onMessage("cancelled"); onMessage('cancelled')
} }
</script> </script>
@@ -59,11 +65,12 @@
<div class="barcode-scanner--area--container"> <div class="barcode-scanner--area--container">
<div class="relative"> <div class="relative">
<p>Aim your camera at a QR code</p> <p>Aim your camera at a QR code</p>
<button class="btn" type="button" on:click={cancelScan}>Cancel</button> <button class="btn" type="button" on:click={cancelScan}>Cancel</button
>
</div> </div>
<div class="square surround-cover"> <div class="square surround-cover">
<div class="barcode-scanner--area--outer surround-cover"> <div class="barcode-scanner--area--outer surround-cover">
<div class="barcode-scanner--area--inner" /> <div class="barcode-scanner--area--inner"></div>
</div> </div>
</div> </div>
</div> </div>
@@ -111,7 +118,7 @@
transition: 0.3s; transition: 0.3s;
} }
.square:after { .square:after {
content: ""; content: '';
top: 0; top: 0;
display: block; display: block;
padding-bottom: 100%; padding-bottom: 100%;
@@ -141,7 +148,8 @@
width: 100%; width: 100%;
margin: 1rem; margin: 1rem;
border: 2px solid #fff; border: 2px solid #fff;
box-shadow: 0px 0px 2px 1px rgb(0 0 0 / 0.5), box-shadow:
0px 0px 2px 1px rgb(0 0 0 / 0.5),
inset 0px 0px 2px 1px rgb(0 0 0 / 0.5); inset 0px 0px 2px 1px rgb(0 0 0 / 0.5);
border-radius: 1rem; border-radius: 1rem;
} }
+60 -18
View File
@@ -1,5 +1,5 @@
<script> <script>
import { Store } from "@tauri-apps/plugin-store"; import { LazyStore } from "@tauri-apps/plugin-store";
import { onMount } from "svelte"; import { onMount } from "svelte";
export let onMessage; export let onMessage;
@@ -7,28 +7,65 @@
let key; let key;
let value; let value;
const store = new Store("cache.json"); let store = new LazyStore("cache.json");
let cache = {}; let cache = {};
onMount(async () => { async function refreshEntries() {
await store.load(); try {
const values = await store.entries(); const values = await store.entries();
for (const [key, value] of values) { cache = {};
cache[key] = value; for (const [key, value] of values) {
cache[key] = value;
}
} catch (error) {
onMessage(error);
} }
cache = cache; }
onMount(async () => {
await refreshEntries();
}); });
function write(key, value) { async function write(key, value) {
store try {
.set(key, value) if (value) {
.then(() => store.get(key)) await store.set(key, value);
.then((v) => { } else {
cache[key] = v; await store.delete(key);
}
const v = await store.get(key);
if (v === undefined) {
delete cache[key];
cache = cache; cache = cache;
}) } else {
.then(() => store.save()) cache[key] = v;
.catch(onMessage); }
} catch (error) {
onMessage(error);
}
}
async function reset() {
try {
await store.reset();
} catch (error) {
onMessage(error);
}
await refreshEntries();
}
async function close() {
try {
await store.close();
onMessage("Store is now closed, any new operations will error out");
} catch (error) {
onMessage(error);
}
}
function reopen() {
store = new LazyStore("cache.json");
onMessage("We made a new `LazyStore` instance, operations will now work");
} }
</script> </script>
@@ -44,7 +81,12 @@
<input class="grow input" bind:value /> <input class="grow input" bind:value />
</div> </div>
<button class="btn" on:click={() => write(key, value)}> Write </button> <div>
<button class="btn" on:click={() => write(key, value)}>Write</button>
<button class="btn" on:click={() => reset()}>Reset</button>
<button class="btn" on:click={() => close()}>Close</button>
<button class="btn" on:click={() => reopen()}>Re-open</button>
</div>
</div> </div>
<div> <div>
+31 -31
View File
@@ -1,56 +1,56 @@
<script> <script>
import { check } from "@tauri-apps/plugin-updater"; import { check } from '@tauri-apps/plugin-updater'
import { relaunch } from "@tauri-apps/plugin-process"; import { relaunch } from '@tauri-apps/plugin-process'
export let onMessage; export let onMessage
let isChecking, isInstalling, newUpdate; let isChecking, isInstalling, newUpdate
let totalSize = 0, let totalSize = 0,
downloadedSize = 0; downloadedSize = 0
async function checkUpdate() { async function checkUpdate() {
isChecking = true; isChecking = true
try { try {
const update = await check(); const update = await check()
onMessage(`Should update: ${update.available}`); onMessage(`Should update: ${update.available}`)
onMessage(update); onMessage(update)
newUpdate = update; newUpdate = update
} catch (e) { } catch (e) {
onMessage(e); onMessage(e)
} finally { } finally {
isChecking = false; isChecking = false
} }
} }
async function install() { async function install() {
isInstalling = true; isInstalling = true
downloadedSize = 0; downloadedSize = 0
try { try {
await newUpdate.downloadAndInstall((downloadProgress) => { await newUpdate.downloadAndInstall((downloadProgress) => {
switch (downloadProgress.event) { switch (downloadProgress.event) {
case "Started": case 'Started':
totalSize = downloadProgress.data.contentLength; totalSize = downloadProgress.data.contentLength
break; break
case "Progress": case 'Progress':
downloadedSize += downloadProgress.data.chunkLength; downloadedSize += downloadProgress.data.chunkLength
break; break
case "Finished": case 'Finished':
break; break
} }
}); })
onMessage("Installation complete, restarting..."); onMessage('Installation complete, restarting...')
await new Promise((resolve) => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000))
await relaunch(); await relaunch()
} catch (e) { } catch (e) {
console.error(e); console.error(e)
onMessage(e); onMessage(e)
} finally { } finally {
isInstalling = false; isInstalling = false
} }
} }
$: progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0; $: progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
</script> </script>
<div class="flex children:grow children:h10"> <div class="flex children:grow children:h10">
@@ -61,7 +61,7 @@
{:else} {:else}
<div class="progress"> <div class="progress">
<span>{progress}%</span> <span>{progress}%</span>
<div class="progress-bar" style="width: {progress}%" /> <div class="progress-bar" style="width: {progress}%"></div>
</div> </div>
{/if} {/if}
</div> </div>
+9 -8
View File
@@ -7,23 +7,24 @@
"build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build", "build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build",
"lint": "eslint .", "lint": "eslint .",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check ." "format:check": "prettier --check .",
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "9.11.1", "@eslint/js": "9.15.0",
"@rollup/plugin-node-resolve": "15.3.0", "@rollup/plugin-node-resolve": "15.3.0",
"@rollup/plugin-terser": "0.4.4", "@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "11.1.6", "@rollup/plugin-typescript": "11.1.6",
"@types/eslint__js": "8.42.3", "@types/eslint__js": "8.42.3",
"covector": "^0.12.3", "covector": "^0.12.3",
"eslint": "9.11.1", "eslint": "9.15.0",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
"eslint-plugin-security": "3.0.1", "eslint-plugin-security": "3.0.1",
"prettier": "3.3.3", "prettier": "3.4.0",
"rollup": "4.22.4", "rollup": "4.27.4",
"tslib": "2.7.0", "tslib": "2.8.1",
"typescript": "5.6.2", "typescript": "5.7.2",
"typescript-eslint": "8.7.0" "typescript-eslint": "8.16.0"
}, },
"resolutions": { "resolutions": {
"semver": ">=7.5.2", "semver": ">=7.5.2",
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.0.1]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+1 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-autostart" name = "tauri-plugin-autostart"
version = "2.0.0" version = "2.0.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 }
@@ -27,6 +27,5 @@ tauri-plugin = { workspace = true, features = ["build"] }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
auto-launch = "0.5" auto-launch = "0.5"
+1 -1
View File
@@ -12,7 +12,7 @@ Automatically launch your application at startup.
## Install ## Install
_This plugin requires a Rust version of at least **1.78**_ _This plugin requires a Rust version of at least **1.77.2**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
-5
View File
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/autostart/banner.png)](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/autostart)
//!
//! Automatically launch your application at startup. Supports Windows, Mac (via AppleScript or Launch Agent), and Linux. //! Automatically launch your application at startup. Supports Windows, Mac (via AppleScript or Launch Agent), and Linux.
#![doc( #![doc(
@@ -13,8 +11,6 @@
#![cfg(not(any(target_os = "android", target_os = "ios")))] #![cfg(not(any(target_os = "android", target_os = "ios")))]
use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use auto_launch::{AutoLaunch, AutoLaunchBuilder};
#[cfg(target_os = "macos")]
use log::info;
use serde::{ser::Serializer, Serialize}; use serde::{ser::Serializer, Serialize};
use tauri::{ use tauri::{
command, command,
@@ -135,7 +131,6 @@ pub fn init<R: Runtime>(
} else { } else {
exe_path exe_path
}; };
info!("auto_start path {}", &app_path);
builder.set_app_path(&app_path); builder.set_app_path(&app_path);
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.0.1]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-barcode-scanner" name = "tauri-plugin-barcode-scanner"
version = "2.0.0" version = "2.0.1"
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 }
+1 -1
View File
@@ -1,4 +1,4 @@
![Barcode Scanner](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/scanner/banner.png) ![Barcode Scanner](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/barcode-scanner/banner.png)
Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes. Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes.
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.0.1]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-biometric" name = "tauri-plugin-biometric"
version = "2.0.0" version = "2.0.1"
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 }
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.0.1]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-cli" name = "tauri-plugin-cli"
version = "2.0.0" version = "2.0.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 }
+1 -1
View File
@@ -12,7 +12,7 @@ Parse arguments from your Command Line Interface.
## Install ## Install
_This plugin requires a Rust version of at least **1.78**_ _This plugin requires a Rust version of at least **1.77.2**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
-2
View File
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/cli/banner.png)](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/cli)
//!
//! Parse arguments from your Command Line Interface. //! Parse arguments from your Command Line Interface.
//! //!
//! - Supported platforms: Windows, Linux and macOS. //! - Supported platforms: Windows, Linux and macOS.
+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## \[2.0.2]
- [`d57df4de`](https://github.com/tauri-apps/plugins-workspace/commit/d57df4debe7c75cfbd6d6558fff1beb07dbee54c) ([#1986](https://github.com/tauri-apps/plugins-workspace/pull/1986) by [@RikaKagurasaka](https://github.com/tauri-apps/plugins-workspace/../../RikaKagurasaka)) Fix that `read_image` wrongly set the image rgba data with binary PNG data.
## \[2.0.1]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+1 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-clipboard-manager" name = "tauri-plugin-clipboard-manager"
version = "2.0.0" version = "2.0.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 }
@@ -37,4 +37,3 @@ 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]
arboard = "3" arboard = "3"
image = "0.25"
+1 -1
View File
@@ -12,7 +12,7 @@ Read and write to the system clipboard.
## Install ## Install
_This plugin requires a Rust version of at least **1.78**_ _This plugin requires a Rust version of at least **1.77.2**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARD_MANAGER__=function(e){"use strict";var t;async function r(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,n){if("a"===r&&!n)throw new TypeError("Private accessor was defined without a getter");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)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,n,a){if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return r("plugin:resources|close",{rid:this.rid})}}t=new WeakMap;class a extends n{constructor(e){super(e)}static async new(e,t,n){return r("plugin:image|new",{rgba:i(e),width:t,height:n}).then((e=>new a(e)))}static async fromBytes(e){return r("plugin:image|from_bytes",{bytes:i(e)}).then((e=>new a(e)))}static async fromPath(e){return r("plugin:image|from_path",{path:e}).then((e=>new a(e)))}async rgba(){return r("plugin:image|rgba",{rid:this.rid}).then((e=>new Uint8Array(e)))}async size(){return r("plugin:image|size",{rid:this.rid})}}function i(e){return null==e?null:"string"==typeof e?e:e instanceof a?e.rid:e}return e.clear=async function(){await r("plugin:clipboard-manager|clear")},e.readImage=async function(){return await r("plugin:clipboard-manager|read_image").then((e=>new a(e)))},e.readText=async function(){return await r("plugin:clipboard-manager|read_text")},e.writeHtml=async function(e,t){await r("plugin:clipboard-manager|write_html",{html:e,altHtml:t})},e.writeImage=async function(e){await r("plugin:clipboard-manager|write_image",{image:i(e)})},e.writeText=async function(e,t){await r("plugin:clipboard-manager|write_text",{label:t?.label,text:e})},e}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARD_MANAGER__})} if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARD_MANAGER__=function(e){"use strict";var t;async function r(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,n){if("a"===r&&!n)throw new TypeError("Private accessor was defined without a getter");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)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,n,a){if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return r("plugin:resources|close",{rid:this.rid})}}t=new WeakMap;class a extends n{constructor(e){super(e)}static async new(e,t,n){return r("plugin:image|new",{rgba:i(e),width:t,height:n}).then((e=>new a(e)))}static async fromBytes(e){return r("plugin:image|from_bytes",{bytes:i(e)}).then((e=>new a(e)))}static async fromPath(e){return r("plugin:image|from_path",{path:e}).then((e=>new a(e)))}async rgba(){return r("plugin:image|rgba",{rid:this.rid}).then((e=>new Uint8Array(e)))}async size(){return r("plugin:image|size",{rid:this.rid})}}function i(e){return null==e?null:"string"==typeof e?e:e instanceof a?e.rid:e}return e.clear=async function(){await r("plugin:clipboard-manager|clear")},e.readImage=async function(){return await r("plugin:clipboard-manager|read_image").then((e=>new a(e)))},e.readText=async function(){return await r("plugin:clipboard-manager|read_text")},e.writeHtml=async function(e,t){await r("plugin:clipboard-manager|write_html",{html:e,altText:t})},e.writeImage=async function(e){await r("plugin:clipboard-manager|write_image",{image:i(e)})},e.writeText=async function(e,t){await r("plugin:clipboard-manager|write_text",{label:t?.label,text:e})},e}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARD_MANAGER__})}
+3 -3
View File
@@ -90,7 +90,7 @@ async function writeImage(
* import { readImage } from '@tauri-apps/plugin-clipboard-manager'; * import { readImage } from '@tauri-apps/plugin-clipboard-manager';
* *
* const clipboardImage = await readImage(); * const clipboardImage = await readImage();
* const blob = new Blob([clipboardImage.bytes], { type: 'image' }) * const blob = new Blob([await clipboardImage.rbga()], { type: 'image' })
* const url = URL.createObjectURL(blob) * const url = URL.createObjectURL(blob)
* ``` * ```
* @since 2.0.0 * @since 2.0.0
@@ -120,10 +120,10 @@ async function readImage(): Promise<Image> {
* *
* @since 2.0.0 * @since 2.0.0
*/ */
async function writeHtml(html: string, altHtml?: string): Promise<void> { async function writeHtml(html: string, altText?: string): Promise<void> {
await invoke('plugin:clipboard-manager|write_html', { await invoke('plugin:clipboard-manager|write_html', {
html, html,
altHtml altText
}) })
} }
+3 -9
View File
@@ -3,7 +3,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use arboard::ImageData; use arboard::ImageData;
use image::ImageEncoder;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tauri::{image::Image, plugin::PluginApi, AppHandle, Runtime}; use tauri::{image::Image, plugin::PluginApi, AppHandle, Runtime};
@@ -85,16 +84,11 @@ impl<R: Runtime> Clipboard<R> {
match &self.clipboard { match &self.clipboard {
Ok(clipboard) => { Ok(clipboard) => {
let image = clipboard.lock().unwrap().get_image()?; let image = clipboard.lock().unwrap().get_image()?;
let image = Image::new_owned(
let mut buffer: Vec<u8> = Vec::new(); image.bytes.to_vec(),
image::codecs::png::PngEncoder::new(&mut buffer).write_image(
&image.bytes,
image.width as u32, image.width as u32,
image.height as u32, image.height as u32,
image::ExtendedColorType::Rgba8, );
)?;
let image = Image::new_owned(buffer, image.width as u32, image.height as u32);
Ok(image) Ok(image)
} }
Err(e) => Err(crate::Error::Clipboard(e.to_string())), Err(e) => Err(crate::Error::Clipboard(e.to_string())),
-3
View File
@@ -15,9 +15,6 @@ pub enum Error {
Clipboard(String), Clipboard(String),
#[error(transparent)] #[error(transparent)]
Tauri(#[from] tauri::Error), Tauri(#[from] tauri::Error),
#[cfg(desktop)]
#[error("invalid image: {0}")]
Image(#[from] image::ImageError),
} }
impl Serialize for Error { impl Serialize for Error {
-2
View File
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/clipboard-manager/banner.png)](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/clipboard-manager)
//!
//! Read and write to the system clipboard. //! Read and write to the system clipboard.
#![doc( #![doc(
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.0.1]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-deep-link" name = "tauri-plugin-deep-link"
version = "2.0.0" version = "2.0.1"
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 }
@@ -32,7 +32,7 @@ serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
tauri-utils = { workspace = true } tauri-utils = { workspace = true }
log = { workspace = true } tracing = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
url = { workspace = true } url = { workspace = true }
+1 -1
View File
@@ -12,7 +12,7 @@ Set your Tauri application as the default handler for an URL.
## Install ## Install
_This plugin requires a Rust version of at least **1.78**_ _This plugin requires a Rust version of at least **1.77.2**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_DEEP_LINK__=function(e){"use strict";function n(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}async function r(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}var t;async function i(e,t,i){const a={kind:"Any"};return r("plugin:event|listen",{event:e,target:a,handler:n(t)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function a(){return await r("plugin:deep-link|get_current")}return"function"==typeof SuppressedError&&SuppressedError,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(t||(t={})),e.getCurrent=a,e.isRegistered=async function(e){return await r("plugin:deep-link|is_registered",{protocol:e})},e.onOpenUrl=async function(e){const n=await a();return n&&e(n),await i("deep-link://new-url",(n=>{e(n.payload)}))},e.register=async function(e){return await r("plugin:deep-link|register",{protocol:e})},e.unregister=async function(e){return await r("plugin:deep-link|unregister",{protocol:e})},e}({});Object.defineProperty(window.__TAURI__,"deepLink",{value:__TAURI_PLUGIN_DEEP_LINK__})} if("__TAURI__"in window){var __TAURI_PLUGIN_DEEP_LINK__=function(e){"use strict";function n(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}async function r(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}var t;async function i(e,t,i){const a={kind:"Any"};return r("plugin:event|listen",{event:e,target:a,handler:n(t)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}return"function"==typeof SuppressedError&&SuppressedError,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(t||(t={})),e.getCurrent=async function(){return await r("plugin:deep-link|get_current")},e.isRegistered=async function(e){return await r("plugin:deep-link|is_registered",{protocol:e})},e.onOpenUrl=async function(e){return await i("deep-link://new-url",(n=>{e(n.payload)}))},e.register=async function(e){return await r("plugin:deep-link|register",{protocol:e})},e.unregister=async function(e){return await r("plugin:deep-link|unregister",{protocol:e})},e}({});Object.defineProperty(window.__TAURI__,"deepLink",{value:__TAURI_PLUGIN_DEEP_LINK__})}
+2 -2
View File
@@ -10,11 +10,11 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0", "@tauri-apps/api": "2.1.1",
"@tauri-apps/plugin-deep-link": "2.0.0" "@tauri-apps/plugin-deep-link": "2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.0.0", "@tauri-apps/cli": "2.1.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.4.7" "vite": "^5.4.7"
} }
@@ -6,7 +6,7 @@ authors = ["you"]
license = "" license = ""
repository = "" repository = ""
edition = "2021" edition = "2021"
rust-version = "1.78" rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+2 -7
View File
@@ -73,7 +73,7 @@ export async function unregister(protocol: string): Promise<null> {
* await isRegistered("my-scheme"); * await isRegistered("my-scheme");
* ``` * ```
* *
* #### - **macOS / Android / iOS**: Unsupported, always returns `true`. * #### - **macOS / Android / iOS**: Unsupported.
* *
* @since 2.0.0 * @since 2.0.0
*/ */
@@ -92,18 +92,13 @@ export async function isRegistered(protocol: string): Promise<boolean> {
* await onOpenUrl((urls) => { console.log(urls) }); * await onOpenUrl((urls) => { console.log(urls) });
* ``` * ```
* *
* #### - **Windows / Linux**: Unsupported, the OS will spawn a new app instance passing the URL as a CLI argument. * #### - **Windows / Linux**: Unsupported without the single-instance plugin. The OS will spawn a new app instance passing the URL as a CLI argument.
* *
* @since 2.0.0 * @since 2.0.0
*/ */
export async function onOpenUrl( export async function onOpenUrl(
handler: (urls: string[]) => void handler: (urls: string[]) => void
): Promise<UnlistenFn> { ): Promise<UnlistenFn> {
const current = await getCurrent()
if (current) {
handler(current)
}
return await listen<string[]>('deep-link://new-url', (event) => { return await listen<string[]>('deep-link://new-url', (event) => {
handler(event.payload) handler(event.payload)
}) })
+3 -15
View File
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use std::sync::Arc;
use tauri::{ use tauri::{
plugin::{Builder, PluginApi, TauriPlugin}, plugin::{Builder, PluginApi, TauriPlugin},
AppHandle, EventId, Listener, Manager, Runtime, AppHandle, EventId, Listener, Manager, Runtime,
@@ -217,7 +215,7 @@ mod imp {
current.replace(vec![url.clone()]); current.replace(vec![url.clone()]);
let _ = self.app.emit("deep-link://new-url", vec![url]); let _ = self.app.emit("deep-link://new-url", vec![url]);
} else if cfg!(debug_assertions) { } else if cfg!(debug_assertions) {
log::warn!("argument {url} does not match any configured deep link scheme; skipping it"); tracing::warn!("argument {url} does not match any configured deep link scheme; skipping it");
} }
} }
} }
@@ -478,13 +476,10 @@ impl OpenUrlEvent {
} }
impl<R: Runtime> DeepLink<R> { impl<R: Runtime> DeepLink<R> {
/// Handle a new deep link being triggered to open the app. /// Helper function for the `deep-link://new-url` event to run a function each time the protocol is triggered while the app is running.
/// ///
/// To avoid race conditions, if the app was started with a deep link, /// Use `get_current` on app load to check whether your app was started via a deep link.
/// the closure gets immediately called with the deep link URL.
pub fn on_open_url<F: Fn(OpenUrlEvent) + Send + Sync + 'static>(&self, f: F) -> EventId { pub fn on_open_url<F: Fn(OpenUrlEvent) + Send + Sync + 'static>(&self, f: F) -> EventId {
let f = Arc::new(f);
let f_ = f.clone();
let event_id = self.app.listen("deep-link://new-url", move |event| { let event_id = self.app.listen("deep-link://new-url", move |event| {
if let Ok(urls) = serde_json::from_str(event.payload()) { if let Ok(urls) = serde_json::from_str(event.payload()) {
f(OpenUrlEvent { f(OpenUrlEvent {
@@ -494,13 +489,6 @@ impl<R: Runtime> DeepLink<R> {
} }
}); });
if let Ok(Some(current)) = self.get_current() {
f_(OpenUrlEvent {
id: event_id,
urls: current,
})
}
event_id event_id
} }
} }
+21
View File
@@ -1,5 +1,24 @@
# Changelog # Changelog
## \[2.0.3]
### Dependencies
- Upgraded to `fs@2.0.3`
## \[2.0.1]
- [`2302c2db`](https://github.com/tauri-apps/plugins-workspace/commit/2302c2db1c49673e61dcbda8cdb01b2c57e9ba6f) ([#1910](https://github.com/tauri-apps/plugins-workspace/pull/1910) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `ask` and `confirm` not using system button texts
- [`aee14ed4`](https://github.com/tauri-apps/plugins-workspace/commit/aee14ed4261cdedc4ed7cc2686f01f437859a5c7) ([#1892](https://github.com/tauri-apps/plugins-workspace/pull/1892) by [@nashaofu](https://github.com/tauri-apps/plugins-workspace/../../nashaofu)) Set `save` dialog mime type from the `filters` extensions on Android.
## \[2.0.1]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
### Dependencies
- Upgraded to `fs@2.0.1`
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
@@ -280,3 +299,5 @@
pull/371)) First v2 alpha release! pull/371)) First v2 alpha release!
lpha release! lpha release!
pull/371)) First v2 alpha release! pull/371)) First v2 alpha release!
lease!
pull/371)) First v2 alpha release!
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.0.0" version = "2.0.3"
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 }
@@ -34,7 +34,7 @@ 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.0.0" } tauri-plugin-fs = { path = "../fs", version = "2.0.3" }
[target.'cfg(target_os = "ios")'.dependencies] [target.'cfg(target_os = "ios")'.dependencies]
tauri = { workspace = true, features = ["wry"] } tauri = { workspace = true, features = ["wry"] }
+1 -1
View File
@@ -12,7 +12,7 @@ Native system dialogs for opening and saving files along with message dialogs.
## Install ## Install
_This plugin requires a Rust version of at least **1.78**_ _This plugin requires a Rust version of at least **1.77.2**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
@@ -10,6 +10,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.webkit.MimeTypeMap
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
import app.tauri.Logger import app.tauri.Logger
import app.tauri.annotation.ActivityCallback import app.tauri.annotation.ActivityCallback
@@ -43,6 +44,7 @@ class MessageOptions {
@InvokeArg @InvokeArg
class SaveFileDialogOptions { class SaveFileDialogOptions {
var fileName: String? = null var fileName: String? = null
lateinit var filters: Array<Filter>
} }
@TauriPlugin @TauriPlugin
@@ -57,20 +59,7 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
val intent = if (parsedTypes.isNotEmpty()) { val intent = if (parsedTypes.isNotEmpty()) {
val intent = Intent(Intent.ACTION_PICK) val intent = Intent(Intent.ACTION_PICK)
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes) setIntentMimeTypes(intent, parsedTypes)
var uniqueMimeType = true
var mimeKind: String? = null
for (mime in parsedTypes) {
val kind = mime.split("/")[0]
if (mimeKind == null) {
mimeKind = kind
} else if (mimeKind != kind) {
uniqueMimeType = false
}
}
intent.type = if (uniqueMimeType) Intent.normalizeMimeType("$mimeKind/*") else "*/*"
intent intent
} else { } else {
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
@@ -130,13 +119,47 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
private fun parseFiltersOption(filters: Array<Filter>): Array<String> { private fun parseFiltersOption(filters: Array<Filter>): Array<String> {
val mimeTypes = mutableListOf<String>() val mimeTypes = mutableListOf<String>()
for (filter in filters) { for (filter in filters) {
for (mime in filter.extensions) { for (ext in filter.extensions) {
mimeTypes.add(if (mime == "text/csv") "text/comma-separated-values" else mime) if (ext.contains('/')) {
mimeTypes.add(if (ext == "text/csv") "text/comma-separated-values" else ext)
} else {
MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)?.let {
mimeTypes.add(it)
}
}
} }
} }
return mimeTypes.toTypedArray() return mimeTypes.toTypedArray()
} }
private fun setIntentMimeTypes(intent: Intent, mimeTypes: Array<String>) {
if (mimeTypes.isNotEmpty()) {
var uniqueMimeKind = true
var mimeKind: String? = null
for (mime in mimeTypes) {
val kind = mime.split("/")[0]
if (mimeKind == null) {
mimeKind = kind
} else if (mimeKind != kind) {
uniqueMimeKind = false
}
}
if (uniqueMimeKind) {
if (mimeTypes.size > 1) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
intent.type = Intent.normalizeMimeType("$mimeKind/*")
} else {
intent.type = mimeTypes[0]
}
} else {
intent.type = "*/*"
}
} else {
intent.type = "*/*"
}
}
@Command @Command
fun showMessageDialog(invoke: Invoke) { fun showMessageDialog(invoke: Invoke) {
val args = invoke.parseArgs(MessageOptions::class.java) val args = invoke.parseArgs(MessageOptions::class.java)
@@ -187,10 +210,12 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
fun saveFileDialog(invoke: Invoke) { fun saveFileDialog(invoke: Invoke) {
try { try {
val args = invoke.parseArgs(SaveFileDialogOptions::class.java) val args = invoke.parseArgs(SaveFileDialogOptions::class.java)
val parsedTypes = parseFiltersOption(args.filters)
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
setIntentMimeTypes(intent, parsedTypes)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("text/plain")
intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "") intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "")
startActivityForResult(invoke, intent, "saveFileDialogResult") startActivityForResult(invoke, intent, "saveFileDialogResult")
} catch (ex: Exception) { } catch (ex: Exception) {
+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,okButtonLabel:i?.okLabel?.toString()??"Yes",cancelButtonLabel:i?.cancelLabel?.toString()??"No"})},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()??"Ok",cancelButtonLabel:i?.cancelLabel?.toString()??"Cancel"})},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)}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__})}
+4 -4
View File
@@ -257,8 +257,8 @@ async function ask(
message: message.toString(), message: message.toString(),
title: opts?.title?.toString(), title: opts?.title?.toString(),
kind: opts?.kind, kind: opts?.kind,
okButtonLabel: opts?.okLabel?.toString() ?? 'Yes', yesButtonLabel: opts?.okLabel?.toString(),
cancelButtonLabel: opts?.cancelLabel?.toString() ?? 'No' noButtonLabel: opts?.cancelLabel?.toString()
}) })
} }
@@ -287,8 +287,8 @@ async function confirm(
message: message.toString(), message: message.toString(),
title: opts?.title?.toString(), title: opts?.title?.toString(),
kind: opts?.kind, kind: opts?.kind,
okButtonLabel: opts?.okLabel?.toString() ?? 'Ok', okButtonLabel: opts?.okLabel?.toString(),
cancelButtonLabel: opts?.cancelLabel?.toString() ?? 'Cancel' cancelButtonLabel: opts?.cancelLabel?.toString()
}) })
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-dialog", "name": "@tauri-apps/plugin-dialog",
"version": "2.0.0", "version": "2.0.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"
+28 -26
View File
@@ -10,7 +10,7 @@ use tauri_plugin_fs::FsExt;
use crate::{ use crate::{
Dialog, FileDialogBuilder, FilePath, MessageDialogButtons, MessageDialogKind, Result, CANCEL, Dialog, FileDialogBuilder, FilePath, MessageDialogButtons, MessageDialogKind, Result, CANCEL,
OK, NO, OK, YES,
}; };
#[derive(Serialize)] #[derive(Serialize)]
@@ -143,7 +143,7 @@ pub(crate) async fn open<R: Runtime>(
for folder in folders { for folder in folders {
if let Ok(path) = folder.clone().into_path() { if let Ok(path) = folder.clone().into_path() {
if let Some(s) = window.try_fs_scope() { if let Some(s) = window.try_fs_scope() {
s.allow_directory(&path, options.recursive); s.allow_directory(&path, options.recursive)?;
} }
tauri_scope.allow_directory(&path, options.directory)?; tauri_scope.allow_directory(&path, options.directory)?;
} }
@@ -157,7 +157,7 @@ pub(crate) async fn open<R: Runtime>(
if let Some(folder) = &folder { if let Some(folder) = &folder {
if let Ok(path) = folder.clone().into_path() { if let Ok(path) = folder.clone().into_path() {
if let Some(s) = window.try_fs_scope() { if let Some(s) = window.try_fs_scope() {
s.allow_directory(&path, options.recursive); s.allow_directory(&path, options.recursive)?;
} }
tauri_scope.allow_directory(&path, options.directory)?; tauri_scope.allow_directory(&path, options.directory)?;
} }
@@ -175,7 +175,7 @@ pub(crate) async fn open<R: Runtime>(
for file in files { for file in files {
if let Ok(path) = file.clone().into_path() { if let Ok(path) = file.clone().into_path() {
if let Some(s) = window.try_fs_scope() { if let Some(s) = window.try_fs_scope() {
s.allow_file(&path); s.allow_file(&path)?;
} }
tauri_scope.allow_file(&path)?; tauri_scope.allow_file(&path)?;
@@ -190,7 +190,7 @@ pub(crate) async fn open<R: Runtime>(
if let Some(file) = &file { if let Some(file) = &file {
if let Ok(path) = file.clone().into_path() { if let Ok(path) = file.clone().into_path() {
if let Some(s) = window.try_fs_scope() { if let Some(s) = window.try_fs_scope() {
s.allow_file(&path); s.allow_file(&path)?;
} }
tauri_scope.allow_file(&path)?; tauri_scope.allow_file(&path)?;
} }
@@ -232,7 +232,7 @@ pub(crate) async fn save<R: Runtime>(
if let Some(p) = &path { if let Some(p) = &path {
if let Ok(path) = p.clone().into_path() { if let Ok(path) = p.clone().into_path() {
if let Some(s) = window.try_fs_scope() { if let Some(s) = window.try_fs_scope() {
s.allow_file(&path); s.allow_file(&path)?;
} }
tauri_scope.allow_file(&path)?; tauri_scope.allow_file(&path)?;
} }
@@ -299,8 +299,8 @@ pub(crate) async fn ask<R: Runtime>(
title: Option<String>, title: Option<String>,
message: String, message: String,
kind: Option<MessageDialogKind>, kind: Option<MessageDialogKind>,
ok_button_label: Option<String>, yes_button_label: Option<String>,
cancel_button_label: Option<String>, no_button_label: Option<String>,
) -> Result<bool> { ) -> Result<bool> {
Ok(message_dialog( Ok(message_dialog(
window, window,
@@ -308,7 +308,16 @@ pub(crate) async fn ask<R: Runtime>(
title, title,
message, message,
kind, kind,
get_ok_cancel_type(ok_button_label, cancel_button_label), if let Some(yes_button_label) = yes_button_label {
MessageDialogButtons::OkCancelCustom(
yes_button_label,
no_button_label.unwrap_or(NO.to_string()),
)
} else if let Some(no_button_label) = no_button_label {
MessageDialogButtons::OkCancelCustom(YES.to_string(), no_button_label)
} else {
MessageDialogButtons::YesNo
},
)) ))
} }
@@ -328,22 +337,15 @@ pub(crate) async fn confirm<R: Runtime>(
title, title,
message, message,
kind, kind,
get_ok_cancel_type(ok_button_label, cancel_button_label), if let Some(ok_button_label) = ok_button_label {
MessageDialogButtons::OkCancelCustom(
ok_button_label,
cancel_button_label.unwrap_or(CANCEL.to_string()),
)
} else if let Some(cancel_button_label) = cancel_button_label {
MessageDialogButtons::OkCancelCustom(OK.to_string(), cancel_button_label)
} else {
MessageDialogButtons::OkCancel
},
)) ))
} }
fn get_ok_cancel_type(
ok_button_label: Option<String>,
cancel_button_label: Option<String>,
) -> MessageDialogButtons {
if let Some(ok_button_label) = ok_button_label {
MessageDialogButtons::OkCancelCustom(
ok_button_label,
cancel_button_label.unwrap_or(CANCEL.to_string()),
)
} else if let Some(cancel_button_label) = cancel_button_label {
MessageDialogButtons::OkCancelCustom(OK.to_string(), cancel_button_label)
} else {
MessageDialogButtons::OkCancel
}
}
+1
View File
@@ -112,6 +112,7 @@ impl From<MessageDialogButtons> for rfd::MessageButtons {
match value { match value {
MessageDialogButtons::Ok => Self::Ok, MessageDialogButtons::Ok => Self::Ok,
MessageDialogButtons::OkCancel => Self::OkCancel, MessageDialogButtons::OkCancel => Self::OkCancel,
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),
} }
+3 -2
View File
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/dialog/banner.png)](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/dialog)
//!
//! Native system dialogs for opening and saving files along with message dialogs. //! Native system dialogs for opening and saving files along with message dialogs.
#![doc( #![doc(
@@ -43,6 +41,8 @@ use mobile::*;
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 NO: &str = "No";
macro_rules! blocking_fn { macro_rules! blocking_fn {
($self:ident, $fn:ident) => {{ ($self:ident, $fn:ident) => {{
@@ -236,6 +236,7 @@ impl<R: Runtime> MessageDialogBuilder<R> {
let (ok_button_label, cancel_button_label) = match &self.buttons { let (ok_button_label, cancel_button_label) = match &self.buttons {
MessageDialogButtons::Ok => (Some(OK), None), MessageDialogButtons::Ok => (Some(OK), None),
MessageDialogButtons::OkCancel => (Some(OK), Some(CANCEL)), MessageDialogButtons::OkCancel => (Some(OK), Some(CANCEL)),
MessageDialogButtons::YesNo => (Some(YES), Some(NO)),
MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), Some(CANCEL)), MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), Some(CANCEL)),
MessageDialogButtons::OkCancelCustom(ok, cancel) => { MessageDialogButtons::OkCancelCustom(ok, cancel) => {
(Some(ok.as_str()), Some(cancel.as_str())) (Some(ok.as_str()), Some(cancel.as_str()))
+2
View File
@@ -59,6 +59,8 @@ pub enum MessageDialogButtons {
Ok, Ok,
/// 2 buttons `Ok` and `Cancel` with OS default dialog texts /// 2 buttons `Ok` and `Cancel` with OS default dialog texts
OkCancel, OkCancel,
/// 2 buttons `Yes` and `No` with OS default dialog texts
YesNo,
/// 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
+16
View File
@@ -1,5 +1,21 @@
# Changelog # Changelog
## \[2.0.2]
- [`77149dc4`](https://github.com/tauri-apps/plugins-workspace/commit/77149dc4320d26b413e4a6bbe82c654367c51b32) ([#1965](https://github.com/tauri-apps/plugins-workspace/pull/1965) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix `writeTextFile` converting UTF-8 characters (for example `äöü`) in the given path into replacement character (``)
## \[2.0.3]
- [`14cee64c`](https://github.com/tauri-apps/plugins-workspace/commit/14cee64c82a72655ae6a4ac0892736a2959dbda5) ([#1958](https://github.com/tauri-apps/plugins-workspace/pull/1958) by [@bWanShiTong](https://github.com/tauri-apps/plugins-workspace/../../bWanShiTong)) Fix compilation on targets with pointer width of `16` or `32`
## \[2.0.1]
- [`ae802456`](https://github.com/tauri-apps/plugins-workspace/commit/ae8024565f074f313084777c8b10d1b5e3bbe220) ([#1950](https://github.com/tauri-apps/plugins-workspace/pull/1950) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Improve performance of the `FileHandle.read` and `writeTextFile` APIs.
## \[2.0.1]
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
## \[2.0.0] ## \[2.0.0]
- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. - [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release.
+11 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.0.0" version = "2.0.3"
description = "Access the file system." description = "Access the file system."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -14,7 +14,7 @@ rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write acces in `$RESOURCES` folder" }
linux = { level = "full", notes = "No write access to `$RESOURCES` folder" } linux = { level = "full", notes = "No write access to `$RESOURCES` folder" }
macos = { level = "full", notes = "No write access to `$RESOURCES` folder" } macos = { level = "full", notes = "No write access to `$RESOURCES` folder" }
android = { level = "partial", notes = "Access is restricted to Application folder by default" } android = { level = "partial", notes = "Access is restricted to Application folder by default" }
@@ -24,6 +24,8 @@ ios = { level = "partial", notes = "Access is restricted to Application folder b
tauri-plugin = { workspace = true, features = ["build"] } tauri-plugin = { workspace = true, features = ["build"] }
schemars = { workspace = true } schemars = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
toml = "0.8"
tauri-utils = { workspace = true, features = ["build"] }
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
@@ -34,9 +36,13 @@ thiserror = { workspace = true }
url = { workspace = true } url = { workspace = true }
anyhow = "1" anyhow = "1"
uuid = { version = "1", features = ["v4"] } uuid = { version = "1", features = ["v4"] }
glob = "0.3" glob = { workspace = true }
notify = { version = "6", optional = true, features = ["serde"] } # TODO: Remove `serialization-compat-6` in v3
notify-debouncer-full = { version = "0.3", optional = true } notify = { version = "7", optional = true, features = [
"serde",
"serialization-compat-6",
] }
notify-debouncer-full = { version = "0.4", optional = true }
dunce = { workspace = true } dunce = { workspace = true }
percent-encoding = "2" percent-encoding = "2"
+1 -1
View File
@@ -12,7 +12,7 @@ Access the file system.
## Install ## Install
_This plugin requires a Rust version of at least **1.78**_ _This plugin requires a Rust version of at least **1.77.2**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
File diff suppressed because one or more lines are too long
+82 -28
View File
@@ -7,6 +7,8 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use tauri_utils::acl::manifest::PermissionFile;
#[path = "src/scope.rs"] #[path = "src/scope.rs"]
#[allow(dead_code)] #[allow(dead_code)]
mod scope; mod scope;
@@ -16,10 +18,23 @@ mod scope;
#[serde(untagged)] #[serde(untagged)]
#[allow(unused)] #[allow(unused)]
enum FsScopeEntry { enum FsScopeEntry {
/// FS scope path. /// A path that can be accessed by the webview when using the fs APIs.
/// FS scope path pattern.
///
/// The pattern can start with a variable that resolves to a system base directory.
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
Value(PathBuf), Value(PathBuf),
Object { Object {
/// FS scope path. /// A path that can be accessed by the webview when using the fs APIs.
///
/// The pattern can start with a variable that resolves to a system base directory.
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
path: PathBuf, path: PathBuf,
}, },
} }
@@ -62,31 +77,32 @@ const BASE_DIR_VARS: &[&str] = &[
"APPCACHE", "APPCACHE",
"APPLOG", "APPLOG",
]; ];
const COMMANDS: &[&str] = &[ const COMMANDS: &[(&str, &[&str])] = &[
"mkdir", ("mkdir", &[]),
"create", ("create", &[]),
"copy_file", ("copy_file", &[]),
"remove", ("remove", &[]),
"rename", ("rename", &[]),
"truncate", ("truncate", &[]),
"ftruncate", ("ftruncate", &[]),
"write", ("write", &[]),
"write_file", ("write_file", &["open", "write"]),
"write_text_file", ("write_text_file", &[]),
"read_dir", ("read_dir", &[]),
"read_file", ("read_file", &[]),
"read", ("read", &[]),
"open", ("open", &[]),
"read_text_file", ("read_text_file", &[]),
"read_text_file_lines", ("read_text_file_lines", &["read_text_file_lines_next"]),
"read_text_file_lines_next", ("read_text_file_lines_next", &[]),
"seek", ("seek", &[]),
"stat", ("stat", &[]),
"lstat", ("lstat", &[]),
"fstat", ("fstat", &[]),
"exists", ("exists", &[]),
"watch", ("watch", &[]),
"unwatch", ("unwatch", &[]),
("size", &[]),
]; ];
fn main() { fn main() {
@@ -192,9 +208,47 @@ permissions = [
} }
} }
tauri_plugin::Builder::new(COMMANDS) tauri_plugin::Builder::new(&COMMANDS.iter().map(|c| c.0).collect::<Vec<_>>())
.global_api_script_path("./api-iife.js") .global_api_script_path("./api-iife.js")
.global_scope_schema(schemars::schema_for!(FsScopeEntry)) .global_scope_schema(schemars::schema_for!(FsScopeEntry))
.android_path("android") .android_path("android")
.build(); .build();
// workaround to include nested permissions as `tauri_plugin` doesn't support it
let permissions_dir = autogenerated.join("commands");
for (command, nested_commands) in COMMANDS {
if nested_commands.is_empty() {
continue;
}
let permission_path = permissions_dir.join(format!("{command}.toml"));
let content = std::fs::read_to_string(&permission_path)
.unwrap_or_else(|_| panic!("failed to read {command}.toml"));
let mut permission_file = toml::from_str::<PermissionFile>(&content)
.unwrap_or_else(|_| panic!("failed to deserialize {command}.toml"));
for p in permission_file
.permission
.iter_mut()
.filter(|p| p.identifier.starts_with("allow"))
{
p.commands
.allow
.extend(nested_commands.iter().map(|s| s.to_string()));
}
let out = toml::to_string_pretty(&permission_file)
.unwrap_or_else(|_| panic!("failed to serialize {command}.toml"));
let out = format!(
r#"# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
{out}"#
);
std::fs::write(permission_path, out)
.unwrap_or_else(|_| panic!("failed to write {command}.toml"));
}
} }
+128 -31
View File
@@ -14,16 +14,29 @@
* *
* The API has a scope configuration that forces you to restrict the paths that can be accessed using glob patterns. * The API has a scope configuration that forces you to restrict the paths that can be accessed using glob patterns.
* *
* The scope configuration is an array of glob patterns describing folder paths that are allowed. * The scope configuration is an array of glob patterns describing file/directory paths that are allowed.
* For instance, this scope configuration only allows accessing files on the * For instance, this scope configuration allows **all** enabled `fs` APIs to (only) access files in the
* *databases* folder of the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | `$APPDATA` directory}: * *databases* directory of the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | `$APPDATA` directory}:
* ```json * ```json
* { * {
* "plugins": { * "permissions": [
* "fs": { * {
* "scope": ["$APPDATA/databases/*"] * "identifier": "fs:scope",
* "allow": [{ "path": "$APPDATA/databases/*" }]
* } * }
* } * ]
* }
* ```
*
* Scopes can also be applied to specific `fs` APIs by using the API's identifier instead of `fs:scope`:
* ```json
* {
* "permissions": [
* {
* "identifier": "fs:allow-exists",
* "allow": [{ "path": "$APPDATA/databases/*" }]
* }
* ]
* } * }
* ``` * ```
* *
@@ -56,8 +69,6 @@
* *
* Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access. * Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access.
* *
* Note that this scope applies to **all** APIs on this module.
*
* @module * @module
*/ */
@@ -243,6 +254,26 @@ function parseFileInfo(r: UnparsedFileInfo): FileInfo {
} }
} }
// https://mstn.github.io/2018/06/08/fixed-size-arrays-in-typescript/
type FixedSizeArray<T, N extends number> = ReadonlyArray<T> & {
length: N
}
// https://gist.github.com/zapthedingbat/38ebfbedd98396624e5b5f2ff462611d
/** Converts a big-endian eight byte array to number */
function fromBytes(buffer: FixedSizeArray<number, 8>): number {
const bytes = new Uint8ClampedArray(buffer)
const size = bytes.byteLength
let x = 0
for (let i = 0; i < size; i++) {
// eslint-disable-next-line security/detect-object-injection
const byte = bytes[i]
x *= 0x100
x += byte
}
return x
}
/** /**
* The Tauri abstraction for reading and writing files. * The Tauri abstraction for reading and writing files.
* *
@@ -285,12 +316,20 @@ class FileHandle extends Resource {
return 0 return 0
} }
const [data, nread] = await invoke<[number[], number]>('plugin:fs|read', { const data = await invoke<ArrayBuffer | number[]>('plugin:fs|read', {
rid: this.rid, rid: this.rid,
len: buffer.byteLength len: buffer.byteLength
}) })
buffer.set(data) // Rust side will never return an empty array for this command and
// ensure there is at least 8 elements there.
//
// This is an optimization to include the number of read bytes (as bigendian bytes)
// at the end of returned array to avoid serialization overhead of separate values.
const nread = fromBytes(data.slice(-8) as FixedSizeArray<number, 8>)
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data
buffer.set(bytes.slice(0, bytes.length - 8))
return nread === 0 ? null : nread return nread === 0 ? null : nread
} }
@@ -389,11 +428,11 @@ class FileHandle extends Resource {
} }
/** /**
* Writes `p.byteLength` bytes from `p` to the underlying data stream. It * Writes `data.byteLength` bytes from `data` to the underlying data stream. It
* resolves to the number of bytes written from `p` (`0` <= `n` <= * resolves to the number of bytes written from `data` (`0` <= `n` <=
* `p.byteLength`) or reject with the error encountered that caused the * `data.byteLength`) or reject with the error encountered that caused the
* write to stop early. `write()` must reject with a non-null error if * write to stop early. `write()` must reject with a non-null error if
* would resolve to `n` < `p.byteLength`. `write()` must not modify the * would resolve to `n` < `data.byteLength`. `write()` must not modify the
* slice data, even temporarily. * slice data, even temporarily.
* *
* @example * @example
@@ -731,10 +770,14 @@ async function readTextFile(
throw new TypeError('Must be a file URL.') throw new TypeError('Must be a file URL.')
} }
return await invoke<string>('plugin:fs|read_text_file', { const arr = await invoke<ArrayBuffer | number[]>('plugin:fs|read_text_file', {
path: path instanceof URL ? path.toString() : path, path: path instanceof URL ? path.toString() : path,
options options
}) })
const bytes = arr instanceof ArrayBuffer ? arr : Uint8Array.from(arr)
return new TextDecoder().decode(bytes)
} }
/** /**
@@ -765,6 +808,7 @@ async function readTextFileLines(
return await Promise.resolve({ return await Promise.resolve({
path: pathStr, path: pathStr,
rid: null as number | null, rid: null as number | null,
async next(): Promise<IteratorResult<string>> { async next(): Promise<IteratorResult<string>> {
if (this.rid === null) { if (this.rid === null) {
this.rid = await invoke<number>('plugin:fs|read_text_file_lines', { this.rid = await invoke<number>('plugin:fs|read_text_file_lines', {
@@ -773,19 +817,35 @@ async function readTextFileLines(
}) })
} }
const [line, done] = await invoke<[string | null, boolean]>( const arr = await invoke<ArrayBuffer | number[]>(
'plugin:fs|read_text_file_lines_next', 'plugin:fs|read_text_file_lines_next',
{ rid: this.rid } { rid: this.rid }
) )
// an iteration is over, reset rid for next iteration const bytes =
if (done) this.rid = null arr instanceof ArrayBuffer ? new Uint8Array(arr) : Uint8Array.from(arr)
// Rust side will never return an empty array for this command and
// ensure there is at least one elements there.
//
// This is an optimization to include whether we finished iteration or not (1 or 0)
// at the end of returned array to avoid serialization overhead of separate values.
const done = bytes[bytes.byteLength - 1] === 1
if (done) {
// a full iteration is over, reset rid for next iteration
this.rid = null
return { value: null, done }
}
const line = new TextDecoder().decode(bytes.slice(0, bytes.byteLength))
return { return {
value: done ? '' : line!, value: line,
done done
} }
}, },
[Symbol.asyncIterator](): AsyncIterableIterator<string> { [Symbol.asyncIterator](): AsyncIterableIterator<string> {
return this return this
} }
@@ -1006,19 +1066,27 @@ interface WriteFileOptions {
*/ */
async function writeFile( async function writeFile(
path: string | URL, path: string | URL,
data: Uint8Array, data: Uint8Array | ReadableStream<Uint8Array>,
options?: WriteFileOptions options?: WriteFileOptions
): Promise<void> { ): Promise<void> {
if (path instanceof URL && path.protocol !== 'file:') { if (path instanceof URL && path.protocol !== 'file:') {
throw new TypeError('Must be a file URL.') throw new TypeError('Must be a file URL.')
} }
await invoke('plugin:fs|write_file', data, { if (data instanceof ReadableStream) {
headers: { const file = await open(path, options)
path: encodeURIComponent(path instanceof URL ? path.toString() : path), for await (const chunk of data) {
options: JSON.stringify(options) await file.write(chunk)
} }
}) await file.close()
} else {
await invoke('plugin:fs|write_file', data, {
headers: {
path: encodeURIComponent(path instanceof URL ? path.toString() : path),
options: JSON.stringify(options)
}
})
}
} }
/** /**
@@ -1041,10 +1109,13 @@ async function writeTextFile(
throw new TypeError('Must be a file URL.') throw new TypeError('Must be a file URL.')
} }
await invoke('plugin:fs|write_text_file', { const encoder = new TextEncoder()
path: path instanceof URL ? path.toString() : path,
data, await invoke('plugin:fs|write_text_file', encoder.encode(data), {
options headers: {
path: encodeURIComponent(path instanceof URL ? path.toString() : path),
options: JSON.stringify(options)
}
}) })
} }
@@ -1251,6 +1322,31 @@ async function watchImmediate(
} }
} }
/**
* Get the size of a file or directory. For files, the `stat` functions can be used as well.
*
* If `path` is a directory, this function will recursively iterate over every file and every directory inside of `path` and therefore will be very time consuming if used on larger directories.
*
* @example
* ```typescript
* import { size, BaseDirectory } from '@tauri-apps/plugin-fs';
* // Get the size of the `$APPDATA/tauri` directory.
* const dirSize = await size('tauri', { baseDir: BaseDirectory.AppData });
* console.log(dirSize); // 1024
* ```
*
* @since 2.1.0
*/
async function size(path: string | URL): Promise<number> {
if (path instanceof URL && path.protocol !== 'file:') {
throw new TypeError('Must be a file URL.')
}
return await invoke('plugin:fs|size', {
path: path instanceof URL ? path.toString() : path
})
}
export type { export type {
CreateOptions, CreateOptions,
OpenOptions, OpenOptions,
@@ -1298,5 +1394,6 @@ export {
writeTextFile, writeTextFile,
exists, exists,
watch, watch,
watchImmediate watchImmediate,
size
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-fs", "name": "@tauri-apps/plugin-fs",
"version": "2.0.0", "version": "2.0.2",
"description": "Access the file system.", "description": "Access the file system.",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
@@ -5,9 +5,18 @@
[[permission]] [[permission]]
identifier = "allow-read-text-file-lines" identifier = "allow-read-text-file-lines"
description = "Enables the read_text_file_lines command without any pre-configured scope." description = "Enables the read_text_file_lines command without any pre-configured scope."
commands.allow = ["read_text_file_lines"]
[permission.commands]
allow = [
"read_text_file_lines",
"read_text_file_lines_next",
]
deny = []
[[permission]] [[permission]]
identifier = "deny-read-text-file-lines" identifier = "deny-read-text-file-lines"
description = "Denies the read_text_file_lines command without any pre-configured scope." description = "Denies the read_text_file_lines command without any pre-configured scope."
commands.deny = ["read_text_file_lines"]
[permission.commands]
allow = []
deny = ["read_text_file_lines"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-size"
description = "Enables the size command without any pre-configured scope."
commands.allow = ["size"]
[[permission]]
identifier = "deny-size"
description = "Denies the size command without any pre-configured scope."
commands.deny = ["size"]
@@ -5,9 +5,19 @@
[[permission]] [[permission]]
identifier = "allow-write-file" identifier = "allow-write-file"
description = "Enables the write_file command without any pre-configured scope." description = "Enables the write_file command without any pre-configured scope."
commands.allow = ["write_file"]
[permission.commands]
allow = [
"write_file",
"open",
"write",
]
deny = []
[[permission]] [[permission]]
identifier = "deny-write-file" identifier = "deny-write-file"
description = "Denies the write_file command without any pre-configured scope." description = "Denies the write_file command without any pre-configured scope."
commands.deny = ["write_file"]
[permission.commands]
allow = []
deny = ["write_file"]
@@ -24,6 +24,7 @@ This default permission set prevents access to critical components
of the Tauri application by default. of the Tauri application by default.
On Windows the webview data folder access is denied. On Windows the webview data folder access is denied.
#### Included permissions within this default permission set:
- `create-app-specific-dirs` - `create-app-specific-dirs`
@@ -3409,6 +3410,32 @@ Denies the seek command without any pre-configured scope.
<tr> <tr>
<td> <td>
`fs:allow-size`
</td>
<td>
Enables the size command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`fs:deny-size`
</td>
<td>
Denies the size command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`fs:allow-stat` `fs:allow-stat`
</td> </td>
+1
View File
@@ -26,6 +26,7 @@ This default permission set prevents access to critical components
of the Tauri application by default. of the Tauri application by default.
On Windows the webview data folder access is denied. On Windows the webview data folder access is denied.
#### Included permissions within this default permission set:
""" """
permissions = [ permissions = [
"create-app-specific-dirs", "create-app-specific-dirs",
+1 -1
View File
@@ -3,4 +3,4 @@
[[permission]] [[permission]]
identifier = "read-meta" identifier = "read-meta"
description = "This enables all index or metadata related commands without any pre-configured accessible paths." description = "This enables all index or metadata related commands without any pre-configured accessible paths."
commands.allow = ["read_dir", "stat", "lstat", "fstat", "exists"] commands.allow = ["read_dir", "stat", "lstat", "fstat", "exists", "size"]
+11 -1
View File
@@ -1589,6 +1589,16 @@
"type": "string", "type": "string",
"const": "deny-seek" "const": "deny-seek"
}, },
{
"description": "Enables the size command without any pre-configured scope.",
"type": "string",
"const": "allow-size"
},
{
"description": "Denies the size command without any pre-configured scope.",
"type": "string",
"const": "deny-size"
},
{ {
"description": "Enables the stat command without any pre-configured scope.", "description": "Enables the stat command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -1665,7 +1675,7 @@
"const": "create-app-specific-dirs" "const": "create-app-specific-dirs"
}, },
{ {
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n", "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string", "type": "string",
"const": "default" "const": "default"
}, },
+282 -150
View File
@@ -9,20 +9,20 @@ use tauri::{
ipc::{CommandScope, GlobalScope}, ipc::{CommandScope, GlobalScope},
path::BaseDirectory, path::BaseDirectory,
utils::config::FsScope, utils::config::FsScope,
AppHandle, Manager, Resource, ResourceId, Runtime, Webview, Manager, Resource, ResourceId, Runtime, Webview,
}; };
use std::{ use std::{
borrow::Cow, borrow::Cow,
fs::File, fs::File,
io::{BufReader, Lines, Read, Write}, io::{BufRead, BufReader, Read, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr, str::FromStr,
sync::Mutex, sync::Mutex,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use crate::{scope::Entry, Error, FsExt, SafeFilePath}; use crate::{scope::Entry, Error, SafeFilePath};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum CommandError { pub enum CommandError {
@@ -245,32 +245,12 @@ pub fn mkdir<R: Runtime>(
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[non_exhaustive] #[non_exhaustive]
pub struct DirEntry { pub struct DirEntry {
pub name: Option<String>, pub name: String,
pub is_directory: bool, pub is_directory: bool,
pub is_file: bool, pub is_file: bool,
pub is_symlink: bool, pub is_symlink: bool,
} }
fn read_dir_inner<P: AsRef<Path>>(path: P) -> crate::Result<Vec<DirEntry>> {
let mut files_and_dirs: Vec<DirEntry> = vec![];
for entry in std::fs::read_dir(path)? {
let path = entry?.path();
let file_type = path.metadata()?.file_type();
files_and_dirs.push(DirEntry {
is_directory: file_type.is_dir(),
is_file: file_type.is_file(),
is_symlink: std::fs::symlink_metadata(&path)
.map(|md| md.file_type().is_symlink())
.unwrap_or(false),
name: path
.file_name()
.map(|name| name.to_string_lossy())
.map(|name| name.to_string()),
});
}
Result::Ok(files_and_dirs)
}
#[tauri::command] #[tauri::command]
pub async fn read_dir<R: Runtime>( pub async fn read_dir<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
@@ -287,27 +267,73 @@ pub async fn read_dir<R: Runtime>(
options.as_ref().and_then(|o| o.base_dir), options.as_ref().and_then(|o| o.base_dir),
)?; )?;
read_dir_inner(&resolved_path) let entries = std::fs::read_dir(&resolved_path).map_err(|e| {
.map_err(|e| { format!(
format!( "failed to read directory at path: {} with error: {e}",
"failed to read directory at path: {} with error: {e}", resolved_path.display()
resolved_path.display() )
) })?;
let entries = entries
.filter_map(|entry| {
let entry = entry.ok()?;
let name = entry.file_name().into_string().ok()?;
let metadata = entry.file_type();
macro_rules! method_or_false {
($method:ident) => {
if let Ok(metadata) = &metadata {
metadata.$method()
} else {
false
}
};
}
Some(DirEntry {
name,
is_file: method_or_false!(is_file),
is_directory: method_or_false!(is_dir),
is_symlink: method_or_false!(is_symlink),
})
}) })
.map_err(Into::into) .collect();
Ok(entries)
} }
#[tauri::command] #[tauri::command]
pub async fn read<R: Runtime>( pub async fn read<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
rid: ResourceId, rid: ResourceId,
len: u32, len: usize,
) -> CommandResult<(Vec<u8>, usize)> { ) -> CommandResult<tauri::ipc::Response> {
let mut data = vec![0; len as usize]; let mut data = vec![0; len];
let file = webview.resources_table().get::<StdFileResource>(rid)?; let file = webview.resources_table().get::<StdFileResource>(rid)?;
let nread = StdFileResource::with_lock(&file, |mut file| file.read(&mut data)) let nread = StdFileResource::with_lock(&file, |mut file| file.read(&mut data))
.map_err(|e| format!("faied to read bytes from file with error: {e}"))?; .map_err(|e| format!("faied to read bytes from file with error: {e}"))?;
Ok((data, nread))
// This is an optimization to include the number of read bytes (as bigendian bytes)
// at the end of returned vector so we can use `tauri::ipc::Response`
// and avoid serialization overhead of separate values.
#[cfg(target_pointer_width = "16")]
let nread = {
let nread = nread.to_be_bytes();
let mut out = [0; 8];
out[6..].copy_from_slice(&nread);
out
};
#[cfg(target_pointer_width = "32")]
let nread = {
let nread = nread.to_be_bytes();
let mut out = [0; 8];
out[4..].copy_from_slice(&nread);
out
};
#[cfg(target_pointer_width = "64")]
let nread = nread.to_be_bytes();
data.extend(nread);
Ok(tauri::ipc::Response::new(data))
} }
#[tauri::command] #[tauri::command]
@@ -346,6 +372,7 @@ pub async fn read_file<R: Runtime>(
Ok(tauri::ipc::Response::new(contents)) Ok(tauri::ipc::Response::new(contents))
} }
// 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>(
webview: Webview<R>, webview: Webview<R>,
@@ -353,33 +380,8 @@ pub async fn read_text_file<R: Runtime>(
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
path: SafeFilePath, path: SafeFilePath,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<String> { ) -> CommandResult<tauri::ipc::Response> {
let (mut file, path) = resolve_file( read_file(webview, global_scope, command_scope, path, options).await
&webview,
&global_scope,
&command_scope,
path,
OpenOptions {
base: BaseOptions {
base_dir: options.as_ref().and_then(|o| o.base_dir),
},
options: crate::OpenOptions {
read: true,
..Default::default()
},
},
)?;
let mut contents = String::new();
file.read_to_string(&mut contents).map_err(|e| {
format!(
"failed to read file as text at path: {} with error: {e}",
path.display()
)
})?;
Ok(contents)
} }
#[tauri::command] #[tauri::command]
@@ -390,8 +392,6 @@ pub fn read_text_file_lines<R: Runtime>(
path: SafeFilePath, path: SafeFilePath,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
use std::io::BufRead;
let resolved_path = resolve_path( let resolved_path = resolve_path(
&webview, &webview,
&global_scope, &global_scope,
@@ -407,7 +407,7 @@ pub fn read_text_file_lines<R: Runtime>(
) )
})?; })?;
let lines = BufReader::new(file).lines(); let lines = BufReader::new(file);
let rid = webview.resources_table().add(StdLinesResource::new(lines)); let rid = webview.resources_table().add(StdLinesResource::new(lines));
Ok(rid) Ok(rid)
@@ -417,18 +417,28 @@ pub fn read_text_file_lines<R: Runtime>(
pub async fn read_text_file_lines_next<R: Runtime>( pub async fn read_text_file_lines_next<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
rid: ResourceId, rid: ResourceId,
) -> CommandResult<(Option<String>, bool)> { ) -> CommandResult<tauri::ipc::Response> {
let mut resource_table = webview.resources_table(); let mut resource_table = webview.resources_table();
let lines = resource_table.get::<StdLinesResource>(rid)?; let lines = resource_table.get::<StdLinesResource>(rid)?;
let ret = StdLinesResource::with_lock(&lines, |lines| { let ret = StdLinesResource::with_lock(&lines, |lines| -> CommandResult<Vec<u8>> {
lines.next().map(|a| (a.ok(), false)).unwrap_or_else(|| { // This is an optimization to include wether we finished iteration or not (1 or 0)
let _ = resource_table.close(rid); // at the end of returned vector so we can use `tauri::ipc::Response`
(None, true) // and avoid serialization overhead of separate values.
}) match lines.next() {
Some(Ok(mut bytes)) => {
bytes.push(false as u8);
Ok(bytes)
}
Some(Err(_)) => Ok(vec![false as u8]),
None => {
resource_table.close(rid)?;
Ok(vec![true as u8])
}
}
}); });
Ok(ret) ret.map(tauri::ipc::Response::new)
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
@@ -779,18 +789,43 @@ fn default_create_value() -> bool {
true true
} }
fn write_file_inner<R: Runtime>( #[tauri::command]
pub async fn write_file<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: CommandScope<Entry>,
path: SafeFilePath, request: tauri::ipc::Request<'_>,
data: &[u8],
options: Option<WriteFileOptions>,
) -> 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
.headers()
.get("path")
.ok_or_else(|| anyhow::anyhow!("missing file path").into())
.and_then(|p| {
percent_encoding::percent_decode(p.as_ref())
.decode_utf8()
.map_err(|_| anyhow::anyhow!("path is not a valid UTF-8").into())
})
.and_then(|p| SafeFilePath::from_str(&p).map_err(CommandError::from))?;
let options: Option<WriteFileOptions> = request
.headers()
.get("options")
.and_then(|p| p.to_str().ok())
.and_then(|opts| serde_json::from_str(opts).ok());
let (mut file, path) = resolve_file( let (mut file, path) = resolve_file(
&webview, &webview,
global_scope, &global_scope,
command_scope, &command_scope,
path, path,
if let Some(opts) = options { if let Some(opts) = options {
OpenOptions { OpenOptions {
@@ -823,7 +858,7 @@ fn write_file_inner<R: Runtime>(
}, },
)?; )?;
file.write_all(data) file.write_all(&data)
.map_err(|e| { .map_err(|e| {
format!( format!(
"failed to write bytes to file at path: {} with error: {e}", "failed to write bytes to file at path: {} with error: {e}",
@@ -833,59 +868,15 @@ fn write_file_inner<R: Runtime>(
.map_err(Into::into) .map_err(Into::into)
} }
// TODO, remove in v3, rely on `write_file` command instead
#[tauri::command] #[tauri::command]
pub async fn write_file<R: Runtime>( pub async fn write_text_file<R: Runtime>(
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() { write_file(webview, global_scope, command_scope, request).await
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
.headers()
.get("path")
.ok_or_else(|| anyhow::anyhow!("missing file path").into())
.and_then(|p| {
percent_encoding::percent_decode(p.as_ref())
.decode_utf8()
.map_err(|_| anyhow::anyhow!("path is not a valid UTF-8").into())
})
.and_then(|p| SafeFilePath::from_str(&p).map_err(CommandError::from))?;
let options = request
.headers()
.get("options")
.and_then(|p| p.to_str().ok())
.and_then(|opts| serde_json::from_str(opts).ok());
write_file_inner(webview, &global_scope, &command_scope, path, &data, options)
}
#[tauri::command]
pub async fn write_text_file<R: Runtime>(
#[allow(unused)] app: AppHandle<R>,
#[allow(unused)] webview: Webview<R>,
#[allow(unused)] global_scope: GlobalScope<Entry>,
#[allow(unused)] command_scope: CommandScope<Entry>,
path: SafeFilePath,
data: String,
#[allow(unused)] options: Option<WriteFileOptions>,
) -> CommandResult<()> {
write_file_inner(
webview,
&global_scope,
&command_scope,
path,
data.as_bytes(),
options,
)
} }
#[tauri::command] #[tauri::command]
@@ -906,6 +897,55 @@ pub fn exists<R: Runtime>(
Ok(resolved_path.exists()) Ok(resolved_path.exists())
} }
#[tauri::command]
pub async fn size<R: Runtime>(
webview: Webview<R>,
global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>,
path: SafeFilePath,
options: Option<BaseOptions>,
) -> CommandResult<u64> {
let resolved_path = resolve_path(
&webview,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
let metadata = resolved_path.metadata()?;
if metadata.is_file() {
Ok(metadata.len())
} else {
let size = get_dir_size(&resolved_path).map_err(|e| {
format!(
"failed to get size at path: {} with error: {e}",
resolved_path.display()
)
})?;
Ok(size)
}
}
fn get_dir_size(path: &PathBuf) -> CommandResult<u64> {
let mut size = 0;
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let metadata = entry.metadata()?;
if metadata.is_file() {
size += metadata.len();
} else if metadata.is_dir() {
size += get_dir_size(&entry.path())?;
}
}
Ok(size)
}
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
pub fn resolve_file<R: Runtime>( pub fn resolve_file<R: Runtime>(
webview: &Webview<R>, webview: &Webview<R>,
@@ -951,6 +991,8 @@ pub fn resolve_file<R: Runtime>(
path: SafeFilePath, path: SafeFilePath,
open_options: OpenOptions, open_options: OpenOptions,
) -> CommandResult<(File, PathBuf)> { ) -> CommandResult<(File, PathBuf)> {
use crate::FsExt;
match path { match path {
SafeFilePath::Url(url) => { SafeFilePath::Url(url) => {
let path = url.as_str().into(); let path = url.as_str().into();
@@ -983,40 +1025,81 @@ pub fn resolve_path<R: Runtime>(
path path
}; };
let fs_scope = webview.state::<crate::Scope>();
let scope = tauri::scope::fs::Scope::new( let scope = tauri::scope::fs::Scope::new(
webview, webview,
&FsScope::Scope { &FsScope::Scope {
allow: webview allow: global_scope
.fs_scope() .allows()
.allowed .iter()
.lock() .filter_map(|e| e.path.clone())
.unwrap()
.clone()
.into_iter()
.chain(global_scope.allows().iter().filter_map(|e| e.path.clone()))
.chain(command_scope.allows().iter().filter_map(|e| e.path.clone())) .chain(command_scope.allows().iter().filter_map(|e| e.path.clone()))
.collect(), .collect(),
deny: webview deny: global_scope
.fs_scope() .denies()
.denied .iter()
.lock() .filter_map(|e| e.path.clone())
.unwrap()
.clone()
.into_iter()
.chain(global_scope.denies().iter().filter_map(|e| e.path.clone()))
.chain(command_scope.denies().iter().filter_map(|e| e.path.clone())) .chain(command_scope.denies().iter().filter_map(|e| e.path.clone()))
.collect(), .collect(),
require_literal_leading_dot: webview.fs_scope().require_literal_leading_dot, require_literal_leading_dot: fs_scope.require_literal_leading_dot,
}, },
)?; )?;
if scope.is_allowed(&path) { let require_literal_leading_dot = fs_scope.require_literal_leading_dot.unwrap_or(cfg!(unix));
if is_forbidden(&fs_scope.scope, &path, require_literal_leading_dot)
|| is_forbidden(&scope, &path, require_literal_leading_dot)
{
return Err(CommandError::Plugin(Error::PathForbidden(path)));
}
if fs_scope.scope.is_allowed(&path) || scope.is_allowed(&path) {
Ok(path) Ok(path)
} else { } else {
Err(CommandError::Plugin(Error::PathForbidden(path))) Err(CommandError::Plugin(Error::PathForbidden(path)))
} }
} }
fn is_forbidden<P: AsRef<Path>>(
scope: &tauri::fs::Scope,
path: P,
require_literal_leading_dot: bool,
) -> bool {
let path = path.as_ref();
let path = if path.is_symlink() {
match std::fs::read_link(path) {
Ok(p) => p,
Err(_) => return false,
}
} else {
path.to_path_buf()
};
let path = if !path.exists() {
crate::Result::Ok(path)
} else {
std::fs::canonicalize(path).map_err(Into::into)
};
if let Ok(path) = path {
let path: PathBuf = path.components().collect();
scope.forbidden_patterns().iter().any(|p| {
p.matches_path_with(
&path,
glob::MatchOptions {
// this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`
// see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>
require_literal_separator: true,
require_literal_leading_dot,
..Default::default()
},
)
})
} else {
false
}
}
struct StdFileResource(Mutex<File>); struct StdFileResource(Mutex<File>);
impl StdFileResource { impl StdFileResource {
@@ -1032,14 +1115,38 @@ impl StdFileResource {
impl Resource for StdFileResource {} impl Resource for StdFileResource {}
struct StdLinesResource(Mutex<Lines<BufReader<File>>>); /// Same as [std::io::Lines] but with bytes
struct LinesBytes<T: BufRead>(T);
impl<B: BufRead> Iterator for LinesBytes<B> {
type Item = std::io::Result<Vec<u8>>;
fn next(&mut self) -> Option<std::io::Result<Vec<u8>>> {
let mut buf = Vec::new();
match self.0.read_until(b'\n', &mut buf) {
Ok(0) => None,
Ok(_n) => {
if buf.last() == Some(&b'\n') {
buf.pop();
if buf.last() == Some(&b'\r') {
buf.pop();
}
}
Some(Ok(buf))
}
Err(e) => Some(Err(e)),
}
}
}
struct StdLinesResource(Mutex<LinesBytes<BufReader<File>>>);
impl StdLinesResource { impl StdLinesResource {
fn new(lines: Lines<BufReader<File>>) -> Self { fn new(lines: BufReader<File>) -> Self {
Self(Mutex::new(lines)) Self(Mutex::new(LinesBytes(lines)))
} }
fn with_lock<R, F: FnMut(&mut Lines<BufReader<File>>) -> R>(&self, mut f: F) -> R { fn with_lock<R, F: FnMut(&mut LinesBytes<BufReader<File>>) -> R>(&self, mut f: F) -> R {
let mut lines = self.0.lock().unwrap(); let mut lines = self.0.lock().unwrap();
f(&mut lines) f(&mut lines)
} }
@@ -1138,7 +1245,12 @@ fn get_stat(metadata: std::fs::Metadata) -> FileInfo {
} }
} }
#[cfg(test)]
mod test { mod test {
use std::io::{BufRead, BufReader};
use super::LinesBytes;
#[test] #[test]
fn safe_file_path_parse() { fn safe_file_path_parse() {
use super::SafeFilePath; use super::SafeFilePath;
@@ -1152,4 +1264,24 @@ mod test {
Ok(SafeFilePath::Url(_)) Ok(SafeFilePath::Url(_))
)); ));
} }
#[test]
fn test_lines_bytes() {
let base = String::from("line 1\nline2\nline 3\nline 4");
let bytes = base.as_bytes();
let string1 = base.lines().collect::<String>();
let string2 = BufReader::new(bytes)
.lines()
.map_while(Result::ok)
.collect::<String>();
let string3 = LinesBytes(BufReader::new(bytes))
.flatten()
.flat_map(String::from_utf8)
.collect::<String>();
assert_eq!(string1, string2);
assert_eq!(string1, string3);
assert_eq!(string2, string3);
}
} }
+22 -17
View File
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/fs/banner.png)](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/fs)
//!
//! Access the file system. //! Access the file system.
#![doc( #![doc(
@@ -17,7 +15,7 @@ use serde::Deserialize;
use tauri::{ use tauri::{
ipc::ScopeObject, ipc::ScopeObject,
plugin::{Builder as PluginBuilder, TauriPlugin}, plugin::{Builder as PluginBuilder, TauriPlugin},
utils::acl::Value, utils::{acl::Value, config::FsScope},
AppHandle, DragDropEvent, Manager, RunEvent, Runtime, WindowEvent, AppHandle, DragDropEvent, Manager, RunEvent, Runtime, WindowEvent,
}; };
@@ -41,7 +39,6 @@ pub use desktop::Fs;
pub use mobile::Fs; pub use mobile::Fs;
pub use error::Error; pub use error::Error;
pub use scope::{Event as ScopeEvent, Scope};
pub use file_path::FilePath; pub use file_path::FilePath;
pub use file_path::SafeFilePath; pub use file_path::SafeFilePath;
@@ -367,21 +364,26 @@ impl ScopeObject for scope::Entry {
} }
} }
pub(crate) struct Scope {
pub(crate) scope: tauri::fs::Scope,
pub(crate) require_literal_leading_dot: Option<bool>,
}
pub trait FsExt<R: Runtime> { pub trait FsExt<R: Runtime> {
fn fs_scope(&self) -> &Scope; fn fs_scope(&self) -> tauri::fs::Scope;
fn try_fs_scope(&self) -> Option<&Scope>; fn try_fs_scope(&self) -> Option<tauri::fs::Scope>;
/// Cross platform file system APIs that also support manipulating Android files. /// Cross platform file system APIs that also support manipulating Android files.
fn fs(&self) -> &Fs<R>; fn fs(&self) -> &Fs<R>;
} }
impl<R: Runtime, T: Manager<R>> FsExt<R> for T { impl<R: Runtime, T: Manager<R>> FsExt<R> for T {
fn fs_scope(&self) -> &Scope { fn fs_scope(&self) -> tauri::fs::Scope {
self.state::<Scope>().inner() self.state::<Scope>().scope.clone()
} }
fn try_fs_scope(&self) -> Option<&Scope> { fn try_fs_scope(&self) -> Option<tauri::fs::Scope> {
self.try_state::<Scope>().map(|s| s.inner()) self.try_state::<Scope>().map(|s| s.scope.clone())
} }
fn fs(&self) -> &Fs<R> { fn fs(&self) -> &Fs<R> {
@@ -415,17 +417,20 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
commands::write_file, commands::write_file,
commands::write_text_file, commands::write_text_file,
commands::exists, commands::exists,
commands::size,
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
watcher::watch, watcher::watch,
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
watcher::unwatch watcher::unwatch
]) ])
.setup(|app, api| { .setup(|app, api| {
let mut scope = Scope::default(); let scope = Scope {
scope.require_literal_leading_dot = api require_literal_leading_dot: api
.config() .config()
.as_ref() .as_ref()
.and_then(|c| c.require_literal_leading_dot); .and_then(|c| c.require_literal_leading_dot),
scope: tauri::fs::Scope::new(app, &FsScope::default())?,
};
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
{ {
@@ -448,9 +453,9 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
let scope = app.fs_scope(); let scope = app.fs_scope();
for path in paths { for path in paths {
if path.is_file() { if path.is_file() {
scope.allow_file(path); let _ = scope.allow_file(path);
} else { } else {
scope.allow_directory(path, true); let _ = scope.allow_directory(path, true);
} }
} }
} }
+6 -118
View File
@@ -2,130 +2,18 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use std::{ use std::path::PathBuf;
collections::HashMap,
path::{Path, PathBuf},
sync::{
atomic::{AtomicU32, Ordering},
Mutex,
},
};
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug)]
pub struct Entry {
pub path: Option<PathBuf>,
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub(crate) enum EntryRaw { pub(crate) enum EntryRaw {
Value(PathBuf), Value(PathBuf),
Object { path: PathBuf }, Object { path: PathBuf },
} }
#[derive(Debug)]
pub struct Entry {
pub path: Option<PathBuf>,
}
pub type EventId = u32;
type EventListener = Box<dyn Fn(&Event) + Send>;
/// Scope change event.
#[derive(Debug, Clone)]
pub enum Event {
/// A path has been allowed.
PathAllowed(PathBuf),
/// A path has been forbidden.
PathForbidden(PathBuf),
}
#[derive(Default)]
pub struct Scope {
pub(crate) allowed: Mutex<Vec<PathBuf>>,
pub(crate) denied: Mutex<Vec<PathBuf>>,
event_listeners: Mutex<HashMap<EventId, EventListener>>,
next_event_id: AtomicU32,
pub(crate) require_literal_leading_dot: Option<bool>,
}
impl Scope {
/// Extend the allowed patterns with the given directory.
///
/// After this function has been called, the frontend will be able to use the Tauri API to read
/// the directory and all of its files. If `recursive` is `true`, subdirectories will be accessible too.
pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
let path = path.as_ref();
{
let mut allowed = self.allowed.lock().unwrap();
allowed.push(path.to_path_buf());
allowed.push(path.join(if recursive { "**" } else { "*" }));
}
self.emit(Event::PathAllowed(path.to_path_buf()));
}
/// Extend the allowed patterns with the given file path.
///
/// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.
pub fn allow_file<P: AsRef<Path>>(&self, path: P) {
let path = path.as_ref();
self.allowed.lock().unwrap().push(path.to_path_buf());
self.emit(Event::PathAllowed(path.to_path_buf()));
}
/// Set the given directory path to be forbidden by this scope.
///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
let path = path.as_ref();
{
let mut denied = self.denied.lock().unwrap();
denied.push(path.to_path_buf());
denied.push(path.join(if recursive { "**" } else { "*" }));
}
self.emit(Event::PathForbidden(path.to_path_buf()));
}
/// Set the given file path to be forbidden by this scope.
///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) {
let path = path.as_ref();
self.denied.lock().unwrap().push(path.to_path_buf());
self.emit(Event::PathForbidden(path.to_path_buf()));
}
/// List of allowed paths.
pub fn allowed(&self) -> Vec<PathBuf> {
self.allowed.lock().unwrap().clone()
}
/// List of forbidden paths.
pub fn forbidden(&self) -> Vec<PathBuf> {
self.denied.lock().unwrap().clone()
}
fn next_event_id(&self) -> u32 {
self.next_event_id.fetch_add(1, Ordering::Relaxed)
}
fn emit(&self, event: Event) {
let listeners = self.event_listeners.lock().unwrap();
let handlers = listeners.values();
for listener in handlers {
listener(&event);
}
}
/// Listen to an event on this scope.
pub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> EventId {
let id = self.next_event_id();
self.event_listeners.lock().unwrap().insert(id, Box::new(f));
id
}
}

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