diff --git a/.cargo/audit.toml b/.cargo/audit.toml deleted file mode 100644 index eb0c6133aa25..000000000000 --- a/.cargo/audit.toml +++ /dev/null @@ -1,4 +0,0 @@ -[advisories] -ignore = [ - "RUSTSEC-2020-0095" # difference is unmaintained (used by `mockito`) -] diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 000000000000..5aa69189f42b --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[env] +__TAURI_WORKSPACE__ = "true" diff --git a/.changes/add-command.md b/.changes/add-command.md new file mode 100644 index 000000000000..7c8638185b4b --- /dev/null +++ b/.changes/add-command.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:feat' +"@tauri-apps/cli": 'patch:feat' +--- + +Added `tauri plugin add` command to add a plugin to the Tauri project. diff --git a/.changes/add-mobile-to-plugin.md b/.changes/add-mobile-to-plugin.md new file mode 100644 index 000000000000..e3c15a50df54 --- /dev/null +++ b/.changes/add-mobile-to-plugin.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'minor:feat' +"@tauri-apps/cli": 'minor:feat' +--- + +Added `plugin android add` and `plugin ios add` commands to add mobile plugin functionality to existing projects. diff --git a/.changes/add-tauri-get-version.md b/.changes/add-tauri-get-version.md new file mode 100644 index 000000000000..2e5013db6194 --- /dev/null +++ b/.changes/add-tauri-get-version.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Added `tauri::VERSION` const to get Tauri's version from Rust. diff --git a/.changes/add-webview-version.md b/.changes/add-webview-version.md new file mode 100644 index 000000000000..71808812df88 --- /dev/null +++ b/.changes/add-webview-version.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Added `tauri::webview_version` , to get webview version. diff --git a/.changes/allow-prevent-window-close.md b/.changes/allow-prevent-window-close.md deleted file mode 100644 index 8cfe999e84f1..000000000000 --- a/.changes/allow-prevent-window-close.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Allow preventing window close when the user requests it. diff --git a/.changes/allowlist-clipboard.md b/.changes/allowlist-clipboard.md deleted file mode 100644 index 451fb8dc6241..000000000000 --- a/.changes/allowlist-clipboard.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-utils": patch ---- - -The `allowlist` configuration now includes a `clipboard` object, controlling the exposure of the `writeText` and `readText` APIs. diff --git a/.changes/allowlist-dialog.md b/.changes/allowlist-dialog.md deleted file mode 100644 index ffbcd6a568cf..000000000000 --- a/.changes/allowlist-dialog.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-utils": patch -"tauri": patch ---- - -The dialog allowlist now includes flags for the `message`, `ask` and `confirm` APIs. diff --git a/.changes/allowlist-process.md b/.changes/allowlist-process.md deleted file mode 100644 index 490c3d1fb5ca..000000000000 --- a/.changes/allowlist-process.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-utils": patch ---- - -The `allowlist` configuration now includes a `process` object, controlling the exposure of the `relaunch` and `exit` APIs. diff --git a/.changes/allowlist-window.md b/.changes/allowlist-window.md deleted file mode 100644 index 7fe9048de4b4..000000000000 --- a/.changes/allowlist-window.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -tauri-utils: patch ---- - -The `window` allowlist now includes options to enable all window modification APIs: `center`, `close`, `create`, `hide`, `maximize`, `minimize`, `print`, `requestUserAttention`, `setAlwaysOnTop`, `setDecorations`, `setFocus`, `setFullscreen`, `setIcon`, `setMaxSize`, `setMinSize`, `setPosition`, `setResizable`, `setSize`, `setSkipTaskbar`, `setTitle`, `show`, `startDragging`, `unmaximize` and `unminimize`. diff --git a/.changes/android-apis-runtime.md b/.changes/android-apis-runtime.md new file mode 100644 index 000000000000..fe14fb1433af --- /dev/null +++ b/.changes/android-apis-runtime.md @@ -0,0 +1,6 @@ +--- +"tauri-runtime": 'minor:feat' +"tauri-runtime-wry": 'minor:feat' +--- + +Add `find_class`, `run_on_android_context` on `RuntimeHandle`. diff --git a/.changes/android-buildsrc-gitignore.md b/.changes/android-buildsrc-gitignore.md new file mode 100644 index 000000000000..9f0ee5f4c62a --- /dev/null +++ b/.changes/android-buildsrc-gitignore.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Do not gitignore the Android project's `buildSrc` folder by default since we removed absolute paths from it. diff --git a/.changes/android-enhance-method-parse.md b/.changes/android-enhance-method-parse.md new file mode 100644 index 000000000000..165f6868d5f6 --- /dev/null +++ b/.changes/android-enhance-method-parse.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Enhance parsing of annotated Android plugin methods to support private functions. diff --git a/.changes/android-load-config.md b/.changes/android-load-config.md new file mode 100644 index 000000000000..021322029632 --- /dev/null +++ b/.changes/android-load-config.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Added static function `loadConfig` on the Android `PluginManager` class. diff --git a/.changes/android-plugin-command-exception.md b/.changes/android-plugin-command-exception.md new file mode 100644 index 000000000000..40ccb8135aaf --- /dev/null +++ b/.changes/android-plugin-command-exception.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Adjust Android plugin exception error. diff --git a/.changes/anonymous-lifetimes.md b/.changes/anonymous-lifetimes.md deleted file mode 100644 index cfab536387c4..000000000000 --- a/.changes/anonymous-lifetimes.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Remove anonymous lifetimes on examples. \ No newline at end of file diff --git a/.changes/api-WebviewWindow-fileDropEnabled.md b/.changes/api-WebviewWindow-fileDropEnabled.md deleted file mode 100644 index 06835a9298fb..000000000000 --- a/.changes/api-WebviewWindow-fileDropEnabled.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Add `fileDropEnabled` property to `WindowOptions` so you can now disable it when creating windows from js. diff --git a/.changes/api-add-log-dir.md b/.changes/api-add-log-dir.md deleted file mode 100644 index b0586cbd633a..000000000000 --- a/.changes/api-add-log-dir.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": minor ---- - -Add `logDir` function to the `path` module to access the sugested log directory. -Add `BaseDirectory.Log` to the `fs` module. diff --git a/.changes/api-change-events.md b/.changes/api-change-events.md deleted file mode 100644 index df0b1f5563dd..000000000000 --- a/.changes/api-change-events.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -* **Breaking change**: Renamed `tauri::Event` to `tauri::RunEvent` -* Exported `tauri::Event` and `tauri::EventHandler` so you can define a function and pass it to `Window::listen` diff --git a/.changes/api-cjs-chunks.md b/.changes/api-cjs-chunks.md deleted file mode 100644 index 672e5b816e81..000000000000 --- a/.changes/api-cjs-chunks.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -CommonJS chunks are now properly exported with `.cjs` extension diff --git a/.changes/api-clipboard-export.md b/.changes/api-clipboard-export.md deleted file mode 100644 index ae1696237ec4..000000000000 --- a/.changes/api-clipboard-export.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": patch ---- - - -`bundle` now exports `clipboard` module so you can `import { clipboard } from "@tauri-apps/api"`. diff --git a/.changes/api-convert-file-url.md b/.changes/api-convert-file-url.md deleted file mode 100644 index 3904389f0e06..000000000000 --- a/.changes/api-convert-file-url.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `convertFileSrc` helper to the `tauri` module, simplifying the process of using file paths as webview source (`img`, `video`, etc). diff --git a/.changes/api-dialog-ask-message-confirm.md b/.changes/api-dialog-ask-message-confirm.md deleted file mode 100644 index f698df09a108..000000000000 --- a/.changes/api-dialog-ask-message-confirm.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Expose `ask`, `message` and `confirm` APIs on the dialog module. diff --git a/.changes/api-emit-payload-type.md b/.changes/api-emit-payload-type.md deleted file mode 100644 index 4ed130564181..000000000000 --- a/.changes/api-emit-payload-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Event `emit` now automatically serialize non-string types. diff --git a/.changes/api-export-BaseDirectory.md b/.changes/api-export-BaseDirectory.md deleted file mode 100644 index 90fb9bb9318a..000000000000 --- a/.changes/api-export-BaseDirectory.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": patch ---- - -Export `BaseDirectory` in `path` module - diff --git a/.changes/api-export-package-json.md b/.changes/api-export-package-json.md deleted file mode 100644 index bc04cc61e6d2..000000000000 --- a/.changes/api-export-package-json.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `package.json` to the `exports` object. diff --git a/.changes/api-export-type-fix.md b/.changes/api-export-type-fix.md deleted file mode 100644 index 8c871e80ce79..000000000000 --- a/.changes/api-export-type-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Export `Response` and `ResponseType` as value instead of type. diff --git a/.changes/api-export-type.md b/.changes/api-export-type.md deleted file mode 100644 index 0acee57af74d..000000000000 --- a/.changes/api-export-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Use `export type` to export TS types, enums and interfaces. diff --git a/.changes/api-feature-flags.md b/.changes/api-feature-flags.md deleted file mode 100644 index 5612620af17e..000000000000 --- a/.changes/api-feature-flags.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Moves `shell`, `dialog::FileDialogBuilder` and `process::Command` APIs behind their allowlist feature flags. diff --git a/.changes/api-features.md b/.changes/api-features.md deleted file mode 100644 index 51c5d93ece9b..000000000000 --- a/.changes/api-features.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `tauri::api` modules `http`, `notification`, `dialog`, and `process::Command` APIs are now hidden behind a feature flag, `http-api`, `notification`, `dialog` and `command`, respectively. diff --git a/.changes/api-fetch-empty-reponse.md b/.changes/api-fetch-empty-reponse.md deleted file mode 100644 index d0b81376f7ac..000000000000 --- a/.changes/api-fetch-empty-reponse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Fix `http.fetch` throwing error if the response is successful but the body is empty. diff --git a/.changes/api-file-dialog-title.md b/.changes/api-file-dialog-title.md deleted file mode 100644 index e61508f97e5c..000000000000 --- a/.changes/api-file-dialog-title.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": minor -"tauri": minor ---- - -Add `title` option to file open/save dialogs. diff --git a/.changes/api-fix-double-window.md b/.changes/api-fix-double-window.md deleted file mode 100644 index 5fe3032e7e73..000000000000 --- a/.changes/api-fix-double-window.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Fix double window creation diff --git a/.changes/api-fix-os-platform-return.md b/.changes/api-fix-os-platform-return.md deleted file mode 100644 index fdf7d03b079b..000000000000 --- a/.changes/api-fix-os-platform-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Fix `os.platform` returning `macos` and `windows` instead of `darwin` and `win32`. diff --git a/.changes/api-focus.md b/.changes/api-focus.md deleted file mode 100644 index 5278795b3bce..000000000000 --- a/.changes/api-focus.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `focus?: boolean` to the WindowOptions interface. diff --git a/.changes/api-format-callback.md b/.changes/api-format-callback.md deleted file mode 100644 index a517cff7c268..000000000000 --- a/.changes/api-format-callback.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -The `formatCallback` helper function now returns a number instead of a string. diff --git a/.changes/api-is-decorated.md b/.changes/api-is-decorated.md deleted file mode 100644 index 0806cdf89822..000000000000 --- a/.changes/api-is-decorated.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `isDecorated` getter on the window API. diff --git a/.changes/api-is-resizable.md b/.changes/api-is-resizable.md deleted file mode 100644 index c81d120a90e6..000000000000 --- a/.changes/api-is-resizable.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `isResizable` getter on the window API. diff --git a/.changes/api-is-visible.md b/.changes/api-is-visible.md deleted file mode 100644 index e3cfd2250f18..000000000000 --- a/.changes/api-is-visible.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `isVisible` getter on the window API. diff --git a/.changes/api-js-os-locale.md b/.changes/api-js-os-locale.md new file mode 100644 index 000000000000..a9616e0b3b26 --- /dev/null +++ b/.changes/api-js-os-locale.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'patch:enhance' +--- + +Add `locale` function in the `os` module to get the system locale. diff --git a/.changes/api-os-module.md b/.changes/api-os-module.md deleted file mode 100644 index 7935a98beec0..000000000000 --- a/.changes/api-os-module.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -"api": patch ---- -Add `os` module which exports `EOL`, `platform()`, `version()`, `type()`, `arch()`, `tempdir()` diff --git a/.changes/api-path-module.md b/.changes/api-path-module.md deleted file mode 100644 index 0a967305019a..000000000000 --- a/.changes/api-path-module.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"api": patch ---- - -- Add new nodejs-inspired functions which are `join`, `resolve`, `normalize`, `dirname`, `basename` and `extname`. -- Add `sep` and `delimiter` constants. -- Removed `resolvePath` API, use `resolve` instead. diff --git a/.changes/api-raw-headers.md b/.changes/api-raw-headers.md deleted file mode 100644 index 2d384f6b38ba..000000000000 --- a/.changes/api-raw-headers.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Added `rawHeaders` to `http > Response`. diff --git a/.changes/api-remove-current-dir.md b/.changes/api-remove-current-dir.md deleted file mode 100644 index 375fa7f78110..000000000000 --- a/.changes/api-remove-current-dir.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": "patch" ---- - -Removed the `currentDir` API from the `path` module. diff --git a/.changes/api-remove-ts-files.md b/.changes/api-remove-ts-files.md deleted file mode 100644 index 8497514e9a03..000000000000 --- a/.changes/api-remove-ts-files.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Remove `.ts` files on the published package. diff --git a/.changes/api-request-user-attention.md b/.changes/api-request-user-attention.md deleted file mode 100644 index 1454f9afb93d..000000000000 --- a/.changes/api-request-user-attention.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `requestUserAttention` API to the `window` module. diff --git a/.changes/api-rs-os-locale.md b/.changes/api-rs-os-locale.md new file mode 100644 index 000000000000..434f43eb93d4 --- /dev/null +++ b/.changes/api-rs-os-locale.md @@ -0,0 +1,5 @@ +--- +"tauri": "patch:enhance" +--- + +Add `tauri::api::os::locale` function to get the system locale. diff --git a/.changes/api-set-focus.md b/.changes/api-set-focus.md deleted file mode 100644 index 1e214ce8e091..000000000000 --- a/.changes/api-set-focus.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `setFocus` to the window API. diff --git a/.changes/api-set-skip-taskbar.md b/.changes/api-set-skip-taskbar.md deleted file mode 100644 index 3546889a497e..000000000000 --- a/.changes/api-set-skip-taskbar.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `setSkipTaskbar` to the window API. diff --git a/.changes/api-skip-taskbar.md b/.changes/api-skip-taskbar.md deleted file mode 100644 index a37747212b3a..000000000000 --- a/.changes/api-skip-taskbar.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `skipTaskbar?: boolean` to the WindowOptions interface. diff --git a/.changes/api-target-es2021.md b/.changes/api-target-es2021.md deleted file mode 100644 index ca3743586043..000000000000 --- a/.changes/api-target-es2021.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Change target to ES2021. diff --git a/.changes/api-toggle-maximize.md b/.changes/api-toggle-maximize.md deleted file mode 100644 index 485d09031521..000000000000 --- a/.changes/api-toggle-maximize.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Add `toggleMaximize()` function to the `WebviewWindow` class. diff --git a/.changes/api-transparent-window.md b/.changes/api-transparent-window.md deleted file mode 100644 index 496af1ca0c5d..000000000000 --- a/.changes/api-transparent-window.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `transparent?: boolean` to the `WindowOptions` interface. diff --git a/.changes/api-ts-expect-error.md b/.changes/api-ts-expect-error.md deleted file mode 100644 index 91038679ca93..000000000000 --- a/.changes/api-ts-expect-error.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -"api": patch ---- -Fix `@ts-expect` error usage diff --git a/.changes/api-use-uint8array.md b/.changes/api-use-uint8array.md deleted file mode 100644 index a23c35bbf564..000000000000 --- a/.changes/api-use-uint8array.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -**Breaking change:** Replaces all usages of `number[]` with `Uint8Array` to be closer aligned with the wider JS ecosystem. diff --git a/.changes/api-window-center.md b/.changes/api-window-center.md deleted file mode 100644 index 30eb31437c73..000000000000 --- a/.changes/api-window-center.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `center?: boolean` to `WindowOptions` and `center()` API to the `appWindow`. diff --git a/.changes/api-window-type-for-size-and-position.md b/.changes/api-window-type-for-size-and-position.md deleted file mode 100644 index 0b7818c1f575..000000000000 --- a/.changes/api-window-type-for-size-and-position.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": patch ---- - -`WindowManager` methods `innerPosition` `outerPosition` now correctly return instance of `PhysicalPosition`. -`WindowManager` methods `innerSize` `outerSize` now correctly return instance of `PhysicalSize`. diff --git a/.changes/app-any-thread.md b/.changes/app-any-thread.md deleted file mode 100644 index a413e36af267..000000000000 --- a/.changes/app-any-thread.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Added `any_thread()` to the `tauri::Builder` to run applications on any thread (only exposed on Linux and Windows). diff --git a/.changes/app-callback.md b/.changes/app-callback.md deleted file mode 100644 index 0f9ebff43643..000000000000 --- a/.changes/app-callback.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Add `App#run` method with callback argument (event loop event handler). diff --git a/.changes/app-dir-refactor.md b/.changes/app-dir-refactor.md deleted file mode 100644 index 023b2071a4e9..000000000000 --- a/.changes/app-dir-refactor.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking:** `api::path::resolve_path()` and `api::path::app_dir()` now takes the config as first argument and the `PackageInfo` as second argument. -**Breaking:** `api::path::app_dir()` now resolves to `${configDir}/${config.tauri.bundle.identifier}`. diff --git a/.changes/app-folder-structure.md b/.changes/app-folder-structure.md deleted file mode 100644 index c4396de4ddf6..000000000000 --- a/.changes/app-folder-structure.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"cli.js": patch -"cli.rs": patch ---- - -Do not force Tauri application code on `src-tauri` folder and use a glob pattern to look for a subfolder with a `tauri.conf.json` file. diff --git a/.changes/app-handle-create-window.md b/.changes/app-handle-create-window.md deleted file mode 100644 index 52cb110536f7..000000000000 --- a/.changes/app-handle-create-window.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `create_window` API to the `AppHandle` struct. diff --git a/.changes/app-handle-exit-cleanup.md b/.changes/app-handle-exit-cleanup.md deleted file mode 100644 index bdec484413a1..000000000000 --- a/.changes/app-handle-exit-cleanup.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Cleanup application on `AppHandle#exit`. diff --git a/.changes/app-handle.md b/.changes/app-handle.md deleted file mode 100644 index bcff2bdee0e7..000000000000 --- a/.changes/app-handle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds a `handle` function to the `App` struct, which returns a `Send` handle to the app instance. diff --git a/.changes/app-set-activation-policy.md b/.changes/app-set-activation-policy.md deleted file mode 100644 index 1bf47c37211c..000000000000 --- a/.changes/app-set-activation-policy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `set_activation_policy` API to the `tauri::App` struct (macOS only). diff --git a/.changes/app-state.md b/.changes/app-state.md deleted file mode 100644 index cee7282c15d0..000000000000 --- a/.changes/app-state.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `manage` API to the `Builder` struct, which manages app state. diff --git a/.changes/appimage-follow-symlinks.md b/.changes/appimage-follow-symlinks.md new file mode 100644 index 000000000000..a2f27051cb9a --- /dev/null +++ b/.changes/appimage-follow-symlinks.md @@ -0,0 +1,6 @@ +--- +'tauri-bundler': 'patch:bug' +--- + +- Updated the AppImage bundler to follow symlinks for `/usr/lib*`. +- Fixes AppImage bundling for Void Linux, which was failing to bundle webkit2gtk because the `/usr/lib64` is a symlink to `/usr/lib`. diff --git a/.changes/appwindow-events.md b/.changes/appwindow-events.md deleted file mode 100644 index c0eb42ae066c..000000000000 --- a/.changes/appwindow-events.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -You can now use `emit`, `listen` and `once` using the `appWindow` exported by the window module. diff --git a/.changes/asset-allowlist.md b/.changes/asset-allowlist.md deleted file mode 100644 index ca00d66be72b..000000000000 --- a/.changes/asset-allowlist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-utils": patch ---- - -Added `asset` allowlist configuration, which enables the `asset` protocol and defines it access scope. diff --git a/.changes/asset-cors.md b/.changes/asset-cors.md deleted file mode 100644 index 4444dc4ffdc6..000000000000 --- a/.changes/asset-cors.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Enable CORS on the `asset` protocol. diff --git a/.changes/asset-path.md b/.changes/asset-path.md deleted file mode 100644 index ed31e639b662..000000000000 --- a/.changes/asset-path.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"api": patch -"tauri": patch ---- - -Fix missing asset protocol path.Now the protocol is `https://asset.localhost/path/to/file` on Windows. Lunix and macOS -is still `asset://path/to/file`. diff --git a/.changes/asset-protocol-feature-flag.md b/.changes/asset-protocol-feature-flag.md deleted file mode 100644 index f3cc874bf255..000000000000 --- a/.changes/asset-protocol-feature-flag.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `asset://` custom protocol is only defined when either the `api-all`, `protocol-all` or `protocol-asset` feature flags are enabled. These feature flags are accessible with the `tauri.conf.json` allowlist. diff --git a/.changes/asset-protocol.md b/.changes/asset-protocol.md deleted file mode 100644 index 64d40a8753b5..000000000000 --- a/.changes/asset-protocol.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Add asset custom protocol to access local file system. diff --git a/.changes/asset-resolver.md b/.changes/asset-resolver.md deleted file mode 100644 index cadbb49e790a..000000000000 --- a/.changes/asset-resolver.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Expose the `asset_resolver` API on the `App` and `AppHandle` structs. diff --git a/.changes/assets-refactor.md b/.changes/assets-refactor.md deleted file mode 100644 index 9fd475e1b498..000000000000 --- a/.changes/assets-refactor.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri-codegen": patch -"tauri-utils": patch -"tauri": patch ---- - -**Breaking:** The `assets` field on the `tauri::Context` struct is now a `Arc`. diff --git a/.changes/async-commands.md b/.changes/async-commands.md deleted file mode 100644 index 49e7831cf424..000000000000 --- a/.changes/async-commands.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-macros": patch ---- - -Only commands with a `async fn` are executed on a separate task. `#[command] fn command_name` runs on the main thread. diff --git a/.changes/async-runtime-handle-api.md b/.changes/async-runtime-handle-api.md deleted file mode 100644 index 58ab8fb18589..000000000000 --- a/.changes/async-runtime-handle-api.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Add `handle` API to `tauri::async_runtime`. diff --git a/.changes/async-runtime-refactor.md b/.changes/async-runtime-refactor.md deleted file mode 100644 index 4367fe552fa3..000000000000 --- a/.changes/async-runtime-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change:** Refactored the types returned from the `async_runtime` module. diff --git a/.changes/async-runtime-set.md b/.changes/async-runtime-set.md deleted file mode 100644 index 7b00e0127e89..000000000000 --- a/.changes/async-runtime-set.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Added `tauri::async_runtime::set` method, allowing to share your tokio runtime with Tauri. diff --git a/.changes/async-runtime-spawn-blocking.md b/.changes/async-runtime-spawn-blocking.md deleted file mode 100644 index b39939c1e7ae..000000000000 --- a/.changes/async-runtime-spawn-blocking.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Added `tauri::async_runtime::spawn_blocking` API. diff --git a/.changes/async-runtime-spawn-returns-join-handle.md b/.changes/async-runtime-spawn-returns-join-handle.md deleted file mode 100644 index 34d76a76b319..000000000000 --- a/.changes/async-runtime-spawn-returns-join-handle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change:** The `tauri::async_runtime::spawn` function now returns `tauri::async_runtime::JoinHandle`. diff --git a/.changes/attohttpc-default-client.md b/.changes/attohttpc-default-client.md deleted file mode 100644 index 6a6204d99351..000000000000 --- a/.changes/attohttpc-default-client.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Use `attohttpc` on the HTTP API by default for bundle size optimization. `reqwest` is implemented behind the `reqwest-client` feature flag. diff --git a/.changes/before-script-envs.md b/.changes/before-script-envs.md deleted file mode 100644 index d1a28bfa0f85..000000000000 --- a/.changes/before-script-envs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Define `TAURI_PLATFORM`, `TAURI_ARCH`, `TAURI_FAMILY`, `TAURI_PLATFORM_TYPE`, `TAURI_PLATFORM_VERSION` and `TAURI_DEBUG` environment variables for the `beforeDevCommand` and `beforeBuildCommand` scripts. diff --git a/.changes/beta.md b/.changes/beta.md deleted file mode 100644 index b959151dbee0..000000000000 --- a/.changes/beta.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"api": major -"tauri-bundler": major -"cli.rs": major -"cli.js": major -"tauri-utils": major -"tauri-codegen": major -"tauri-macros": major -"tauri-build": major -"tauri-runtime": minor -"tauri-runtime-wry": minor -"tauri": major -"create-tauri-app": major ---- - -Upgrade to beta! diff --git a/.changes/build-android-env-vars.md b/.changes/build-android-env-vars.md new file mode 100644 index 000000000000..a5402774ae58 --- /dev/null +++ b/.changes/build-android-env-vars.md @@ -0,0 +1,5 @@ +--- +"tauri-build": 'patch:enhance' +--- + +Set environment variables used by `tauri::mobile_entry_point`. diff --git a/.changes/build-on-m1.md b/.changes/build-on-m1.md deleted file mode 100644 index cdb3fd7ddf8f..000000000000 --- a/.changes/build-on-m1.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Support `cargo tauri build` on Apple M1 chip. diff --git a/.changes/build-specify-win-sdk.md b/.changes/build-specify-win-sdk.md deleted file mode 100644 index 1eab9d981be7..000000000000 --- a/.changes/build-specify-win-sdk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-build": patch ---- - -Allow user to specify windows sdk path in build.rs. \ No newline at end of file diff --git a/.changes/bump-1.3.md b/.changes/bump-1.3.md new file mode 100644 index 000000000000..47dabadd5b59 --- /dev/null +++ b/.changes/bump-1.3.md @@ -0,0 +1,10 @@ +--- +"tauri-bundler": 'patch:enhance' +"tauri-codegen": 'patch:enhance' +"tauri-macros": 'patch:enhance' +"tauri-utils": 'patch:enhance' +"tauri-runtime": 'patch:enhance' +"tauri-runtime-wry": 'patch:enhance' +--- + +Pull changes from Tauri 1.3 release. diff --git a/.changes/bundle-deb-installed-size.md b/.changes/bundle-deb-installed-size.md deleted file mode 100644 index 932472af2b60..000000000000 --- a/.changes/bundle-deb-installed-size.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Fixes the `Installed-Size` value on the debian package. diff --git a/.changes/bundler-add-provider-short-name.md b/.changes/bundler-add-provider-short-name.md deleted file mode 100644 index 65eaae6be092..000000000000 --- a/.changes/bundler-add-provider-short-name.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Provide a provider short name for macOS app notarization if your Apple developer account is connected to more than one team. diff --git a/.changes/bundler-appimage-fuse.md b/.changes/bundler-appimage-fuse.md deleted file mode 100644 index ec7a25876ef4..000000000000 --- a/.changes/bundler-appimage-fuse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Allow building AppImages on systems without FUSE setup. diff --git a/.changes/bundler-arm.md b/.changes/bundler-arm.md deleted file mode 100644 index a6b99d1cd268..000000000000 --- a/.changes/bundler-arm.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Use `armhf` as Debian package architecture on `arm` CPUs. diff --git a/.changes/bundler-docs.md b/.changes/bundler-docs.md deleted file mode 100644 index 2efc8e1504b2..000000000000 --- a/.changes/bundler-docs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Adds basic library documentation. diff --git a/.changes/bundler-fix-appimage.md b/.changes/bundler-fix-appimage.md deleted file mode 100644 index fcaf8b4dbaca..000000000000 --- a/.changes/bundler-fix-appimage.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'tauri-bundler': patch ---- - -Fixes AppImage crashes caused by missing WebKit runtime files. diff --git a/.changes/bundler-merge-info-plist.md b/.changes/bundler-merge-info-plist.md deleted file mode 100644 index 545b18e71cb9..000000000000 --- a/.changes/bundler-merge-info-plist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Merge Tauri-generated Info.plist with the contents of `src-tauri/Info.plist` if it exists. diff --git a/.changes/bundler-msi-init-installdir.md b/.changes/bundler-msi-init-installdir.md deleted file mode 100644 index 430f2ba1c2f7..000000000000 --- a/.changes/bundler-msi-init-installdir.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Initialize the preselected installation path with the location of the previous installation. \ No newline at end of file diff --git a/.changes/bundler-msi-upgrade-same-version.md b/.changes/bundler-msi-upgrade-same-version.md deleted file mode 100644 index 5778a0df80c1..000000000000 --- a/.changes/bundler-msi-upgrade-same-version.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Change the WiX config to allow upgrading installation with same version instead of duplicating the application. diff --git a/.changes/bundler-not-copy-resources-outdir.md b/.changes/bundler-not-copy-resources-outdir.md deleted file mode 100644 index 81f615d3cb3e..000000000000 --- a/.changes/bundler-not-copy-resources-outdir.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -The process of copying binaries and resources to `project_out_directory` was moved to the Tauri CLI. diff --git a/.changes/bundler-package-types.md b/.changes/bundler-package-types.md deleted file mode 100644 index e48c03154f0a..000000000000 --- a/.changes/bundler-package-types.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -The `PackageTypes` enum now includes all options, including Windows packages. diff --git a/.changes/bundler-print-cfg.md b/.changes/bundler-print-cfg.md deleted file mode 100644 index d382978884e9..000000000000 --- a/.changes/bundler-print-cfg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Replaces usage of the nightly command `RUSTC_BOOTSTRAP=1 rustc -Z unstable-options --print target-spec-json` with the stable command `rustc --print cfg`, improving target triple detection. diff --git a/.changes/bundler-remove-dialog-option.md b/.changes/bundler-remove-dialog-option.md new file mode 100644 index 000000000000..9ee8f8e02bae --- /dev/null +++ b/.changes/bundler-remove-dialog-option.md @@ -0,0 +1,5 @@ +--- +"tauri-bundler": 'patch:enhance' +--- + +Removed the `UpdaterSettings::dialog` field. diff --git a/.changes/bundler-runtime-target-arch.md b/.changes/bundler-runtime-target-arch.md deleted file mode 100644 index 2cedf258f8e4..000000000000 --- a/.changes/bundler-runtime-target-arch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Check target architecture at runtime running `$ RUSTC_BOOTSTRAP=1 rustc -Z unstable-options --print target-spec-json` and parsing the `llvm-target` field, fixing macOS M1 sidecar check until we can compile the CLI with M1 target on GitHub Actions. diff --git a/.changes/bundler-windows-icon-path.md b/.changes/bundler-windows-icon-path.md deleted file mode 100644 index 25063f829154..000000000000 --- a/.changes/bundler-windows-icon-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Adds `icon_path` field to the `WindowsSettings` struct (defaults to `icons/icon.ico`). diff --git a/.changes/bundler-windows-uninstaller.md b/.changes/bundler-windows-uninstaller.md deleted file mode 100644 index 306fdd0fc4ac..000000000000 --- a/.changes/bundler-windows-uninstaller.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Fix WIX uninstaller by using unique `GUID` shortcut. diff --git a/.changes/bundler-wix-banner-icon.md b/.changes/bundler-wix-banner-icon.md deleted file mode 100644 index fbe360201afd..000000000000 --- a/.changes/bundler-wix-banner-icon.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Added `banner_path` field to the `WixSettings` struct. - diff --git a/.changes/bundler-wix-dialog-image.md b/.changes/bundler-wix-dialog-image.md deleted file mode 100644 index 3f29587c35aa..000000000000 --- a/.changes/bundler-wix-dialog-image.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Added `dialog_image_path` field to the `WixSettings` struct. diff --git a/.changes/callback-validation.md b/.changes/callback-validation.md deleted file mode 100644 index 4aa93397bf85..000000000000 --- a/.changes/callback-validation.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `callback` and `error` invoke fields, along with other `transformCallback` usages, are now validated to be numeric. diff --git a/.changes/cargo-cache.md b/.changes/cargo-cache.md deleted file mode 100644 index 07e9eabea311..000000000000 --- a/.changes/cargo-cache.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Fixes a cargo `target/` cache issue. diff --git a/.changes/channel-api.md b/.changes/channel-api.md new file mode 100644 index 000000000000..716eca43ad3a --- /dev/null +++ b/.changes/channel-api.md @@ -0,0 +1,6 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Add channel API for sending data across the IPC. diff --git a/.changes/child-process-cleanup.md b/.changes/child-process-cleanup.md deleted file mode 100644 index c38d83da74f2..000000000000 --- a/.changes/child-process-cleanup.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Kill child processes spawned with `tauri::api::process::Command` on `tauri::App` drop. Can be skipped with `tauri::Builder#skip_cleanup_on_drop`. diff --git a/.changes/clap-beta.4-core.md b/.changes/clap-beta.4-core.md deleted file mode 100644 index bc371dabe284..000000000000 --- a/.changes/clap-beta.4-core.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Change `Error::ParseCliArguments(clap::Error)` to `Error::ParseCliArguments(String)` because `clap::Error` is not `Send`. diff --git a/.changes/clap-beta.4.md b/.changes/clap-beta.4.md deleted file mode 100644 index d29f9f87f9de..000000000000 --- a/.changes/clap-beta.4.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-utils": patch ---- - -Change `CliArg` numeric types from `u64` to `usize`. diff --git a/.changes/cli-android-build.md b/.changes/cli-android-build.md new file mode 100644 index 000000000000..708a482db8db --- /dev/null +++ b/.changes/cli-android-build.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'minor:feat' +"@tauri-apps/cli": 'minor:feat' +--- + +Added `android build` command. diff --git a/.changes/cli-android-dev-release.md b/.changes/cli-android-dev-release.md new file mode 100644 index 000000000000..638c0130503d --- /dev/null +++ b/.changes/cli-android-dev-release.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Add `--release` flag for `tauri android dev` however you will need to sign your Android app, see https://next--tauri.netlify.app/next/guides/distribution/sign-android diff --git a/.changes/cli-android-specified-targets-only.md b/.changes/cli-android-specified-targets-only.md new file mode 100644 index 000000000000..3eace2f7e9dd --- /dev/null +++ b/.changes/cli-android-specified-targets-only.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Build only specified rust targets for `tauri android build` instead of all. diff --git a/.changes/cli-android-split-per-abit-target.md b/.changes/cli-android-split-per-abit-target.md new file mode 100644 index 000000000000..00330291d66d --- /dev/null +++ b/.changes/cli-android-split-per-abit-target.md @@ -0,0 +1,5 @@ +--- +'tauri-cli': 'patch:bug' +--- + +Fix `--split-per-abi` not building any targets unless specified by `--target` flag. diff --git a/.changes/cli-autoreload-mime-type.md b/.changes/cli-autoreload-mime-type.md new file mode 100644 index 000000000000..3b0fe2601c90 --- /dev/null +++ b/.changes/cli-autoreload-mime-type.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:bug' +'@tauri-apps/cli': 'patch:bug' +--- + +Fix built-in devserver adding hot-reload code to non-html files. diff --git a/.changes/cli-built-in-dev-server-mobile.md b/.changes/cli-built-in-dev-server-mobile.md new file mode 100644 index 000000000000..5ad54dfa0a7f --- /dev/null +++ b/.changes/cli-built-in-dev-server-mobile.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Use local ip address for built-in dev server on mobile. diff --git a/.changes/cli-config-path.md b/.changes/cli-config-path.md deleted file mode 100644 index 8354c6e53a9c..000000000000 --- a/.changes/cli-config-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Allow `config` arg to be a path to a JSON file on the `dev` and `build` commands. diff --git a/.changes/cli-error-logging.md b/.changes/cli-error-logging.md deleted file mode 100644 index a4d885a7edfa..000000000000 --- a/.changes/cli-error-logging.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Improve error logging. diff --git a/.changes/cli-error-message.md b/.changes/cli-error-message.md deleted file mode 100644 index 8e041a5102b8..000000000000 --- a/.changes/cli-error-message.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Improve error message when the product name is invalid. diff --git a/.changes/cli-init-infer-prompts.md b/.changes/cli-init-infer-prompts.md deleted file mode 100644 index 1c54c5a133c2..000000000000 --- a/.changes/cli-init-infer-prompts.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"cli.rs": patch ---- - -Infer `app name` and `window title` from `package.json > productName` or `package.json > name`. -Infer `distDir` and `devPath` by reading the package.json and trying to determine the UI framework (Vue.js, Angular, React, Svelte and some UI frameworks). diff --git a/.changes/cli-ios-build.md b/.changes/cli-ios-build.md new file mode 100644 index 000000000000..bb0a39b951d4 --- /dev/null +++ b/.changes/cli-ios-build.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Added `ios build` command. diff --git a/.changes/cli-key-properties.md b/.changes/cli-key-properties.md new file mode 100644 index 000000000000..1ccb6623e53b --- /dev/null +++ b/.changes/cli-key-properties.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Add `key.properties` file to android's `.gitignore`. diff --git a/.changes/cli-libname-dashes.md b/.changes/cli-libname-dashes.md new file mode 100644 index 000000000000..c341ca92c1fb --- /dev/null +++ b/.changes/cli-libname-dashes.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +On mobile, fix regression introduced in `tauri-cli` version `2.0.0-alpha.3` where library not found error was thrown. diff --git a/.changes/cli-library-compilation.md b/.changes/cli-library-compilation.md new file mode 100644 index 000000000000..c26ac0db1484 --- /dev/null +++ b/.changes/cli-library-compilation.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Don't build library files when building desktop targets. diff --git a/.changes/cli-mobile-auto-ip.md b/.changes/cli-mobile-auto-ip.md new file mode 100644 index 000000000000..6a21831f7a4b --- /dev/null +++ b/.changes/cli-mobile-auto-ip.md @@ -0,0 +1,5 @@ +--- +'tauri-cli': 'patch:enhance' +--- + +Auto select an external IP for mobile development and fallback to prompting the user. Use `--force-ip-prompt` to force prompting. diff --git a/.changes/cli-mobile-cwd-config.md b/.changes/cli-mobile-cwd-config.md new file mode 100644 index 000000000000..dc6ab98e57da --- /dev/null +++ b/.changes/cli-mobile-cwd-config.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:bug' +'@tauri-apps/cli': 'patch:bug' +--- + +Set current directory to tauri directory before reading config file. diff --git a/.changes/cli-mobile-dev.md b/.changes/cli-mobile-dev.md new file mode 100644 index 000000000000..395680aad11b --- /dev/null +++ b/.changes/cli-mobile-dev.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'minor:feat' +"@tauri-apps/cli": 'minor:feat' +--- + +Added `android dev` and `ios dev` commands. diff --git a/.changes/cli-mobile-plugin.md b/.changes/cli-mobile-plugin.md new file mode 100644 index 000000000000..31e8538dcccb --- /dev/null +++ b/.changes/cli-mobile-plugin.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'minor:feat' +"@tauri-apps/cli": 'minor:feat' +--- + +Add commands to add native Android and iOS functionality to plugins. diff --git a/.changes/cli-nodejs-detection.md b/.changes/cli-nodejs-detection.md new file mode 100644 index 000000000000..1bd8ddc60f9f --- /dev/null +++ b/.changes/cli-nodejs-detection.md @@ -0,0 +1,5 @@ +--- +'tauri-cli': 'patch:enhance' +--- + +In mobile commands, correctly detect when nodejs binary has the version in its name, for example `node-18` diff --git a/.changes/cli-pnpm.md b/.changes/cli-pnpm.md new file mode 100644 index 000000000000..c22fa2baad4d --- /dev/null +++ b/.changes/cli-pnpm.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Fix android project build crashing when using `pnpm` caused by extra `--`. diff --git a/.changes/cli-profile.md b/.changes/cli-profile.md new file mode 100644 index 000000000000..7c0d4da94e3b --- /dev/null +++ b/.changes/cli-profile.md @@ -0,0 +1,5 @@ +--- +'tauri-cli': 'patch:bug' +--- + +Fix building with a custom cargo profile diff --git a/.changes/cli-refactor-ipc-mobile.md b/.changes/cli-refactor-ipc-mobile.md new file mode 100644 index 000000000000..2494371aa8e1 --- /dev/null +++ b/.changes/cli-refactor-ipc-mobile.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Use temp file instead of environment variable to pass CLI IPC websocket address to the IDE. diff --git a/.changes/cli-rs-info-webview2.md b/.changes/cli-rs-info-webview2.md deleted file mode 100644 index 302bda7e1ce7..000000000000 --- a/.changes/cli-rs-info-webview2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Adds Webview2 version on `info` command. diff --git a/.changes/cli-rs-macos-signing-id-env.md b/.changes/cli-rs-macos-signing-id-env.md deleted file mode 100644 index 228ebed5dffc..000000000000 --- a/.changes/cli-rs-macos-signing-id-env.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Added `APPLE_SIGNING_IDENTITY` as supported environment variable for the bundler. diff --git a/.changes/cli-rs-powerhsell-no-profile.md b/.changes/cli-rs-powerhsell-no-profile.md deleted file mode 100644 index 3ea422d8a5af..000000000000 --- a/.changes/cli-rs-powerhsell-no-profile.md +++ /dev/null @@ -1,7 +0,0 @@ - ---- - "cli.rs": patch - "cli.js": patch - "tauri-bundler": patch ---- -Run powershell commands with `-NoProfile` flag diff --git a/.changes/cli-runner-arg.md b/.changes/cli-runner-arg.md deleted file mode 100644 index f3ee8baaf75d..000000000000 --- a/.changes/cli-runner-arg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Adds `--runner [PROGRAM]` argument on the `dev` and `build` command, allowing using the specified program to run and build the application (example program: `cross`). diff --git a/.changes/cli-skip-targets-install.md b/.changes/cli-skip-targets-install.md new file mode 100644 index 000000000000..e5825cb0c7c5 --- /dev/null +++ b/.changes/cli-skip-targets-install.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Skip Rust target installation if they are already installed. diff --git a/.changes/cli-target-triple.md b/.changes/cli-target-triple.md deleted file mode 100644 index 4ba3cea99ad3..000000000000 --- a/.changes/cli-target-triple.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Adds `--target [TARGET_TRIPLE]` option to the `build` command (example: `--target arm-unknown-linux-gnueabihf`). diff --git a/.changes/cli-targets-refactor.md b/.changes/cli-targets-refactor.md deleted file mode 100644 index 0b6deb8fee99..000000000000 --- a/.changes/cli-targets-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Rename `--target` option on the `build` command to `--bundle`. diff --git a/.changes/cli-windows-arm64.md b/.changes/cli-windows-arm64.md new file mode 100644 index 000000000000..0347ef8e6959 --- /dev/null +++ b/.changes/cli-windows-arm64.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'minor:feat' +'@tauri-apps/cli': 'minor:feat' +--- + +Provide prebuilt CLIs for Windows ARM64 targets. diff --git a/.changes/cli-wry-0-28.md b/.changes/cli-wry-0-28.md new file mode 100644 index 000000000000..b6a0210f3526 --- /dev/null +++ b/.changes/cli-wry-0-28.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Update mobile template to `wry@0.28` diff --git a/.changes/cli.js-cjs-output.md b/.changes/cli.js-cjs-output.md deleted file mode 100644 index 53bf7d818539..000000000000 --- a/.changes/cli.js-cjs-output.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Added CommonJS output to the `dist` folder. diff --git a/.changes/cli.js-deps-update-manifest.md b/.changes/cli.js-deps-update-manifest.md deleted file mode 100644 index ec8694ae25be..000000000000 --- a/.changes/cli.js-deps-update-manifest.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Force Cargo manifest update when running the `deps update` command and fix the version that is written to the file. diff --git a/.changes/cli.js-empty-args.md b/.changes/cli.js-empty-args.md deleted file mode 100644 index 1750745bcabb..000000000000 --- a/.changes/cli.js-empty-args.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Allow empty argument when running `cli.rs`. diff --git a/.changes/cli.js-error-propagation.md b/.changes/cli.js-error-propagation.md deleted file mode 100644 index 4fa07c3b51fb..000000000000 --- a/.changes/cli.js-error-propagation.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Fixes `UnhandledPromiseRejectionWarning` when the Rust CLI call fails. diff --git a/.changes/cli.js-es-module.md b/.changes/cli.js-es-module.md deleted file mode 100644 index 3e0a0cdf906b..000000000000 --- a/.changes/cli.js-es-module.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -The CLI is now a ES module and requires at least Node.js v12.20. diff --git a/.changes/cli.js-fix-ico.md b/.changes/cli.js-fix-ico.md deleted file mode 100644 index 05b5bab9eb48..000000000000 --- a/.changes/cli.js-fix-ico.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Fixes `.ico` icon generation. diff --git a/.changes/cli.js-package-check.md b/.changes/cli.js-package-check.md deleted file mode 100644 index 4f4d496590a6..000000000000 --- a/.changes/cli.js-package-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Packages are checked with `!=` instead of `semver` for beta releases. diff --git a/.changes/cli.js-proxy.md b/.changes/cli.js-proxy.md deleted file mode 100644 index c2e4d13b2d80..000000000000 --- a/.changes/cli.js-proxy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Adds support to HTTPS proxy set with the `HTTPS_PROXY` environment variable. diff --git a/.changes/cli.js-rustup.md b/.changes/cli.js-rustup.md deleted file mode 100644 index a943c5134502..000000000000 --- a/.changes/cli.js-rustup.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Download `rustup` script on runtime instead of shipping it. diff --git a/.changes/cli.rs-active-toolchain.md b/.changes/cli.rs-active-toolchain.md deleted file mode 100644 index b39202bcdca3..000000000000 --- a/.changes/cli.rs-active-toolchain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Add `rustup` version and active rust toolchain to the `info` command output. diff --git a/.changes/cli.rs-build-tools-info.md b/.changes/cli.rs-build-tools-info.md deleted file mode 100644 index 6165773ca2fd..000000000000 --- a/.changes/cli.rs-build-tools-info.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Add `Visual Studio Build Tools` installed versions to the `info` command output. diff --git a/.changes/cli.rs-default-svelte-port.md b/.changes/cli.rs-default-svelte-port.md deleted file mode 100644 index 97ba9adb88f6..000000000000 --- a/.changes/cli.rs-default-svelte-port.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -The inferred development server port for Svelte is now `8080` (assumes latest Svelte with `sirv-cli >= 2.0.0`). diff --git a/.changes/cli.rs-detect-git.md b/.changes/cli.rs-detect-git.md deleted file mode 100644 index d6cde127f145..000000000000 --- a/.changes/cli.rs-detect-git.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Detect if tauri is used from git in the `info` command. diff --git a/.changes/cli.rs-dev-workspaces.md b/.changes/cli.rs-dev-workspaces.md deleted file mode 100644 index 0e84c549df3a..000000000000 --- a/.changes/cli.rs-dev-workspaces.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Watch workspace crates on `dev` command. diff --git a/.changes/cli.rs-dialoguer.md b/.changes/cli.rs-dialoguer.md deleted file mode 100644 index 073e45e828ab..000000000000 --- a/.changes/cli.rs-dialoguer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Drop the `dialoguer` soft fork and use the published version instead. diff --git a/.changes/cli.rs-features-arg.md b/.changes/cli.rs-features-arg.md deleted file mode 100644 index d078b6ba6ae7..000000000000 --- a/.changes/cli.rs-features-arg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Adds `features` argument to the `dev` and `build` commands. diff --git a/.changes/cli.rs-fix-cli.yml-assertion.md b/.changes/cli.rs-fix-cli.yml-assertion.md deleted file mode 100644 index e218de586bb1..000000000000 --- a/.changes/cli.rs-fix-cli.yml-assertion.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Update cli.yml to pass clap ArgSettings::MultipleValues assertion. diff --git a/.changes/cli.rs-fix-windows-x86.md b/.changes/cli.rs-fix-windows-x86.md deleted file mode 100644 index e4c4833aa1bd..000000000000 --- a/.changes/cli.rs-fix-windows-x86.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Fix `build` command when executed on a 32-bit Windows machine when pulling from the `binary-releases` repo. diff --git a/.changes/cli.rs-libwebkit2gtk-4.0-37.md b/.changes/cli.rs-libwebkit2gtk-4.0-37.md deleted file mode 100644 index 6892b1f7b1b9..000000000000 --- a/.changes/cli.rs-libwebkit2gtk-4.0-37.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Fixes the libwebkit2gtk package name. diff --git a/.changes/cli.rs-refactor-signer.md b/.changes/cli.rs-refactor-signer.md deleted file mode 100644 index fbafd3be5b1b..000000000000 --- a/.changes/cli.rs-refactor-signer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -The `generate` and `sign` commands are now available under a `signer` subcommand. diff --git a/.changes/cli.rs-release-arg.md b/.changes/cli.rs-release-arg.md deleted file mode 100644 index 9fa0060c3967..000000000000 --- a/.changes/cli.rs-release-arg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Adds `release` argument to the `dev` command. Allowing to run the backend in release mode during development. diff --git a/.changes/cli.rs-use-tauri-utils.md b/.changes/cli.rs-use-tauri-utils.md deleted file mode 100644 index d83665e22d8a..000000000000 --- a/.changes/cli.rs-use-tauri-utils.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Use `tauri-utils` to get the `Config` types. diff --git a/.changes/cli.rs-validate-dist-dir.md b/.changes/cli.rs-validate-dist-dir.md deleted file mode 100644 index cd00837b7834..000000000000 --- a/.changes/cli.rs-validate-dist-dir.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Print warning and exit if `distDir` contains `node_modules`, `src-tauri` or `target` folders. diff --git a/.changes/cli.rs-wix-banner-icon.md b/.changes/cli.rs-wix-banner-icon.md deleted file mode 100644 index 71bafa4d764b..000000000000 --- a/.changes/cli.rs-wix-banner-icon.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Added configuration for the WiX banner icon under `tauri.conf.json > tauri > bundle > windows > wix > bannerPath`. diff --git a/.changes/cli.rs-wix-dialog-image.md b/.changes/cli.rs-wix-dialog-image.md deleted file mode 100644 index b2622d924c54..000000000000 --- a/.changes/cli.rs-wix-dialog-image.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Added configuration for the WiX dialog background bitmap under `tauri.conf.json > tauri > bundle > windows > wix > dialogImagePath`. diff --git a/.changes/cli.rs-wix-license.md b/.changes/cli.rs-wix-license.md deleted file mode 100644 index 25b10f15f9c3..000000000000 --- a/.changes/cli.rs-wix-license.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Fix `tauri build` failing on Windows if `tauri.conf.json > tauri > bundle > Windows > wix > license` is used. diff --git a/.changes/clijs-node-version-20.md b/.changes/clijs-node-version-20.md new file mode 100644 index 000000000000..7a09bd17f9fe --- /dev/null +++ b/.changes/clijs-node-version-20.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/cli': 'patch:bug' +--- + +Fix nodejs binary regex when `0` is in the version name, for example `node-20` diff --git a/.changes/clipboard-api.md b/.changes/clipboard-api.md deleted file mode 100644 index 681ddf14fe36..000000000000 --- a/.changes/clipboard-api.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"api": patch -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `clipboard` APIs (write and read text). diff --git a/.changes/cmd-invoke-binding.md b/.changes/cmd-invoke-binding.md deleted file mode 100644 index 216b66138915..000000000000 --- a/.changes/cmd-invoke-binding.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-macros": patch ---- - -Fixes a name collision when the command function is named `invoke`. diff --git a/.changes/cmd-touch-bindings.md b/.changes/cmd-touch-bindings.md deleted file mode 100644 index 4872790badb5..000000000000 --- a/.changes/cmd-touch-bindings.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-macros": patch ---- - -Fixes a name collision when the command function is named `message` or `resolver`. diff --git a/.changes/codegen-mobile-devurl.md b/.changes/codegen-mobile-devurl.md new file mode 100644 index 000000000000..ba385474425b --- /dev/null +++ b/.changes/codegen-mobile-devurl.md @@ -0,0 +1,5 @@ +--- +"tauri-codegen": 'patch:enhance' +--- + +Change `devPath` URL to use the local IP address on iOS and Android. diff --git a/.changes/command-api-module.md b/.changes/command-api-module.md deleted file mode 100644 index dbb27d1091a3..000000000000 --- a/.changes/command-api-module.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Renamed the `command` API module to `process`. diff --git a/.changes/command-app-handle.md b/.changes/command-app-handle.md deleted file mode 100644 index c6ec27bedb11..000000000000 --- a/.changes/command-app-handle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Allow accessing an `AppHandle` instance on a command through dependency injection. diff --git a/.changes/command-feature-flag.md b/.changes/command-feature-flag.md deleted file mode 100644 index 7868da9f2051..000000000000 --- a/.changes/command-feature-flag.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `api::process::Command` APIs are now hidden behind the `command` feature flag. \ No newline at end of file diff --git a/.changes/command-generics.md b/.changes/command-generics.md deleted file mode 100644 index 0eeba6879725..000000000000 --- a/.changes/command-generics.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-macros": patch ---- - -`#[command]` now generates a macro instead of a function to allow passing through `Params` and other generics. -`generate_handler!` has been changed to consume the generated `#[command]` macro diff --git a/.changes/command-macros-binding-refactor.md b/.changes/command-macros-binding-refactor.md deleted file mode 100644 index 1d48c96b8288..000000000000 --- a/.changes/command-macros-binding-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-macros": patch ---- - -internal: Refactor all macro code that expects specific bindings to be passed Idents diff --git a/.changes/command-message-blocking.md b/.changes/command-message-blocking.md deleted file mode 100644 index 720060d53aef..000000000000 --- a/.changes/command-message-blocking.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes child processes messages not arriving until the subprocess is terminated. diff --git a/.changes/command-options.md b/.changes/command-options.md deleted file mode 100644 index 1405460d977b..000000000000 --- a/.changes/command-options.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": patch -"tauri": patch ---- - -Adds `options` argument to the shell command API (`env` and `cwd` configuration). diff --git a/.changes/command-return.md b/.changes/command-return.md deleted file mode 100644 index 21b918d7451f..000000000000 --- a/.changes/command-return.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-macros": patch ---- - -Improves support for commands returning `Result`. diff --git a/.changes/command-state.md b/.changes/command-state.md deleted file mode 100644 index 9ed0ae453c8c..000000000000 --- a/.changes/command-state.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-macros": patch ---- - -Adds support to command state, triggered when a command argument is `arg: State<'_, StateType>`. diff --git a/.changes/command-status-and-output.md b/.changes/command-status-and-output.md deleted file mode 100644 index 004de55596b2..000000000000 --- a/.changes/command-status-and-output.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `status` and `output` APIs to the `tauri::api::process::Command` struct. diff --git a/.changes/config-incognito.md b/.changes/config-incognito.md new file mode 100644 index 000000000000..1ac657deb1a0 --- /dev/null +++ b/.changes/config-incognito.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:feat' +--- + +Add `incognito` option to the window configuration object. diff --git a/.changes/config-mut-getter.md b/.changes/config-mut-getter.md deleted file mode 100644 index 93a24dd5a69c..000000000000 --- a/.changes/config-mut-getter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds a mutable `config` getter on the `Context` struct. diff --git a/.changes/config-package-info-getters.md b/.changes/config-package-info-getters.md deleted file mode 100644 index 26c1dc48e44f..000000000000 --- a/.changes/config-package-info-getters.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `config` and `package_info` getters to the `App` and `AppHandle` structs. diff --git a/.changes/config-require-literal_leading_dot.md b/.changes/config-require-literal_leading_dot.md new file mode 100644 index 000000000000..eddfd480dfa5 --- /dev/null +++ b/.changes/config-require-literal_leading_dot.md @@ -0,0 +1,5 @@ +--- +'tauri-utils': 'minor:feat' +--- + +Add option to configure `require_literal_leading_dot` on `fs` and `asset` protcol scopes. diff --git a/.changes/config-scope-url.md b/.changes/config-scope-url.md new file mode 100644 index 000000000000..d9ca875ef4bd --- /dev/null +++ b/.changes/config-scope-url.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:bug' +--- + +Fix parsing `allowlist > http > scope` urls that added a trailing slash which broke matching the incoming requests url. diff --git a/.changes/config.json b/.changes/config.json index e36973c60869..33ce3a189261 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -1,14 +1,22 @@ { "gitSiteUrl": "https://www.github.com/tauri-apps/tauri/", "timeout": 3600000, + "changeTags": { + "feat": "New Features", + "enhance": "Enhancements", + "bug": "Bug Fixes", + "pref": "Performance Improvements", + "changes": "What's Changed", + "deps": "Dependencies" + }, + "defaultChangeTag": "changes", "pkgManagers": { "rust": { - "errorOnVersionRange": "1.0.0-beta.100 - 99.x || ^1.1.0-0 || ^2.0.0-0", "version": true, - "getPublishedVersion": "cargo search ${ pkgFile.pkg.package.name } --limit 1 | sed -nE \"s/^[^\\\"]*\\\"//; s/\\\".*//1p\"", + "getPublishedVersion": "node ../../.scripts/covector/package-latest-version.js cargo ${ pkgFile.pkg.package.name } ${ pkgFile.pkg.package.version }", "prepublish": [ "sudo apt-get update", - "sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf", + "sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev", "cargo install cargo-audit --features=fix", { "command": "cargo generate-lockfile", @@ -17,12 +25,7 @@ "pipe": true }, { - "command": "echo \"# Cargo Audit\"", - "dryRunCommand": true, - "pipe": true - }, - { - "command": "echo '```'", + "command": "echo '
\n

Cargo Audit

\n\n```'", "dryRunCommand": true, "pipe": true }, @@ -33,7 +36,7 @@ "pipe": true }, { - "command": "echo '```'", + "command": "echo '```\n\n
\n'", "dryRunCommand": true, "pipe": true } @@ -41,26 +44,21 @@ "publish": [ "sleep 15s", { - "command": "cargo package --allow-dirty", + "command": "cargo package --no-verify", "dryRunCommand": true }, { - "command": "echo \"# Cargo Publish\"", - "dryRunCommand": true, - "pipe": true - }, - { - "command": "echo '```'", + "command": "echo '
\n

Cargo Publish

\n\n```'", "dryRunCommand": true, "pipe": true }, { - "command": "cargo publish --no-verify", - "dryRunCommand": "cargo publish --no-verify --dry-run --allow-dirty", + "command": "cargo publish", + "dryRunCommand": "cargo publish --dry-run", "pipe": true }, { - "command": "echo '```'", + "command": "echo '```\n\n
\n'", "dryRunCommand": true, "pipe": true } @@ -72,27 +70,21 @@ ], "assets": [ { - "path": "${ pkg.path }/${ pkg.pkg }-${ pkgFile.version }.crate", + "path": "${ pkg.path }/target/package/${ pkg.pkg }-${ pkgFile.version }.crate", "name": "${ pkg.pkg }-${ pkgFile.version }.crate" } ] }, "javascript": { - "errorOnVersionRange": "1.0.0-beta-rc.100 - 99.x || ^1.1.0-0 || ^2.0.0-0", "version": true, - "getPublishedVersion": "npm view ${ pkgFile.pkg.name } version", + "getPublishedVersion": "node ../../.scripts/covector/package-latest-version.js npm ${ pkgFile.pkg.name } ${ pkgFile.pkg.version }", "prepublish": [ { "command": "yarn", "dryRunCommand": true }, { - "command": "echo \"# Yarn Audit\n\"", - "dryRunCommand": true, - "pipe": true - }, - { - "command": "echo '
\nclick to view\n\n```'", + "command": "echo '
\n

Yarn Audit

\n\n```'", "dryRunCommand": true, "pipe": true }, @@ -115,22 +107,17 @@ "publish": [ "sleep 15s", { - "command": "echo \"# Yarn Package Publish\"", + "command": "echo '
\n

Yarn Publish

\n\n```'", "dryRunCommand": true, "pipe": true }, { - "command": "echo '```'", - "dryRunCommand": true, - "pipe": true - }, - { - "command": "yarn publish --access public --loglevel silly", + "command": "yarn publish --access public --loglevel silly --tag next", "dryRunCommand": "npm publish --dry-run --access public", "pipe": true }, { - "command": "echo '```'", + "command": "echo '```\n\n
\n'", "dryRunCommand": true, "pipe": true } @@ -143,13 +130,13 @@ } }, "packages": { - "api": { + "@tauri-apps/api": { "path": "./tooling/api", "manager": "javascript", "assets": [ { "path": "./tooling/api/dist/tauri-apps-api-${ pkgFile.version }.tgz", - "name": "api-${ pkgFile.version }.tgz" + "name": "tauri-apps-api-${ pkgFile.version }.tgz" } ], "prepublish": [ @@ -158,12 +145,7 @@ "dryRunCommand": true }, { - "command": "echo \"# Yarn Audit\n\"", - "dryRunCommand": true, - "pipe": true - }, - { - "command": "echo '
\nclick to view\n\n```'", + "command": "echo '
\n

Yarn Audit

\n\n```'", "dryRunCommand": true, "pipe": true }, @@ -185,12 +167,7 @@ ], "publish": [ { - "command": "echo \"# Yarn Package Publish\"", - "dryRunCommand": true, - "pipe": true - }, - { - "command": "echo '```'", + "command": "echo '
\n

Yarn Publish

\n\n```'", "dryRunCommand": true, "pipe": true }, @@ -200,32 +177,30 @@ "pipe": true }, { - "command": "echo '```'", + "command": "echo '```\n\n
\n'", "dryRunCommand": true, "pipe": true } ] }, + "tauri-utils": { + "path": "./core/tauri-utils", + "manager": "rust" + }, "tauri-bundler": { "path": "./tooling/bundler", "manager": "rust", "dependencies": ["tauri-utils"] }, - "tauri-utils": { - "path": "./core/tauri-utils", - "manager": "rust" - }, "tauri-runtime": { "path": "./core/tauri-runtime", "manager": "rust", - "dependencies": ["tauri-utils"], - "postversion": "node ../../.scripts/covector/sync-prerelease.js ${ pkg.pkg } ${ release.type }" + "dependencies": ["tauri-utils"] }, "tauri-runtime-wry": { "path": "./core/tauri-runtime-wry", "manager": "rust", - "dependencies": ["tauri-utils", "tauri-runtime"], - "postversion": "node ../../.scripts/covector/sync-prerelease.js ${ pkg.pkg } ${ release.type }" + "dependencies": ["tauri-utils", "tauri-runtime"] }, "tauri-codegen": { "path": "./core/tauri-codegen", @@ -235,13 +210,22 @@ "tauri-macros": { "path": "./core/tauri-macros", "manager": "rust", - "dependencies": ["tauri-codegen"] + "dependencies": ["tauri-codegen", "tauri-utils"] }, "tauri-build": { "path": "./core/tauri-build", "manager": "rust", "dependencies": ["tauri-codegen", "tauri-utils"], - "postversion": "node ../../.scripts/covector/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }" + "postversion": [ + "node ../../.scripts/covector/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }", + "cargo build --manifest-path ../tauri-config-schema/Cargo.toml" + ], + "assets": [ + { + "path": "./tooling/cli/schema.json", + "name": "schema.json" + } + ] }, "tauri": { "path": "./core/tauri", @@ -250,32 +234,42 @@ "tauri-macros", "tauri-utils", "tauri-runtime", - "tauri-runtime-wry" + "tauri-runtime-wry", + "tauri-build" ], "postversion": "node ../../.scripts/covector/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }" }, - "cli.js": { - "path": "./tooling/cli.rs/node", + "@tauri-apps/cli": { + "path": "./tooling/cli/node", "manager": "javascript", - "dependencies": ["cli.rs"], - "postversion": "node ../../.scripts/covector/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }", + "getPublishedVersion": "node ../../../.scripts/covector/package-latest-version.js npm ${ pkgFile.pkg.name } ${ pkgFile.pkg.version }", + "dependencies": ["tauri-cli"], + "postversion": [ + "node ../../../.scripts/covector/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }", + "cargo build --manifest-path ../../../core/tauri-config-schema/Cargo.toml" + ], "prepublish": [], "publish": [], "postpublish": [] }, - "cli.rs": { - "path": "./tooling/cli.rs", + "tauri-cli": { + "path": "./tooling/cli", "manager": "rust", - "dependencies": ["tauri-bundler", "tauri-utils"] - }, - "create-tauri-app": { - "path": "./tooling/create-tauri-app", - "manager": "javascript" + "dependencies": ["tauri-bundler", "tauri-utils"], + "postversion": [ + "cargo check", + "cargo build --manifest-path ../../core/tauri-config-schema/Cargo.toml" + ], + "assets": [ + { + "path": "${ pkg.path }/target/package/tauri-cli-${ pkgFile.version }.crate", + "name": "${ pkg.pkg }-${ pkgFile.version }.crate" + } + ] }, "tauri-driver": { "path": "./tooling/webdriver", - "manager": "rust", - "postversion": "node ../../.scripts/covector/sync-prerelease.js ${ pkg.pkg } ${ release.type }" + "manager": "rust" } } } diff --git a/.changes/consistent-event-name-usage.md b/.changes/consistent-event-name-usage.md deleted file mode 100644 index b76e25400d5c..000000000000 --- a/.changes/consistent-event-name-usage.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": minor ---- - -Change the `event` field of the `Event` interface to type `EventName` instead of `string`. \ No newline at end of file diff --git a/.changes/context-mutable-methods.md b/.changes/context-mutable-methods.md deleted file mode 100644 index 53a0d21328ed..000000000000 --- a/.changes/context-mutable-methods.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"tauri": patch ---- - -Expose mutable getters for the rest of the public `Context` getters. -* `pub fn assets_mut(&mut self) -> &mut Arc` -* `pub fn default_window_icon_mut(&mut self) -> &mut Option>` -* `pub fn system_tray_icon_mut(&mut self) -> &mut Option` -* `pub fn package_info_mut(&mut self) -> &mut tauri::api::PackageInfo` diff --git a/.changes/core-add-log-dir.md b/.changes/core-add-log-dir.md deleted file mode 100644 index d338b59f349b..000000000000 --- a/.changes/core-add-log-dir.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": minor ---- - -Add `tauri::api::path::log_dir` function to access the sugested log directory path. diff --git a/.changes/core-android-proxy-method.md b/.changes/core-android-proxy-method.md new file mode 100644 index 000000000000..8d81a2dfd874 --- /dev/null +++ b/.changes/core-android-proxy-method.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:enhance' +--- + +Use correct HTTP method when making requests to the proxied server on mobile. diff --git a/.changes/core-asset-fallback.md b/.changes/core-asset-fallback.md deleted file mode 100644 index 2a3da7765f53..000000000000 --- a/.changes/core-asset-fallback.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Assets will now fallback to `/index.html` before `/index.html`, allowing anchor links to work as expected. diff --git a/.changes/core-asset-protocol-streaming-crash.md b/.changes/core-asset-protocol-streaming-crash.md new file mode 100644 index 000000000000..44bcd09a31af --- /dev/null +++ b/.changes/core-asset-protocol-streaming-crash.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:enhance' +--- + +Enhance the `asset` protocol to support streaming of large files. diff --git a/.changes/core-center-window.md b/.changes/core-center-window.md deleted file mode 100644 index 4b9653be87fe..000000000000 --- a/.changes/core-center-window.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Fix `window.center` panic when window size is bigger than screen size. diff --git a/.changes/core-channel-clone.md b/.changes/core-channel-clone.md new file mode 100644 index 000000000000..d4446fc60790 --- /dev/null +++ b/.changes/core-channel-clone.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:enhance' +--- + +Implement `Clone` for `Channel` diff --git a/.changes/core-drag-region-resizable.md b/.changes/core-drag-region-resizable.md deleted file mode 100644 index 423b9a6896b9..000000000000 --- a/.changes/core-drag-region-resizable.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fix `data-tauri-drag-region` double-click, will now respect `resizable: false` and won't maximize. diff --git a/.changes/core-env.md b/.changes/core-env.md deleted file mode 100644 index 328d91833c75..000000000000 --- a/.changes/core-env.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `process`, `path` and `updater` APIs now takes a `tauri::Env` argument, used to force environment variables load on startup to prevent env var update attacks. diff --git a/.changes/core-features.md b/.changes/core-features.md deleted file mode 100644 index 7ce2353e4195..000000000000 --- a/.changes/core-features.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Properly keep all `tauri` features that are not managed by the CLI. diff --git a/.changes/core-incognito.md b/.changes/core-incognito.md new file mode 100644 index 000000000000..ebfdeb5314f3 --- /dev/null +++ b/.changes/core-incognito.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:feat' +--- + +Add `WindowBuilder::incognito` diff --git a/.changes/core-ipc-failed-navigation.md b/.changes/core-ipc-failed-navigation.md new file mode 100644 index 000000000000..c8609b79203b --- /dev/null +++ b/.changes/core-ipc-failed-navigation.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:bug' +--- + +Fix IPC failing after a failed navigation to an external URL. diff --git a/.changes/core-navigation-handler.md b/.changes/core-navigation-handler.md new file mode 100644 index 000000000000..64a6c0857123 --- /dev/null +++ b/.changes/core-navigation-handler.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:bug' +--- + +Fix `WindowBuilder::on_navigation` handler not registered properly. diff --git a/.changes/core-notification-typo.md b/.changes/core-notification-typo.md deleted file mode 100644 index 45a49dd81a1f..000000000000 --- a/.changes/core-notification-typo.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fix `Notification.requestPermission()` throwing `Unhandled Promise Rejection: TypeError: undefined is not a function (near '...window.__TAURI__.invoke...')` diff --git a/.changes/core-path-endpoint-path-doesnt-exist-error.md b/.changes/core-path-endpoint-path-doesnt-exist-error.md deleted file mode 100644 index 2a2d24bd0cc0..000000000000 --- a/.changes/core-path-endpoint-path-doesnt-exist-error.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"api": patch ---- - -Now `resolve()`, `join()` and `normalize()` from the `path` module, won't throw errors if the path doesn't exist, which matches NodeJS behavior. diff --git a/.changes/core-request-user-attention.md b/.changes/core-request-user-attention.md deleted file mode 100644 index 5fdc2a64b7ba..000000000000 --- a/.changes/core-request-user-attention.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `request_user_attention` API to the `Window` struct. diff --git a/.changes/core-show-hide-menu.md b/.changes/core-show-hide-menu.md deleted file mode 100644 index bd3c98775546..000000000000 --- a/.changes/core-show-hide-menu.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `show`, `hide`, `is_visible` and `toggle` APIs to the `MenuHandle`. diff --git a/.changes/core-updater-204-js-event.md b/.changes/core-updater-204-js-event.md new file mode 100644 index 000000000000..ce025b14ce05 --- /dev/null +++ b/.changes/core-updater-204-js-event.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:bug' +--- + +Emit `UPTODATE` update status to javascript when the updater server returns status code `204` diff --git a/.changes/core-webview-events.md b/.changes/core-webview-events.md deleted file mode 100644 index fd3e26a92ffc..000000000000 --- a/.changes/core-webview-events.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Fix blur/focus events being incorrect on Windows. diff --git a/.changes/core-window-config.md b/.changes/core-window-config.md new file mode 100644 index 000000000000..9f3ae01afbca --- /dev/null +++ b/.changes/core-window-config.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:enhance' +--- + +Fix some configurations not applied when creating the window through Javascript. diff --git a/.changes/core-windows-notification-sound.md b/.changes/core-windows-notification-sound.md new file mode 100644 index 000000000000..8c3499ffaf33 --- /dev/null +++ b/.changes/core-windows-notification-sound.md @@ -0,0 +1,5 @@ +--- +"tauri": "patch:enhance" +--- + +Play a sound when showing a notification on Windows. diff --git a/.changes/core-wry-0-28.md b/.changes/core-wry-0-28.md new file mode 100644 index 000000000000..00542fa265b3 --- /dev/null +++ b/.changes/core-wry-0-28.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:enhance' +--- + +On Android, update proguard rules. diff --git a/.changes/create-window-refactor.md b/.changes/create-window-refactor.md deleted file mode 100644 index 28102ada34f2..000000000000 --- a/.changes/create-window-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `create_window` API callback now takes two arguments: the `WindowBuilder` and the `WebviewAttributes` and must return a tuple containing both values. diff --git a/.changes/create-window-return-window.md b/.changes/create-window-return-window.md deleted file mode 100644 index 2f0ba66f615e..000000000000 --- a/.changes/create-window-return-window.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": minor ---- - -**Breaking change:** Return `Window` on `App` and `AppHandle`'s `create_window` function. diff --git a/.changes/csp-nonces.md b/.changes/csp-nonces.md deleted file mode 100644 index 4762aeeea201..000000000000 --- a/.changes/csp-nonces.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-codegen": patch -"tauri-utils": patch ---- - -Apply `nonce` to `script` and `style` tags and set them on the `CSP` (`script-src` and `style-src` fetch directives). diff --git a/.changes/csp-self.md b/.changes/csp-self.md deleted file mode 100644 index cba1c072a034..000000000000 --- a/.changes/csp-self.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Add `'self'` to default CSP because otherwise no joy on macOS. diff --git a/.changes/csp.md b/.changes/csp.md deleted file mode 100644 index ec79a527dd74..000000000000 --- a/.changes/csp.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri-codegen": patch -"tauri-utils": patch -"tauri": patch ---- - -Reintroduce `csp` injection, configured on `tauri.conf.json > tauri > security > csp`. diff --git a/.changes/cta-add-empty-description-for-rust-dominator.md b/.changes/cta-add-empty-description-for-rust-dominator.md deleted file mode 100644 index 8de51d27359e..000000000000 --- a/.changes/cta-add-empty-description-for-rust-dominator.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Add empty description to Cargo.toml in dominator recipe. \ No newline at end of file diff --git a/.changes/cta-added-angular-cli.md b/.changes/cta-added-angular-cli.md deleted file mode 100644 index e4787aa4341a..000000000000 --- a/.changes/cta-added-angular-cli.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Added Angular CLI recipe. \ No newline at end of file diff --git a/.changes/cta-ci-compatible.md b/.changes/cta-ci-compatible.md deleted file mode 100644 index 71c4e8dcb85f..000000000000 --- a/.changes/cta-ci-compatible.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -`create-tauri-app` should now be fully compatiable with CI environments. diff --git a/.changes/cta-explicitly-install-vite.md b/.changes/cta-explicitly-install-vite.md deleted file mode 100644 index 722f6951fb01..000000000000 --- a/.changes/cta-explicitly-install-vite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Explicitly install deps after a vite recipe. diff --git a/.changes/cta-pnpm-support.md b/.changes/cta-pnpm-support.md deleted file mode 100644 index 287192e868a4..000000000000 --- a/.changes/cta-pnpm-support.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -[`pnpm`](https://pnpm.io) package manager is now officially supported, either run `pnpx create-tauri-app` or explicitly specifiy it `npx create-tauri-app --manager pnpm`. diff --git a/.changes/cta-prompt-api.md b/.changes/cta-prompt-api.md deleted file mode 100644 index 9d1d44bb72fc..000000000000 --- a/.changes/cta-prompt-api.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -`create-tauri-app` will prompt users to install `@tauri-apps/api` npm package. diff --git a/.changes/cta-react-disable-open-browser.md b/.changes/cta-react-disable-open-browser.md deleted file mode 100644 index 9103cf6fa25a..000000000000 --- a/.changes/cta-react-disable-open-browser.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Stop react recipe from opening in browser by default. diff --git a/.changes/cta-shift-and-type.md b/.changes/cta-shift-and-type.md deleted file mode 100644 index c9ca82e8d458..000000000000 --- a/.changes/cta-shift-and-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Shift everything out of the `bin` and into `.ts` so we can apply Typescript types. diff --git a/.changes/cta-solid-recipe.md b/.changes/cta-solid-recipe.md deleted file mode 100644 index 6dccde7d79c9..000000000000 --- a/.changes/cta-solid-recipe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'create-tauri-app': patch ---- - -Add SolidJS recipe using the official template. diff --git a/.changes/cta-svelte-recipe.md b/.changes/cta-svelte-recipe.md deleted file mode 100644 index f0fd464c92cb..000000000000 --- a/.changes/cta-svelte-recipe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Add Svelte recipe using the official template. \ No newline at end of file diff --git a/.changes/cta-test-script.md b/.changes/cta-test-script.md deleted file mode 100644 index 6d952cc5f024..000000000000 --- a/.changes/cta-test-script.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"create-tauri-app": patch -"cli.js": patch ---- - -Adjust check for `dev` mode and switch CTA test to a script runner. The script gives us more control and better output into any failures. diff --git a/.changes/cta-testing-suite.md b/.changes/cta-testing-suite.md deleted file mode 100644 index 7f77533b3a05..000000000000 --- a/.changes/cta-testing-suite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -We setup an e2e type test suite for CTA. It is mostly an internal change, but should help with stability moving forward. diff --git a/.changes/cta-update-vite-package.md b/.changes/cta-update-vite-package.md deleted file mode 100644 index 05597ba317e8..000000000000 --- a/.changes/cta-update-vite-package.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Update vite recipe to use the new vite npm [package](https://github.com/vitejs/vite/tree/main/packages/create-vite). diff --git a/.changes/cta-vite-before-dev.md b/.changes/cta-vite-before-dev.md deleted file mode 100644 index cf3d317daabd..000000000000 --- a/.changes/cta-vite-before-dev.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Fixes the `beforeDevCommand` on vite recipe. diff --git a/.changes/cta-vite-esbuild-install-direct.md b/.changes/cta-vite-esbuild-install-direct.md deleted file mode 100644 index 01c9dd5923ab..000000000000 --- a/.changes/cta-vite-esbuild-install-direct.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Work around bugs between esbuild and npm by installing directly at the end of the sequence. Also default to using the latest on all of the installs instead of npx's cache. diff --git a/.changes/cta-vite-templates.md b/.changes/cta-vite-templates.md deleted file mode 100644 index 955a162a1902..000000000000 --- a/.changes/cta-vite-templates.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Add support for all vite templates diff --git a/.changes/cta-welcome-prompt-and-links.md b/.changes/cta-welcome-prompt-and-links.md deleted file mode 100644 index 4b36854b8eef..000000000000 --- a/.changes/cta-welcome-prompt-and-links.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-tauri-app": patch ---- - -Add a welcome prompt to let the user know about the process and links to more info including prerequisite setup steps. Also add links to each of the templates to give the user more context what they are getting into. diff --git a/.changes/current-binary-caching.md b/.changes/current-binary-caching.md deleted file mode 100644 index c404d6ac9fc5..000000000000 --- a/.changes/current-binary-caching.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -tauri: patch -tauri-utils: patch ---- - -The path returned from `tauri::api::process::current_binary` is now cached when loading the binary. diff --git a/.changes/custom-invoke-system.md b/.changes/custom-invoke-system.md deleted file mode 100644 index 66396453907d..000000000000 --- a/.changes/custom-invoke-system.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Added an API to use a custom invoke system to receive and respond to commands (`Builder#invoke_system`). diff --git a/.changes/data-url-csp.md b/.changes/data-url-csp.md deleted file mode 100644 index 461446cd39e3..000000000000 --- a/.changes/data-url-csp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Inject configured `CSP` on `data:` URLs. diff --git a/.changes/dblclick-tauri-drag-maximize.md b/.changes/dblclick-tauri-drag-maximize.md deleted file mode 100644 index 785f18d426d1..000000000000 --- a/.changes/dblclick-tauri-drag-maximize.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -"tauri": patch ---- -Double clicking a `data-tauri-drag-region` element will toggle the window maximized state. diff --git a/.changes/deb-custom-desktop-file-config.md b/.changes/deb-custom-desktop-file-config.md new file mode 100644 index 000000000000..a9505970fe15 --- /dev/null +++ b/.changes/deb-custom-desktop-file-config.md @@ -0,0 +1,7 @@ +--- +"tauri-utils": 'patch:enhance' +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Added the `desktop_template` option on `tauri.conf.json > tauri > bundle > deb`. diff --git a/.changes/deb-custom-desktop-file.md b/.changes/deb-custom-desktop-file.md new file mode 100644 index 000000000000..ba66f4a20f24 --- /dev/null +++ b/.changes/deb-custom-desktop-file.md @@ -0,0 +1,5 @@ +--- +"tauri-bundler": "minor:feat" +--- + +Added `desktop_template` option on `DebianSettings`. diff --git a/.changes/debian-depends.md b/.changes/debian-depends.md deleted file mode 100644 index 342b81e5f836..000000000000 --- a/.changes/debian-depends.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Automatically add Tauri dependencies to the debian package `Depends` section. diff --git a/.changes/default-params-type.md b/.changes/default-params-type.md deleted file mode 100644 index e574da383296..000000000000 --- a/.changes/default-params-type.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -Adds the default types used with `Builder::default()` to items that expose `Params` in their type. This allows you to -skip specifying a generic parameter to types like `Window

` if you use the default type. diff --git a/.changes/default-tls-features.md b/.changes/default-tls-features.md new file mode 100644 index 000000000000..5ad707dfe977 --- /dev/null +++ b/.changes/default-tls-features.md @@ -0,0 +1,5 @@ +--- +"tauri": major:feat +--- + +Added the `default-tls` and `reqwest-default-tls` Cargo features for enabling TLS suppport to connect over HTTPS. diff --git a/.changes/default-window-icon.md b/.changes/default-window-icon.md new file mode 100644 index 000000000000..b1d13172ecdf --- /dev/null +++ b/.changes/default-window-icon.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Add `default_window_icon` getter on `App` and `AppHandle`. diff --git a/.changes/dev-copy-resources.md b/.changes/dev-copy-resources.md deleted file mode 100644 index 461c095c7a98..000000000000 --- a/.changes/dev-copy-resources.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Copy resources and binaries to `OUT_DIR` on `tauri dev` command. diff --git a/.changes/dev-csp.md b/.changes/dev-csp.md deleted file mode 100644 index ce9f40968998..000000000000 --- a/.changes/dev-csp.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-utils": patch -"cli.rs": patch ---- - -Added `dev_csp` to the `security` configuration object. diff --git a/.changes/dev-path-dist-dir-array.md b/.changes/dev-path-dist-dir-array.md deleted file mode 100644 index 1ad11730a008..000000000000 --- a/.changes/dev-path-dist-dir-array.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-codegen": patch -"tauri-utils": patch ---- - -Allow `dev_path` and `dist_dir` to be an array of root files and directories to embed. diff --git a/.changes/dev-path-dist-dir-validation.md b/.changes/dev-path-dist-dir-validation.md deleted file mode 100644 index 648ec4b65021..000000000000 --- a/.changes/dev-path-dist-dir-validation.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-utils": patch -"tauri-codegen": patch ---- - -Validate `tauri.conf.json > build > devPath` and `tauri.conf.json > build > distDir` values. diff --git a/.changes/dev-proxy-response-cache.md b/.changes/dev-proxy-response-cache.md new file mode 100644 index 000000000000..53f6623162de --- /dev/null +++ b/.changes/dev-proxy-response-cache.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Implement response cache on the dev server proxy, used when the server responds with status 304. diff --git a/.changes/dev-proxy.md b/.changes/dev-proxy.md new file mode 100644 index 000000000000..86d86fea489e --- /dev/null +++ b/.changes/dev-proxy.md @@ -0,0 +1,5 @@ +--- +"tauri": major:feat +--- + +**Breaking change:** Use the custom protocol as a proxy to the development server on all platforms except Linux. diff --git a/.changes/dev-server-exit.md b/.changes/dev-server-exit.md deleted file mode 100644 index 6e92eaea9263..000000000000 --- a/.changes/dev-server-exit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Kill process if `beforeDevCommand` exits with a non-zero status code. diff --git a/.changes/dialog-ask-message-parent.md b/.changes/dialog-ask-message-parent.md deleted file mode 100644 index 2e02eb4f1571..000000000000 --- a/.changes/dialog-ask-message-parent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change:** Added `window_parent: Option<&Window>` as first argument to the `ask` and `message` APIs on the `tauri::api::dialog` module. diff --git a/.changes/dialog-parent.md b/.changes/dialog-parent.md deleted file mode 100644 index 78117dc24db6..000000000000 --- a/.changes/dialog-parent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Set the Tauri window as parent for dialogs. diff --git a/.changes/dialog-thread-handling.md b/.changes/dialog-thread-handling.md deleted file mode 100644 index f3727360b888..000000000000 --- a/.changes/dialog-thread-handling.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -Allow the `tauri::api::dialog` APIs to be executed on any secondary thread. -**Breaking change:** All dialog APIs now takes a closure instead of returning the response on the function call. diff --git a/.changes/disable-window-controls-api-options.md b/.changes/disable-window-controls-api-options.md new file mode 100644 index 000000000000..4a129f0043e3 --- /dev/null +++ b/.changes/disable-window-controls-api-options.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'minor:feat' +--- + +Added the `maximizable`, `minimizable` and `closable` fields on `WindowOptions`. diff --git a/.changes/disable-window-controls-api.md b/.changes/disable-window-controls-api.md new file mode 100644 index 000000000000..7485a1cfc350 --- /dev/null +++ b/.changes/disable-window-controls-api.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'minor:feat' +--- + +Added the `setMaximizable`, `setMinimizable`, `setClosable`, `isMaximizable`, `isMinimizable` and `isClosable` methods. diff --git a/.changes/disable-window-controls-config.md b/.changes/disable-window-controls-config.md new file mode 100644 index 000000000000..11827a66d70a --- /dev/null +++ b/.changes/disable-window-controls-config.md @@ -0,0 +1,5 @@ +--- +'tauri-utils': 'minor:feat' +--- + +Added the `maximizable`, `minimizable` and `closable` options to the window configuration. diff --git a/.changes/disable-window-controls-runtime-builder.md b/.changes/disable-window-controls-runtime-builder.md new file mode 100644 index 000000000000..9112b4b04fc1 --- /dev/null +++ b/.changes/disable-window-controls-runtime-builder.md @@ -0,0 +1,6 @@ +--- +'tauri-runtime-wry': 'minor:feat' +'tauri-runtime': 'minor:feat' +--- + +Added the `maximizable`, `minimizable` and `closable` methods to `WindowBuilder`. diff --git a/.changes/disable-window-controls-runtime-window.md b/.changes/disable-window-controls-runtime-window.md new file mode 100644 index 000000000000..f4da492df95a --- /dev/null +++ b/.changes/disable-window-controls-runtime-window.md @@ -0,0 +1,6 @@ +--- +'tauri-runtime-wry': 'minor:feat' +'tauri-runtime': 'minor:feat' +--- + +Added `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods to the `Dispatch` trait. diff --git a/.changes/disable-window-controls-window-builder.md b/.changes/disable-window-controls-window-builder.md new file mode 100644 index 000000000000..d568786f9ffb --- /dev/null +++ b/.changes/disable-window-controls-window-builder.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Added the `maximizable`, `minimizable` and `closable` options to the window builder. diff --git a/.changes/disable-window-controls-window.md b/.changes/disable-window-controls-window.md new file mode 100644 index 000000000000..5390d81e842b --- /dev/null +++ b/.changes/disable-window-controls-window.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Added the `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods on `Window`. diff --git a/.changes/dmg-bundle-fixes.md b/.changes/dmg-bundle-fixes.md deleted file mode 100644 index 43b908237b58..000000000000 --- a/.changes/dmg-bundle-fixes.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Pull latest changes from `create-dmg`, fixing unmount issue. diff --git a/.changes/doc-tauri-utils.md b/.changes/doc-tauri-utils.md deleted file mode 100644 index 73d4c438c114..000000000000 --- a/.changes/doc-tauri-utils.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": minor ---- - -Move items which `tauri::api` re-exports from `tauri-utils` to individual module `utils`. Because these items has their -own Error/Result types which are not related to api module at all. - diff --git a/.changes/downgrade-min-sdk-version.md b/.changes/downgrade-min-sdk-version.md new file mode 100644 index 000000000000..98b033b1fe7c --- /dev/null +++ b/.changes/downgrade-min-sdk-version.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Change minimum Android SDK version to 21 for the plugin library. diff --git a/.changes/drag-and-drop-config.md b/.changes/drag-and-drop-config.md deleted file mode 100644 index 572893fef739..000000000000 --- a/.changes/drag-and-drop-config.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-utils": patch ---- - -Adds `file_drop_enabled` flag on `WindowConfig`. diff --git a/.changes/drag-region-refactor.md b/.changes/drag-region-refactor.md deleted file mode 100644 index a8a1027f88df..000000000000 --- a/.changes/drag-region-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Change draggable region element detection from `drag-region` class to `data-tauri-drag-region` attribute. diff --git a/.changes/drag-window-api.md b/.changes/drag-window-api.md deleted file mode 100644 index 1e0d5ad39c81..000000000000 --- a/.changes/drag-window-api.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Adds `startDragging` API on the window module. diff --git a/.changes/dynamic-plugin.md b/.changes/dynamic-plugin.md deleted file mode 100644 index f93c708633fe..000000000000 --- a/.changes/dynamic-plugin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Allow registering a plugin through an `AppHandle` instance using the `plugin` method. diff --git a/.changes/dynamic-wry-plugin.md b/.changes/dynamic-wry-plugin.md new file mode 100644 index 000000000000..cdcf95ed4b81 --- /dev/null +++ b/.changes/dynamic-wry-plugin.md @@ -0,0 +1,6 @@ +--- +"tauri-runtime-wry": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Allow a wry plugin to be registered at runtime. diff --git a/.changes/early-panic-for-png-not-rgba.md b/.changes/early-panic-for-png-not-rgba.md new file mode 100644 index 000000000000..ace15393da86 --- /dev/null +++ b/.changes/early-panic-for-png-not-rgba.md @@ -0,0 +1,5 @@ +--- +"tauri-codegen": 'patch:enhance' +--- + +Early panic if the PNG icon is not RGBA. diff --git a/.changes/embed-plist.md b/.changes/embed-plist.md deleted file mode 100644 index 4939235333b6..000000000000 --- a/.changes/embed-plist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-codegen": patch -"tauri": patch ---- - -Embed Info.plist file contents on binary on dev. diff --git a/.changes/emit-and-trigger-tauri-events.md b/.changes/emit-and-trigger-tauri-events.md deleted file mode 100644 index 1e925fe0311a..000000000000 --- a/.changes/emit-and-trigger-tauri-events.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Emit `tauri://*` events to Rust listeners. diff --git a/.changes/emit-window-created-backend.md b/.changes/emit-window-created-backend.md deleted file mode 100644 index f03e855d8bd7..000000000000 --- a/.changes/emit-window-created-backend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Emit `tauri://window-created` event for windows created on the backend. diff --git a/.changes/emit-window-events.md b/.changes/emit-window-events.md deleted file mode 100644 index 948c4b81e10d..000000000000 --- a/.changes/emit-window-events.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Emit `tauri://resize`, `tauri://move`, `tauri://close-requested`, `tauri://destroyed`, `tauri://focus`, `tauri://blur` and `tauri://scale-change` events to the window. diff --git a/.changes/enable-linux-cookie-persistence.md b/.changes/enable-linux-cookie-persistence.md deleted file mode 100644 index 0a51cdc1108c..000000000000 --- a/.changes/enable-linux-cookie-persistence.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": "patch" -"tauri-runtime-wry": patch ---- - -Enable non-session cookie persistence on Linux. diff --git a/.changes/enable-minify.md b/.changes/enable-minify.md new file mode 100644 index 000000000000..0535a886aa59 --- /dev/null +++ b/.changes/enable-minify.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Change the Android template to enable minification on release and pull ProGuard rules from proguard-tauri.pro. diff --git a/.changes/enable-path-commands.md b/.changes/enable-path-commands.md new file mode 100644 index 000000000000..522441c61e48 --- /dev/null +++ b/.changes/enable-path-commands.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Fixes path commands not being added. diff --git a/.changes/enhance-jsobject-return-types.md b/.changes/enhance-jsobject-return-types.md new file mode 100644 index 000000000000..d70bdbdb7f76 --- /dev/null +++ b/.changes/enhance-jsobject-return-types.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Enhance Android's `JSObject` return types. diff --git a/.changes/error-on-identifier-change.md b/.changes/error-on-identifier-change.md new file mode 100644 index 000000000000..9229a370b4bf --- /dev/null +++ b/.changes/error-on-identifier-change.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Print an error if the Android project was generated with an older bundle identifier or package name. diff --git a/.changes/event-api-window-label.md b/.changes/event-api-window-label.md new file mode 100644 index 000000000000..d84aa7b3e939 --- /dev/null +++ b/.changes/event-api-window-label.md @@ -0,0 +1,5 @@ +--- +"@tauri-apps/api": 'patch:enhance' +--- + +Expose the window target option on event APIs. diff --git a/.changes/event-refactor.md b/.changes/event-refactor.md deleted file mode 100644 index c950f5114e6d..000000000000 --- a/.changes/event-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The event `emit` function payload type is now `impl Serialize` instead of `Option`. diff --git a/.changes/exit-requested-event.md b/.changes/exit-requested-event.md deleted file mode 100644 index c8cea2d5e82e..000000000000 --- a/.changes/exit-requested-event.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Add `ExitRequested` event that allows preventing the app from exiting when all windows are closed, and an `AppHandle.exit()` function to exit the app manually. diff --git a/.changes/expose-escape-json-string.md b/.changes/expose-escape-json-string.md deleted file mode 100644 index 792d69a8ff45..000000000000 --- a/.changes/expose-escape-json-string.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Expose `tauri::api::ipc::{serialize_js_with, serialize_js}` functions. diff --git a/.changes/feat-shell-completions.md b/.changes/feat-shell-completions.md new file mode 100644 index 000000000000..b94758d67170 --- /dev/null +++ b/.changes/feat-shell-completions.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'minor:feat' +'@tauri-apps/cli': 'minor:feat' +--- + +Added `tauri completions` to generate shell completions scripts. diff --git a/.changes/features-support.md b/.changes/features-support.md deleted file mode 100644 index c891bb3d05ea..000000000000 --- a/.changes/features-support.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Read cargo features from `tauri.conf.json > build > features` and propagate them on `dev` and `build`. diff --git a/.changes/fix-asset-loading-vue-router.md b/.changes/fix-asset-loading-vue-router.md deleted file mode 100644 index c59c745d2457..000000000000 --- a/.changes/fix-asset-loading-vue-router.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fallback to `index.html` on asset loading so router with history mode works. diff --git a/.changes/fix-asset-loading.md b/.changes/fix-asset-loading.md deleted file mode 100644 index ff5a486aca9d..000000000000 --- a/.changes/fix-asset-loading.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes custom protocol asset loader not decoding the percent-encoded path. diff --git a/.changes/fix-asset-protocol-panicking.md b/.changes/fix-asset-protocol-panicking.md deleted file mode 100644 index 332a9861ed5e..000000000000 --- a/.changes/fix-asset-protocol-panicking.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Resolve `asset` protocol HTTP request instead of panicking if the file does not exist or cannot be read. diff --git a/.changes/fix-attribute-drag-region.md b/.changes/fix-attribute-drag-region.md deleted file mode 100644 index 50f840b8a43c..000000000000 --- a/.changes/fix-attribute-drag-region.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -As some frameworks automatically add "true" as the value of the attribute, we need to check if it exists instead. diff --git a/.changes/fix-before-dev-command-kill.md b/.changes/fix-before-dev-command-kill.md deleted file mode 100644 index bd1b8ce4f4ff..000000000000 --- a/.changes/fix-before-dev-command-kill.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Properly kill `beforeDevCommand` process. diff --git a/.changes/fix-block-on-runtime.md b/.changes/fix-block-on-runtime.md deleted file mode 100644 index 44ef3ba2d7a3..000000000000 --- a/.changes/fix-block-on-runtime.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Avoid `async_runtime::block_on` panics when used along another tokio runtime. diff --git a/.changes/fix-build-script-mobile-runner-npm.md b/.changes/fix-build-script-mobile-runner-npm.md new file mode 100644 index 000000000000..db7700583e15 --- /dev/null +++ b/.changes/fix-build-script-mobile-runner-npm.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fixes the generated mobile build script when using an NPM runner. diff --git a/.changes/fix-bundle-targets-config.md b/.changes/fix-bundle-targets-config.md deleted file mode 100644 index 1db217ff5907..000000000000 --- a/.changes/fix-bundle-targets-config.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Fixes `tauri.conf.json > tauri > bundle > targets` not applying to the bundler. diff --git a/.changes/fix-bundler-platform-detection.md b/.changes/fix-bundler-platform-detection.md deleted file mode 100644 index 3bd6dc577bad..000000000000 --- a/.changes/fix-bundler-platform-detection.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"cli.rs": patch -"tauri-bundler": patch ---- - -Properly detect target platform's architecture. diff --git a/.changes/fix-cli-target-set.md b/.changes/fix-cli-target-set.md deleted file mode 100644 index 21d9de851d38..000000000000 --- a/.changes/fix-cli-target-set.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Fixes `build` command when the `target` arg is set. diff --git a/.changes/fix-cli.rs-bundle-arg.md b/.changes/fix-cli.rs-bundle-arg.md deleted file mode 100644 index b61cc1c12caf..000000000000 --- a/.changes/fix-cli.rs-bundle-arg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": minor ---- - -Fix a typo that would result in bundle arg being ignored. diff --git a/.changes/fix-close-requested-js-event.md b/.changes/fix-close-requested-js-event.md deleted file mode 100644 index cfc1519a9a51..000000000000 --- a/.changes/fix-close-requested-js-event.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Prevent window closing if `tauri://close-requested` is listened on the JS layer. Users must call `appWindow.close()` manually when listening to that event. diff --git a/.changes/fix-command-event-channel.md b/.changes/fix-command-event-channel.md deleted file mode 100644 index 54710cd568a3..000000000000 --- a/.changes/fix-command-event-channel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `api::process::Command` events not firing consistently. diff --git a/.changes/fix-command-named-cmd.md b/.changes/fix-command-named-cmd.md deleted file mode 100644 index 59e50df90113..000000000000 --- a/.changes/fix-command-named-cmd.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-macros": patch ---- - -Fixes a name collision when the command function is named `cmd`. diff --git a/.changes/fix-create-window-str.md b/.changes/fix-create-window-str.md deleted file mode 100644 index f5dbf5946c3d..000000000000 --- a/.changes/fix-create-window-str.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Change `App.create_window()` and `AppHandle.create_window()` to accept an `Into` type instead of `String`. diff --git a/.changes/fix-cursor-icon-zoomin.md b/.changes/fix-cursor-icon-zoomin.md new file mode 100644 index 000000000000..f604d3b27ea6 --- /dev/null +++ b/.changes/fix-cursor-icon-zoomin.md @@ -0,0 +1,5 @@ +--- +'tauri-runtime': 'patch:bug' +--- + +Fixes typo in `CursorIcon` deserialization of the `ZoomIn` variant. diff --git a/.changes/fix-custom-protocol.md b/.changes/fix-custom-protocol.md deleted file mode 100644 index 2d31f7f2503c..000000000000 --- a/.changes/fix-custom-protocol.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `asset` protocol crashing application. diff --git a/.changes/fix-data-tauri-drag-region.md b/.changes/fix-data-tauri-drag-region.md deleted file mode 100644 index 944bd5561277..000000000000 --- a/.changes/fix-data-tauri-drag-region.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `data-tauri-drag-region` not firing its events. diff --git a/.changes/fix-deadlock-create-window-from-menu.md b/.changes/fix-deadlock-create-window-from-menu.md deleted file mode 100644 index 6ac2fb83515b..000000000000 --- a/.changes/fix-deadlock-create-window-from-menu.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri-runtime-wry": patch -"tauri": patch ---- - -Fixes a deadlock when creating a window from a menu event handler. - \ No newline at end of file diff --git a/.changes/fix-deadlock-path-iter.md b/.changes/fix-deadlock-path-iter.md deleted file mode 100644 index 3aa9c8a92fa1..000000000000 --- a/.changes/fix-deadlock-path-iter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Fixes a deadlock on the `ResourcePaths` iterator. diff --git a/.changes/fix-dev-server-proxy-path.md b/.changes/fix-dev-server-proxy-path.md new file mode 100644 index 000000000000..eb8fd9f26e47 --- /dev/null +++ b/.changes/fix-dev-server-proxy-path.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Properly proxy dev server requests with query strings and fragments. diff --git a/.changes/fix-dialog-allowlist..md b/.changes/fix-dialog-allowlist..md deleted file mode 100644 index 46cd20bf3514..000000000000 --- a/.changes/fix-dialog-allowlist..md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes build without the dialog Cargo features. diff --git a/.changes/fix-dialog-default-path.md b/.changes/fix-dialog-default-path.md deleted file mode 100644 index 1aefe3f2c320..000000000000 --- a/.changes/fix-dialog-default-path.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"api": patch ---- - -Fixes the dialog `defaultPath` usage on Linux. diff --git a/.changes/fix-dialog-save-default-path-linux.md b/.changes/fix-dialog-save-default-path-linux.md deleted file mode 100644 index 376aae0971cd..000000000000 --- a/.changes/fix-dialog-save-default-path-linux.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `defaultPath` option on dialog API not setting the file name if it doesn't exist on Linux. diff --git a/.changes/fix-dmg-volume-icon.md b/.changes/fix-dmg-volume-icon.md deleted file mode 100644 index aae9bb286ef1..000000000000 --- a/.changes/fix-dmg-volume-icon.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Fixes DMG volume icon. diff --git a/.changes/fix-drag-and-drop.md b/.changes/fix-drag-and-drop.md deleted file mode 100644 index 6468b1918a01..000000000000 --- a/.changes/fix-drag-and-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Allow disabling the webview file drop handler (required to use drag and drop on the frontend on Windows) using the `tauri.conf.json > tauri > windows > fileDropEnabled` flag or the `WebviewAttributes#disable_file_drop_handler` method. diff --git a/.changes/fix-empty-identifier.md b/.changes/fix-empty-identifier.md new file mode 100644 index 000000000000..e929844ef52a --- /dev/null +++ b/.changes/fix-empty-identifier.md @@ -0,0 +1,7 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +"tauri-macros": 'patch:enhance' +--- + +Resolve Android package name from single word bundle identifiers. diff --git a/.changes/fix-esm-detection.md b/.changes/fix-esm-detection.md deleted file mode 100644 index 9bd6c327f3a4..000000000000 --- a/.changes/fix-esm-detection.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-codegen": patch ---- - -Fix ES Module detection for default imports with relative paths or scoped packages and exporting of async functions. diff --git a/.changes/fix-event-queue.md b/.changes/fix-event-queue.md deleted file mode 100644 index 9ac753a29c5c..000000000000 --- a/.changes/fix-event-queue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fix `listen` calls receiving past events. diff --git a/.changes/fix-feature-removal.md b/.changes/fix-feature-removal.md new file mode 100644 index 000000000000..8cc8be0a3e11 --- /dev/null +++ b/.changes/fix-feature-removal.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:bug' +'@tauri-apps/cli': 'patch:bug' +--- + +Fixes Cargo.toml feature rewriting. diff --git a/.changes/fix-file-drop-events.md b/.changes/fix-file-drop-events.md deleted file mode 100644 index 0ece0fa1e422..000000000000 --- a/.changes/fix-file-drop-events.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"api": patch ---- - -Fixes file drop events being swapped (`file-drop-hover` on drop and `file-drop` on hover). \ No newline at end of file diff --git a/.changes/fix-focus-blur-events-wry.md b/.changes/fix-focus-blur-events-wry.md deleted file mode 100644 index a853b593cef4..000000000000 --- a/.changes/fix-focus-blur-events-wry.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch -"tauri": patch ---- - -Fixes `WindowEvent::Focus` and `WindowEvent::Blur` events not firing. diff --git a/.changes/fix-focus-blur-events.md b/.changes/fix-focus-blur-events.md deleted file mode 100644 index 1d14f5c961d4..000000000000 --- a/.changes/fix-focus-blur-events.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `tauri://focus` and `tauri://blur` events not firing. diff --git a/.changes/fix-global-tauri.md b/.changes/fix-global-tauri.md deleted file mode 100644 index a48b59744eaa..000000000000 --- a/.changes/fix-global-tauri.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Fixes the global bundle UMD code. diff --git a/.changes/fix-http-binary-response.md b/.changes/fix-http-binary-response.md deleted file mode 100644 index 8339bc1f20e4..000000000000 --- a/.changes/fix-http-binary-response.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes the HTTP API binary response serialization. diff --git a/.changes/fix-http-resolve-error.md b/.changes/fix-http-resolve-error.md deleted file mode 100644 index 964a33723035..000000000000 --- a/.changes/fix-http-resolve-error.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"api": patch ---- - -The `http` APIs now resolve the returned promise when the API call finishes with an error status code. diff --git a/.changes/fix-inner-size.md b/.changes/fix-inner-size.md deleted file mode 100644 index 989445b6c8ed..000000000000 --- a/.changes/fix-inner-size.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime-wry": patch ---- - -Use webview's inner_size instead of window's value to get the correct size on macOS. diff --git a/.changes/fix-ios-plugin-throws-command.md b/.changes/fix-ios-plugin-throws-command.md new file mode 100644 index 000000000000..01d302107d8a --- /dev/null +++ b/.changes/fix-ios-plugin-throws-command.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Use actual iOS plugin instance to run command with `throws`. diff --git a/.changes/fix-ios-run-xcode14.md b/.changes/fix-ios-run-xcode14.md new file mode 100644 index 000000000000..0774a5cffd39 --- /dev/null +++ b/.changes/fix-ios-run-xcode14.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fixes running on device using Xcode 14. diff --git a/.changes/fix-ios-template.md b/.changes/fix-ios-template.md new file mode 100644 index 000000000000..f8f1df92dc94 --- /dev/null +++ b/.changes/fix-ios-template.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fixes the iOS project script to build the Rust library. diff --git a/.changes/fix-javascript-iife-esm-rewrite.md b/.changes/fix-javascript-iife-esm-rewrite.md deleted file mode 100644 index cfa0450ffef1..000000000000 --- a/.changes/fix-javascript-iife-esm-rewrite.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-codegen": patch ---- - -Detect ESM scripts and inject the invoke key directly instead of using an IIFE. diff --git a/.changes/fix-linux-resource-path.md b/.changes/fix-linux-resource-path.md deleted file mode 100644 index 7b684afd5c08..000000000000 --- a/.changes/fix-linux-resource-path.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-utils": patch -"tauri": patch ---- - -Fixes resource directory resolution on Linux. diff --git a/.changes/fix-listen_global-not-getting-events.md b/.changes/fix-listen_global-not-getting-events.md deleted file mode 100644 index d1d67117444e..000000000000 --- a/.changes/fix-listen_global-not-getting-events.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `app.listen_global` not receiving events emitted in javascript. \ No newline at end of file diff --git a/.changes/fix-macos-updater.md b/.changes/fix-macos-updater.md deleted file mode 100644 index 50fa6397c8e6..000000000000 --- a/.changes/fix-macos-updater.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fix macOS `EXC_BAD_ACCESS` panic when app is code-signed. diff --git a/.changes/fix-menu-ids.md b/.changes/fix-menu-ids.md deleted file mode 100644 index 6b87270a0bd3..000000000000 --- a/.changes/fix-menu-ids.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes the menu id mapping not reflecting the current window. diff --git a/.changes/fix-min-height-typo.md b/.changes/fix-min-height-typo.md deleted file mode 100644 index 00343c9fce52..000000000000 --- a/.changes/fix-min-height-typo.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri-utils": patch -"tauri-runtime": patch -"tauri": patch ---- - -Fixes minimum window height being used as maximum height. diff --git a/.changes/fix-mobile-env-vars.md b/.changes/fix-mobile-env-vars.md new file mode 100644 index 000000000000..1433afd99dd5 --- /dev/null +++ b/.changes/fix-mobile-env-vars.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fixes `TAURI_*` environment variables for hook scripts on mobile commands. diff --git a/.changes/fix-monitor-api.md b/.changes/fix-monitor-api.md deleted file mode 100644 index 978757558502..000000000000 --- a/.changes/fix-monitor-api.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": patch ---- - -- Fixes monitor api not working. -- Fixes window.print() not working on macOS. diff --git a/.changes/fix-nodejs-android-cmds.md b/.changes/fix-nodejs-android-cmds.md new file mode 100644 index 000000000000..41c62ca0d792 --- /dev/null +++ b/.changes/fix-nodejs-android-cmds.md @@ -0,0 +1,5 @@ +--- +"@tauri-apps/cli": 'patch:enhance' +--- + +Update tauri-mobile to fix running ADB scripts. diff --git a/.changes/fix-notifications-on-windows.md b/.changes/fix-notifications-on-windows.md deleted file mode 100644 index 3d875648dd52..000000000000 --- a/.changes/fix-notifications-on-windows.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Run the `notification.show()` method on a dedicated async task to prevent a panic on Windows. diff --git a/.changes/fix-orientation-crash.md b/.changes/fix-orientation-crash.md new file mode 100644 index 000000000000..ad181f56b1d2 --- /dev/null +++ b/.changes/fix-orientation-crash.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Update Android project template with fix to crash on orientation change. diff --git a/.changes/fix-out-dir-detection.md b/.changes/fix-out-dir-detection.md deleted file mode 100644 index 4d58c009ae21..000000000000 --- a/.changes/fix-out-dir-detection.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Fixes output directory detection when using Cargo workspaces. diff --git a/.changes/fix-plugin-ios-bool.md b/.changes/fix-plugin-ios-bool.md new file mode 100644 index 000000000000..37a13079bf26 --- /dev/null +++ b/.changes/fix-plugin-ios-bool.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Fixes boolean plugin parameters freezing the application. diff --git a/.changes/fix-plugin-removal.md b/.changes/fix-plugin-removal.md new file mode 100644 index 000000000000..6cf606b7e88c --- /dev/null +++ b/.changes/fix-plugin-removal.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Clear Android plugin JSON file before building Rust library to ensure removed plugins are propagated to the Android project. diff --git a/.changes/fix-plugin-template-cargotoml.md b/.changes/fix-plugin-template-cargotoml.md new file mode 100644 index 000000000000..e3ccd5b7ace8 --- /dev/null +++ b/.changes/fix-plugin-template-cargotoml.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Readd the Cargo.toml file to the plugin template. diff --git a/.changes/fix-proguard-injection.md b/.changes/fix-proguard-injection.md new file mode 100644 index 000000000000..eedcabbc0c04 --- /dev/null +++ b/.changes/fix-proguard-injection.md @@ -0,0 +1,5 @@ +--- +"tauri-build": 'patch:bug' +--- + +Fixes injection of the proguard rules on the Android project. diff --git a/.changes/fix-proguard-rules.md b/.changes/fix-proguard-rules.md new file mode 100644 index 000000000000..31e67486b72f --- /dev/null +++ b/.changes/fix-proguard-rules.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Fixes ProGuard rules. diff --git a/.changes/fix-ready-check.md b/.changes/fix-ready-check.md deleted file mode 100644 index 02ddcf78d151..000000000000 --- a/.changes/fix-ready-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Properly check if document is loaded before invoking commands. diff --git a/.changes/fix-reqwest-headers.md b/.changes/fix-reqwest-headers.md deleted file mode 100644 index 8bbe3bb762b3..000000000000 --- a/.changes/fix-reqwest-headers.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes HTTP API headers being overwritten when using the `reqwest` client. diff --git a/.changes/fix-shell-build.md b/.changes/fix-shell-build.md new file mode 100644 index 000000000000..ce7c3dc676f9 --- /dev/null +++ b/.changes/fix-shell-build.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Fix compilation issues without the shell API features. diff --git a/.changes/fix-svg-mime-type.md b/.changes/fix-svg-mime-type.md deleted file mode 100644 index 4f29c44e08f9..000000000000 --- a/.changes/fix-svg-mime-type.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch -"tauri": patch ---- - -Fixes SVG loading on custom protocol. diff --git a/.changes/fix-tauri-binary-windows.md b/.changes/fix-tauri-binary-windows.md new file mode 100644 index 000000000000..8e932906a42c --- /dev/null +++ b/.changes/fix-tauri-binary-windows.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fixes the Android build gradle plugin implementation on Windows. diff --git a/.changes/fix-tray-command.md b/.changes/fix-tray-command.md deleted file mode 100644 index 6de01cedc5b2..000000000000 --- a/.changes/fix-tray-command.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Initialize system tray before windows so `tray_handle` can be accessed on command handlers. diff --git a/.changes/fix-tray-remove-windows.md b/.changes/fix-tray-remove-windows.md deleted file mode 100644 index ab347c29c5df..000000000000 --- a/.changes/fix-tray-remove-windows.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime-wry": patch ---- - -Reimplement `remove_system_tray` on Windows to drop the `SystemTray` to run its cleanup code. diff --git a/.changes/fix-unlisten-js.md b/.changes/fix-unlisten-js.md deleted file mode 100644 index 29e261512680..000000000000 --- a/.changes/fix-unlisten-js.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `unlisten` calls from JavaScript removing every registered event listener. \ No newline at end of file diff --git a/.changes/fix-url-concatenation.md b/.changes/fix-url-concatenation.md deleted file mode 100644 index 34661324d335..000000000000 --- a/.changes/fix-url-concatenation.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -Use [`Url.join()`](https://docs.rs/url/2.2.2/url/struct.Url.html#method.join) when building webview URLs in -`WindowManager`, to handle edge cases and leading/trailing slashes in paths and urls. diff --git a/.changes/fix-window-config-center-focus.md b/.changes/fix-window-config-center-focus.md deleted file mode 100644 index 33c43c96c02f..000000000000 --- a/.changes/fix-window-config-center-focus.md +++ /dev/null @@ -1,7 +0,0 @@ - ---- - "cli.rs": patch - "tauri-runtime-wry": patch ---- - -Fixes `center` and `focus` not being allowed in `tauri.conf.json > tauri > windows` and ignored in `WindowBuilderWrapper`. \ No newline at end of file diff --git a/.changes/fix-window-created-listen.md b/.changes/fix-window-created-listen.md deleted file mode 100644 index 6713df383243..000000000000 --- a/.changes/fix-window-created-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Immediately listen to `tauri://window-created` event to catch it before the application triggers it. diff --git a/.changes/fix-window-events.md b/.changes/fix-window-events.md deleted file mode 100644 index f09eef5c8754..000000000000 --- a/.changes/fix-window-events.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Fixes window event being emitted to all windows listeners. diff --git a/.changes/fix-window-get-all.md b/.changes/fix-window-get-all.md deleted file mode 100644 index 3328955d3355..000000000000 --- a/.changes/fix-window-get-all.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Remove closed window from the `window.__TAURI__.__windows` array, used by the `window.getAll` API from `@tauri-apps/api`. diff --git a/.changes/fix-window-getter-deadlock.md b/.changes/fix-window-getter-deadlock.md deleted file mode 100644 index bf4c137a5ba5..000000000000 --- a/.changes/fix-window-getter-deadlock.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch -"tauri": patch ---- - -Panic on window getters usage on the main thread when the event loop is not running and document it. diff --git a/.changes/fix-window-label-api.md b/.changes/fix-window-label-api.md deleted file mode 100644 index e2c3ac45937b..000000000000 --- a/.changes/fix-window-label-api.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Fixes `window.label` property returning null instead of the actual label. diff --git a/.changes/fix-window-specific-event-system.md b/.changes/fix-window-specific-event-system.md deleted file mode 100644 index 2682c8745756..000000000000 --- a/.changes/fix-window-specific-event-system.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -The `tauri::Window#emit` functiow now correctly sends the event to all windows that has a registered listener. -**Breaking change:** `Window#emit_and_trigger` and `Window#emit` now requires the payload to be cloneable. diff --git a/.changes/fix-windows-info-cmd.md b/.changes/fix-windows-info-cmd.md deleted file mode 100644 index d01b3293120b..000000000000 --- a/.changes/fix-windows-info-cmd.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Fixes `info` command not striping `\r` from child process version output. diff --git a/.changes/fix-wix-resources.md b/.changes/fix-wix-resources.md deleted file mode 100644 index 5239c020d0cf..000000000000 --- a/.changes/fix-wix-resources.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Properly bundle resources with nested folder structure. diff --git a/.changes/fix-xcodescript-lib-path.md b/.changes/fix-xcodescript-lib-path.md new file mode 100644 index 000000000000..99b7bfab55ef --- /dev/null +++ b/.changes/fix-xcodescript-lib-path.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fixes iOS build script using the wrong path for the app library file. diff --git a/.changes/fix-yarn-pnp.md b/.changes/fix-yarn-pnp.md deleted file mode 100644 index d56e9f3296ba..000000000000 --- a/.changes/fix-yarn-pnp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Automatically unplug `@tauri-apps/cli` in yarn 2+ installations to fix the download of the rust-cli. diff --git a/.changes/fixed-webview2-runtime.md b/.changes/fixed-webview2-runtime.md deleted file mode 100644 index 8cc45d8dc943..000000000000 --- a/.changes/fixed-webview2-runtime.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-utils": patch -"cli.rs": patch ---- - -Allow using a fixed version for the Webview2 runtime via the `tauri > bundle > windows > webviewFixedRuntimePath` config option. diff --git a/.changes/focus.md b/.changes/focus.md deleted file mode 100644 index 25359b18a320..000000000000 --- a/.changes/focus.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `focus` API to the WindowBuilder. diff --git a/.changes/force-colored-logs.md b/.changes/force-colored-logs.md new file mode 100644 index 000000000000..27d48e4b0cb1 --- /dev/null +++ b/.changes/force-colored-logs.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Force colored logs on mobile commands. diff --git a/.changes/force-updater-public-key.md b/.changes/force-updater-public-key.md deleted file mode 100644 index 24e25f514a2c..000000000000 --- a/.changes/force-updater-public-key.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-utils": patch ---- - -The updater `pubkey` is now a required field for security reasons. Sign your updates with the `tauri signer` command. diff --git a/.changes/fs-all-feature-base64-crate.md b/.changes/fs-all-feature-base64-crate.md deleted file mode 100644 index 7e5f89b992a6..000000000000 --- a/.changes/fs-all-feature-base64-crate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `fs-all` feature not requiring the `base64` crate. diff --git a/.changes/generate-tauri-activity.md b/.changes/generate-tauri-activity.md new file mode 100644 index 000000000000..3be10eafc01e --- /dev/null +++ b/.changes/generate-tauri-activity.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Generate `TauriActivity` Kotlin class on the build script. diff --git a/.changes/get-menu.md b/.changes/get-menu.md deleted file mode 100644 index db34c3bf1600..000000000000 --- a/.changes/get-menu.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Replace `WindowBuilder`'s `has_menu` with `get_menu`. diff --git a/.changes/global-shortcut-refactor.md b/.changes/global-shortcut-refactor.md deleted file mode 100644 index 71f253e96bc6..000000000000 --- a/.changes/global-shortcut-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change**: The global shortcut API is now managed by `tao` so it cannot be accessed globally, the manager is now exposed on the `App` and `AppHandle` structs. diff --git a/.changes/gradle-8.md b/.changes/gradle-8.md new file mode 100644 index 000000000000..845db01dbe82 --- /dev/null +++ b/.changes/gradle-8.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Update android template to gradle 8.0 diff --git a/.changes/gtk-window.md b/.changes/gtk-window.md deleted file mode 100644 index f0611a9504dc..000000000000 --- a/.changes/gtk-window.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Expose `gtk_window` getter. diff --git a/.changes/gtk14.md b/.changes/gtk14.md deleted file mode 100644 index 8c93f2940421..000000000000 --- a/.changes/gtk14.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -tauri: patch -tauri-runtime: patch -tauri-runtime-wry: patch ---- - -Update gtk and its related libraries to v0.14. This also remove requirements of `clang` as build dependency. - diff --git a/.changes/gtk16.md b/.changes/gtk16.md new file mode 100644 index 000000000000..4ca4359b7b01 --- /dev/null +++ b/.changes/gtk16.md @@ -0,0 +1,8 @@ +--- +"tauri-runtime": 'minor:feat' +"tauri-runtime-wry": 'minor:feat' +"tauri": 'minor:feat' +--- + +Update gtk to 0.16. + diff --git a/.changes/header-value-bytes.md b/.changes/header-value-bytes.md deleted file mode 100644 index a80e96f82946..000000000000 --- a/.changes/header-value-bytes.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Use `HeaderValue::from_bytes` instead of `HeaderValue::from_str` and `HeaderValue#to_bytes` instead of `HeaderValue#to_str` to improve compatibility. diff --git a/.changes/hide-phf.md b/.changes/hide-phf.md deleted file mode 100644 index 2dd2240de3ae..000000000000 --- a/.changes/hide-phf.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-utils": patch -"tauri": patch ---- - -Hide `phf` crate export (not public API). diff --git a/.changes/hotkey-0.1.2.md b/.changes/hotkey-0.1.2.md deleted file mode 100644 index d24e62f967e1..000000000000 --- a/.changes/hotkey-0.1.2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Update `tauri-hotkey` to v0.1.2, fixing a compilation issue on 32-bit platforms. diff --git a/.changes/http-refactor.md b/.changes/http-refactor.md deleted file mode 100644 index 75e53e9e789b..000000000000 --- a/.changes/http-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -`tauri::api::HttpRequestBuilder::new` now returns a `Result` to validate the url. diff --git a/.changes/icon-png-support.md b/.changes/icon-png-support.md deleted file mode 100644 index 4e740dabac11..000000000000 --- a/.changes/icon-png-support.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Adds support to PNG icons. diff --git a/.changes/implement-debug.md b/.changes/implement-debug.md deleted file mode 100644 index 34032a0b28e1..000000000000 --- a/.changes/implement-debug.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"tauri": patch -"tauri-build": patch -"tauri-utils": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Implement `Debug` on public API structs and enums. diff --git a/.changes/improve-api-types.md b/.changes/improve-api-types.md deleted file mode 100644 index f9a4d1d56e58..000000000000 --- a/.changes/improve-api-types.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Improve `EventName` type using `type-fest`'s `LiteralUnion`. diff --git a/.changes/improve-async-cmd-error-message.md b/.changes/improve-async-cmd-error-message.md new file mode 100644 index 000000000000..48bbbb02c72c --- /dev/null +++ b/.changes/improve-async-cmd-error-message.md @@ -0,0 +1,5 @@ +--- +"tauri-macros": 'patch:enhance' +--- + +Improve compiler error message when generating an async command that has a reference input and don't return a Result. diff --git a/.changes/improve-esm-detection.md b/.changes/improve-esm-detection.md deleted file mode 100644 index e1c4b8185f51..000000000000 --- a/.changes/improve-esm-detection.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-codegen": patch ---- - -Improve ESM detection with regexes. diff --git a/.changes/improve-local-ip-detection.md b/.changes/improve-local-ip-detection.md new file mode 100644 index 000000000000..5668e53ab19b --- /dev/null +++ b/.changes/improve-local-ip-detection.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Improve local IP address detection with user selection. diff --git a/.changes/improve-mobile-plugin-error-handling.md b/.changes/improve-mobile-plugin-error-handling.md new file mode 100644 index 000000000000..a819ad07ef0c --- /dev/null +++ b/.changes/improve-mobile-plugin-error-handling.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Improve the `run_mobile_plugin` function error handling. diff --git a/.changes/inject-config.md b/.changes/inject-config.md new file mode 100644 index 000000000000..735e86f2d71e --- /dev/null +++ b/.changes/inject-config.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Inject Tauri configuration in the Android assets. diff --git a/.changes/inject-invoke-key-module-script.md b/.changes/inject-invoke-key-module-script.md deleted file mode 100644 index b0bc07ce0389..000000000000 --- a/.changes/inject-invoke-key-module-script.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-codegen": patch -"tauri-utils": patch ---- - -Inject invoke key on `script` tags with `type="module"`. diff --git a/.changes/inject-proguard.md b/.changes/inject-proguard.md new file mode 100644 index 000000000000..f9eae0c5dd9b --- /dev/null +++ b/.changes/inject-proguard.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Inject `proguard-tauri.pro` file in the Android project. diff --git a/.changes/internal-default-args.md b/.changes/internal-default-args.md deleted file mode 100644 index 843ec299bdbd..000000000000 --- a/.changes/internal-default-args.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch ---- - -(internal): allow `wry` dependency to be optional again while keeping default args. -code that wishes to expose a struct with a default arg should use the `crate::manager::default_args!` macro to declare -the struct, so that it can automatically feature-gate `DefaultArgs` behind using `wry`. diff --git a/.changes/invoke-handler-attributes.md b/.changes/invoke-handler-attributes.md new file mode 100644 index 000000000000..35cedd0529f2 --- /dev/null +++ b/.changes/invoke-handler-attributes.md @@ -0,0 +1,6 @@ +--- +"tauri-macros": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Added support to attibutes for each command path in the `generate_handler` macro. diff --git a/.changes/invoke-key-performance.md b/.changes/invoke-key-performance.md deleted file mode 100644 index 6296fa58272d..000000000000 --- a/.changes/invoke-key-performance.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-codegen": patch ---- - -Improve invoke key code injection performance time rewriting code at compile time. diff --git a/.changes/invoke-return-bool.md b/.changes/invoke-return-bool.md new file mode 100644 index 000000000000..5e475c5aa17f --- /dev/null +++ b/.changes/invoke-return-bool.md @@ -0,0 +1,7 @@ +--- +"tauri-macros": major:feat +"tauri-codegen": major:feat +"tauri": major:feat +--- + +Return `bool` in the invoke handler. diff --git a/.changes/ios-deployment-target.md b/.changes/ios-deployment-target.md new file mode 100644 index 000000000000..e66aec4e7ab6 --- /dev/null +++ b/.changes/ios-deployment-target.md @@ -0,0 +1,5 @@ +--- +"tauri-build": 'patch:enhance' +--- + +Read the `IPHONEOS_DEPLOYMENT_TARGET` environment variable to set the Swift iOS target version, defaults to 13. diff --git a/.changes/ios-icon-color.md b/.changes/ios-icon-color.md new file mode 100644 index 000000000000..7cfb8802e917 --- /dev/null +++ b/.changes/ios-icon-color.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Added `--ios-color` option to the `tauri icon` command. diff --git a/.changes/ios-keep-alive.md b/.changes/ios-keep-alive.md new file mode 100644 index 000000000000..0d2829aa3a8f --- /dev/null +++ b/.changes/ios-keep-alive.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Keep the process alive even when the iOS application is closed. diff --git a/.changes/ios-logs.md b/.changes/ios-logs.md new file mode 100644 index 000000000000..cfaa27570c13 --- /dev/null +++ b/.changes/ios-logs.md @@ -0,0 +1,7 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Show all application logs on iOS. diff --git a/.changes/ipc-scope-remove-enable-tauri-api.md b/.changes/ipc-scope-remove-enable-tauri-api.md new file mode 100644 index 000000000000..3e0a77095bb7 --- /dev/null +++ b/.changes/ipc-scope-remove-enable-tauri-api.md @@ -0,0 +1,6 @@ +--- +"tauri": 'patch:enhance' +"tauri-utils": 'patch:enhance' +--- + +Remove `enable_tauri_api` from the IPC scope. diff --git a/.changes/is-decorated.md b/.changes/is-decorated.md deleted file mode 100644 index 7e4b2e406c7c..000000000000 --- a/.changes/is-decorated.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `is_decorated` getter on Window. diff --git a/.changes/is-resizable.md b/.changes/is-resizable.md deleted file mode 100644 index e4b096eef75a..000000000000 --- a/.changes/is-resizable.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `is_resizable` getter on Window. diff --git a/.changes/is-visible.md b/.changes/is-visible.md deleted file mode 100644 index a3931eca32e7..000000000000 --- a/.changes/is-visible.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `is_visible` getter on Window. diff --git a/.changes/is_focused-api.md b/.changes/is_focused-api.md new file mode 100644 index 000000000000..fee5a93a50eb --- /dev/null +++ b/.changes/is_focused-api.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'minor:feat' +--- + +Add `WebviewWindow.is_focused` and `WebviewWindow.getFocusedWindow` getters. diff --git a/.changes/is_focused-runtime.md b/.changes/is_focused-runtime.md new file mode 100644 index 000000000000..3991e4af6d4c --- /dev/null +++ b/.changes/is_focused-runtime.md @@ -0,0 +1,6 @@ +--- +'tauri-runtime': 'minor:feat' +'tauri-runtime-wry': 'minor:feat' +--- + +Add `Window::is_focused` getter. diff --git a/.changes/is_focused-tauri.md b/.changes/is_focused-tauri.md new file mode 100644 index 000000000000..928f34f9097b --- /dev/null +++ b/.changes/is_focused-tauri.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Add `Window::is_focused` and `Manager::get_focused_window` getters. diff --git a/.changes/isolation-pattern.md b/.changes/isolation-pattern.md deleted file mode 100644 index 12b4942ad892..000000000000 --- a/.changes/isolation-pattern.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-utils": patch -"tauri-codegen": patch ---- - -Added the `isolation` pattern. diff --git a/.changes/join-handle-abort.md b/.changes/join-handle-abort.md deleted file mode 100644 index 3e4d7622947b..000000000000 --- a/.changes/join-handle-abort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Added `abort` method to `tauri::async_runtime::JoinHandle`. diff --git a/.changes/json5.md b/.changes/json5.md deleted file mode 100644 index 00a934e8818d..000000000000 --- a/.changes/json5.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"tauri": patch -"tauri-build": patch -"tauri-codegen": patch -"tauri-macros": patch -"tauri-utils": patch -"cli.rs": patch ---- - -Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension. - -Here is the logic flow that determines if JSON or JSON5 will be used to parse the config: - -1. Check if `tauri.conf.json` exists - a. Parse it with `serde_json` - b. Parse it with `json5` if `serde_json` fails - c. Return original `serde_json` error if all above steps failed -2. Check if `tauri.conf.json5` exists - a. Parse it with `json5` - b. Return error if all above steps failed -3. Return error if all above steps failed diff --git a/.changes/lib-name-xcode.md b/.changes/lib-name-xcode.md new file mode 100644 index 000000000000..5af4eb4b36c0 --- /dev/null +++ b/.changes/lib-name-xcode.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:enhance' +--- + +Use correct lib name in xcode project. diff --git a/.changes/link-stop-propagation.md b/.changes/link-stop-propagation.md deleted file mode 100644 index 5f7c8a5feb6c..000000000000 --- a/.changes/link-stop-propagation.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Allow preventing opening the default browser on a click on an `` element via `stopImmediatePropagation()`. diff --git a/.changes/linux-app-name.md b/.changes/linux-app-name.md deleted file mode 100644 index b0218c5bd330..000000000000 --- a/.changes/linux-app-name.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"cli.rs": patch -"tauri-bundler": patch ---- - -Only convert package name and binary name to kebab-case, keeping the `.desktop` `Name` field with the original configured value. diff --git a/.changes/linux-cors.md b/.changes/linux-cors.md deleted file mode 100644 index 4dc7ecd290f2..000000000000 --- a/.changes/linux-cors.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Fix empty header from CORS on Linux. - diff --git a/.changes/linux-run-return.md b/.changes/linux-run-return.md deleted file mode 100644 index f59b6cbe1f1e..000000000000 --- a/.changes/linux-run-return.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -The `run_return` API is now available on Linux. diff --git a/.changes/linux-window-icon.md b/.changes/linux-window-icon.md deleted file mode 100644 index 16215cf8e9e0..000000000000 --- a/.changes/linux-window-icon.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-codegen": patch -"tauri": patch ---- - -Read `tauri.conf.json > tauri > bundle > icons` and use the first `.png` icon as window icon on Linux. Defaults to `icon/icon.png` if a PNG icon is not configured. diff --git a/.changes/linuxdeploy.md b/.changes/linuxdeploy.md deleted file mode 100644 index ec29b0a6f093..000000000000 --- a/.changes/linuxdeploy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Use `linuxdeploy` instead of `appimagetool` for `AppImage` bundling. diff --git a/.changes/local-dev-path-mobile.md b/.changes/local-dev-path-mobile.md new file mode 100644 index 000000000000..5a1381323b61 --- /dev/null +++ b/.changes/local-dev-path-mobile.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fixes HMR on mobile when devPath is configured to load a filesystem path. diff --git a/.changes/locks.md b/.changes/locks.md deleted file mode 100644 index e4164234910e..000000000000 --- a/.changes/locks.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Remove a few locks requirement in tauri-runtime-wry \ No newline at end of file diff --git a/.changes/log-file-fix-for-linux-and-windows.md b/.changes/log-file-fix-for-linux-and-windows.md new file mode 100644 index 000000000000..4949b3d2dcee --- /dev/null +++ b/.changes/log-file-fix-for-linux-and-windows.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:bug' +--- + +Fix default log path for linux and windows diff --git a/.changes/logcat-all-tags.md b/.changes/logcat-all-tags.md new file mode 100644 index 000000000000..8d40bcfe7866 --- /dev/null +++ b/.changes/logcat-all-tags.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Print log output for all tags on Android development. diff --git a/.changes/mac-cpu.md b/.changes/mac-cpu.md deleted file mode 100644 index bf1935d75a2a..000000000000 --- a/.changes/mac-cpu.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch -"tauri": patch ---- - - Fix macOS high CPU usage. diff --git a/.changes/main-events-cleared-and-resumed.md b/.changes/main-events-cleared-and-resumed.md deleted file mode 100644 index 8d4e65d4891e..000000000000 --- a/.changes/main-events-cleared-and-resumed.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `Resumed` and `MainEventsCleared` variants to the `Event` enum. diff --git a/.changes/main-thread-api-calls.md b/.changes/main-thread-api-calls.md deleted file mode 100644 index a9bddb9feb21..000000000000 --- a/.changes/main-thread-api-calls.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime-wry": patch ---- - -Allow window, global shortcut and clipboard APIs to be called on the main thread. diff --git a/.changes/menu-export.md b/.changes/menu-export.md deleted file mode 100644 index 0b7b1dc07ca1..000000000000 --- a/.changes/menu-export.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Export `MenuHandle` and `MenuEvent` types on `tauri::window`. diff --git a/.changes/menu-item-accelerator.md b/.changes/menu-item-accelerator.md deleted file mode 100644 index 0799864b9b3f..000000000000 --- a/.changes/menu-item-accelerator.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch ---- - -Adds `accelerator` method to the `CustomMenuItem` struct to define a keyboard shortcut for the menu item. diff --git a/.changes/menu-refactor.md b/.changes/menu-refactor.md deleted file mode 100644 index 45910136e77a..000000000000 --- a/.changes/menu-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change**: The `menu` API was not designed to have all the new features: submenus, item updates, disabled state... so we broke it before going to stable. diff --git a/.changes/menu-with-items-constructor.md b/.changes/menu-with-items-constructor.md deleted file mode 100644 index 1c5a43f0f20e..000000000000 --- a/.changes/menu-with-items-constructor.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": patch -"tauri": patch ---- - -Add `Menu::with_items` constructor, taking an iterator of `MenuEntry`. diff --git a/.changes/menu.md b/.changes/menu.md deleted file mode 100644 index e9f51810d3ba..000000000000 --- a/.changes/menu.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Implemented window menus APIs. diff --git a/.changes/menuhash-fix.md b/.changes/menuhash-fix.md deleted file mode 100644 index a9fea561ab83..000000000000 --- a/.changes/menuhash-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Fix the error "cannot find type MenuHash in this scope" diff --git a/.changes/migrate-cmd.md b/.changes/migrate-cmd.md new file mode 100644 index 000000000000..4fae458f3b64 --- /dev/null +++ b/.changes/migrate-cmd.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:feat' +"@tauri-apps/cli": 'patch:feat' +--- + +Added `migrate` command. diff --git a/.changes/mime-type.md b/.changes/mime-type.md new file mode 100644 index 000000000000..5b05f6199940 --- /dev/null +++ b/.changes/mime-type.md @@ -0,0 +1,5 @@ +--- +'tauri-utils': 'patch:enhance' +--- + +Add `MimeType::parse_with_fallback` and `MimeType::parse_from_uri_with_fallback` diff --git a/.changes/min-sdk-version.md b/.changes/min-sdk-version.md new file mode 100644 index 000000000000..633247f34488 --- /dev/null +++ b/.changes/min-sdk-version.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": 'patch:enhance' +--- + +Added `android` configuration object under `tauri > bundle`. diff --git a/.changes/misign-update.md b/.changes/misign-update.md deleted file mode 100644 index e1a0281d82a3..000000000000 --- a/.changes/misign-update.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The updater now expects signatures created with the latest CLI release. diff --git a/.changes/mobile-config.md b/.changes/mobile-config.md new file mode 100644 index 000000000000..85026f10ea56 --- /dev/null +++ b/.changes/mobile-config.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": 'minor:feat' +--- + +Parse `android` and `ios` Tauri configuration files. diff --git a/.changes/mobile-dev-watcher-ignore-gen.md b/.changes/mobile-dev-watcher-ignore-gen.md new file mode 100644 index 000000000000..96ca550d81a0 --- /dev/null +++ b/.changes/mobile-dev-watcher-ignore-gen.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Ignore the `gen` folder on the dev watcher. diff --git a/.changes/mobile-entry-point-macro.md b/.changes/mobile-entry-point-macro.md new file mode 100644 index 000000000000..5f428bdc4cad --- /dev/null +++ b/.changes/mobile-entry-point-macro.md @@ -0,0 +1,5 @@ +--- +"tauri-macros": 'minor:feat' +--- + +Added the `mobile_entry_point` macro. diff --git a/.changes/mobile-env-vars-rename.md b/.changes/mobile-env-vars-rename.md new file mode 100644 index 000000000000..7067ce7bad2c --- /dev/null +++ b/.changes/mobile-env-vars-rename.md @@ -0,0 +1,6 @@ +--- +"tauri-build": 'patch:enhance' +"tauri-macros": 'patch:enhance' +--- + +Refactor mobile environment variables. diff --git a/.changes/mobile-init.md b/.changes/mobile-init.md new file mode 100644 index 000000000000..7b47ed16cd62 --- /dev/null +++ b/.changes/mobile-init.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'minor:feat' +"@tauri-apps/cli": 'minor:feat' +--- + +Added `android init` and `ios init` commands. diff --git a/.changes/mobile-lib-name.md b/.changes/mobile-lib-name.md new file mode 100644 index 000000000000..496d4e7f8217 --- /dev/null +++ b/.changes/mobile-lib-name.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Add support to custom and kebab case library names for mobile apps. diff --git a/.changes/mobile-open.md b/.changes/mobile-open.md new file mode 100644 index 000000000000..007eafe53bc5 --- /dev/null +++ b/.changes/mobile-open.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'minor:feat' +"@tauri-apps/cli": 'minor:feat' +--- + +Added `android open` and `ios open` commands. diff --git a/.changes/mobile-plugin-config.md b/.changes/mobile-plugin-config.md new file mode 100644 index 000000000000..6eb4f772b583 --- /dev/null +++ b/.changes/mobile-plugin-config.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Expose plugin configuration on the Android and iOS plugin classes. diff --git a/.changes/mobile-plugins.md b/.changes/mobile-plugins.md new file mode 100644 index 000000000000..03a673a0ed78 --- /dev/null +++ b/.changes/mobile-plugins.md @@ -0,0 +1,5 @@ +--- +"tauri": 'minor:feat' +--- + +Run Android and iOS native plugins on the invoke handler if a Rust plugin command is not found. diff --git a/.changes/mobile-webview-access.md b/.changes/mobile-webview-access.md new file mode 100644 index 000000000000..be725d5e7cc2 --- /dev/null +++ b/.changes/mobile-webview-access.md @@ -0,0 +1,6 @@ +--- +"tauri-runtime-wry": 'minor:feat' +"tauri": 'minor:feat' +--- + +Support `with_webview` for Android platform alowing execution of JNI code in context. diff --git a/.changes/mobile.md b/.changes/mobile.md new file mode 100644 index 000000000000..e494dfd8507d --- /dev/null +++ b/.changes/mobile.md @@ -0,0 +1,13 @@ +--- +"api": major:feat +"tauri-utils": major:feat +"tauri-bundler": major:feat +"tauri-codegen": major:feat +"tauri-macros": major:feat +"tauri-build": major:feat +"tauri": major:feat +"tauri-cli": major:feat +"@tauri-apps/cli": major:feat +--- + +First mobile alpha release! diff --git a/.changes/move-app.md b/.changes/move-app.md new file mode 100644 index 000000000000..746c84ab4355 --- /dev/null +++ b/.changes/move-app.md @@ -0,0 +1,6 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Moved the `app` feature to its own plugin in the plugins-workspace repository. diff --git a/.changes/move-cli.md b/.changes/move-cli.md new file mode 100644 index 000000000000..0cd3ac386b2d --- /dev/null +++ b/.changes/move-cli.md @@ -0,0 +1,7 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +"tauri-utils": 'patch:enhance' +--- + +Moved the `cli` feature to its own plugin in the plugins-workspace repository. diff --git a/.changes/move-dialog-plugin.md b/.changes/move-dialog-plugin.md new file mode 100644 index 000000000000..16a7e40404fa --- /dev/null +++ b/.changes/move-dialog-plugin.md @@ -0,0 +1,6 @@ +--- +"tauri": 'patch:enhance' +"api": 'patch:enhance' +--- + +Moved the dialog APIs to its own plugin in the plugins-workspace repository. diff --git a/.changes/move-event.md b/.changes/move-event.md new file mode 100644 index 000000000000..40509a43b51d --- /dev/null +++ b/.changes/move-event.md @@ -0,0 +1,8 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +"tauri-runtime": 'patch:enhance' +"tauri-runtime-wry": 'patch:enhance' +--- + +Moved the `event` JS APIs to a plugin. diff --git a/.changes/move-fs.md b/.changes/move-fs.md new file mode 100644 index 000000000000..b468af4e17ca --- /dev/null +++ b/.changes/move-fs.md @@ -0,0 +1,6 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Moved the file system APIs to its own plugin in the plugins-workspace repository. diff --git a/.changes/move-http-api.md b/.changes/move-http-api.md new file mode 100644 index 000000000000..98dde1732b54 --- /dev/null +++ b/.changes/move-http-api.md @@ -0,0 +1,6 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Moved the `http` feature to its own plugin in the plugins-workspace repository. diff --git a/.changes/move-os.md b/.changes/move-os.md new file mode 100644 index 000000000000..b1dfd4ae8b27 --- /dev/null +++ b/.changes/move-os.md @@ -0,0 +1,6 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Moved the `os` feature to its own plugin in the plugins-workspace repository. diff --git a/.changes/move-process.md b/.changes/move-process.md new file mode 100644 index 000000000000..637d13e07b2e --- /dev/null +++ b/.changes/move-process.md @@ -0,0 +1,6 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Moved the `process` feature to its own plugin in the plugins-workspace repository. diff --git a/.changes/move-protocol-asset.md b/.changes/move-protocol-asset.md new file mode 100644 index 000000000000..3dd27fbf0969 --- /dev/null +++ b/.changes/move-protocol-asset.md @@ -0,0 +1,6 @@ +--- +"tauri": 'patch:enhance' +"tauri-utils": 'patch:enhance' +--- + +Moved the `protocol` scope configuration to the `asset_protocol` field in `SecurityConfig`. diff --git a/.changes/move-shell.md b/.changes/move-shell.md new file mode 100644 index 000000000000..0bd1264df456 --- /dev/null +++ b/.changes/move-shell.md @@ -0,0 +1,8 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +"tauri-codegen": 'patch:enhance' +"tauri-macros": 'patch:enhance' +--- + +Moved the `shell` functionality to its own plugin in the plugins-workspace repository. diff --git a/.changes/move-updater-config.md b/.changes/move-updater-config.md new file mode 100644 index 000000000000..279834d9f8b6 --- /dev/null +++ b/.changes/move-updater-config.md @@ -0,0 +1,8 @@ +--- +"tauri": 'patch:enhance' +"tauri-utils": 'patch:enhance' +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Moved the updater configuration to the `BundleConfig`. diff --git a/.changes/move-updater.md b/.changes/move-updater.md new file mode 100644 index 000000000000..9a9b1df02990 --- /dev/null +++ b/.changes/move-updater.md @@ -0,0 +1,7 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +"tauri-utils": 'patch:enhance' +--- + +Moved the `updater` feature to its own plugin in the plugins-workspace repository. diff --git a/.changes/msi-license.md b/.changes/msi-license.md deleted file mode 100644 index 953e3662949f..000000000000 --- a/.changes/msi-license.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"cli.rs": patch -"tauri-bundler": patch ---- - -Allow setting a path to a license file for the Windows Installer (`tauri.conf.json > bundle > windows > wix > license`). diff --git a/.changes/msrv-1.64.md b/.changes/msrv-1.64.md new file mode 100644 index 000000000000..2a6186c6bf71 --- /dev/null +++ b/.changes/msrv-1.64.md @@ -0,0 +1,13 @@ +--- +"tauri-cli": 'minor:feat' +"tauri-bundler": 'minor:feat' +"tauri": 'minor:feat' +"tauri-build": 'minor:feat' +"tauri-codegen": 'minor:feat' +"tauri-macros": 'minor:feat' +"tauri-utils": 'minor:feat' +"tauri-runtime": 'minor:feat' +"tauri-runtime-wry": 'minor:feat' +--- + +Bump the MSRV to 1.64. diff --git a/.changes/msrv-1.65.md b/.changes/msrv-1.65.md new file mode 100644 index 000000000000..911372072c97 --- /dev/null +++ b/.changes/msrv-1.65.md @@ -0,0 +1,13 @@ +--- +"tauri-cli": 'minor:feat' +"tauri-bundler": 'minor:feat' +"tauri": 'minor:feat' +"tauri-build": 'minor:feat' +"tauri-codegen": 'minor:feat' +"tauri-macros": 'minor:feat' +"tauri-utils": 'minor:feat' +"tauri-runtime": 'minor:feat' +"tauri-runtime-wry": 'minor:feat' +--- + +Bump the MSRV to 1.65. diff --git a/.changes/mutable-callbacks.md b/.changes/mutable-callbacks.md deleted file mode 100644 index 979bd92c5930..000000000000 --- a/.changes/mutable-callbacks.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri-runtime-wry": minor -"tauri-runtime": minor -"tauri": minor ---- - -Change event loop callbacks definition to allow callers to move in mutable values. \ No newline at end of file diff --git a/.changes/napi-rs.md b/.changes/napi-rs.md new file mode 100644 index 000000000000..165a06339e3b --- /dev/null +++ b/.changes/napi-rs.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/cli': 'patch:enhance' +--- + +Update `napi-rs` dependencies to latest to fix CLI hanging up forever. diff --git a/.changes/non-exhaustive.md b/.changes/non-exhaustive.md deleted file mode 100644 index 2d6a6df8735e..000000000000 --- a/.changes/non-exhaustive.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"tauri": patch -"tauri-bundler": patch -"tauri-codegen": patch -"tauri-utils": patch ---- - -Added the `#[non_exhaustive] attribute where appropriate. diff --git a/.changes/notification-permission.md b/.changes/notification-permission.md deleted file mode 100644 index 6eae571a46c4..000000000000 --- a/.changes/notification-permission.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -`Notification.requestPermission()` now returns `"denied"` when not allowlisted. -`IsNotificationPermissionGranted` returns `false` when not allowlisted. diff --git a/.changes/notification-regression.md b/.changes/notification-regression.md deleted file mode 100644 index 2e2d1193d4d7..000000000000 --- a/.changes/notification-regression.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes `Notification.requestPermission()` deadlock. diff --git a/.changes/npm-pass-args.md b/.changes/npm-pass-args.md new file mode 100644 index 000000000000..6496d32f5faf --- /dev/null +++ b/.changes/npm-pass-args.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Correctly pass arguments from `npm run` to `tauri`. diff --git a/.changes/nsis-accurate-app-size.md b/.changes/nsis-accurate-app-size.md new file mode 100644 index 000000000000..913e6602a63a --- /dev/null +++ b/.changes/nsis-accurate-app-size.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:bug' +--- + +Fix incorrect estimated app size for NSIS bundler when installed to a non-empty directory. diff --git a/.changes/nsis-branding.md b/.changes/nsis-branding.md new file mode 100644 index 000000000000..0c0c802760f5 --- /dev/null +++ b/.changes/nsis-branding.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:enhance' +--- + +Added Copyright field as BrandingText to the NSIS bundler. diff --git a/.changes/nsis-custom-language-files.md b/.changes/nsis-custom-language-files.md new file mode 100644 index 000000000000..3e0077901075 --- /dev/null +++ b/.changes/nsis-custom-language-files.md @@ -0,0 +1,7 @@ +--- +'tauri-bundler': 'minor:feat' +'tauri-utils': 'minor:feat' +'tauri-cli': 'minor:feat' +--- + +Allow specifying custom language files of Tauri's custom messages for the NSIS installer diff --git a/.changes/nsis-custom-template.md b/.changes/nsis-custom-template.md new file mode 100644 index 000000000000..fdf7db88ea1b --- /dev/null +++ b/.changes/nsis-custom-template.md @@ -0,0 +1,8 @@ +--- +'tauri-utils': 'minor:feat' +'tauri-bundler': 'minor:feat' +'tauri-cli': 'minor:feat' +'@tauri-apps/cli': 'minor:feat' +--- + +Add `nsis > template` option to specify custom NSIS installer template. diff --git a/.changes/nsis-dutch.md b/.changes/nsis-dutch.md new file mode 100644 index 000000000000..d576dd612268 --- /dev/null +++ b/.changes/nsis-dutch.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:enhance' +--- + +Added Dutch language support to the NSIS bundler. diff --git a/.changes/nsis-encoding.md b/.changes/nsis-encoding.md new file mode 100644 index 000000000000..5e936e96e403 --- /dev/null +++ b/.changes/nsis-encoding.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:bug' +--- + +Fix NSIS bundler failing to build when `productName` contained chinsese characters. diff --git a/.changes/nsis-install-mode-args.md b/.changes/nsis-install-mode-args.md new file mode 100644 index 000000000000..2a449223c102 --- /dev/null +++ b/.changes/nsis-install-mode-args.md @@ -0,0 +1,5 @@ +--- +'tauri-utils': 'patch:enhance' +--- + +Add `WindowsUpdateInstallMode::nsis_args` diff --git a/.changes/nsis-japanese.md b/.changes/nsis-japanese.md new file mode 100644 index 000000000000..e3f2739fccb2 --- /dev/null +++ b/.changes/nsis-japanese.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:enhance' +--- + +Added Japanese language support to the NSIS bundler. diff --git a/.changes/nsis-korean.md b/.changes/nsis-korean.md new file mode 100644 index 000000000000..320d0f2d742c --- /dev/null +++ b/.changes/nsis-korean.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:enhance' +--- + +Added Korean language support to the NSIS bundler. diff --git a/.changes/nsis-passive-mode.md b/.changes/nsis-passive-mode.md new file mode 100644 index 000000000000..59bddb73768b --- /dev/null +++ b/.changes/nsis-passive-mode.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Support `passive` mode for NSIS updater. diff --git a/.changes/nsis-persian.md b/.changes/nsis-persian.md new file mode 100644 index 000000000000..0b0008227ed6 --- /dev/null +++ b/.changes/nsis-persian.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:enhance' +--- + +Added Persian language support to the NSIS bundler. diff --git a/.changes/nsis-restart-flag.md b/.changes/nsis-restart-flag.md new file mode 100644 index 000000000000..e80b0c2ba8d6 --- /dev/null +++ b/.changes/nsis-restart-flag.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'minor:feat' +--- + +For NSIS, Add support for `/P` to install or uninstall in passive mode, `/R` to (re)start the app and `/NS` to disable creating shortcuts in `silent` and `passive` modes. diff --git a/.changes/nsis-restore-installation-path.md b/.changes/nsis-restore-installation-path.md new file mode 100644 index 000000000000..f5e83dc057d9 --- /dev/null +++ b/.changes/nsis-restore-installation-path.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:bug' +--- + +Fix NSIS installer not using the old installation path as a default when using `perMachine` or `currentUser` install modes. Also fixes NSIS not respecting the `/D` flag which used to set the installation directory from command line. diff --git a/.changes/nsis-silent-kill.md b/.changes/nsis-silent-kill.md new file mode 100644 index 000000000000..c0a5c3ebbdb6 --- /dev/null +++ b/.changes/nsis-silent-kill.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'minor:feat' +--- + +NSIS `silent` and `passive` installer/updater will auto-kill the app if its running. diff --git a/.changes/nsis-swedish.md b/.changes/nsis-swedish.md new file mode 100644 index 000000000000..18e2e7835dea --- /dev/null +++ b/.changes/nsis-swedish.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:enhance' +--- + +Added Swedish language support to the NSIS bundler. diff --git a/.changes/nsis-turkish.md b/.changes/nsis-turkish.md new file mode 100644 index 000000000000..0527faa5a1c3 --- /dev/null +++ b/.changes/nsis-turkish.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:enhance' +--- + +Added Turkish language support to the NSIS bundler. diff --git a/.changes/nsis-uninstall-wix.md b/.changes/nsis-uninstall-wix.md new file mode 100644 index 000000000000..0431d639e415 --- /dev/null +++ b/.changes/nsis-uninstall-wix.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'minor:feat' +--- + +NSIS installer will now check if a previous WiX `.msi` installation exist and will prompt users to uninstall it. diff --git a/.changes/nsis-updater-restart.md b/.changes/nsis-updater-restart.md new file mode 100644 index 000000000000..f07075713e6f --- /dev/null +++ b/.changes/nsis-updater-restart.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:enhance' +--- + +Restart the app after the NSIS updater is finished. diff --git a/.changes/on-new-intent.md b/.changes/on-new-intent.md new file mode 100644 index 000000000000..c7c89f80fc71 --- /dev/null +++ b/.changes/on-new-intent.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Added the `onNewIntent` Plugin hook on Android. diff --git a/.changes/only-proxy-on-mobile.md b/.changes/only-proxy-on-mobile.md new file mode 100644 index 000000000000..2998169183c3 --- /dev/null +++ b/.changes/only-proxy-on-mobile.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Only proxy the dev server on mobile to simplify desktop usage. diff --git a/.changes/open-devtools.md b/.changes/open-devtools.md deleted file mode 100644 index 416969967ce5..000000000000 --- a/.changes/open-devtools.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Added `Window#open_devtools` API. diff --git a/.changes/open-ts-overload.md b/.changes/open-ts-overload.md new file mode 100644 index 000000000000..6ea100f344ec --- /dev/null +++ b/.changes/open-ts-overload.md @@ -0,0 +1,5 @@ +--- +"api": 'patch:enhance' +--- + +Overload the dialog `open` function to have better TS result types. diff --git a/.changes/package-info-crate-name.md b/.changes/package-info-crate-name.md new file mode 100644 index 000000000000..c4d3aa93098d --- /dev/null +++ b/.changes/package-info-crate-name.md @@ -0,0 +1,6 @@ +--- +"tauri-utils": 'patch:enhance' +"tauri-codegen": 'patch:enhance' +--- + +Added `crate_name` field on `PackageInfo`. diff --git a/.changes/package-json-configure-app-path.md b/.changes/package-json-configure-app-path.md deleted file mode 100644 index 39ba4210136d..000000000000 --- a/.changes/package-json-configure-app-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Read `package.json` and check for a `tauri` object containing the `appPath` string, which points to the tauri crate path. diff --git a/.changes/panic-dispatcher-getter-main-thread.md b/.changes/panic-dispatcher-getter-main-thread.md deleted file mode 100644 index 157e6ee5d087..000000000000 --- a/.changes/panic-dispatcher-getter-main-thread.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Panic when a dispatcher getter method (`Window`, `GlobalShortcutHandle`, `ClipboardManager` and `MenuHandle` APIs) is called on the main thread. diff --git a/.changes/path-resolver.md b/.changes/path-resolver.md deleted file mode 100644 index 2fc012fb1a84..000000000000 --- a/.changes/path-resolver.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds a `PathResolver` struct to simplify the usage of the `tauri::api::path::{app_dir, resource_dir}` APIs, accessible through the `App` and `AppHandle` `path_resolver` methods. diff --git a/.changes/path-sep-delimter.md b/.changes/path-sep-delimter.md new file mode 100644 index 000000000000..6882135eb2b4 --- /dev/null +++ b/.changes/path-sep-delimter.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'minor:enhance' +--- + +Changed `sep` and `delimiter` from `path` module into functions to fix import in frameworks like `next.js` diff --git a/.changes/percent-decode-asset-protocol.md b/.changes/percent-decode-asset-protocol.md deleted file mode 100644 index 771bd470ec9a..000000000000 --- a/.changes/percent-decode-asset-protocol.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Use `percent_encoding::percent_decode` on the `asset` custom protocol URL before reading the file. diff --git a/.changes/platform-specific-config.md b/.changes/platform-specific-config.md deleted file mode 100644 index ddb617fccbbb..000000000000 --- a/.changes/platform-specific-config.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Merge platform-specific `tauri.linux.conf.json`, `tauri.windows.conf.json` and `tauri.macos.conf.json` into the config JSON from `tauri.conf.json`. diff --git a/.changes/plugin-android-project-refactor.md b/.changes/plugin-android-project-refactor.md new file mode 100644 index 000000000000..d79965d73809 --- /dev/null +++ b/.changes/plugin-android-project-refactor.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"tauri-build": 'patch:enhance' +--- + +Use absolute path to each Android plugin project instead of copying the files to enhance developer experience. diff --git a/.changes/plugin-api-handle.md b/.changes/plugin-api-handle.md new file mode 100644 index 000000000000..8a02043df0d5 --- /dev/null +++ b/.changes/plugin-api-handle.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Add `app` method for the `PluginApi` struct. diff --git a/.changes/plugin-builder.md b/.changes/plugin-builder.md deleted file mode 100644 index 5751204cd8e5..000000000000 --- a/.changes/plugin-builder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": minor ---- - -Add a `plugin::Builder` struct to make plugin creation more convenient. diff --git a/.changes/plugin-command.md b/.changes/plugin-command.md deleted file mode 100644 index 865b5af03e0f..000000000000 --- a/.changes/plugin-command.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Added `$ tauri plugin init` command, which initializes a Tauri plugin. diff --git a/.changes/plugin-handle-clone.md b/.changes/plugin-handle-clone.md new file mode 100644 index 000000000000..7863ad76228e --- /dev/null +++ b/.changes/plugin-handle-clone.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Implement `Clone` for `plugin::PluginHandle`. diff --git a/.changes/plugin-init-fns.md b/.changes/plugin-init-fns.md new file mode 100644 index 000000000000..ad85adf940ed --- /dev/null +++ b/.changes/plugin-init-fns.md @@ -0,0 +1,5 @@ +--- +"tauri": 'minor:feat' +--- + +Added `initialize_android_plugin` and `initialize_ios_plugin` APIs on `AppHandle`. diff --git a/.changes/plugin-init-refactor.md b/.changes/plugin-init-refactor.md new file mode 100644 index 000000000000..b2cbbafd73d7 --- /dev/null +++ b/.changes/plugin-init-refactor.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'minor:feat' +"@tauri-apps/cli": 'minor:feat' +--- + +Changed the `--api` flag on `plugin init` to `--no-api`. diff --git a/.changes/plugin-initialize-app-handle.md b/.changes/plugin-initialize-app-handle.md deleted file mode 100644 index fcff2fabed5d..000000000000 --- a/.changes/plugin-initialize-app-handle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change**: The `Plugin` trait `initialize` method now takes an `AppHandle` reference instead of `App`. diff --git a/.changes/plugin-on_event.md b/.changes/plugin-on_event.md deleted file mode 100644 index d844fd78d857..000000000000 --- a/.changes/plugin-on_event.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Added `on_event` on the `Plugin` trait, which allows a plugin to react to the event loop. diff --git a/.changes/plugin-refactor.md b/.changes/plugin-refactor.md deleted file mode 100644 index fa9cc5c26f36..000000000000 --- a/.changes/plugin-refactor.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch ---- - -Refactored the `Plugin` trait `initialize` and `extend_api` signatures. -`initialize` now takes the `App` as first argument, and `extend_api` takes an `Invoke` instead of `InvokeMessage`. -This adds support to managed state on plugins. diff --git a/.changes/plugin-setup-refactor.md b/.changes/plugin-setup-refactor.md new file mode 100644 index 000000000000..6e8afa6fba5e --- /dev/null +++ b/.changes/plugin-setup-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": major:feat +--- + +Changed the plugin setup hook to take a second argument of type `PluginApi`. diff --git a/.changes/plugin-template-examples-manifest.md b/.changes/plugin-template-examples-manifest.md new file mode 100644 index 000000000000..fd082fe3f0dc --- /dev/null +++ b/.changes/plugin-template-examples-manifest.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Add Cargo manifest files for the plugin example templates. diff --git a/.changes/pnpm-android.md b/.changes/pnpm-android.md new file mode 100644 index 000000000000..d91a2b4efca5 --- /dev/null +++ b/.changes/pnpm-android.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fix `tauri android build/dev` crashing when used with standalone `pnpm` executable on Windows. diff --git a/.changes/pnpm-support.md b/.changes/pnpm-support.md deleted file mode 100644 index 70e9ed886c17..000000000000 --- a/.changes/pnpm-support.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Adds `pnpm` support. diff --git a/.changes/pre.json b/.changes/pre.json index 9eaf60d72b18..043b4fba8bbc 100644 --- a/.changes/pre.json +++ b/.changes/pre.json @@ -1,306 +1,153 @@ { - "tag": "beta", + "tag": "alpha", "changes": [ - ".changes/allow-prevent-window-close.md", - ".changes/anonymous-lifetimes.md", - ".changes/api-cjs-chunks.md", - ".changes/api-clipboard-export.md", - ".changes/api-convert-file-url.md", - ".changes/api-export-BaseDirectory.md", - ".changes/api-export-package-json.md", - ".changes/api-export-type-fix.md", - ".changes/api-export-type.md", - ".changes/api-feature-flags.md", - ".changes/api-fix-double-window.md", - ".changes/api-focus.md", - ".changes/api-is-decorated.md", - ".changes/api-is-resizable.md", - ".changes/api-is-visible.md", - ".changes/api-os-module.md", - ".changes/api-path-module.md", - ".changes/api-request-user-attention.md", - ".changes/api-set-focus.md", - ".changes/api-set-skip-taskbar.md", - ".changes/api-skip-taskbar.md", - ".changes/api-target-es2021.md", - ".changes/api-toggle-maximize.md", - ".changes/api-transparent-window.md", - ".changes/api-ts-expect-error.md", - ".changes/api-window-center.md", - ".changes/app-callback.md", - ".changes/app-dir-refactor.md", - ".changes/app-handle-create-window.md", - ".changes/app-handle-exit-cleanup.md", - ".changes/app-handle.md", - ".changes/app-set-activation-policy.md", - ".changes/app-state.md", - ".changes/appwindow-events.md", - ".changes/asset-path.md", - ".changes/asset-protocol.md", - ".changes/assets-refactor.md", - ".changes/async-commands.md", - ".changes/async-runtime-handle-api.md", - ".changes/async-runtime-spawn-returns-join-handle.md", - ".changes/attohttpc-default-client.md", - ".changes/beta.md", - ".changes/build-on-m1.md", - ".changes/bundle-deb-installed-size.md", - ".changes/bundler-arm.md", - ".changes/bundler-docs.md", - ".changes/bundler-merge-info-plist.md", - ".changes/bundler-msi-upgrade-same-version.md", - ".changes/bundler-not-copy-resources-outdir.md", - ".changes/bundler-package-types.md", - ".changes/bundler-runtime-target-arch.md", - ".changes/bundler-windows-icon-path.md", - ".changes/bundler-windows-uninstaller.md", - ".changes/bundler-wix-banner-icon.md", - ".changes/bundler-wix-dialog-image.md", - ".changes/cargo-cache.md", - ".changes/child-process-cleanup.md", - ".changes/cli-error-logging.md", - ".changes/cli-error-message.md", - ".changes/cli-init-infer-prompts.md", - ".changes/cli-rs-info-webview2.md", - ".changes/cli-rs-macos-signing-id-env.md", - ".changes/cli-rs-powerhsell-no-profile.md", - ".changes/cli-runner-arg.md", - ".changes/cli-target-triple.md", - ".changes/cli-targets-refactor.md", - ".changes/cli.js-deps-update-manifest.md", - ".changes/cli.js-empty-args.md", - ".changes/cli.js-error-propagation.md", - ".changes/cli.js-es-module.md", - ".changes/cli.js-package-check.md", - ".changes/cli.js-proxy.md", - ".changes/cli.js-rustup.md", - ".changes/cli.rs-dev-workspaces.md", - ".changes/cli.rs-features-arg.md", - ".changes/cli.rs-fix-cli.yml-assertion.md", - ".changes/cli.rs-libwebkit2gtk-4.0-37.md", - ".changes/cli.rs-release-arg.md", - ".changes/cli.rs-wix-banner-icon.md", - ".changes/cli.rs-wix-dialog-image.md", - ".changes/clipboard-api.md", - ".changes/cmd-invoke-binding.md", - ".changes/cmd-touch-bindings.md", - ".changes/command-api-module.md", - ".changes/command-app-handle.md", - ".changes/command-generics.md", - ".changes/command-macros-binding-refactor.md", - ".changes/command-message-blocking.md", - ".changes/command-options.md", - ".changes/command-return.md", - ".changes/command-state.md", - ".changes/command-status-and-output.md", - ".changes/config-mut-getter.md", - ".changes/config-package-info-getters.md", - ".changes/context-mutable-methods.md", - ".changes/core-asset-fallback.md", - ".changes/core-drag-region-resizable.md", - ".changes/core-features.md", - ".changes/core-notification-typo.md", - ".changes/core-request-user-attention.md", - ".changes/core-show-hide-menu.md", - ".changes/core-webview-events.md", - ".changes/create-window-refactor.md", - ".changes/csp-self.md", - ".changes/csp.md", - ".changes/cta-added-angular-cli.md", - ".changes/cta-explicitly-install-vite.md", - ".changes/cta-pnpm-support.md", - ".changes/cta-prompt-api.md", - ".changes/cta-shift-and-type.md", - ".changes/cta-svelte-recipe.md", - ".changes/cta-test-script.md", - ".changes/cta-testing-suite.md", - ".changes/cta-update-vite-package.md", - ".changes/cta-vite-before-dev.md", - ".changes/cta-vite-esbuild-install-direct.md", - ".changes/cta-vite-templates.md", - ".changes/cta-welcome-prompt-and-links.md", - ".changes/dblclick-tauri-drag-maximize.md", - ".changes/debian-depends.md", - ".changes/default-params-type.md", - ".changes/dev-copy-resources.md", - ".changes/dev-path-dist-dir-array.md", - ".changes/dev-path-dist-dir-validation.md", - ".changes/dialog-ask-message-parent.md", - ".changes/dialog-parent.md", - ".changes/dialog-thread-handling.md", - ".changes/dmg-bundle-fixes.md", - ".changes/doc-tauri-utils.md", - ".changes/drag-and-drop-config.md", - ".changes/drag-region-refactor.md", - ".changes/drag-window-api.md", - ".changes/dynamic-plugin.md", - ".changes/embed-plist.md", - ".changes/emit-window-events.md", - ".changes/event-refactor.md", - ".changes/exit-requested-event.md", - ".changes/features-support.md", - ".changes/fix-asset-loading-vue-router.md", - ".changes/fix-asset-loading.md", - ".changes/fix-attribute-drag-region.md", - ".changes/fix-before-dev-command-kill.md", - ".changes/fix-bundle-targets-config.md", - ".changes/fix-bundler-platform-detection.md", - ".changes/fix-cli-target-set.md", - ".changes/fix-cli.rs-bundle-arg.md", - ".changes/fix-command-event-channel.md", - ".changes/fix-command-named-cmd.md", - ".changes/fix-create-window-str.md", - ".changes/fix-custom-protocol.md", - ".changes/fix-data-tauri-drag-region.md", - ".changes/fix-dialog-allowlist..md", - ".changes/fix-dialog-save-default-path-linux.md", - ".changes/fix-dmg-volume-icon.md", - ".changes/fix-drag-and-drop.md", - ".changes/fix-esm-detection.md", - ".changes/fix-event-queue.md", - ".changes/fix-file-drop-events.md", - ".changes/fix-global-tauri.md", - ".changes/fix-http-binary-response.md", - ".changes/fix-http-resolve-error.md", - ".changes/fix-javascript-iife-esm-rewrite.md", - ".changes/fix-listen_global-not-getting-events.md", - ".changes/fix-macos-updater.md", - ".changes/fix-min-height-typo.md", - ".changes/fix-monitor-api.md", - ".changes/fix-notifications-on-windows.md", - ".changes/fix-reqwest-headers.md", - ".changes/fix-svg-mime-type.md", - ".changes/fix-unlisten-js.md", - ".changes/fix-url-concatenation.md", - ".changes/fix-window-config-center-focus.md", - ".changes/fix-window-events.md", - ".changes/fix-window-get-all.md", - ".changes/fix-window-getter-deadlock.md", - ".changes/fix-windows-info-cmd.md", - ".changes/fix-wix-resources.md", - ".changes/focus.md", - ".changes/fs-all-feature-base64-crate.md", - ".changes/global-shortcut-refactor.md", - ".changes/gtk-window.md", - ".changes/gtk14.md", - ".changes/header-value-bytes.md", - ".changes/hide-phf.md", - ".changes/hotkey-0.1.2.md", - ".changes/icon-png-support.md", - ".changes/implement-debug.md", - ".changes/improve-api-types.md", - ".changes/improve-esm-detection.md", - ".changes/inject-invoke-key-module-script.md", - ".changes/internal-default-args.md", - ".changes/invoke-key-performance.md", - ".changes/is-decorated.md", - ".changes/is-resizable.md", - ".changes/is-visible.md", - ".changes/linux-app-name.md", - ".changes/linux-window-icon.md", - ".changes/linuxdeploy.md", - ".changes/locks.md", - ".changes/mac-cpu.md", - ".changes/main-events-cleared-and-resumed.md", - ".changes/menu-export.md", - ".changes/menu-item-accelerator.md", - ".changes/menu-refactor.md", - ".changes/menu.md", - ".changes/menuhash-fix.md", - ".changes/msi-license.md", - ".changes/non-exhaustive.md", - ".changes/notification-permission.md", - ".changes/panic-dispatcher-getter-main-thread.md", - ".changes/path-resolver.md", - ".changes/percent-decode-asset-protocol.md", - ".changes/platform-specific-config.md", - ".changes/plugin-initialize-app-handle.md", - ".changes/plugin-refactor.md", - ".changes/pnpm-support.md", - ".changes/print.md", - ".changes/private-context.md", - ".changes/process-api.md", - ".changes/product-name-original.md", - ".changes/raw-window-handle-dependency.md", - ".changes/readd-esm-cli.js.md", - ".changes/refactor-register-uri-scheme-protocol.md", - ".changes/refactor-settings.md", - ".changes/refactor-window-management.md", - ".changes/regular-script-invoke-key-injection.md", - ".changes/remove-api-modules.md", - ".changes/remove-image-crate.md", - ".changes/remove-menu-flag.md", - ".changes/remove-salt.md", - ".changes/remove-window-on-exit.md", - ".changes/remove-with-window.md", - ".changes/resources-dir-refactor.md", - ".changes/restart-application.md", - ".changes/revert-cli.js-es-module.md", - ".changes/revert-es2021.md", - ".changes/rpc-security.md", - ".changes/run-iteration.md", - ".changes/run-on-main-thread-refactor.md", - ".changes/runtime-crate.md", - ".changes/runtime-global-shortcut.md", - ".changes/runtime-main-events-cleared-and-resumed.md", - ".changes/runtime-request-user-attention.md", - ".changes/runtime-run-iteration.md", - ".changes/runtime-set-activation-policy.md", - ".changes/runtime-show-hide-menu.md", - ".changes/rust-1.54.md", - ".changes/set-focus.md", - ".changes/set-skip-taskbar.md", - ".changes/setup-error-send.md", - ".changes/setup-fix.md", - ".changes/shell-command-output-order.md", - ".changes/simplify-handle-event-loop.md", - ".changes/simplify-tag-label-usage.md", - ".changes/skip-taskbar.md", - ".changes/support-dep-formats.md", - ".changes/system-tray-refactor.md", - ".changes/system-tray-usage-fix.md", - ".changes/tauri-build-icon-path.md", - ".changes/tauri-data-directory.md", - ".changes/tauri-driver.md", - ".changes/tauri-empty-window.md", - ".changes/tauri-expose-shortcut-clipboard.md", - ".changes/tauri-icon-fix.md", - ".changes/tauri-info-framework-bundler.md", - ".changes/tauri-macos-tray-icon-template.md", - ".changes/tauri-protocol.md", - ".changes/tauri-ready-event.md", - ".changes/tauri-updater-linux.md", - ".changes/tauri-updater-windows.md", - ".changes/tauri-wry-migrate.md", - ".changes/tauri-wry.md", - ".changes/template-csp-change.md", - ".changes/throw-error-on-cli-download-failure.md", - ".changes/transparency-fix.md", - ".changes/tray.md", - ".changes/try-state.md", - ".changes/uri-parse.md", - ".changes/weak-typing.md", - ".changes/webdriver.md", - ".changes/webview-window-constructor-pub.md", - ".changes/webview-window-extends-window-manager.md", - ".changes/window-api-validations.md", - ".changes/window-attributes-rename.md", - ".changes/window-center.md", - ".changes/window-create-refactor.md", - ".changes/window-events.md", - ".changes/window-getters.md", - ".changes/window-label-unique.md", - ".changes/window-parent-and-owner.md", - ".changes/window-parent.md", - ".changes/window-send-sync.md", - ".changes/windows-protocol-path.md", - ".changes/windows-resources-set-values.md", - ".changes/windows-shortcut.md", - ".changes/wix-bundle-language.md", - ".changes/wix-config-language.md", - ".changes/wix-resource-bundling-fix.md", - ".changes/wix-uninstall-skip-uac-task.md", - ".changes/wix-update-on-windows-without-uac.md", - ".changes/wry-update.md" + ".changes/add-mobile-to-plugin.md", + ".changes/android-apis-runtime.md", + ".changes/android-buildsrc-gitignore.md", + ".changes/android-enhance-method-parse.md", + ".changes/android-load-config.md", + ".changes/android-plugin-command-exception.md", + ".changes/build-android-env-vars.md", + ".changes/bump-1.3.md", + ".changes/bundler-remove-dialog-option.md", + ".changes/channel-api.md", + ".changes/cli-android-build.md", + ".changes/cli-android-dev-release.md", + ".changes/cli-android-specified-targets-only.md", + ".changes/cli-built-in-dev-server-mobile.md", + ".changes/cli-ios-build.md", + ".changes/cli-key-properties.md", + ".changes/cli-libname-dashes.md", + ".changes/cli-mobile-auto-ip.md", + ".changes/cli-mobile-dev.md", + ".changes/cli-mobile-plugin.md", + ".changes/cli-nodejs-detection.md", + ".changes/cli-pnpm.md", + ".changes/cli-refactor-ipc-mobile.md", + ".changes/cli-wry-0-28.md", + ".changes/codegen-mobile-devurl.md", + ".changes/core-wry-0-28.md", + ".changes/default-tls-features.md", + ".changes/default-window-icon.md", + ".changes/dev-proxy-response-cache.md", + ".changes/dev-proxy.md", + ".changes/downgrade-min-sdk-version.md", + ".changes/dynamic-wry-plugin.md", + ".changes/enable-minify.md", + ".changes/enhance-jsobject-return-types.md", + ".changes/error-on-identifier-change.md", + ".changes/fix-build-script-mobile-runner-npm.md", + ".changes/fix-dev-server-proxy-path.md", + ".changes/fix-empty-identifier.md", + ".changes/fix-ios-plugin-throws-command.md", + ".changes/fix-ios-run-xcode14.md", + ".changes/fix-ios-template.md", + ".changes/fix-mobile-env-vars.md", + ".changes/fix-nodejs-android-cmds.md", + ".changes/fix-orientation-crash.md", + ".changes/fix-plugin-ios-bool.md", + ".changes/fix-plugin-removal.md", + ".changes/fix-plugin-template-cargotoml.md", + ".changes/fix-proguard-rules.md", + ".changes/fix-shell-build.md", + ".changes/fix-tauri-binary-windows.md", + ".changes/fix-wix-escape-resources.md", + ".changes/fix-xcodescript-lib-path.md", + ".changes/force-colored-logs.md", + ".changes/generate-tauri-activity.md", + ".changes/gradle-8.md", + ".changes/gtk16.md", + ".changes/improve-local-ip-detection.md", + ".changes/improve-mobile-plugin-error-handling.md", + ".changes/inject-config.md", + ".changes/inject-proguard.md", + ".changes/invoke-handler-attributes.md", + ".changes/invoke-return-bool.md", + ".changes/ios-deployment-target.md", + ".changes/ios-icon-color.md", + ".changes/ios-keep-alive.md", + ".changes/ios-logs.md", + ".changes/ipc-scope-remove-enable-tauri-api.md", + ".changes/lib-name-xcode.md", + ".changes/local-dev-path-mobile.md", + ".changes/logcat-all-tags.md", + ".changes/min-sdk-version.md", + ".changes/mobile-config.md", + ".changes/mobile-dev-watcher-ignore-gen.md", + ".changes/mobile-entry-point-macro.md", + ".changes/mobile-env-vars-rename.md", + ".changes/mobile-init.md", + ".changes/mobile-lib-name.md", + ".changes/mobile-open.md", + ".changes/mobile-plugin-config.md", + ".changes/mobile-plugins.md", + ".changes/mobile-webview-access.md", + ".changes/mobile.md", + ".changes/move-app.md", + ".changes/move-cli.md", + ".changes/move-dialog-plugin.md", + ".changes/move-event.md", + ".changes/move-fs.md", + ".changes/move-http-api.md", + ".changes/move-os.md", + ".changes/move-process.md", + ".changes/move-protocol-asset.md", + ".changes/move-shell.md", + ".changes/move-updater-config.md", + ".changes/move-updater.md", + ".changes/msrv-1.64.md", + ".changes/msrv-1.65.md", + ".changes/napi-rs.md", + ".changes/npm-pass-args.md", + ".changes/nsis-spanish.md", + ".changes/nsis-webview-installmodes.md", + ".changes/on-new-intent.md", + ".changes/only-proxy-on-mobile.md", + ".changes/open-ts-overload.md", + ".changes/package-info-crate-name.md", + ".changes/plugin-android-project-refactor.md", + ".changes/plugin-api-handle.md", + ".changes/plugin-handle-clone.md", + ".changes/plugin-init-fns.md", + ".changes/plugin-init-refactor.md", + ".changes/plugin-setup-refactor.md", + ".changes/plugin-template-examples-manifest.md", + ".changes/pnpm-android.md", + ".changes/process-mod-refactor.md", + ".changes/raw-encoding.md", + ".changes/refactor-macros.md", + ".changes/refactor-setup.md", + ".changes/refactor-tauri-android-dependency.md", + ".changes/remove-allowlist.md", + ".changes/remove-attohttpc.md", + ".changes/remove-clipboard.md", + ".changes/remove-fs-apis.md", + ".changes/remove-global-shortcut.md", + ".changes/remove-macros-command-module.md", + ".changes/remove-mobile-log.md", + ".changes/remove-sdk-dir.md", + ".changes/remove-shell-constructor.md", + ".changes/remove-tray-icon-mobile.md", + ".changes/remove-updater-dialog.md", + ".changes/remove-updater-event.md", + ".changes/remove-window.md", + ".changes/rfd101.md", + ".changes/run-mobile-plugin.md", + ".changes/safepathbuf-refactor.md", + ".changes/shadow-api.md", + ".changes/shadow-config.md", + ".changes/shadow.md", + ".changes/shadows-default-on.md", + ".changes/simplify-ios-plugin-init-fn.md", + ".changes/target-dir-detection.md", + ".changes/tauri-build-mobile.md", + ".changes/tauri-mobile-entry-point.md", + ".changes/tauri-runtime-wry-wry-0-28.md", + ".changes/tls-features-automatically-enabled.md", + ".changes/tls-features-refactor.md", + ".changes/ubuntu-20.04-cli.js.md", + ".changes/ubuntu-20.04-cli.rs.md", + ".changes/with-webview.md", + ".changes/wry26.md" ] } diff --git a/.changes/prevent-path-traversal.md b/.changes/prevent-path-traversal.md deleted file mode 100644 index f7e83be938f9..000000000000 --- a/.changes/prevent-path-traversal.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Prevent path traversal on the file system APIs. diff --git a/.changes/print.md b/.changes/print.md deleted file mode 100644 index 62db4e424169..000000000000 --- a/.changes/print.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -`window.print()` now works on all platforms. diff --git a/.changes/private-api.md b/.changes/private-api.md deleted file mode 100644 index 1db7aabeb898..000000000000 --- a/.changes/private-api.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime-wry": patch -"cli.rs": patch ---- - -Add `macos-private-api` feature flag, enabled via `tauri.conf.json > tauri > macOSPrivateApi`. diff --git a/.changes/private-context.md b/.changes/private-context.md deleted file mode 100644 index 315cd2916224..000000000000 --- a/.changes/private-context.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -**Breaking:** `Context` fields are now private, and is expected to be created through `Context::new(...)`. -All fields previously available through `Context` are now public methods. diff --git a/.changes/process-api.md b/.changes/process-api.md deleted file mode 100644 index 4a29c35efdbb..000000000000 --- a/.changes/process-api.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Move `exit` and `relaunch` APIs from `app` to `process` module. diff --git a/.changes/process-mod-refactor.md b/.changes/process-mod-refactor.md new file mode 100644 index 000000000000..47517cff796c --- /dev/null +++ b/.changes/process-mod-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Moved the `tauri::api::process` module to `tauri::process`. diff --git a/.changes/product-name-original.md b/.changes/product-name-original.md deleted file mode 100644 index 6a7f48a93cac..000000000000 --- a/.changes/product-name-original.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-utils": patch ---- - -Keep original value on `config > package > productName` on Linux (previously converted to kebab-case). diff --git a/.changes/raw-encoding.md b/.changes/raw-encoding.md new file mode 100644 index 000000000000..8c43cfa78f6c --- /dev/null +++ b/.changes/raw-encoding.md @@ -0,0 +1,6 @@ +--- +"api": 'minor:feat' +"tauri": 'minor:feat' +--- + +Added `raw` encoding option to read stdout and stderr raw bytes. diff --git a/.changes/raw-headers.md b/.changes/raw-headers.md deleted file mode 100644 index 87ea3e7d4137..000000000000 --- a/.changes/raw-headers.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Add `raw_headers` to `tauri::api::http::ResponseData`. diff --git a/.changes/raw-window-handle-dependency.md b/.changes/raw-window-handle-dependency.md deleted file mode 100644 index f8d4ef3b0887..000000000000 --- a/.changes/raw-window-handle-dependency.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fix `raw-window-handle` dependency declaration. diff --git a/.changes/raw-window-handle.md b/.changes/raw-window-handle.md deleted file mode 100644 index ffa0856fdd74..000000000000 --- a/.changes/raw-window-handle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Implement `raw_window_handle::RawWindowHandle` for `tauri::Window` on `Windows` and `macOS`. The `tauri::api::dialog::window_parent` function was removed since now you can use the window directly. diff --git a/.changes/readd-esm-cli.js.md b/.changes/readd-esm-cli.js.md deleted file mode 100644 index 6f7e16ba91c0..000000000000 --- a/.changes/readd-esm-cli.js.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -The CLI is now an ES module and requires at least Node.js v12.20. Fixed previous releases by using Rollup instead of Webpack. diff --git a/.changes/readme.md b/.changes/readme.md index fe4afd7d62c2..318eea024e77 100644 --- a/.changes/readme.md +++ b/.changes/readme.md @@ -2,7 +2,7 @@ ##### via https://github.com/jbolda/covector -As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend it represents the overall change for our sanity. +As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend that it represents the overall change for organizational purposes. When you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process. @@ -10,15 +10,8 @@ Use the following format: ```md --- -"api": patch -"tauri-bundler": patch -"cli.rs": patch -"cli.js": patch -"tauri-utils": patch -"tauri-macros": patch -"tauri-build": patch -"tauri-codegen": patch -"tauri": patch +'package-a': 'patch:enhance' +'package-b': 'patch:enhance' --- Change summary goes here @@ -35,3 +28,17 @@ Given a version number MAJOR.MINOR.PATCH, increment the: - PATCH version when you make backwards compatible bug fixes. Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing). + +Additionally you could specify a tag for the change file to group it with other changes by prefixing the bump with `:`, for example: + +```md +--- +'package-a': 'patch:enhance' +--- + +Change summary goes here +``` + +which will group this change file with other changes that specify the `bug` tag. + +For list of available tags, see the `changeTags` key in [./config.json](./config.json) diff --git a/.changes/refactor-create-tao-window.md b/.changes/refactor-create-tao-window.md deleted file mode 100644 index 8bad9c78b84c..000000000000 --- a/.changes/refactor-create-tao-window.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch -"tauri": patch ---- - -Refactor `create_tao_window` API to return `Weak` instead of `Arc`. diff --git a/.changes/refactor-dialog-apis.md b/.changes/refactor-dialog-apis.md deleted file mode 100644 index d78dbeda50d4..000000000000 --- a/.changes/refactor-dialog-apis.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Added the `tauri::api::dialog::blocking` module. diff --git a/.changes/refactor-macros.md b/.changes/refactor-macros.md new file mode 100644 index 000000000000..49b3fb4ed36f --- /dev/null +++ b/.changes/refactor-macros.md @@ -0,0 +1,6 @@ +--- +"tauri-macros": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Refactored the implementation of the `mobile_entry_point` macro. diff --git a/.changes/refactor-notification-permission-check.md b/.changes/refactor-notification-permission-check.md deleted file mode 100644 index 3c16e2f2b5b3..000000000000 --- a/.changes/refactor-notification-permission-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The notification endpoint now checks for the permission flag and requests if the value is not set. diff --git a/.changes/refactor-register-uri-scheme-protocol.md b/.changes/refactor-register-uri-scheme-protocol.md deleted file mode 100644 index f04210b75ae6..000000000000 --- a/.changes/refactor-register-uri-scheme-protocol.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch ---- - -**Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttibutes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure. diff --git a/.changes/refactor-resources-sidecar-copying.md b/.changes/refactor-resources-sidecar-copying.md deleted file mode 100644 index b20407f19acd..000000000000 --- a/.changes/refactor-resources-sidecar-copying.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"cli.rs": patch -"tauri-bundler": patch -"tauri-utils": patch -"tauri-build": patch ---- - -Move the copying of resources and sidecars from `cli.rs` to `tauri-build` so using the Cargo CLI directly processes the files for the application execution in development. diff --git a/.changes/refactor-settings.md b/.changes/refactor-settings.md deleted file mode 100644 index 58a4776ee59d..000000000000 --- a/.changes/refactor-settings.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -`Settings` is now serialized using `bincode`. diff --git a/.changes/refactor-setup.md b/.changes/refactor-setup.md new file mode 100644 index 000000000000..584392242468 --- /dev/null +++ b/.changes/refactor-setup.md @@ -0,0 +1,5 @@ +--- +"tauri": major:feat +--- + +**Breaking change:** The window creation and setup hook are now called when the event loop is ready. diff --git a/.changes/refactor-tauri-android-dependency.md b/.changes/refactor-tauri-android-dependency.md new file mode 100644 index 000000000000..a0bd094ab688 --- /dev/null +++ b/.changes/refactor-tauri-android-dependency.md @@ -0,0 +1,6 @@ +--- +"tauri": 'patch:enhance' +"tauri-build": 'patch:enhance' +--- + +Changed how the `tauri-android` dependency is injected. This requires the `gen/android` project to be recreated. diff --git a/.changes/refactor-window-event-close-requested.md b/.changes/refactor-window-event-close-requested.md deleted file mode 100644 index 1c5219b14ed4..000000000000 --- a/.changes/refactor-window-event-close-requested.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change:** The `WindowEvent::CloseRequested` variant now includes `label` and `signal_tx` fields to allow preventing closing the window. diff --git a/.changes/refactor-window-management.md b/.changes/refactor-window-management.md deleted file mode 100644 index d75d406eb9d3..000000000000 --- a/.changes/refactor-window-management.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": patch -"tauri": patch ---- - -The window management API was refactored: removed `setX`, `setY`, `setWidth`, `setHeight` APIs, renamed `resize` to `setSize` and the size and position APIs now allow defining both logical and physical values. diff --git a/.changes/refactor-window-metadata.md b/.changes/refactor-window-metadata.md deleted file mode 100644 index ba23c2abce9e..000000000000 --- a/.changes/refactor-window-metadata.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": "patch" ---- - -**Breaking change:** Move `__currentWindow` and `__windows` values from `window.__TAURI__` to `window.__TAURI_METADATA__`. diff --git a/.changes/regular-script-invoke-key-injection.md b/.changes/regular-script-invoke-key-injection.md deleted file mode 100644 index 25bb64b18781..000000000000 --- a/.changes/regular-script-invoke-key-injection.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-utils": patch -"tauri": patch ---- - -Inject the invoke key on regular `` tags. diff --git a/.changes/remove-allowlist.md b/.changes/remove-allowlist.md new file mode 100644 index 000000000000..55cf32fb82ee --- /dev/null +++ b/.changes/remove-allowlist.md @@ -0,0 +1,8 @@ +--- +"tauri": 'patch:enhance' +"tauri-utils": 'patch:enhance' +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Removed the allowlist configuration. diff --git a/.changes/remove-api-modules.md b/.changes/remove-api-modules.md deleted file mode 100644 index da25d60e7136..000000000000 --- a/.changes/remove-api-modules.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Removed the `tcp` module from `tauri::api`. diff --git a/.changes/remove-attohttpc.md b/.changes/remove-attohttpc.md new file mode 100644 index 000000000000..239701a7fb27 --- /dev/null +++ b/.changes/remove-attohttpc.md @@ -0,0 +1,5 @@ +--- +"tauri": major:feat +--- + +Removed the attohttpc client. The `reqwest-*` Cargo features were also removed. diff --git a/.changes/remove-clipboard.md b/.changes/remove-clipboard.md new file mode 100644 index 000000000000..4469be99acf8 --- /dev/null +++ b/.changes/remove-clipboard.md @@ -0,0 +1,8 @@ +--- +"tauri": 'patch:enhance' +"tauri-runtime": 'patch:enhance' +"tauri-runtime-wry": 'patch:enhance' +"api": 'patch:enhance' +--- + +Moved the `clipboard` feature to its own plugin in the plugins-workspace repository. diff --git a/.changes/remove-current-base-directory.md b/.changes/remove-current-base-directory.md deleted file mode 100644 index 8413cce8927d..000000000000 --- a/.changes/remove-current-base-directory.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"api": patch ---- - -Remove the `BaseDirectory::Current` enum variant for security reasons. diff --git a/.changes/remove-fs-apis.md b/.changes/remove-fs-apis.md new file mode 100644 index 000000000000..4c684e94113a --- /dev/null +++ b/.changes/remove-fs-apis.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Removed extract and move APIs from `tauri::api::file`. diff --git a/.changes/remove-global-shortcut.md b/.changes/remove-global-shortcut.md new file mode 100644 index 000000000000..67017fd1e694 --- /dev/null +++ b/.changes/remove-global-shortcut.md @@ -0,0 +1,8 @@ +--- +"api": 'patch:enhance' +"tauri": 'patch:enhance' +"tauri-runtime": 'patch:enhance' +"tauri-runtime-wry": 'patch:enhance' +--- + +Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository. diff --git a/.changes/remove-image-crate.md b/.changes/remove-image-crate.md deleted file mode 100644 index 91b5bdfc6be6..000000000000 --- a/.changes/remove-image-crate.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime-wry": patch ---- - -Removes `image` dependency. For now only `.ico` icons on Windows are supported, and we'll implement other types on demand to optimize bundle size. diff --git a/.changes/remove-macros-command-module.md b/.changes/remove-macros-command-module.md new file mode 100644 index 000000000000..fdf72026796b --- /dev/null +++ b/.changes/remove-macros-command-module.md @@ -0,0 +1,5 @@ +--- +"tauri-macros": 'patch:enhance' +--- + +Removed the module command macros. diff --git a/.changes/remove-menu-flag.md b/.changes/remove-menu-flag.md deleted file mode 100644 index 42563fd7c255..000000000000 --- a/.changes/remove-menu-flag.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": minor -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Remove menu feature flag since there's no package dependency need to be installed on any platform anymore. diff --git a/.changes/remove-mobile-log.md b/.changes/remove-mobile-log.md new file mode 100644 index 000000000000..3f02355e816b --- /dev/null +++ b/.changes/remove-mobile-log.md @@ -0,0 +1,7 @@ +--- +"tauri": 'patch:enhance' +"tauri-macros": 'patch:enhance' +"tauri-build": 'patch:enhance' +--- + +Removed mobile logging initialization, which will be handled by `tauri-plugin-log`. diff --git a/.changes/remove-salt.md b/.changes/remove-salt.md deleted file mode 100644 index 31729dd0f1b2..000000000000 --- a/.changes/remove-salt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Remove salt-related APIs (no longer needed after the `__TAURI_INVOKE_KEY__` implementation). diff --git a/.changes/remove-sdk-dir.md b/.changes/remove-sdk-dir.md new file mode 100644 index 000000000000..c4150a9257c6 --- /dev/null +++ b/.changes/remove-sdk-dir.md @@ -0,0 +1,5 @@ +--- +"tauri-build": 'patch:enhance' +--- + +Remove `WindowsAttributes::sdk_dir`. diff --git a/.changes/remove-shell-constructor.md b/.changes/remove-shell-constructor.md new file mode 100644 index 000000000000..c8c4b0bbdc1e --- /dev/null +++ b/.changes/remove-shell-constructor.md @@ -0,0 +1,5 @@ +--- +"api": 'minor:feat' +--- + +Removed shell's `Command` constructor and added the `Command.create` static function instead. diff --git a/.changes/remove-tauricon.md b/.changes/remove-tauricon.md deleted file mode 100644 index 658eb068ae5e..000000000000 --- a/.changes/remove-tauricon.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Removed the `icon` command, now exposed as a separate package, see https://github.com/tauri-apps/tauricon. diff --git a/.changes/remove-tray-icon-mobile.md b/.changes/remove-tray-icon-mobile.md new file mode 100644 index 000000000000..2ab793e0e0c9 --- /dev/null +++ b/.changes/remove-tray-icon-mobile.md @@ -0,0 +1,6 @@ +--- +"tauri-codegen": 'patch:enhance' +"tauri": 'patch:enhance' +--- + +Refactor the `Context` conditional fields and only parse the tray icon on desktop. diff --git a/.changes/remove-updater-dialog.md b/.changes/remove-updater-dialog.md new file mode 100644 index 000000000000..bf12438c45a0 --- /dev/null +++ b/.changes/remove-updater-dialog.md @@ -0,0 +1,6 @@ +--- +"tauri": 'patch:enhance' +"tauri-utils": 'patch:enhance' +--- + +Remove the updater's dialog option. diff --git a/.changes/remove-updater-event.md b/.changes/remove-updater-event.md new file mode 100644 index 000000000000..fcaa80e4e492 --- /dev/null +++ b/.changes/remove-updater-event.md @@ -0,0 +1,6 @@ +--- +"tauri": 'patch:enhance' +--- + +Removed `UpdaterEvent`. See `tauri-plugin-updater` for new usage. + diff --git a/.changes/remove-window-on-exit.md b/.changes/remove-window-on-exit.md deleted file mode 100644 index 13b70977cbb0..000000000000 --- a/.changes/remove-window-on-exit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Remove window object from the `Manager` internal `HashMap` on close. This fixes the behavior of using `[App|AppHandle|Window]#get_window` after the window is closed (now correctly returns `None`). diff --git a/.changes/remove-window.md b/.changes/remove-window.md new file mode 100644 index 000000000000..15662c88eae9 --- /dev/null +++ b/.changes/remove-window.md @@ -0,0 +1,6 @@ +--- +"tauri": 'patch:enhance' +"api": 'patch:enhance' +--- + +Moved the `window` JS APIs to its own plugin in the plugins-workspace repository. diff --git a/.changes/remove-with-window.md b/.changes/remove-with-window.md deleted file mode 100644 index 36cabc9ca931..000000000000 --- a/.changes/remove-with-window.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `CommandArg` trait. diff --git a/.changes/resources-dir-refactor.md b/.changes/resources-dir-refactor.md deleted file mode 100644 index 347c0a4086da..000000000000 --- a/.changes/resources-dir-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-utils": patch ---- - -The `platform::resource_dir` API now takes the `PackageInfo`. diff --git a/.changes/restart-application.md b/.changes/restart-application.md deleted file mode 100644 index a9e3aa3b5678..000000000000 --- a/.changes/restart-application.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Move `restart_application` API from `app` module to `process` module. diff --git a/.changes/revert-cli.js-es-module.md b/.changes/revert-cli.js-es-module.md deleted file mode 100644 index c99ddb127c2b..000000000000 --- a/.changes/revert-cli.js-es-module.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Reverts ESM distribution. diff --git a/.changes/revert-es2021.md b/.changes/revert-es2021.md deleted file mode 100644 index 094a24b825e4..000000000000 --- a/.changes/revert-es2021.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Revert target back to ES5. diff --git a/.changes/rfd101.md b/.changes/rfd101.md new file mode 100644 index 000000000000..78feeb53d28f --- /dev/null +++ b/.changes/rfd101.md @@ -0,0 +1,6 @@ +--- +"tauri": 'minor:feat' +--- + +Update rfd to 0.11. + diff --git a/.changes/rpc-mod-refactor.md b/.changes/rpc-mod-refactor.md deleted file mode 100644 index 6f54a83845a8..000000000000 --- a/.changes/rpc-mod-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change:** Renamed the `rpc` module to `ipc`. diff --git a/.changes/rpc-security.md b/.changes/rpc-security.md deleted file mode 100644 index f584b4162c46..000000000000 --- a/.changes/rpc-security.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": patch -"tauri": patch ---- - -Improve RPC security by requiring a numeric code to invoke commands. The codes are generated by the Rust side and injected into the app's code using a closure, so external scripts can't access the backend. This change doesn't protect `withGlobalTauri` (`window.__TAURI__`) usage. diff --git a/.changes/run-iteration.md b/.changes/run-iteration.md deleted file mode 100644 index ecf43ea6d9a6..000000000000 --- a/.changes/run-iteration.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `run_iteration` API to the `App` and return the app instance on the `build` method of the `Builder`. The `run_iteration` method runs the window event loop step by step, allowing Tauri to be run along other applications. diff --git a/.changes/run-mobile-plugin.md b/.changes/run-mobile-plugin.md new file mode 100644 index 000000000000..1d18a0b48b0b --- /dev/null +++ b/.changes/run-mobile-plugin.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Added `App::run_mobile_plugin` and `AppHandle::run_mobile_plugin`. diff --git a/.changes/run-on-main-thread-refactor.md b/.changes/run-on-main-thread-refactor.md deleted file mode 100644 index 541cd5f51930..000000000000 --- a/.changes/run-on-main-thread-refactor.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch -"tauri": patch ---- - -The `run_on_main_thread` API now uses WRY's UserEvent, so it wakes the event loop. diff --git a/.changes/run-on-main-thread.md b/.changes/run-on-main-thread.md deleted file mode 100644 index 5af644d68554..000000000000 --- a/.changes/run-on-main-thread.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Expose `run_on_main_thread` APIs on `Window` and `AppHandle`. diff --git a/.changes/runtime-any-thread.md b/.changes/runtime-any-thread.md deleted file mode 100644 index f0b20ccec620..000000000000 --- a/.changes/runtime-any-thread.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Added `any_thread` constructor on the `Runtime` trait (only possible on Linux and Windows). diff --git a/.changes/runtime-crate.md b/.changes/runtime-crate.md deleted file mode 100644 index 4df240148d53..000000000000 --- a/.changes/runtime-crate.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": minor -"tauri": patch ---- - -`tauri-runtime` crate initial release. diff --git a/.changes/runtime-global-shortcut.md b/.changes/runtime-global-shortcut.md deleted file mode 100644 index c6e29b688d55..000000000000 --- a/.changes/runtime-global-shortcut.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds global shortcut interfaces. diff --git a/.changes/runtime-handle-run-on-main-thread.md b/.changes/runtime-handle-run-on-main-thread.md deleted file mode 100644 index 06854acdc229..000000000000 --- a/.changes/runtime-handle-run-on-main-thread.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": "patch" -"tauri-runtime-wry": patch ---- - -Added `run_on_main_thread` API on `RuntimeHandle`. diff --git a/.changes/runtime-ipc.md b/.changes/runtime-ipc.md deleted file mode 100644 index d7c7d63ccaf0..000000000000 --- a/.changes/runtime-ipc.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -**Breaking change:** Renamed the `RPC` interface to `IPC`. diff --git a/.changes/runtime-main-events-cleared-and-resumed.md b/.changes/runtime-main-events-cleared-and-resumed.md deleted file mode 100644 index b163fe01ab2e..000000000000 --- a/.changes/runtime-main-events-cleared-and-resumed.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `Resumed` and `MainEventsCleared` variants to the `RunEvent` enum. diff --git a/.changes/runtime-open-devtools.md b/.changes/runtime-open-devtools.md deleted file mode 100644 index 72d31a52d2d1..000000000000 --- a/.changes/runtime-open-devtools.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Added `open_devtools` to the `Dispatcher` trait. diff --git a/.changes/runtime-request-user-attention.md b/.changes/runtime-request-user-attention.md deleted file mode 100644 index 9476e6d3c693..000000000000 --- a/.changes/runtime-request-user-attention.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `request_user_attention` API to the `Dispatcher` trait. diff --git a/.changes/runtime-run-iteration.md b/.changes/runtime-run-iteration.md deleted file mode 100644 index 38ba2a19b766..000000000000 --- a/.changes/runtime-run-iteration.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Adds `fn run_iteration` (macOS and Windows only) to the Runtime trait. diff --git a/.changes/runtime-set-activation-policy.md b/.changes/runtime-set-activation-policy.md deleted file mode 100644 index b409873c3d3d..000000000000 --- a/.changes/runtime-set-activation-policy.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Adds `set_activation_policy` API to the `Runtime` trait (macOS only). diff --git a/.changes/runtime-show-hide-menu.md b/.changes/runtime-show-hide-menu.md deleted file mode 100644 index b9835f01d23c..000000000000 --- a/.changes/runtime-show-hide-menu.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `show_menu`, `hide_menu` and `is_menu_visible` APIs to the `Dispatcher` trait. diff --git a/.changes/rust-1.54.md b/.changes/rust-1.54.md deleted file mode 100644 index e05c6a6c00e6..000000000000 --- a/.changes/rust-1.54.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"cli.rs": patch -"tauri": patch ---- - -Update minimum Rust version to 1.54.0. diff --git a/.changes/rust-1.56.0.md b/.changes/rust-1.56.0.md deleted file mode 100644 index c41f453a69e0..000000000000 --- a/.changes/rust-1.56.0.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"tauri": patch -"cli.rs": patch -"tauri-bundler": patch -"tauri-utils": patch -"tauri-macros": patch -"tauri-codegen": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch -"tauri-driver": patch -"tauri-build": patch ---- - -The minimum Rust version is now `1.56`. diff --git a/.changes/rust-1.57.md b/.changes/rust-1.57.md deleted file mode 100644 index 24cb734f547b..000000000000 --- a/.changes/rust-1.57.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The minimum Rust version is now 1.57. diff --git a/.changes/rustls-default.md b/.changes/rustls-default.md new file mode 100644 index 000000000000..19fd4de4347a --- /dev/null +++ b/.changes/rustls-default.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Add `rustls` as default Cargo feature. diff --git a/.changes/safepathbuf-refactor.md b/.changes/safepathbuf-refactor.md new file mode 100644 index 000000000000..c915f36345a3 --- /dev/null +++ b/.changes/safepathbuf-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Expose `SafePathBuf` type in `tauri::path`. diff --git a/.changes/scope-config.md b/.changes/scope-config.md deleted file mode 100644 index b364a27be192..000000000000 --- a/.changes/scope-config.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri-utils": patch ---- - -Adds `scope` glob array config under `tauri > allowlist > fs`. -Adds `assetScope` glob array config under `tauri > allowlist > protocol`. -Adds `scope` URL array config under `tauri > allowlist > http`. diff --git a/.changes/scopes.md b/.changes/scopes.md deleted file mode 100644 index 096838e7b4f5..000000000000 --- a/.changes/scopes.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"tauri": patch ---- - -Scopes the `filesystem` APIs from the webview access using `tauri.conf.json > tauri > allowlist > fs > scope`. -Scopes the `asset` protocol access using `tauri.conf.json > tauri > allowlist > protocol > assetScope`. -Scopes the `http` APIs from the webview access using `tauri.conf.json > tauri > allowlist > http > scope`. -Scopes the `shell` execute API from the webview access using `tauri.conf.json > tauri > allowlist > shell > scope`. Additionally, check the `tauri.conf.json > tauri > bundle > externalBin` to prevent access to unknown sidecars. diff --git a/.changes/set-focus.md b/.changes/set-focus.md deleted file mode 100644 index 322690c1dfff..000000000000 --- a/.changes/set-focus.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `set_focus` API on Window. diff --git a/.changes/set-skip-taskbar.md b/.changes/set-skip-taskbar.md deleted file mode 100644 index 9cd73be1a483..000000000000 --- a/.changes/set-skip-taskbar.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `set_skip_taskbar` API on Window. diff --git a/.changes/setup-error-send.md b/.changes/setup-error-send.md deleted file mode 100644 index ea45aa89dc6d..000000000000 --- a/.changes/setup-error-send.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `setup` Error type must be `Send`. diff --git a/.changes/setup-fix.md b/.changes/setup-fix.md deleted file mode 100644 index c9ebba535510..000000000000 --- a/.changes/setup-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Run the setup callback after preparing the system tray. diff --git a/.changes/setup-fn-once.md b/.changes/setup-fn-once.md deleted file mode 100644 index 7f06a05ed92a..000000000000 --- a/.changes/setup-fn-once.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": "patch" ---- - -`Builder#setup` closure type changed from `Fn` to `FnOnce`. diff --git a/.changes/shadow-api.md b/.changes/shadow-api.md new file mode 100644 index 000000000000..01b7797aad5a --- /dev/null +++ b/.changes/shadow-api.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'minor:feat' +--- + +Added the `shadow` option when creating a window and `setShadow` function. diff --git a/.changes/shadow-config.md b/.changes/shadow-config.md new file mode 100644 index 000000000000..6d01ed00d716 --- /dev/null +++ b/.changes/shadow-config.md @@ -0,0 +1,5 @@ +--- +'tauri-utils': 'minor:feat' +--- + +Added the `shadow` option to the window configuration and `set_shadow` option to the `window` allow list. diff --git a/.changes/shadow.md b/.changes/shadow.md new file mode 100644 index 000000000000..1eb40dda020c --- /dev/null +++ b/.changes/shadow.md @@ -0,0 +1,7 @@ +--- +'tauri': 'minor:feat' +'tauri-runtime-wry': 'minor:feat' +'tauri-runtime': 'minor:feat' +--- + +Added the `shadow` option when creating a window and `Window::set_shadow`. diff --git a/.changes/shadows-default-on.md b/.changes/shadows-default-on.md new file mode 100644 index 000000000000..e46ed93a8ad8 --- /dev/null +++ b/.changes/shadows-default-on.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:enhance' +--- + +Enable shadows by default. diff --git a/.changes/shell-command-output-order.md b/.changes/shell-command-output-order.md deleted file mode 100644 index b06ad0d0fe13..000000000000 --- a/.changes/shell-command-output-order.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fixes a consistency issue on the order of `tauri::process::Command` emitted events. diff --git a/.changes/shell-open-with-refactor.md b/.changes/shell-open-with-refactor.md deleted file mode 100644 index d2124c7b43f6..000000000000 --- a/.changes/shell-open-with-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `tauri::api::shell::open`'s `with` argument is now an enum value instead of any string. diff --git a/.changes/sidecar-allowlist.md b/.changes/sidecar-allowlist.md deleted file mode 100644 index f85d37c34368..000000000000 --- a/.changes/sidecar-allowlist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-utils": patch -"tauri": patch ---- - -The `shell` allowlist now includes a `sidecar` flag, which enables the use of the `shell` API to execute sidecars. diff --git a/.changes/sidecar-runtime-rename.md b/.changes/sidecar-runtime-rename.md deleted file mode 100644 index 97828c5bd674..000000000000 --- a/.changes/sidecar-runtime-rename.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-bundler": patch ---- - -**Breaking change**: The sidecar's target triple suffix is now removed at build time. diff --git a/.changes/simplify-handle-event-loop.md b/.changes/simplify-handle-event-loop.md deleted file mode 100644 index 114789dc19b2..000000000000 --- a/.changes/simplify-handle-event-loop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-runtime-wry": minor ---- - -Update `wry` to v0.10.0 and replace the removed `dispatch_script` and `evaluate_script` methods with the new `evaluate_script` method in `handle_event_loop`. diff --git a/.changes/simplify-ios-plugin-init-fn.md b/.changes/simplify-ios-plugin-init-fn.md new file mode 100644 index 000000000000..099d9e11878c --- /dev/null +++ b/.changes/simplify-ios-plugin-init-fn.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Change iOS plugin init function signature to `func init_plugin() -> Plugin`. diff --git a/.changes/simplify-tag-label-usage.md b/.changes/simplify-tag-label-usage.md deleted file mode 100644 index 38ca6a143424..000000000000 --- a/.changes/simplify-tag-label-usage.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -"tauri": patch ---- - -Simplify usage of app event and window label types. The following functions now -accept references the `Tag` can be borrowed as. This means an `&str` can now be -accepted for functions like `Window::emit`. This is a breaking change for the -following items, which now need to take a reference. Additionally, type inference -for `&"event".into()` will no longer work, but `&"event".to_string()` will. The -solution for this is to now just pass `"event"` because `Borrow` is implemented -for the default event type `String`. - -* **Breaking:** `Window::emit` now accepts `Borrow` for the event. -* **Breaking:** `Window::emit_others` now accepts `Borrow` for the event -* **Breaking:** `Window::trigger` now accepts `Borrow` for the event. -* **Breaking:** `Manager::emit_all` now accepts `Borrow` for the event. -* **Breaking:** `Manager::emit_to` now accepts `Borrow` for both the event and window label. -* **Breaking:** `Manager::trigger_global` now accepts `Borrow` for the event. -* **Breaking:** `Manager::get_window` now accepts `Borrow` for the window label. -* _(internal):_ `trait tauri::runtime::tag::TagRef` helper for accepting tag references. - Any time you want to accept a tag reference, that trait will handle requiring the reference - to have all the necessary bounds, and generate errors when the exposed function doesn't - set a bound like `P::Event: Borrow`. diff --git a/.changes/simplify-window-label-types.md b/.changes/simplify-window-label-types.md deleted file mode 100644 index 5aab845e1d81..000000000000 --- a/.changes/simplify-window-label-types.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Change `WindowLabel` type to `string`. diff --git a/.changes/skip-target-install-arg.md b/.changes/skip-target-install-arg.md new file mode 100644 index 000000000000..98e96702cf69 --- /dev/null +++ b/.changes/skip-target-install-arg.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:enhance' +'@tauri-apps/cli': 'patch:enhance' +--- + +Add `--skip-targets-install` flag for `tauri android init` and `tauri ios init` to skip installing needed rust targets vie rustup. diff --git a/.changes/skip-taskbar.md b/.changes/skip-taskbar.md deleted file mode 100644 index b5cc84e6b9dd..000000000000 --- a/.changes/skip-taskbar.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Adds `skip_taskbar` API to the WindowBuilder. diff --git a/.changes/streaming-small-file-fix.md b/.changes/streaming-small-file-fix.md deleted file mode 100644 index e4b316e40800..000000000000 --- a/.changes/streaming-small-file-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fix streaming of small files using the `asset` protocol. diff --git a/.changes/strip.md b/.changes/strip.md deleted file mode 100644 index f49c954b149e..000000000000 --- a/.changes/strip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": "patch" ---- - -Automatically `strip` the built binary on Linux and macOS if `--debug` is not specified. diff --git a/.changes/support-dep-formats.md b/.changes/support-dep-formats.md deleted file mode 100644 index 0b40adea43b6..000000000000 --- a/.changes/support-dep-formats.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Adds support to `tauri` dependency as string and table on `Cargo.toml`. diff --git a/.changes/system-tray-refactor.md b/.changes/system-tray-refactor.md deleted file mode 100644 index cd109fdf2774..000000000000 --- a/.changes/system-tray-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -**Breaking change**: The `system_tray` and `on_system_tray_event` APIs were not designed to have all the new features: submenus, item updates, click events, positioning... so we broke it before going to stable. diff --git a/.changes/system-tray-usage-fix.md b/.changes/system-tray-usage-fix.md deleted file mode 100644 index d6a9a44e90f1..000000000000 --- a/.changes/system-tray-usage-fix.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Fixes `system-tray` feature usage. diff --git a/.changes/target-dir-detection.md b/.changes/target-dir-detection.md new file mode 100644 index 000000000000..c77b1adb7d31 --- /dev/null +++ b/.changes/target-dir-detection.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Fix target directory detection when compiling for Android. diff --git a/.changes/tauri-build-icon-path.md b/.changes/tauri-build-icon-path.md deleted file mode 100644 index 8f46aa002790..000000000000 --- a/.changes/tauri-build-icon-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-build": patch ---- - -The `try_build` method now has a `Attributes` argument to allow specifying the window icon path used on Windows. diff --git a/.changes/tauri-build-mobile.md b/.changes/tauri-build-mobile.md new file mode 100644 index 000000000000..4e55d09cfd3d --- /dev/null +++ b/.changes/tauri-build-mobile.md @@ -0,0 +1,5 @@ +--- +"tauri-build": 'minor:feat' +--- + +Add `mobile::PluginBuilder` for running build tasks related to Tauri plugins. diff --git a/.changes/tauri-build-shortdesc-copyright.md b/.changes/tauri-build-shortdesc-copyright.md new file mode 100644 index 000000000000..227e2b1c3a0c --- /dev/null +++ b/.changes/tauri-build-shortdesc-copyright.md @@ -0,0 +1,5 @@ +--- +'tauri-build': 'patch:enhance' +--- + +On Windows, set `LegalCopyright` and `FileDescription` file properties on the executable from `tauri.bundle.copyright` and `tauri.bundle.shortDescription`, diff --git a/.changes/tauri-data-directory.md b/.changes/tauri-data-directory.md deleted file mode 100644 index 99af363f1304..000000000000 --- a/.changes/tauri-data-directory.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Force data directory even on non-local window. diff --git a/.changes/tauri-driver.md b/.changes/tauri-driver.md deleted file mode 100644 index 70d3b46238d0..000000000000 --- a/.changes/tauri-driver.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-driver": minor ---- - -Initial release including Linux and Windows support. diff --git a/.changes/tauri-empty-window.md b/.changes/tauri-empty-window.md deleted file mode 100644 index 4e6b6e9d3686..000000000000 --- a/.changes/tauri-empty-window.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Allow creation of empty Window with `create_tao_window()` and management with `send_tao_window_event()` on the AppHandler. diff --git a/.changes/tauri-expose-shortcut-clipboard.md b/.changes/tauri-expose-shortcut-clipboard.md deleted file mode 100644 index ca33ca8f3db7..000000000000 --- a/.changes/tauri-expose-shortcut-clipboard.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -Make `ClipboardManager` and `GlobalShortcutManager` public as they are exposed in the `AppHandle`. - diff --git a/.changes/tauri-icon-fix.md b/.changes/tauri-icon-fix.md deleted file mode 100644 index 799322e249fd..000000000000 --- a/.changes/tauri-icon-fix.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"cli.js": patch ---- - -Updates to `tauri icon` -- detect if icon is NOT transparent via metadata -- detect again on pixel level -- remove pngquant as a waste of space -- make optipng default -- relax optipng settings diff --git a/.changes/tauri-info-framework-bundler.md b/.changes/tauri-info-framework-bundler.md deleted file mode 100644 index f0e191f41f97..000000000000 --- a/.changes/tauri-info-framework-bundler.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Show `framework` and `bundler` on the `info` command by reading the `package.json` file and matching known dependencies. diff --git a/.changes/tauri-info-msvc-detection.md b/.changes/tauri-info-msvc-detection.md new file mode 100644 index 000000000000..d1e5071b37e2 --- /dev/null +++ b/.changes/tauri-info-msvc-detection.md @@ -0,0 +1,5 @@ +--- +'tauri-cli': 'patch:enhance' +--- + +Improve Visual Studio installation detection in `tauri info` command to check for the necessary components instead of whole workloads. This also fixes the detection of minimal installations and auto-installations done by `rustup`. diff --git a/.changes/tauri-macos-tray-icon-template.md b/.changes/tauri-macos-tray-icon-template.md deleted file mode 100644 index 97f2ba2fd7ac..000000000000 --- a/.changes/tauri-macos-tray-icon-template.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -- Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color. - -- Images you mark as template images should consist of only black and clear colors. You can use the alpha channel in the image to adjust the opacity of black content, however. diff --git a/.changes/tauri-mobile-entry-point.md b/.changes/tauri-mobile-entry-point.md new file mode 100644 index 000000000000..35e06db71882 --- /dev/null +++ b/.changes/tauri-mobile-entry-point.md @@ -0,0 +1,5 @@ +--- +"tauri": 'minor:feat' +--- + +Export types required by the `mobile_entry_point` macro. diff --git a/.changes/tauri-protocol.md b/.changes/tauri-protocol.md deleted file mode 100644 index a7f84e45fafc..000000000000 --- a/.changes/tauri-protocol.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": minor -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing. diff --git a/.changes/tauri-ready-event.md b/.changes/tauri-ready-event.md deleted file mode 100644 index 1e6b781012c7..000000000000 --- a/.changes/tauri-ready-event.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": minor -"tauri-runtime": minor -"tauri-runtime-wry": minor ---- - -Add `Event::Ready` on the `run()` callback. Triggered once when the event loop is ready. diff --git a/.changes/tauri-runtime-wry-wry-0-28.md b/.changes/tauri-runtime-wry-wry-0-28.md new file mode 100644 index 000000000000..6225b352b35e --- /dev/null +++ b/.changes/tauri-runtime-wry-wry-0-28.md @@ -0,0 +1,5 @@ +--- +'tauri-runtime-wry': 'patch:enhance' +--- + +Update `wry` to `0.28` diff --git a/.changes/tauri-updater-linux.md b/.changes/tauri-updater-linux.md deleted file mode 100644 index 2414249e2ba9..000000000000 --- a/.changes/tauri-updater-linux.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Use glib context for linux updater to prevent GTK panic. diff --git a/.changes/tauri-updater-windows.md b/.changes/tauri-updater-windows.md deleted file mode 100644 index b27aa62021aa..000000000000 --- a/.changes/tauri-updater-windows.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch ---- - -- Do not run the updater with UAC task if server don't tell us. (Allow toggling server-side) -- The updater expect a field named `with_elevated_task` with a `boolean` and will not run if the task is not installed first. (windows only) diff --git a/.changes/tauri-wry-migrate.md b/.changes/tauri-wry-migrate.md deleted file mode 100644 index 2425560baf3b..000000000000 --- a/.changes/tauri-wry-migrate.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime-wry": patch ---- - -Bump `wry` 0.11 and fix focus integration to make it compatible with tao 0.4. diff --git a/.changes/tauri-wry.md b/.changes/tauri-wry.md deleted file mode 100644 index 85090d8728da..000000000000 --- a/.changes/tauri-wry.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime-wry": minor ---- - -`tauri-runtime-wry` initial release. diff --git a/.changes/tempdir-api.md b/.changes/tempdir-api.md new file mode 100644 index 000000000000..6a16a157a263 --- /dev/null +++ b/.changes/tempdir-api.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'patch:enhance' +--- + +Add `tempDir` function to `path` module diff --git a/.changes/tempdir-core.md b/.changes/tempdir-core.md new file mode 100644 index 000000000000..e16b80f128f5 --- /dev/null +++ b/.changes/tempdir-core.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:enhance' +--- + +Add `temp_dir` method to `PathResolver` diff --git a/.changes/template-csp-change.md b/.changes/template-csp-change.md deleted file mode 100644 index be04ca81ba62..000000000000 --- a/.changes/template-csp-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Change the `csp` value on the template to include `wss:` and `tauri:` to the `default-src` attribute. diff --git a/.changes/throw-error-on-cli-download-failure.md b/.changes/throw-error-on-cli-download-failure.md deleted file mode 100644 index 2258e72bd2e4..000000000000 --- a/.changes/throw-error-on-cli-download-failure.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.js": patch ---- - -Throw error on `cli.rs` download failure instead of silent exit. diff --git a/.changes/tls-features-automatically-enabled.md b/.changes/tls-features-automatically-enabled.md new file mode 100644 index 000000000000..e8629a839036 --- /dev/null +++ b/.changes/tls-features-automatically-enabled.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": 'patch:enhance' +"@tauri-apps/cli": 'patch:enhance' +--- + +Automatically enable the `rustls-tls` tauri feature on mobile and `native-tls` on desktop if `rustls-tls` is not enabled. diff --git a/.changes/tls-features-refactor.md b/.changes/tls-features-refactor.md new file mode 100644 index 000000000000..8dd54f548916 --- /dev/null +++ b/.changes/tls-features-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:enhance' +--- + +Renamed the `default-tls` feature to `native-tls` and added `rustls-tls` feature. diff --git a/.changes/tooling-fix-pnpm-info-error.md b/.changes/tooling-fix-pnpm-info-error.md deleted file mode 100644 index f5494b3412ab..000000000000 --- a/.changes/tooling-fix-pnpm-info-error.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'cli.rs': patch ---- - -Fixes pnpm error when running `pnpm tauri info`. diff --git a/.changes/transparency-fix.md b/.changes/transparency-fix.md deleted file mode 100644 index 3287454c82d1..000000000000 --- a/.changes/transparency-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Fixes webview transparency. diff --git a/.changes/tray-set-menu.md b/.changes/tray-set-menu.md deleted file mode 100644 index d0b2dc500fb7..000000000000 --- a/.changes/tray-set-menu.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Add `set_menu` API on `tauri::SystemTrayHandle`. diff --git a/.changes/tray.md b/.changes/tray.md deleted file mode 100644 index c89950c19a52..000000000000 --- a/.changes/tray.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds system tray support. diff --git a/.changes/tray_get_item.md b/.changes/tray_get_item.md new file mode 100644 index 000000000000..4d60ca1a3fd8 --- /dev/null +++ b/.changes/tray_get_item.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:feat' +--- + +Add `MenuHandle::try_get_item` and `SystemTrayHandle::try_get_item` which returns a `Option` instead of panicking. diff --git a/.changes/try-state.md b/.changes/try-state.md deleted file mode 100644 index d59192098fa1..000000000000 --- a/.changes/try-state.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Add `try_state` API to the `Manager` trait. diff --git a/.changes/ubuntu-20.04-cli.js.md b/.changes/ubuntu-20.04-cli.js.md new file mode 100644 index 000000000000..640558ce4c4b --- /dev/null +++ b/.changes/ubuntu-20.04-cli.js.md @@ -0,0 +1,5 @@ +--- +"@tauri-apps/cli": 'patch:enhance' +--- + +Use Ubuntu 20.04 to compile the CLI, increasing the minimum libc version required. diff --git a/.changes/ubuntu-20.04-cli.rs.md b/.changes/ubuntu-20.04-cli.rs.md new file mode 100644 index 000000000000..58f0adc7666d --- /dev/null +++ b/.changes/ubuntu-20.04-cli.rs.md @@ -0,0 +1,5 @@ +--- +"tauri-cli": 'patch:enhance' +--- + +- Use Ubuntu 20.04 to compile the CLI for cargo-binstall, increasing the minimum libc required. diff --git a/.changes/universal-apple-target-sidecar.md b/.changes/universal-apple-target-sidecar.md deleted file mode 100644 index 13eb66b43453..000000000000 --- a/.changes/universal-apple-target-sidecar.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"tauri-bundler": patch -"api": patch ---- - -When building Universal macOS Binaries through the virtual target `universal-apple-darwin`: - -- Expect a universal binary to be created by the user -- Ensure that binary is bundled and accessed correctly at runtime diff --git a/.changes/universal-apple-target.md b/.changes/universal-apple-target.md deleted file mode 100644 index fcd7eead2a18..000000000000 --- a/.changes/universal-apple-target.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Add support to building Universal macOS Binaries through the virtual target `universal-apple-darwin` (run `tauri build --target universal-apple-darwin`). diff --git a/.changes/unlisten.md b/.changes/unlisten.md deleted file mode 100644 index d60cce4adc40..000000000000 --- a/.changes/unlisten.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `unlisten` function to the `Window` struct. diff --git a/.changes/unpin-deps.md b/.changes/unpin-deps.md new file mode 100644 index 000000000000..b269aed34507 --- /dev/null +++ b/.changes/unpin-deps.md @@ -0,0 +1,9 @@ +--- +'tauri': 'patch:enhance' +'tauri-build': 'patch:enhance' +'tauri-codegen': 'patch:enhance' +'tauri-runtime': 'patch:enhance' +'tauri-runtime-wry': 'patch:enhance' +--- + +Unpin `time`, `ignore`, `winnow`, and `ignore` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85. diff --git a/.changes/updater-endpoint-url-https.md b/.changes/updater-endpoint-url-https.md deleted file mode 100644 index cd998f3bd6b0..000000000000 --- a/.changes/updater-endpoint-url-https.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-utils": patch ---- - -Force updater endpoint URL to use `https` on release builds. diff --git a/.changes/uri-parse.md b/.changes/uri-parse.md deleted file mode 100644 index 2b12788d9b69..000000000000 --- a/.changes/uri-parse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Fix loading url containing URI fragment diff --git a/.changes/validate-allowlist.md b/.changes/validate-allowlist.md deleted file mode 100644 index a8ead7e2e92d..000000000000 --- a/.changes/validate-allowlist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-build": patch ---- - -Validate `tauri` dependency `features` under `Cargo.toml` matching `tauri.conf.json`'s `allowlist`. diff --git a/.changes/validate-appimage.md b/.changes/validate-appimage.md deleted file mode 100644 index ef18a4764083..000000000000 --- a/.changes/validate-appimage.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Validate the `std::env::current_exe` return value if `APPDIR` or `APPIMAGE` environment variables are set. diff --git a/.changes/validate-event-name.md b/.changes/validate-event-name.md deleted file mode 100644 index 43badb3fa53f..000000000000 --- a/.changes/validate-event-name.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": "patch" ---- - -The event name is now validated. On a IPC message, it returns an error if it fails validation; on the Rust side, it panics. -It must include only alphanumeric characters, `-`, `/`, `:` and `_`. diff --git a/.changes/validate-window-label.md b/.changes/validate-window-label.md deleted file mode 100644 index 9f4b71deaaa0..000000000000 --- a/.changes/validate-window-label.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch ---- - -The window label is now validated and must be alphanumeric, resulting in a panic if it isn't. diff --git a/.changes/version-package-json.md b/.changes/version-package-json.md deleted file mode 100644 index f22887f99121..000000000000 --- a/.changes/version-package-json.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Allow `tauri.conf.json > package > version` to specify a path to a `package.json` file and pull the version from it. diff --git a/.changes/wait-dev-server-url.md b/.changes/wait-dev-server-url.md deleted file mode 100644 index 612b53e55e96..000000000000 --- a/.changes/wait-dev-server-url.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Wait for `devPath` URL to be reachable before starting the application. Skipped if the `TAURI_SKIP_DEVSERVER_CHECK` environment variable is set to `true`. diff --git a/.changes/weak-typing.md b/.changes/weak-typing.md deleted file mode 100644 index 69f721613dd4..000000000000 --- a/.changes/weak-typing.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch -"tauri-macros": patch -"tauri-utils": patch ---- - -`Params` has been removed, along with all the associated types on it. Functions that previously accepted those -associated types now accept strings instead. Type that used a generic parameter `Params` now use `Runtime` instead. If -you use the `wry` feature, then types with a `Runtime` generic parameter should default to `Wry`, letting you omit the -explicit type and let the compiler infer it instead. - -`tauri`: - -* See `Params` note -* If you were using `Params` inside a function parameter or definition, all references to it have been replaced with a - simple runtime that defaults to `Wry`. If you are not using a custom runtime, just remove `Params` from the definition - of functions/items that previously took it. If you are using a custom runtime, you _may_ need to pass the runtime type - to these functions. -* If you were using custom types for `Params` (uncommon and if you don't understand you probably were not using it), all - methods that were previously taking the custom type now takes an `Into` or a `&str`. The types were already - required to be string-able, so just make sure to convert it into a string before passing it in if this breaking change - affects you. - -`tauri-macros`: - -* (internal) Added private `default_runtime` proc macro to allow us to give item definitions a custom runtime only when - the specified feature is enabled. - -`tauri-runtime`: - -* See `Params` note -* Removed `Params`, `MenuId`, `Tag`, `TagRef`. -* Added `menu::{MenuHash, MenuId, MenuIdRef}` as type aliases for the internal type that menu types now use. - * All previous menu items that had a `MenuId` generic now use the underlying `MenuId` type without a generic. -* `Runtime`, `RuntimeHandle`, and `Dispatch` have no more generic parameter on `create_window(...)` and instead use the - `Runtime` type directly -* `Runtime::system_tray` has no more `MenuId` generic and uses the string based `SystemTray` type directly. -* (internal) `CustomMenuItem::id_value()` is now hashed on creation and exposed as the `id` field with type `MenuHash`. - -`tauri-runtime-wry`: - -* See `Params` note -* update menu and runtime related types to the ones changed in `tauri-runtime`. - -`tauri-utils`: - -* `Assets::get` signature has changed to take a `&AssetKey` instead of `impl Into` to become trait object - safe. diff --git a/.changes/webdriver-args.md b/.changes/webdriver-args.md deleted file mode 100644 index 7296a4bbee50..000000000000 --- a/.changes/webdriver-args.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-driver": patch ---- - -Add `args` field (array of application CLI arguments) to the `tauri:options` capabilities. diff --git a/.changes/webdriver.md b/.changes/webdriver.md deleted file mode 100644 index 426b54e3cef7..000000000000 --- a/.changes/webdriver.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Add webdriver support to Tauri. diff --git a/.changes/webview-attributes-clipboard.md b/.changes/webview-attributes-clipboard.md deleted file mode 100644 index 7f34d4c05f92..000000000000 --- a/.changes/webview-attributes-clipboard.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch ---- - -Added `clipboard` field on the `WebviewAttributes` struct, which must be set to `true` to enable clipboard access on the webview. diff --git a/.changes/webview-attributes-from-window-config-impl.md b/.changes/webview-attributes-from-window-config-impl.md new file mode 100644 index 000000000000..aca7fe2204d4 --- /dev/null +++ b/.changes/webview-attributes-from-window-config-impl.md @@ -0,0 +1,5 @@ +--- +'tauri-runtime': 'patch:enhance' +--- + +impl `From<&WindowConfig>` for `WebviewAttributes`. diff --git a/.changes/webview-window-constructor-pub.md b/.changes/webview-window-constructor-pub.md deleted file mode 100644 index 6a3306d02686..000000000000 --- a/.changes/webview-window-constructor-pub.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Mark the `WebviewWindow` constructor as public. diff --git a/.changes/webview-window-extends-window-manager.md b/.changes/webview-window-extends-window-manager.md deleted file mode 100644 index ff78a2ec80b2..000000000000 --- a/.changes/webview-window-extends-window-manager.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Allow manipulating a spawned window directly using `WebviewWindow`, which now extends `WindowManager`. diff --git a/.changes/webview2-com.md b/.changes/webview2-com.md deleted file mode 100644 index bddac4929581..000000000000 --- a/.changes/webview2-com.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Replace all of the `winapi` crate references with the `windows` crate, and replace `webview2` and `webview2-sys` with `webview2-com` and `webview2-com-sys` built with the `windows` crate. This goes along with updates to the TAO and WRY `next` branches. \ No newline at end of file diff --git a/.changes/window-api-validations.md b/.changes/window-api-validations.md deleted file mode 100644 index 6e39784185c2..000000000000 --- a/.changes/window-api-validations.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Validate arguments on the window `setLocation`, `setSize`, `setMinSize` and `setMaxSize` API. diff --git a/.changes/window-attributes-rename.md b/.changes/window-attributes-rename.md deleted file mode 100644 index 28d298d47415..000000000000 --- a/.changes/window-attributes-rename.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Rename `Attributes` to `WindowBuilder`. diff --git a/.changes/window-center.md b/.changes/window-center.md deleted file mode 100644 index 48b38a83abb7..000000000000 --- a/.changes/window-center.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri-runtime": patch -"tauri-runtime-wry": patch -"tauri": patch ---- - -Adds `Window#center` and `WindowBuilder#center` APIs. diff --git a/.changes/window-confirm.md b/.changes/window-confirm.md deleted file mode 100644 index e6ddc59adf25..000000000000 --- a/.changes/window-confirm.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Show `Ok/Cancel` buttons instead of `Yes/No` when executing `window.confirm`. diff --git a/.changes/window-create-refactor.md b/.changes/window-create-refactor.md deleted file mode 100644 index cb31e8f40c03..000000000000 --- a/.changes/window-create-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -The `Window#create_window` API now has the same signature as `App#create_window`. diff --git a/.changes/window-effects-api.md b/.changes/window-effects-api.md new file mode 100644 index 000000000000..44e25bb01da7 --- /dev/null +++ b/.changes/window-effects-api.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'minor:feat' +--- + +Added the `windowEffects` option when creating a window and `setWindowEffects` method to change it at runtime. diff --git a/.changes/window-effects-config.md b/.changes/window-effects-config.md new file mode 100644 index 000000000000..941771324202 --- /dev/null +++ b/.changes/window-effects-config.md @@ -0,0 +1,5 @@ +--- +'tauri-utils': 'minor:feat' +--- + +Added the `window_effects` option to the window configuration. diff --git a/.changes/window-effects.md b/.changes/window-effects.md new file mode 100644 index 000000000000..f5f7678071c7 --- /dev/null +++ b/.changes/window-effects.md @@ -0,0 +1,7 @@ +--- +'tauri': 'minor:feat' +'tauri-runtime-wry': 'minor:feat' +'tauri-runtime': 'minor:feat' +--- + +Added the `window_effects` option when creating a window and `Window::set_effects` to change it at runtime. diff --git a/.changes/window-events.md b/.changes/window-events.md deleted file mode 100644 index 1d8e9d5cf20d..000000000000 --- a/.changes/window-events.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Adds `on_window_event` API to the `Window` struct. diff --git a/.changes/window-getters.md b/.changes/window-getters.md deleted file mode 100644 index 51c1c94b1480..000000000000 --- a/.changes/window-getters.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"api": patch -"tauri": patch ---- - -Adds window getters. diff --git a/.changes/window-label-unique.md b/.changes/window-label-unique.md deleted file mode 100644 index 27aa3f6caea0..000000000000 --- a/.changes/window-label-unique.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Enfore uniqueness of window label. diff --git a/.changes/window-parent-and-owner.md b/.changes/window-parent-and-owner.md deleted file mode 100644 index 3fbfc0c1d3e8..000000000000 --- a/.changes/window-parent-and-owner.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Adds `parent_window` and `owner_window` setters to the `WindowBuilder` (Windows only). diff --git a/.changes/window-parent.md b/.changes/window-parent.md deleted file mode 100644 index 69bc531d11ae..000000000000 --- a/.changes/window-parent.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Adds window native handle getter (HWND on Windows). diff --git a/.changes/window-send-sync.md b/.changes/window-send-sync.md deleted file mode 100644 index bc547f2df0fd..000000000000 --- a/.changes/window-send-sync.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime-wry": patch -"tauri-runtime": patch ---- - -`Window` is now `Send + Sync` on Windows. diff --git a/.changes/windows-0.25.0.md b/.changes/windows-0.25.0.md deleted file mode 100644 index 60365d3157d5..000000000000 --- a/.changes/windows-0.25.0.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri": patch -"tauri-runtime": patch -"tauri-runtime-wry": patch ---- - -Update the `windows` crate to 0.25.0, which comes with pre-built libraries. WRY and Tao can both reference the same types directly from the `windows` crate instead of sharing bindings in `webview2-com-sys`. \ No newline at end of file diff --git a/.changes/windows-modify-cmd-string-behaviour.md b/.changes/windows-modify-cmd-string-behaviour.md deleted file mode 100644 index 9be3b3c64c06..000000000000 --- a/.changes/windows-modify-cmd-string-behaviour.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -On Windows, Fix `beforeDevCommand` and `beforeBuildCommand` not executing the expected command if it contains quotes. This is done by executing them with `CMD /S /C {command}` instead of `CMD /C {command}` on Windows. \ No newline at end of file diff --git a/.changes/windows-null.md b/.changes/windows-null.md deleted file mode 100644 index 532322bb0a3f..000000000000 --- a/.changes/windows-null.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -This is a temporary fix of null pointer crash on `get_content` of web resource request. -We will switch it back once upstream is updated. diff --git a/.changes/windows-protocol-path.md b/.changes/windows-protocol-path.md deleted file mode 100644 index b98d9c15754e..000000000000 --- a/.changes/windows-protocol-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"api": patch ---- - -Update protocol url path with wry 0.12.1 on Windows. diff --git a/.changes/windows-resources-set-values.md b/.changes/windows-resources-set-values.md deleted file mode 100644 index c212e100e2e6..000000000000 --- a/.changes/windows-resources-set-values.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-build": patch ---- - -Pull Windows resource information (`FileVersion`, `ProductVersion`, `ProductName` and `FileDescription`) from `tauri.conf.json > package` configuration. diff --git a/.changes/windows-shortcut.md b/.changes/windows-shortcut.md deleted file mode 100644 index afb7d42e8c48..000000000000 --- a/.changes/windows-shortcut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Configure app shortcut on the Windows Installer. diff --git a/.changes/with-webview.md b/.changes/with-webview.md new file mode 100644 index 000000000000..4b1e8a575b53 --- /dev/null +++ b/.changes/with-webview.md @@ -0,0 +1,7 @@ +--- +"tauri": 'minor:feat' +"tauri-runtime": 'minor:feat' +"tauri-runtime-wry": 'minor:feat' +--- + +Implemented `with_webview` on Android and iOS. diff --git a/.changes/wix-bundle-language.md b/.changes/wix-bundle-language.md deleted file mode 100644 index 62792b914c85..000000000000 --- a/.changes/wix-bundle-language.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Allow setting the Windows installer language and using project names that contains non-Unicode characters. diff --git a/.changes/wix-config-language.md b/.changes/wix-config-language.md deleted file mode 100644 index 287de9228b2d..000000000000 --- a/.changes/wix-config-language.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"cli.rs": patch ---- - -Adds `tauri > bundle > windows > wix > language` config option. See https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables. diff --git a/.changes/wix-localization.md b/.changes/wix-localization.md deleted file mode 100644 index 4187442a40e2..000000000000 --- a/.changes/wix-localization.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"cli.rs": patch -"tauri-bundler": patch ---- - -Allow setting the localization file for WiX. diff --git a/.changes/wix-registry-keys.md b/.changes/wix-registry-keys.md deleted file mode 100644 index f9930f3d4c27..000000000000 --- a/.changes/wix-registry-keys.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Fix registry keys on the WiX template. diff --git a/.changes/wix-resource-bundling-fix.md b/.changes/wix-resource-bundling-fix.md deleted file mode 100644 index c691900d8422..000000000000 --- a/.changes/wix-resource-bundling-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Fixes resource bundling on Windows when there is nested resource folders. diff --git a/.changes/wix-run-app.md b/.changes/wix-run-app.md deleted file mode 100644 index 30db1d3ff296..000000000000 --- a/.changes/wix-run-app.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Configure WiX to add an option to launch the application after finishing setup. diff --git a/.changes/wix-signing.md b/.changes/wix-signing.md deleted file mode 100644 index af26aca9189f..000000000000 --- a/.changes/wix-signing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Sign WiX installer in addition to the executable file. diff --git a/.changes/wix-uninstall-skip-uac-task.md b/.changes/wix-uninstall-skip-uac-task.md deleted file mode 100644 index d016d0c6d1f6..000000000000 --- a/.changes/wix-uninstall-skip-uac-task.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Added script to uninstall the Skip-UAC Windows Task during application uninstall. diff --git a/.changes/wix-update-on-windows-without-uac.md b/.changes/wix-update-on-windows-without-uac.md deleted file mode 100644 index c44677867c74..000000000000 --- a/.changes/wix-update-on-windows-without-uac.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-bundler": patch ---- - -Added option to update a Tauri application without Windows UAC Prompt. diff --git a/.changes/wry-13.md b/.changes/wry-13.md deleted file mode 100644 index b6248fcc68e8..000000000000 --- a/.changes/wry-13.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-runtime-wry": patch ---- - -Update wry to 0.13. - diff --git a/.changes/wry-update.md b/.changes/wry-update.md deleted file mode 100644 index 26f588031276..000000000000 --- a/.changes/wry-update.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch ---- - -Update `wry` to v0.9. diff --git a/.changes/wry26.md b/.changes/wry26.md new file mode 100644 index 000000000000..dee422188ea5 --- /dev/null +++ b/.changes/wry26.md @@ -0,0 +1,6 @@ +--- +"tauri-runtime-wry": 'minor:feat' +--- + +Update wry to 0.26. + diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000000..63d7bcd3ae6e --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/master/containers/ubuntu/.devcontainer/base.Dockerfile +# [Choice] Ubuntu version (use jammy or bionic on local arm64/Apple Silicon): jammy, focal, bionic +ARG VARIANT="jammy" +FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} + +# Derived from Tauri contribution and setup guides: +# See: https://github.com/tauri-apps/tauri/blob/dev/.github/CONTRIBUTING.md#development-guide +# See: https://tauri.app/v1/guides/getting-started/prerequisites/#setting-up-linux +ARG TAURI_BUILD_DEPS="build-essential curl libappindicator3-dev libgtk-3-dev librsvg2-dev libssl-dev libwebkit2gtk-4.1-dev wget" + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get install -y --no-install-recommends $TAURI_BUILD_DEPS diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 000000000000..84212611ec7b --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,38 @@ +# VS Code Devcontainer for Tauri + +## Overview + +Please note that most of these instructions are derived from Microsoft's VS Code documentation: [Developing inside a Container](https://code.visualstudio.com/docs/remote/containers). Check the official documentation if you encounter problems and submit a PR with any corrections you find for the instructions below. + +The development container included in this repository is derived from [Microsoft's default Ubuntu development container](https://github.com/microsoft/vscode-dev-containers/tree/master/containers/ubuntu). Contents of the Ubuntu Docker image can be in the [VS Code devcontainer Ubuntu base Dockerfile](https://github.com/microsoft/vscode-dev-containers/blob/main/containers/ubuntu/.devcontainer/base.Dockerfile). The contents of the container used for development can be found in the [Dockerfile](./Dockerfile) located in the same directory as this README. + +## Usage + +1. Ensure you have all [Devcontainer Prerequisites](#devcontainer-prerequisites) +2. Open the directory containing your [`tauri-apps/tauri`](https://github.com/tauri-apps/tauri) code. +3. Install the [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) extension pack for VS Code. This will be included if you install recommended workspace extensions upon opening this repository. +4. Ensure Docker is running +5. [Open your workspace in the provided devcontainer](https://code.visualstudio.com/docs/remote/containers#_open-an-existing-workspace-in-a-container): Open this repository in VS Code and run **Remote-Containers: Reopen in Container...** from the Command Palette (F1). + +### Devcontainer Prerequisites + +Prerequisites are mainly derived from VS Code's instructions for usage of development containers, documented here: [Developing inside a Container: Getting Started](https://code.visualstudio.com/docs/remote/containers#_getting-started). + +1. Docker (Docker Desktop recommended) +2. VS Code +3. X window host - required if you want to be able to interact with a GUI from your Docker host + +### A note on filesystem performance + +Due to limitations in how Docker shares files between the Docker host and a container, it's also recommended that developers [clone Tauri source code into a container volume](https://code.visualstudio.com/remote/advancedcontainers/improve-performance#_use-clone-repository-in-container-volume). This is optional, but highly advised as many filesystem/IO heavy operations (`cargo build`, `yarn install`, etc) will be very slow if they operate on directories shared with a Docker container from the Docker host. + +To do this, open your project with VS Code and run **Remote-Containers: Clone Repository in Container Volume...** from the Command Palette (F1). + +### Accessing a Tauri application running you the devcontainer + +Docker Desktop provides facilities for [allowing the development container to connect to a service on the Docker host](https://docs.docker.com/desktop/windows/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host). So long as you have an X window server running on your Docker host, the devcontainer can connect to it and expose your Tauri GUI via an X window. + +**Export the `DISPLAY` variable within the devcontainer terminal you launch your Tauri application from to expose your GUI outside of the devcontainer**. +```bash +export DISPLAY="host.docker.internal:0" +``` diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000000..a7a9805c37a9 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,30 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/ubuntu +{ + "name": "Ubuntu", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick an Ubuntu version: jammy / ubuntu-22.04, focal / ubuntu-20.04, bionic /ubuntu-18.04 + // Use ubuntu-22.04 or ubuntu-18.04 on local arm64/Apple Silicon. + "args": { "VARIANT": "ubuntu-22.04" } + }, + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode", + "features": { + "node": "lts", + "rust": "latest" + } +} diff --git a/.docker/cross/aarch64.Dockerfile b/.docker/cross/aarch64.Dockerfile new file mode 100644 index 000000000000..498d055b75f9 --- /dev/null +++ b/.docker/cross/aarch64.Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:18.04 +ARG DEBIAN_FRONTEND=noninteractive + +COPY common.sh lib.sh / +RUN /common.sh + +COPY cmake.sh / +RUN /cmake.sh + +COPY xargo.sh / +RUN /xargo.sh + +RUN apt-get update && apt-get install --assume-yes --no-install-recommends \ + g++-aarch64-linux-gnu \ + libc6-dev-arm64-cross + +COPY deny-debian-packages.sh / +RUN TARGET_ARCH=arm64 /deny-debian-packages.sh \ + binutils \ + binutils-aarch64-linux-gnu + +COPY qemu.sh / +RUN /qemu.sh aarch64 softmmu + +COPY dropbear.sh / +RUN /dropbear.sh + +COPY linux-image.sh / +RUN /linux-image.sh aarch64 + +COPY linux-runner / + +ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER="/linux-runner aarch64" \ + CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \ + CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \ + BINDGEN_EXTRA_CLANG_ARGS_aarch64_unknown_linux_gnu="--sysroot=/usr/aarch64-linux-gnu" \ + QEMU_LD_PREFIX=/usr/aarch64-linux-gnu \ + RUST_TEST_THREADS=1 \ + PKG_CONFIG_PATH="/usr/lib/aarch64-linux-gnu/pkgconfig/:${PKG_CONFIG_PATH}" + +RUN dpkg --add-architecture arm64 +RUN apt-get update +RUN apt-get install --assume-yes --no-install-recommends libssl-dev:arm64 libdbus-1-dev:arm64 libsoup2.4-dev:arm64 libssl-dev:arm64 libgtk-3-dev:arm64 webkit2gtk-4.1-dev:arm64 libappindicator3-1:arm64 librsvg2-dev:arm64 patchelf:arm64 diff --git a/.docker/cross/cmake.sh b/.docker/cross/cmake.sh new file mode 100755 index 000000000000..9ed4ee6a008d --- /dev/null +++ b/.docker/cross/cmake.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -x +set -euo pipefail + +# shellcheck disable=SC1091 +. lib.sh + +main() { + local version=3.23.1 + + install_packages curl + + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "https://github.com/Kitware/CMake/releases/download/v${version}/cmake-${version}-linux-x86_64.sh" -o cmake.sh + sh cmake.sh --skip-license --prefix=/usr/local + + popd + + purge_packages + + rm -rf "${td}" + rm -rf /var/lib/apt/lists/* + rm "${0}" +} + +main "${@}" diff --git a/.docker/cross/common.sh b/.docker/cross/common.sh new file mode 100755 index 000000000000..24d56194d806 --- /dev/null +++ b/.docker/cross/common.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -x +set -euo pipefail + +# shellcheck disable=SC1091 +. lib.sh + +# For architectures except amd64 and i386, look for packages on ports.ubuntu.com instead. +# This is important if you enable additional architectures so you can install libraries to cross-compile against. +# Look for 'dpkg --add-architecture' in the README for more details. +if grep -i ubuntu /etc/os-release >/dev/null; then + sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list > /etc/apt/sources.list.d/ports.list + sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list +fi + +install_packages \ + autoconf \ + automake \ + binutils \ + ca-certificates \ + curl \ + file \ + gcc \ + git \ + libtool \ + m4 \ + make + +if_centos install_packages \ + clang-devel \ + gcc-c++ \ + glibc-devel \ + pkgconfig + +if_ubuntu install_packages \ + g++ \ + libc6-dev \ + libclang-dev \ + pkg-config diff --git a/.docker/cross/deny-debian-packages.sh b/.docker/cross/deny-debian-packages.sh new file mode 100755 index 000000000000..3193333e936d --- /dev/null +++ b/.docker/cross/deny-debian-packages.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -x +set -euo pipefail + +main() { + local package + + for package in "${@}"; do + echo "Package: ${package}:${TARGET_ARCH} +Pin: release * +Pin-Priority: -1" > "/etc/apt/preferences.d/${package}" + echo "${package}" + done + + rm "${0}" +} + +main "${@}" diff --git a/.docker/cross/dropbear.sh b/.docker/cross/dropbear.sh new file mode 100755 index 000000000000..097814e8601d --- /dev/null +++ b/.docker/cross/dropbear.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -x +set -euo pipefail + +# shellcheck disable=SC1091 +. lib.sh + +main() { + local version=2022.82 + + install_packages \ + autoconf \ + automake \ + bzip2 \ + curl \ + make + + if_centos install_packages zlib-devel + if_ubuntu install_packages zlib1g-dev + + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "https://matt.ucc.asn.au/dropbear/releases/dropbear-${version}.tar.bz2" -O + tar --strip-components=1 -xjf "dropbear-${version}.tar.bz2" + + # Remove some unwanted message + sed -i '/skipping hostkey/d' cli-kex.c + sed -i '/failed to identify current user/d' cli-runopts.c + + ./configure \ + --disable-syslog \ + --disable-shadow \ + --disable-lastlog \ + --disable-utmp \ + --disable-utmpx \ + --disable-wtmp \ + --disable-wtmpx \ + --disable-pututline \ + --disable-pututxline + + make "-j$(nproc)" PROGRAMS=dbclient + cp dbclient /usr/local/bin/ + + purge_packages + + popd + + rm -rf "${td}" + rm "${0}" +} + +main "${@}" diff --git a/.docker/cross/lib.sh b/.docker/cross/lib.sh new file mode 100644 index 000000000000..0d299b8a6e71 --- /dev/null +++ b/.docker/cross/lib.sh @@ -0,0 +1,45 @@ +purge_list=() + +install_packages() { + if grep -i ubuntu /etc/os-release; then + apt-get update + + for pkg in "${@}"; do + if ! dpkg -L "${pkg}" >/dev/null 2>/dev/null; then + apt-get install --assume-yes --no-install-recommends "${pkg}" + + purge_list+=( "${pkg}" ) + fi + done + else + for pkg in "${@}"; do + if ! yum list installed "${pkg}" >/dev/null 2>/dev/null; then + yum install -y "${pkg}" + + purge_list+=( "${pkg}" ) + fi + done + fi +} + +purge_packages() { + if (( ${#purge_list[@]} )); then + if grep -i ubuntu /etc/os-release; then + apt-get purge --assume-yes --auto-remove "${purge_list[@]}" + else + yum remove -y "${purge_list[@]}" + fi + fi +} + +if_centos() { + if grep -q -i centos /etc/os-release; then + eval "${@}" + fi +} + +if_ubuntu() { + if grep -q -i ubuntu /etc/os-release; then + eval "${@}" + fi +} diff --git a/.docker/cross/linux-image.sh b/.docker/cross/linux-image.sh new file mode 100755 index 000000000000..dc85ed45c571 --- /dev/null +++ b/.docker/cross/linux-image.sh @@ -0,0 +1,274 @@ +#!/usr/bin/env bash + +set -x +set -euo pipefail + +# shellcheck disable=SC1091 +. lib.sh + +main() { + # arch in the rust target + local arch="${1}" \ + kversion=4.19.0-20 + + local debsource="deb http://http.debian.net/debian/ buster main" + debsource="${debsource}\ndeb http://security.debian.org/ buster/updates main" + + local dropbear="dropbear-bin" + + local -a deps + local kernel= + local libgcc="libgcc1" + + # select debian arch and kernel version + case "${arch}" in + aarch64) + arch=arm64 + kernel="${kversion}-arm64" + ;; + armv7) + arch=armhf + kernel="${kversion}-armmp" + ;; + i686) + arch=i386 + kernel="${kversion}-686" + ;; + mips|mipsel) + kernel="${kversion}-4kc-malta" + ;; + mips64el) + kernel="${kversion}-5kc-malta" + ;; + powerpc) + # there is no buster powerpc port, so we use jessie + # use a more recent kernel from backports + kversion='4.9.0-0.bpo.6' + kernel="${kversion}-powerpc" + debsource="deb http://archive.debian.org/debian jessie main" + debsource="${debsource}\ndeb http://archive.debian.org/debian jessie-backports main" + debsource="${debsource}\ndeb http://ftp.ports.debian.org/debian-ports unstable main" + debsource="${debsource}\ndeb http://ftp.ports.debian.org/debian-ports unreleased main" + + # archive.debian.org Release files are expired. + echo "Acquire::Check-Valid-Until false;" | tee -a /etc/apt/apt.conf.d/10-nocheckvalid + echo "APT::Get::AllowUnauthenticated true;" | tee -a /etc/apt/apt.conf.d/10-nocheckvalid + + dropbear="dropbear" + deps=(libcrypt1:"${arch}") + ;; + powerpc64) + # there is no stable port + arch=ppc64 + # https://packages.debian.org/en/sid/linux-image-powerpc64 + kversion='5.*' + kernel="${kversion}-powerpc64" + libgcc="libgcc-s1" + debsource="deb http://ftp.ports.debian.org/debian-ports unstable main" + debsource="${debsource}\ndeb http://ftp.ports.debian.org/debian-ports unreleased main" + # sid version of dropbear requires these dependencies + deps=(libcrypt1:"${arch}") + ;; + powerpc64le) + arch=ppc64el + kernel="${kversion}-powerpc64le" + ;; + s390x) + arch=s390x + kernel="${kversion}-s390x" + ;; + sparc64) + # there is no stable port + # https://packages.debian.org/en/sid/linux-image-sparc64 + kernel='*-sparc64' + libgcc="libgcc-s1" + debsource="deb http://ftp.ports.debian.org/debian-ports unstable main" + debsource="${debsource}\ndeb http://ftp.ports.debian.org/debian-ports unreleased main" + # sid version of dropbear requires these dependencies + deps=(libcrypt1:"${arch}") + ;; + x86_64) + arch=amd64 + kernel="${kversion}-amd64" + ;; + *) + echo "Invalid arch: ${arch}" + exit 1 + ;; + esac + + install_packages ca-certificates \ + curl \ + cpio \ + sharutils \ + gnupg + + # Download packages + mv /etc/apt/sources.list /etc/apt/sources.list.bak + echo -e "${debsource}" > /etc/apt/sources.list + + # Old ubuntu does not support --add-architecture, so we directly change multiarch file + if [ -f /etc/dpkg/dpkg.cfg.d/multiarch ]; then + cp /etc/dpkg/dpkg.cfg.d/multiarch /etc/dpkg/dpkg.cfg.d/multiarch.bak + fi + dpkg --add-architecture "${arch}" || echo "foreign-architecture ${arch}" > /etc/dpkg/dpkg.cfg.d/multiarch + + # Add Debian keys. + curl --retry 3 -sSfL 'https://ftp-master.debian.org/keys/archive-key-{7.0,8,9,10}.asc' -O + curl --retry 3 -sSfL 'https://ftp-master.debian.org/keys/archive-key-{8,9,10}-security.asc' -O + curl --retry 3 -sSfL 'https://ftp-master.debian.org/keys/release-{7,8,9,10}.asc' -O + curl --retry 3 -sSfL 'https://www.ports.debian.org/archive_{2020,2021,2022}.key' -O + + for key in *.asc *.key; do + apt-key add "${key}" + rm "${key}" + done + + # allow apt-get to retry downloads + echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries + + apt-get update + + mkdir -p "/qemu/${arch}" + chmod 777 /qemu "/qemu/${arch}" + + cd "/qemu/${arch}" + apt-get -d --no-install-recommends download \ + ${deps[@]+"${deps[@]}"} \ + "busybox:${arch}" \ + "${dropbear}:${arch}" \ + "libtommath1:${arch}" \ + "libtomcrypt1:${arch}" \ + "libgmp10:${arch}" \ + "libc6:${arch}" \ + "${libgcc}:${arch}" \ + "libstdc++6:${arch}" \ + "linux-image-${kernel}:${arch}" \ + ncurses-base \ + "zlib1g:${arch}" + cd /qemu + + # Install packages + root="root-${arch}" + mkdir -p "${root}"/{bin,etc/dropbear,root,sys,dev,proc,sbin,tmp,usr/{bin,sbin},var/log} + for deb in "${arch}"/*deb; do + dpkg -x "${deb}" "${root}"/ + done + + cp "${root}/boot/vmlinu"* kernel + + # initrd + mkdir -p "${root}/modules" + cp -v \ + "${root}/lib/modules"/*/kernel/drivers/net/net_failover.ko \ + "${root}/lib/modules"/*/kernel/drivers/net/virtio_net.ko \ + "${root}/lib/modules"/*/kernel/drivers/virtio/* \ + "${root}/lib/modules"/*/kernel/fs/netfs/netfs.ko \ + "${root}/lib/modules"/*/kernel/fs/9p/9p.ko \ + "${root}/lib/modules"/*/kernel/fs/fscache/fscache.ko \ + "${root}/lib/modules"/*/kernel/net/9p/9pnet.ko \ + "${root}/lib/modules"/*/kernel/net/9p/9pnet_virtio.ko \ + "${root}/lib/modules"/*/kernel/net/core/failover.ko \ + "${root}/modules" || true # some file may not exist + rm -rf "${root:?}/boot" + rm -rf "${root:?}/lib/modules" + + cat << 'EOF' > "${root}/etc/hosts" +127.0.0.1 localhost qemu +EOF + + cat << 'EOF' > $root/etc/hostname +qemu +EOF + + cat << 'EOF' > $root/etc/passwd +root::0:0:root:/root:/bin/sh +EOF + +cat << 'EOF' | uudecode -o $root/etc/dropbear/dropbear_rsa_host_key +begin 600 dropbear_rsa_host_key +M````!W-S:"UR+3[X=QMH,B*4$RYULV,V3X6]K:7@Q?80"#WXGGQZNFN6CZ7LTDX(F6J[\]F5<0`HEOF:Z +MX;^53`L'4I/A```!``$L:$Z*#6<^3@+O%.[-#/5H+.C'3\#QQZN[1;J>L`8I +MZ_&T'!"J'/Y+?R?55G:M^=]R*-&I3TOJYZA8@&H51ZOAF59'1_>>Z@?E4#)$ +MQU)X/RWH51ZB5KSDWJS:D'7GD(!?NAY`C'7\)I:_4)J")QBV/P"RJQGHG'%B +M1BT2LE6676>`1K,0\NIMZTKQNB(IC+88<7#8%_-=P<&6<"9LH>60TSS?3?-C +MN`T36YB/3^<(Q;`N1NT>I9EZS`BAC^-?.:,R\7EL"<4>7E=]^1]B\K9])AQU +MBM\]M;4V(S(6KH-I.4[6>9E+@\UEM.J6:[2LUEEJDG:G:+:/EVF^Y75@(S$` +M``"!`.O+KW=&*CBCHL"11&SVO4/K]$R-]7MV7,3RR)Q[X'0;6.?4JHW!3VR6 +M*FGBY--37ZD-+UV.8_+"$6PH9)(/E.\G19#G0K`LRM?JWS!58&;D0C1```` +M@0"\[@NYWSTW(?Q@:_A*1Y3/AKYO5?S=0"<2>#V-AH6W-NCSDTSRP=2D79FS +M"D?[;.)V>8'#9&I3"MU@+:2\Z%$0-MG0+J'(0>T1_C6?*C=4U0I$DI<=@D]1 +H_&DE8Y(OT%%EPG]!$H&5HX*),_D1A2\P=R.7G'`0L%YM-79Y"T">$0`` +` +end +EOF + + # dropbear complains when this file is missing + touch "${root}/var/log/lastlog" + + cat << 'EOF' > $root/init +#!/bin/busybox sh + +set -e + +/bin/busybox --install + +mount -t devtmpfs devtmpfs /dev +mount -t proc none /proc +mount -t sysfs none /sys +mkdir /dev/pts +mount -t devpts none /dev/pts/ + +# some archs does not have virtio modules +insmod /modules/failover.ko || true +insmod /modules/net_failover.ko || true +insmod /modules/virtio.ko || true +insmod /modules/virtio_ring.ko || true +insmod /modules/virtio_mmio.ko || true +insmod /modules/virtio_pci_legacy_dev.ko || true +insmod /modules/virtio_pci_modern_dev.ko || true +insmod /modules/virtio_pci.ko || true +insmod /modules/virtio_net.ko || true +insmod /modules/netfs.ko || true +insmod /modules/fscache.ko +insmod /modules/9pnet.ko +insmod /modules/9pnet_virtio.ko || true +insmod /modules/9p.ko + +ifconfig lo 127.0.0.1 +ifconfig eth0 10.0.2.15 +route add default gw 10.0.2.2 eth0 + +mkdir /target +mount -t 9p -o trans=virtio target /target -oversion=9p2000.u || true + +exec dropbear -F -E -B +EOF + + chmod +x "${root}/init" + cd "${root}" + find . | cpio --create --format='newc' --quiet | gzip > ../initrd.gz + cd - + + # Clean up + rm -rf "/qemu/${root}" "/qemu/${arch}" + mv -f /etc/apt/sources.list.bak /etc/apt/sources.list + if [ -f /etc/dpkg/dpkg.cfg.d/multiarch.bak ]; then + mv /etc/dpkg/dpkg.cfg.d/multiarch.bak /etc/dpkg/dpkg.cfg.d/multiarch + fi + # can fail if arch is used (amd64 and/or i386) + dpkg --remove-architecture "${arch}" || true + apt-get update + + purge_packages + + ls -lh /qemu +} + +main "${@}" diff --git a/.docker/cross/linux-runner b/.docker/cross/linux-runner new file mode 100755 index 000000000000..2ef0efc674d4 --- /dev/null +++ b/.docker/cross/linux-runner @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +set -e + +LOG=/tmp/qemu.log +LOCK=/tmp/qemu.lock + +if [ -n "${CROSS_DEBUG}" ]; then + set -x +fi + +# arch in the rust target +arch="${1}" +shift + +if [ "${CROSS_RUNNER}" = "" ]; then + if [[ "${arch}" == i?86 ]] || [[ "${arch}" == x86_64 ]]; then + CROSS_RUNNER=native + else + CROSS_RUNNER=qemu-user + fi +fi + +# select qemu arch +qarch="${arch}" +case "${arch}" in + armv7) + qarch="arm" + ;; + i686) + qarch="i386" + ;; + powerpc) + qarch="ppc" + ;; + powerpc64) + qarch="ppc64" + ;; + powerpc64le) + if [ "${CROSS_RUNNER}" = "qemu-user" ]; then + qarch="ppc64le" + else + qarch="ppc64" + fi + ;; +esac + +case "${CROSS_RUNNER}" in + native) + exec "${@}" + ;; + qemu-user) + exec "qemu-${qarch}" "${@}" + ;; + qemu-system) + true + ;; + *) + echo "Invalid runner: \"${CROSS_RUNNER}\""; + echo "Valid runners are: native, qemu-user and qemu-system" + exit 1 + ;; +esac + +n="$(nproc)" +memory=1G +driver9p="virtio-9p-pci" +drivernet="virtio-net-pci" + +# select qemu parameters +case "${arch}" in + aarch64) + # 8 is the max number of cpu supported by qemu-aarch64 + n=$(( n > 8 ? 8 : n )) + opt="-machine virt -cpu cortex-a57" + ;; + armv7) + opt="-machine virt" + driver9p="virtio-9p-device" + drivernet="virtio-net-device" + ;; + i686) + opt="-append console=ttyS0" + ;; + mips|mipsel) + # avoid kernel error + # https://blahcat.github.io/2017/07/14/building-a-debian-stretch-qemu-image-for-mipsel/ + opt="-append nokaslr" + n=1 + ;; + mips64el) + # avoid kernel error + # https://blahcat.github.io/2017/07/14/building-a-debian-stretch-qemu-image-for-mipsel/ + opt="-append nokaslr -cpu MIPS64R2-generic" + n=1 + ;; + powerpc) + opt="-append console=ttyPZ0" + n=1 + ;; + powerpc64|powerpc64le) + opt="-append console=hvc0 --nodefaults -serial stdio" + ;; + s390x) + n=1 + driver9p="virtio-9p-ccw" + drivernet="virtio-net-ccw" + ;; + sparc64) + n=1 + driver9p+=",bus=pciB" + drivernet+=",bus=pciB" + ;; + x86_64) + opt="-append console=ttyS0" + ;; +esac + +( + flock -n 200 || exit 0 + + echo Booting QEMU virtual machine with $n cpus... + + QEMU_CMD="qemu-system-${qarch} \ + -m ${memory} \ + -smp ${n} \ + -nographic \ + -monitor none \ + -netdev user,id=net0,hostfwd=tcp::10022-:22 \ + -device ${drivernet},netdev=net0 \ + -kernel /qemu/kernel \ + -initrd /qemu/initrd.gz \ + ${opt} \ + -fsdev local,id=fs0,path=/target,security_model=mapped \ + -device ${driver9p},fsdev=fs0,mount_tag=target" + + touch "${LOG}" + if [[ -n "${CROSS_DEBUG}" ]]; then + (${QEMU_CMD} 2>&1 | tee -a "${LOG}") & + else + ${QEMU_CMD} >> "${LOG}" 2>&1 & + fi + + # wait for dropbear + for _ in $(seq 240); do + if grep -q "Not backgrounding" "${LOG}"; then + READY=1 + break + fi + sleep 0.5s + done + + if [ -z "${READY}" ]; then + if [ -n "${CROSS_DEBUG}" ]; then + echo "Not ready but continuing because CROSS_DEBUG is set" + else + echo "Qemu is not ready after ${SECONDS} seconds..." + echo "Set the environment variable CROSS_DEBUG=1 to debug" + echo "Last 100 lines of qemu output:" + tail -n 100 "${LOG}" + exit 1 + fi + fi + + echo "Booted in ${SECONDS} seconds" + +) 200>"${LOCK}" + +if [[ -t 1 ]] && [[ -t 2 ]]; then + tty_flag='-t' +fi + +exec dbclient ${tty_flag} -p 10022 -y -y root@localhost "${@}" diff --git a/.docker/cross/qemu.sh b/.docker/cross/qemu.sh new file mode 100755 index 000000000000..75626c55cb35 --- /dev/null +++ b/.docker/cross/qemu.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash + +set -x +set -euo pipefail + +# shellcheck disable=SC1091 +. lib.sh + +build_static_libffi () { + local version=3.0.13 + + local td + td="$(mktemp -d)" + + pushd "${td}" + + + curl --retry 3 -sSfL "https://github.com/libffi/libffi/archive/refs/tags/v${version}.tar.gz" -O -L + tar --strip-components=1 -xzf "v${version}.tar.gz" + ./configure --prefix="$td"/lib --disable-builddir --disable-shared --enable-static + make "-j$(nproc)" + install -m 644 ./.libs/libffi.a /usr/lib64/ + + popd + + rm -rf "${td}" +} + +build_static_libmount () { + local version_spec=2.23.2 + local version=2.23 + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "https://kernel.org/pub/linux/utils/util-linux/v${version}/util-linux-${version_spec}.tar.xz" -O -L + tar --strip-components=1 -xJf "util-linux-${version_spec}.tar.xz" + ./configure --disable-shared --enable-static --without-ncurses + make "-j$(nproc)" mount blkid + install -m 644 ./.libs/*.a /usr/lib64/ + + popd + + rm -rf "${td}" +} + + +build_static_libattr() { + local version=2.4.46 + + local td + td="$(mktemp -d)" + + pushd "${td}" + + yum install -y gettext + + curl --retry 3 -sSfL "https://download.savannah.nongnu.org/releases/attr/attr-${version}.src.tar.gz" -O + tar --strip-components=1 -xzf "attr-${version}.src.tar.gz" + cp /usr/share/automake*/config.* . + + ./configure + make "-j$(nproc)" + install -m 644 ./libattr/.libs/libattr.a /usr/lib64/ + + yum remove -y gettext + + popd + + rm -rf "${td}" +} + +build_static_libcap() { + local version=2.22 + + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "https://www.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-${version}.tar.xz" -O + tar --strip-components=1 -xJf "libcap-${version}.tar.xz" + make "-j$(nproc)" + install -m 644 libcap/libcap.a /usr/lib64/ + + popd + + rm -rf "${td}" +} + +build_static_pixman() { + local version=0.34.0 + + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "https://www.cairographics.org/releases/pixman-${version}.tar.gz" -O + tar --strip-components=1 -xzf "pixman-${version}.tar.gz" + ./configure + make "-j$(nproc)" + install -m 644 ./pixman/.libs/libpixman-1.a /usr/lib64/ + + popd + + rm -rf "${td}" +} + +main() { + local version=5.1.0 + + if_centos version=4.2.1 + + local arch="${1}" \ + softmmu="${2:-}" + + install_packages \ + autoconf \ + automake \ + bison \ + bzip2 \ + curl \ + flex \ + libtool \ + make \ + patch \ + python3 \ + + if_centos install_packages \ + gcc-c++ \ + pkgconfig \ + xz \ + glib2-devel \ + glib2-static \ + glibc-static \ + libattr-devel \ + libcap-devel \ + libfdt-devel \ + pcre-static \ + pixman-devel \ + libselinux-devel \ + libselinux-static \ + libffi \ + libuuid-devel \ + libblkid-devel \ + libmount-devel \ + zlib-devel \ + zlib-static + + if_centos 'curl --retry 3 -sSfL "https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD" -o /usr/share/automake*/config.guess' + if_centos 'curl --retry 3 -sSfL "https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD" -o /usr/share/automake*/config.sub' + + # these are not packaged as static libraries in centos; build them manually + if_centos build_static_libffi + if_centos build_static_libmount + if_centos build_static_libattr + if_centos build_static_libcap + if_centos build_static_pixman + + if_ubuntu install_packages \ + g++ \ + pkg-config \ + xz-utils \ + libattr1-dev \ + libcap-ng-dev \ + libffi-dev \ + libglib2.0-dev \ + libpixman-1-dev \ + libselinux1-dev \ + zlib1g-dev + + # if we have python3.6+, we can install qemu 6.1.0, which needs ninja-build + # ubuntu 16.04 only provides python3.5, so remove when we have a newer qemu. + is_ge_python36=$(python3 -c "import sys; print(int(sys.version_info >= (3, 6)))") + if [[ "${is_ge_python36}" == "1" ]]; then + if_ubuntu version=6.1.0 + if_ubuntu install_packages ninja-build + fi + + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "https://download.qemu.org/qemu-${version}.tar.xz" -O + tar --strip-components=1 -xJf "qemu-${version}.tar.xz" + + local targets="${arch}-linux-user" + local virtfs="" + case "${softmmu}" in + softmmu) + if [ "${arch}" = "ppc64le" ]; then + targets="${targets},ppc64-softmmu" + else + targets="${targets},${arch}-softmmu" + fi + virtfs="--enable-virtfs" + ;; + "") + true + ;; + *) + echo "Invalid softmmu option: ${softmmu}" + exit 1 + ;; + esac + + ./configure \ + --disable-kvm \ + --disable-vnc \ + --disable-guest-agent \ + --enable-linux-user \ + --static \ + ${virtfs} \ + --target-list="${targets}" + make "-j$(nproc)" + make install + + # HACK the binfmt_misc interpreter we'll use expects the QEMU binary to be + # in /usr/bin. Create an appropriate symlink + ln -s "/usr/local/bin/qemu-${arch}" "/usr/bin/qemu-${arch}-static" + + purge_packages + + popd + + rm -rf "${td}" + rm "${0}" +} + +main "${@}" diff --git a/.docker/cross/xargo.sh b/.docker/cross/xargo.sh new file mode 100755 index 000000000000..5f7e2dcd623b --- /dev/null +++ b/.docker/cross/xargo.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -x +set -euo pipefail + +# shellcheck disable=SC1091 +. lib.sh + +main() { + install_packages ca-certificates curl + + export RUSTUP_HOME=/tmp/rustup + export CARGO_HOME=/tmp/cargo + + curl --retry 3 -sSfL https://sh.rustup.rs -o rustup-init.sh + sh rustup-init.sh -y --no-modify-path --profile minimal + rm rustup-init.sh + + PATH="${CARGO_HOME}/bin:${PATH}" cargo install xargo --root /usr/local + + rm -r "${RUSTUP_HOME}" "${CARGO_HOME}" + + purge_packages + + rm "${0}" +} + +main "${@}" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 66cbbae915ed..a5296b46d1ea 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,24 +1,8 @@ # Current WG Code Sub Teams: -# @tauri-apps/admins -# @tauri-apps/bundler -# @tauri-apps/core -# @tauri-apps/js-cli -# @tauri-apps/testing +# @tauri-apps/wg-tauri +# @tauri-apps/wg-devops -# admins default to review # Order is important; the last matching pattern takes the most precedence. -* @tauri-apps/admins +* @tauri-apps/wg-tauri -.github @tauri-apps/admins @tauri-apps/testing - -/examples/ @tauri-apps/testing - -/tooling/api/ @tauri-apps/core - -/tooling/bundler/ @tauri-apps/bundler - -/tooling/cli.rs/ @tauri-apps/core - -/tooling/cli.rs/node/ @tauri-apps/js-cli - -/core/** @tauri-apps/core +.github @tauri-apps/wg-devops diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 4ead0e1daf29..8a344e4fc8d2 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,13 +1,133 @@ -# Contributor Code of Conduct -As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. +# Contributor Covenant Code of Conduct -We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. +## Our Pledge -Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. +## Our Standards -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[contact@tauri.app](mailto:contact@tauri.app). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 382d254e570b..cdf2d5c619c6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -28,8 +28,7 @@ Hi! We, the maintainers, are really excited that you are interested in contribut ## Pull Request Guidelines -- We only accept *signed commits*, so sign your commits! [Here is a great guide](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) that walks you through all the required steps. - > Note: Despite what the guide claims, GitHub Desktop honors the repo/system wide git config, so after running `git config --global commit.gpgsign true` GitHub Desktop will sign commits as well. +- You have to [sign your commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). - It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging. @@ -49,30 +48,30 @@ Hi! We, the maintainers, are really excited that you are interested in contribut First, [join our Discord server](https://discord.gg/SpmNs4S) and let us know that you want to contribute. This way we can point you in the right direction and help ensure your contribution will be as helpful as possible. -To set up your machine for development, follow the [Tauri setup guide](https://tauri.studio/en/docs/get-started/intro#setting-up-your-environment) to get all the tools you need to develop Tauri apps. The only additional tool you may need is [Yarn](https://yarnpkg.com/), it is only required if you are developing the Node CLI or API packages (`tooling/cli.rs/node` and `tooling/api`). Next, fork and clone this repo. It is structured as a monorepo, which means that all the various Tauri packages are under the same repository. The development process varies depending on what part of Tauri you are contributing to, see the guides below for per-package instructions. +To set up your machine for development, follow the [Tauri setup guide](https://tauri.app/v1/guides/getting-started/prerequisites/) to get all the tools you need to develop Tauri apps. The only additional tool you may need is [Yarn](https://yarnpkg.com/), it is only required if you are developing the Node CLI or API packages (`tooling/cli/node` and `tooling/api`). Next, fork and clone this repo. It is structured as a monorepo, which means that all the various Tauri packages are under the same repository. The development process varies depending on what part of Tauri you are contributing to, see the guides below for per-package instructions. -Some Tauri packages will be automatically built when running one of the examples. Others, however, will need to be built beforehand. To build these automatically, run the `.scripts/setup.sh` (Linux and macOS) or `.scripts/setup.ps1` (Windows) script. This will install the Rust and Node.js CLI and build the JS API. After that, you should be able to run all the examples. Note that the setup script should be executed from the root folder of the respository in order to run correctly. +Some Tauri packages will be automatically built when running one of the examples. Others, however, will need to be built beforehand. To build these automatically, run the `.scripts/setup.sh` (Linux and macOS) or `.scripts/setup.ps1` (Windows) script. This will install the Rust and Node.js CLI and build the JS API. After that, you should be able to run all the examples. Note that the setup script should be executed from the root folder of the repository in order to run correctly. ### Packages Overview -- The JS API (`/tooling/api`) contains JS bindings to the builtin Rust functions in the Rust API. -- The Rust CLI (`/tooling/cli.rs`) is the primary CLI for creating and developing Tauri apps. -- cli.js (`/tooling/cli.rs/node`) is a Node.js CLI wrapper for `cli.rs`. -- Tauri Bundler (`/tooling/bundler`) is used by the Rust CLI to package executables into installers. - Tauri Core (`/core/tauri`) is the heart of Tauri. It contains the code that starts the app, configures communication between Rust and the Webview, and ties all the other packages together. - The Macros (`/core/tauri-macros`) are used by Tauri Core for various functions. +- Tauri Bundler (`/tooling/bundler`) is used by the Rust CLI to package executables into installers. +- The Rust CLI aka `tauri-cli` (`/tooling/cli`) is the primary CLI for creating and developing Tauri apps. +- The JS CLI aka `@tauri-apps/cli` (`/tooling/cli/node`) is a Node.js CLI wrapper for `tauri-cli`. +- The JS API aka `@tauri-apps/api` (`/tooling/api`) contains JS bindings to the builtin Rust functions in the Rust API. -### Developing The Node.js CLI (cli.js) +### Developing Tauri Bundler and Rust CLI -`cli.js` is a wrapper to `cli.rs` so most changes should be written on the Rust CLI. The `[Tauri repo root]/tooling/cli.rs/node` folder contains only packaging scripts to properly publish the Rust CLI binaries to NPM. +The code for the bundler is located in `[Tauri repo root]/tooling/bundler`, and the code for the Rust CLI is located in `[Tauri repo root]/tooling/cli`. If you are using your local copy of `@tauri-apps/cli` (see above), any changes you make to the bundler and CLI will be automatically built and applied when running the build or dev command. Otherwise, running `cargo install --path .` in the Rust CLI directory will allow you to run `cargo tauri build` and `cargo tauri dev` anywhere, using the updated copy of the bundler and cli. You will have to run this command each time you make a change in either package. -### Developing Tauri Bundler and Rust CLI +### Developing The Node.js CLI (`@tauri-apps/cli`) -The code for the bundler is located in `[Tauri repo root]/tooling/bundler`, and the code for the Rust CLI is located in `[Tauri repo root]/tooling/cli.rs`. If you are using your local copy of cli.js (see above), any changes you make to the bundler and CLI will be automatically built and applied when running the build or dev command. Otherwise, running `cargo install --path .` in the Rust CLI directory will allow you to run `cargo tauri build` and `cargo tauri dev` anywhere, using the updated copy of the bundler and cli. You will have to run this command each time you make a change in either package. +`@tauri-apps/cli` is a wrapper to `tauri-cli` so most changes should be written on the Rust CLI. The `[Tauri repo root]/tooling/cli/node` folder contains only packaging scripts to properly publish the Rust CLI binaries to NPM. ### Developing Tauri Core and Related Components (Rust API, Macros, Codegen, and Utils) -The code for Tauri Core is located in `[Tauri repo root]/core/tauri`, and the Rust API, Macros, and Utils are in `[Tauri repo root]/core/tauri-(api/macros/utils)`. The easiest way to test your changes is to use the `[Tauri repo root]/examples/helloworld` app. It automatically rebuilds and uses your local copy of the Tauri core packages. Just run `yarn tauri build` or `yarn tauri dev` in the helloworld app directory after making changes to test them out. To use your local changes in another project, edit its `src-tauri/Cargo.toml` file so that the `tauri` key looks like `tauri = { path = "PATH", features = [ "api-all", "cli" ] }`, where `PATH` is the relative path to `[Tauri repo root]/core/tauri`. Then, your local copy of the Tauri core packages will be rebuilt and used whenever you build that project. +The code for Tauri Core is located in `[Tauri repo root]/core/tauri`, and the Rust API, Macros, and Utils are in `[Tauri repo root]/core/tauri-(api/macros/utils)`. The easiest way to test your changes is to use the `[Tauri repo root]/examples/helloworld` app. It automatically rebuilds and uses your local copy of the Tauri core packages. Just run `yarn tauri build` or `yarn tauri dev` in the helloworld app directory after making changes to test them out. To use your local changes in another project, edit its `src-tauri/Cargo.toml` file so that the `tauri` key looks like `tauri = { path = "PATH" }`, where `PATH` is the relative path to `[Tauri repo root]/core/tauri`. Then, your local copy of the Tauri core packages will be rebuilt and used whenever you build that project. #### Building the documentation locally @@ -88,4 +87,4 @@ The JS API provides bindings between the developer's JS in the Webview and the b ## Financial Contribution -Tauri is an MIT-licensed open source project. Its ongoing development can be supported via [Github Sponsors](https://github.com/sponsors/nothingismagick) or [Open Collective](https://opencollective.com/tauri). We prefer Github Sponsors as donations made are doubled through the matching fund program. +Tauri is an MIT-licensed open source project. Its ongoing development can be supported via [GitHub Sponsors](https://github.com/sponsors/nothingismagick) or [Open Collective](https://opencollective.com/tauri). We prefer GitHub Sponsors as donations made are doubled through the matching fund program. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0c0d6c42e1e3..8862ef7bd89f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,10 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + # These are supported funding model platforms -github: nothingismagick +github: tauri-apps patreon: # open_collective: tauri ko_fi: # Replace with a single Ko-fi username diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b2a5c15882b9..23393b98a3ba 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,7 +1,11 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + name: 🐞 Bug Report title: '[bug] ' description: Report a bug -labels: 'type: bug' +labels: ['type: bug', 'status: needs triage'] body: - type: markdown @@ -26,11 +30,10 @@ body: id: reproduction attributes: label: Reproduction - description: Steps to reproduce the behavior. + description: A link to a reproduction repo or steps to reproduce the behaviour. placeholder: | - 1. Go to ... - 2. Click on ... - 3. See error + Please provide a minimal reproduction or steps to reproduce, see this guide https://stackoverflow.com/help/minimal-reproducible-example + Why reproduction is required? see this article https://antfu.me/posts/why-reproductions-are-required - type: textarea id: expected-behavior @@ -43,7 +46,7 @@ body: attributes: label: Platform and versions description: "Output of `npm run tauri info` or `cargo tauri info`" - render: shell + render: text validations: required: true @@ -51,8 +54,8 @@ body: id: logs attributes: label: Stack trace - render: shell - + render: text + - type: textarea id: context attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index de285b08c7b5..d23ca5084f1a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,7 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + contact_links: - name: 💬 Discord Chat url: https://discord.com/invite/tauri diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 0b611fafba94..ce1bbe9d4f41 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,3 +1,7 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + name: 💡 Feature Request title: '[feat] ' description: Suggest an idea diff --git a/.github/config.yml b/.github/config.yml index e69de29bb2d1..52c9c7a8c105 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -0,0 +1,3 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT diff --git a/.github/workflows/artifacts-updater.yml b/.github/workflows/artifacts-updater.yml deleted file mode 100644 index 8508333d46f3..000000000000 --- a/.github/workflows/artifacts-updater.yml +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: updater test artifacts -on: - push: - branches: - - dev - - next - pull_request: - paths: - - '.github/workflows/artifacts-updater.yml' - - 'core/tauri/**' - - 'tooling/cli.rs/**' - - 'tooling/bundler/**' - - 'examples/updater/**' - -jobs: - build-artifacs: - runs-on: ${{ matrix.platform }} - - strategy: - fail-fast: false - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v2 - - name: install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - - name: install webkit2gtk (ubuntu only) - if: matrix.platform == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - if: matrix.platform == 'macos-latest' || matrix.platform == 'ubuntu-latest' - - - name: Get current date - if: matrix.platform == 'windows-latest' - run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-index- - - - name: Cache core cargo target - uses: actions/cache@v2 - with: - path: target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-core-${{ hashFiles('core/**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-core-${{ hashFiles('core/**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-core- - - - name: Cache CLI cargo target - uses: actions/cache@v2 - with: - path: tooling/cli.rs/target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }} - ${{ matrix.platform }}-stable-cargo-cli- - - - name: build and install cli.rs - run: cargo install --path tooling/cli.rs - - name: Check whether code signing should be enabled - id: enablecodesigning - env: - ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }} - run: | - echo "Enable code signing: ${{ env.ENABLE_CODE_SIGNING != '' }}" - echo "::set-output name=enabled::${{ env.ENABLE_CODE_SIGNING != '' }}" - # run only on tauri-apps/tauri repo (require secrets) - - name: build sample artifacts + code signing (updater) - if: steps.enablecodesigning.outputs.enabled == 'true' - working-directory: ./examples/updater - run: | - yarn install - cargo tauri build - env: - # Notarization (disabled) - # FIXME: enable only on `dev` push maybe? as it take some times... - # - # APPLE_ID: ${{ secrets.APPLE_ID }} - # APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} - - # Apple code signing testing - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} - # Updater signature is exposed here to make sure it works in PR's - TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg== - # run on PRs and forks - - name: build sample artifacts (updater) - if: steps.enablecodesigning.outputs.enabled != 'true' - working-directory: ./examples/updater - run: | - yarn install - cargo tauri build - env: - TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg== - # upload assets - - uses: actions/upload-artifact@v2 - if: matrix.platform == 'ubuntu-latest' - with: - name: linux-updater-artifacts - path: ./examples/updater/src-tauri/target/release/bundle/appimage/updater-example_*.AppImage.* - - uses: actions/upload-artifact@v2 - if: matrix.platform == 'windows-latest' - with: - name: windows-updater-artifacts - path: ./examples/updater/src-tauri/target/release/bundle/msi/* - - uses: actions/upload-artifact@v2 - if: matrix.platform == 'macos-latest' - with: - name: macos-updater-artifacts - path: ./examples/updater/src-tauri/target/release/bundle/macos/updater-example.app.tar.* diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index b2897c1bf1bd..a11f6de7bf15 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -1,4 +1,4 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT @@ -10,10 +10,14 @@ on: - cron: '0 0 * * *' push: paths: - - "**/Cargo.lock" - - "**/Cargo.toml" - - "**/package.json" - - "**/yarn.lock" + - '**/Cargo.lock' + - '**/Cargo.toml' + - '**/package.json' + - '**/yarn.lock' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: audit-rust: @@ -30,5 +34,5 @@ jobs: steps: - uses: actions/checkout@v2 - name: yarn audit - working-directory: tooling/cli.rs/node + working-directory: tooling/cli/node run: yarn audit diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 878b3745b830..6738a4d563eb 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -1,4 +1,8 @@ -name: benches +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: bench on: push: @@ -7,96 +11,65 @@ on: - next workflow_dispatch: +env: + RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency. + LC_ALL: en_US.UTF-8 # This prevents strace from changing it's number format to use commas. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: bench: strategy: fail-fast: false matrix: - rust_version: [stable] + rust: [nightly] platform: - - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-20.04 } runs-on: ${{ matrix.platform.os }} steps: - uses: actions/checkout@v2 - - name: install nightly + - name: install ${{ matrix.rust }} uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: ${{ matrix.rust }} override: true + default: true components: rust-src target: ${{ matrix.platform.target }} - name: setup python uses: actions/setup-python@v2 with: - python-version: "3.x" + python-version: '3.10' architecture: x64 - - name: install depedencies + - name: install dependencies run: | python -m pip install --upgrade pip sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf xvfb + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev xvfb wget https://github.com/sharkdp/hyperfine/releases/download/v1.11.0/hyperfine_1.11.0_amd64.deb sudo dpkg -i hyperfine_1.11.0_amd64.deb pip install memory_profiler - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 + - uses: Swatinem/rust-cache@v2 with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-nightly-cargo-registry- + workspaces: | + core -> ../target + tooling/bench/tests - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-nightly-cargo-index- - - - name: Cache core cargo target - uses: actions/cache@v2 - with: - path: target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-nightly-cargo-core-${{ hashFiles('core/**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-nightly-cargo-core-${{ hashFiles('core/**/Cargo.toml') }} - ${{ matrix.platform }}-nightly-cargo-core- - - - name: cache cargo `tooling/bench/tests` target - uses: actions/cache@v2 - with: - path: tooling/bench/tests/target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-nightly-cargo-benches-${{ hashFiles('tooling/bench/tests/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-nightly-cargo-benches-${{ hashFiles('tooling/bench/tests/Cargo.lock') }} - ${{ matrix.platform }}-nightly-cargo-benches- - - name: run benchmarks run: | - cargo +nightly build --release -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target ${{ matrix.platform.target }} --manifest-path tooling/bench/tests/Cargo.toml + cargo build --release -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target ${{ matrix.platform.target }} --manifest-path tooling/bench/tests/Cargo.toml xvfb-run --auto-servernum cargo run --manifest-path tooling/bench/Cargo.toml --bin run_benchmark - name: clone benchmarks_results - if: github.repository == 'tauri-apps/tauri' && github.ref == 'refs/heads/dev' + if: github.repository == 'tauri-apps/tauri' && github.ref == 'refs/heads/dev' uses: actions/checkout@v2 with: token: ${{ secrets.BENCH_PAT }} @@ -104,7 +77,7 @@ jobs: repository: tauri-apps/benchmark_results - name: push new benchmarks - if: github.repository == 'tauri-apps/tauri' && github.ref == 'refs/heads/dev' + if: github.repository == 'tauri-apps/tauri' && github.ref == 'refs/heads/dev' run: | cargo run --manifest-path tooling/bench/Cargo.toml --bin build_benchmark_jsons cd gh-pages @@ -115,7 +88,7 @@ jobs: git commit --message "Update Tauri benchmarks" git push origin gh-pages - - name: Worker info + - name: Print worker info run: | cat /proc/cpuinfo cat /proc/meminfo diff --git a/.github/workflows/build-smoke-tests.yml b/.github/workflows/build-smoke-tests.yml deleted file mode 100644 index ea0d916926a9..000000000000 --- a/.github/workflows/build-smoke-tests.yml +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: build smoke test with source - -on: - workflow_dispatch: - inputs: - repository: - description: "Repository from which to pull and create a Tauri app." - required: false - default: "tauri-apps/tauri" - ref: - description: "Branch or ref to pull down." - required: false - default: "dev" - dir: - description: "Directory we expect to run in." - required: false - default: "tauri/examples/helloworld" - buildAssets: - description: "Command to build the assets." - required: false - default: "echo no build needed" - buildTauri: - description: "Command to build the Tauri app." - required: false - default: "tauri" -env: - RUST_BACKTRACE: 1 - -jobs: - create-and-upload-assets: - runs-on: ${{ matrix.platform }} - timeout-minutes: 30 - - strategy: - fail-fast: false - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - include: - - platform: ubuntu-latest - releaseFolder: target/release/bundle/deb - - platform: macos-latest - releaseFolder: target/release/bundle/macos - - platform: windows-latest - releaseFolder: target/release/bundle/msi - - steps: - - uses: actions/checkout@v2 - with: - path: tauri - - uses: actions/checkout@v2 - with: - repository: ${{ github.event.inputs.repository }} - ref: ${{ github.event.inputs.ref }} - path: example - - name: setup node - uses: actions/setup-node@v2 - with: - node-version: 14 - cache: yarn - cache-dependency-path: tooling/*/yarn.lock - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - - name: install webkit2gtk (ubuntu only) - if: matrix.platform == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - name: build and install cli.rs - run: cargo install --path tauri/tooling/cli.rs - - name: install and build assets - working-directory: "example/${{ github.event.inputs.dir }}" - run: ${{ github.event.inputs.buildAssets }} - - name: build tauri app - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tauriScript: cargo tauri - includeDebug: true - projectPath: "example/${{ github.event.inputs.dir }}" - preferGlobal: true - - uses: actions/upload-artifact@v2 - if: success() - with: - name: tauri-app.${{ matrix.platform }} - path: ${{ format('./{0}{1}/src-tauri/{2}/**', 'example/', github.event.inputs.dir, matrix.releaseFolder ) }} diff --git a/.github/workflows/change-status-on-PR.yml b/.github/workflows/change-status-on-PR.yml deleted file mode 100644 index e5732f88c869..000000000000 --- a/.github/workflows/change-status-on-PR.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: covector status -on: [pull_request] - -jobs: - covector: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: covector status - uses: jbolda/covector/packages/action@feat/enable-prereleases - id: covector - with: - command: "status" diff --git a/.github/workflows/check-change-tags.yml b/.github/workflows/check-change-tags.yml new file mode 100644 index 000000000000..8a947760b9bc --- /dev/null +++ b/.github/workflows/check-change-tags.yml @@ -0,0 +1,32 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: check change tags + +on: + pull_request: + paths: + - '.changes/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: dorny/paths-filter@v2 + id: filter + with: + list-files: shell + filters: | + changes: + - '.changes/*.md' + + - name: check + run: node ./.scripts/ci/check-change-tags.js ${{ steps.filter.outputs.changes_files }} + if: ${{ steps.filter.outputs.changes == 'true' }} diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml new file mode 100644 index 000000000000..0f5b6ecc64c1 --- /dev/null +++ b/.github/workflows/check-generated-files.yml @@ -0,0 +1,86 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: check generated files + +on: + pull_request: + paths: + - '.github/workflows/check-generated-files.yml' + - 'tooling/api/src/**' + - 'core/tauri/scripts/bundle.global.js' + - 'core/tauri-utils/src/config.rs' + - 'tooling/cli/schema.json' + - 'core/tauri-config-schema/schema.json' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + api: ${{ steps.filter.outputs.api }} + schema: ${{ steps.filter.outputs.schema }} + steps: + - uses: actions/checkout@v3 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + api: + - 'tooling/api/src/**' + - 'tooling/api/docs/js-api.json' + - 'core/tauri/scripts/bundle.global.js' + schema: + - 'core/tauri-utils/src/config.rs' + - 'tooling/cli/schema.json' + - 'core/tauri-config-schema/schema.json' + + api: + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.api == 'true' + steps: + - uses: actions/checkout@v3 + + - name: build api + working-directory: tooling/api + run: yarn && yarn build + - name: check api + run: | + git restore tooling/api/docs/js-api.json + ./.scripts/ci/has-diff.sh + + schema: + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.schema == 'true' + steps: + - uses: actions/checkout@v3 + + - name: install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: core -> ../target + + - name: generate schema.json + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path ./core/tauri-config-schema/Cargo.toml + + - name: check schema + run: ./.scripts/ci/has-diff.sh diff --git a/.github/workflows/check-license-header.yml b/.github/workflows/check-license-header.yml new file mode 100644 index 000000000000..00893dc0cc4e --- /dev/null +++ b/.github/workflows/check-license-header.yml @@ -0,0 +1,28 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: check license headers + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: dorny/paths-filter@v2 + id: filter + with: + list-files: shell + filters: | + added: + - added: '**' + - name: check header license on new files + if: ${{ steps.filter.outputs.added == 'true' }} + run: node ./.scripts/ci/check-license-header.js ${{ steps.filter.outputs.added_files }} diff --git a/.github/workflows/core-lint-fmt.yml b/.github/workflows/core-lint-fmt.yml deleted file mode 100644 index d63663b9e887..000000000000 --- a/.github/workflows/core-lint-fmt.yml +++ /dev/null @@ -1,209 +0,0 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: clippy and fmt check - -on: - push: - branches: - - dev - - next - pull_request: - paths: - - '.github/workflows/core-lint-fmt.yml' - - 'core/**' - - 'examples/**' - - 'tooling/cli.rs/**' - -jobs: - workspace_clippy_fmt_check: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: install webkit2gtk - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt, clippy - - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-index- - - - name: Cache core cargo target - uses: actions/cache@v2 - with: - path: target - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-core-${{ hashFiles('core/**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-core-${{ hashFiles('core/**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-core- - - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-targets --all-features -- -D warnings - name: workspace - - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - cli_clippy_fmt_check: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt, clippy - - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --manifest-path ./tooling/cli.rs/Cargo.toml --all-targets --all-features -- -D warnings - name: cli - - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-index- - - - name: Cache CLI cargo target - uses: actions/cache@v2 - with: - path: tooling/cli.rs/target - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }} - ubuntu-latest-nightly-cargo-cli- - - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --manifest-path ./tooling/cli.rs/Cargo.toml --all -- --check - - core_clippy_check: - runs-on: ubuntu-latest - strategy: - matrix: - feature: [custom-protocol, api-all] - - steps: - - uses: actions/checkout@v2 - - name: install webkit2gtk - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt, clippy - - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - if: matrix.platform == 'macos-latest' || matrix.platform == 'ubuntu-latest' - - - name: Get current date - if: matrix.platform == 'windows-latest' - run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-index- - - - name: Cache core cargo target - uses: actions/cache@v2 - with: - path: target - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-core-${{ hashFiles('core/**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-core-${{ hashFiles('core/**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-core- - - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --manifest-path ./core/tauri/Cargo.toml --all-targets --features ${{ matrix.feature }} -- -D warnings - name: core diff --git a/.github/workflows/covector-status.yml b/.github/workflows/covector-status.yml new file mode 100644 index 000000000000..cdc3f5ca47e9 --- /dev/null +++ b/.github/workflows/covector-status.yml @@ -0,0 +1,20 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: covector status +on: [pull_request] + +jobs: + covector: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: covector status + uses: jbolda/covector/packages/action@covector-v0 + id: covector + with: + command: 'status' diff --git a/.github/workflows/covector-version-or-publish-next.yml b/.github/workflows/covector-version-or-publish-next.yml new file mode 100644 index 000000000000..a2f79ce4a62f --- /dev/null +++ b/.github/workflows/covector-version-or-publish-next.yml @@ -0,0 +1,88 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: version or publish + +on: + push: + branches: + - next + +jobs: + version-or-publish: + runs-on: ubuntu-latest + timeout-minutes: 65 + outputs: + change: ${{ steps.covector.outputs.change }} + commandRan: ${{ steps.covector.outputs.commandRan }} + successfulPublish: ${{ steps.covector.outputs.successfulPublish }} + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-node@v2 + with: + node-version: 14 + registry-url: 'https://registry.npmjs.org' + cache: yarn + cache-dependency-path: tooling/*/yarn.lock + + - name: cargo login + run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }} + - name: git config + run: | + git config --global user.name "${{ github.event.pusher.name }}" + git config --global user.email "${{ github.event.pusher.email }}" + + - name: covector version or publish (publish when no change files present) + uses: jbolda/covector/packages/action@covector-v0 + id: covector + env: + NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }} + CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }} + with: + token: ${{ secrets.GITHUB_TOKEN }} + command: 'version-or-publish' + createRelease: true + + - name: Create Pull Request With Versions Bumped + if: steps.covector.outputs.commandRan == 'version' + uses: tauri-apps/create-pull-request@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: release/version-updates-next + title: (NEXT) Apply Version Updates From Current Changes + commit-message: 'apply version updates' + labels: 'version updates' + body: ${{ steps.covector.outputs.change }} + + - name: Trigger doc update + if: | + steps.covector.outputs.successfulPublish == 'true' && + steps.covector.outputs.packagesPublished != '' + uses: peter-evans/repository-dispatch@v1 + with: + token: ${{ secrets.ORG_TAURI_BOT_PAT }} + repository: tauri-apps/tauri-docs + event-type: update-docs + + - name: Trigger cli.js publishing workflow + if: | + steps.covector.outputs.successfulPublish == 'true' && + contains(steps.covector.outputs.packagesPublished, 'cli.rs') + uses: benc-uk/workflow-dispatch@v1 + with: + token: ${{ secrets.ORG_TAURI_BOT_PAT }} + workflow: publish-cli-js.yml + inputs: '{"releaseId": "${{ steps.covector.outputs.cli.js-releaseId }}", "ref": "${{ github.ref }}" }' + + - name: Trigger cli.rs publishing workflow + if: | + steps.covector.outputs.successfulPublish == 'true' && + contains(steps.covector.outputs.packagesPublished, 'cli.rs') + uses: benc-uk/workflow-dispatch@v1 + with: + token: ${{ secrets.ORG_TAURI_BOT_PAT }} + workflow: publish-cli-rs.yml diff --git a/.github/workflows/covector-version-or-publish.yml b/.github/workflows/covector-version-or-publish.yml index a5fd020a11a7..16b3eb0f5ce3 100644 --- a/.github/workflows/covector-version-or-publish.yml +++ b/.github/workflows/covector-version-or-publish.yml @@ -1,8 +1,8 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT -name: version or publish +name: covector version or publish on: push: @@ -10,6 +10,51 @@ on: - dev jobs: + run-integration-tests: + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: install Linux dependencies + if: matrix.platform == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev libfuse2 + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + core -> ../target + tooling/cli + + - name: build CLI + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path ./tooling/cli/Cargo.toml + + - name: run integration tests + run: cargo test --test '*' -- --ignored + + - name: run CLI tests + timeout-minutes: 30 + run: | + cd ./tooling/cli/node + yarn + yarn build + yarn test + version-or-publish: runs-on: ubuntu-latest timeout-minutes: 65 @@ -17,6 +62,8 @@ jobs: change: ${{ steps.covector.outputs.change }} commandRan: ${{ steps.covector.outputs.commandRan }} successfulPublish: ${{ steps.covector.outputs.successfulPublish }} + needs: + - run-integration-tests steps: - uses: actions/checkout@v2 @@ -29,42 +76,27 @@ jobs: cache: yarn cache-dependency-path: tooling/*/yarn.lock - - name: Cache CLI cargo target - uses: actions/cache@v2 - with: - path: tooling/cli.rs/target - # Add date to the cache to keep it up to date - key: ubuntu-latest-stable-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-stable-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }} - ubuntu-latest-stable-cargo-cli- - - - name: build CLI - uses: actions-rs/cargo@v1 - with: - command: build - args: --manifest-path ./tooling/cli.rs/Cargo.toml - - name: cargo login - run: cargo login ${{ secrets.crate_token }} + run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }} - name: git config run: | git config --global user.name "${{ github.event.pusher.name }}" git config --global user.email "${{ github.event.pusher.email }}" + - name: covector version or publish (publish when no change files present) - uses: jbolda/covector/packages/action@feat/enable-prereleases + uses: jbolda/covector/packages/action@covector-v0 id: covector env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }} CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }} with: token: ${{ secrets.GITHUB_TOKEN }} command: 'version-or-publish' createRelease: true + - name: Create Pull Request With Versions Bumped if: steps.covector.outputs.commandRan == 'version' - uses: tauri-apps/create-pull-request@v3.4.1 + uses: tauri-apps/create-pull-request@v3 with: token: ${{ secrets.GITHUB_TOKEN }} branch: release/version-updates @@ -74,19 +106,43 @@ jobs: body: ${{ steps.covector.outputs.change }} - name: Trigger doc update - if: steps.covector.outputs.successfulPublish == 'true' + if: | + steps.covector.outputs.successfulPublish == 'true' && + steps.covector.outputs.packagesPublished != '' uses: peter-evans/repository-dispatch@v1 with: - token: ${{ secrets.TAURI_BOT_PAT }} + token: ${{ secrets.ORG_TAURI_BOT_PAT }} repository: tauri-apps/tauri-docs event-type: update-docs - - name: Trigger cli.js publishing workflow + - name: Get `@tauri-apps/cli` release id + id: cliReleaseId + if: | + steps.covector.outputs.successfulPublish == 'true' && + contains(steps.covector.outputs.packagesPublished, '@tauri-apps/cli') + run: | + echo '${{ steps.covector.outputs }}' > output.json + id=$(jq '.["-tauri-apps-cli-releaseId"]' < output.json) + rm output.json + echo "cliReleaseId=$id" >> "$GITHUB_OUTPUT" + + - name: Trigger `@tauri-apps/cli` publishing workflow + if: | + steps.covector.outputs.successfulPublish == 'true' && + contains(steps.covector.outputs.packagesPublished, '@tauri-apps/cli') + uses: peter-evans/repository-dispatch@v1 + with: + token: ${{ secrets.ORG_TAURI_BOT_PAT }} + repository: tauri-apps/tauri + event-type: publish-js-cli + client-payload: '{"releaseId": "${{ steps.cliReleaseId.outputs.cliReleaseId }}" }' + + - name: Trigger `tauri-cli` publishing workflow if: | steps.covector.outputs.successfulPublish == 'true' && contains(steps.covector.outputs.packagesPublished, 'tauri-cli') uses: peter-evans/repository-dispatch@v1 with: - token: ${{ secrets.TAURI_BOT_PAT }} + token: ${{ secrets.ORG_TAURI_BOT_PAT }} repository: tauri-apps/tauri - event-type: publish-clijs + event-type: publish-clirs diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000000..f9b066b5dc8b --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,137 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: docker + +on: + workflow_dispatch: + #pull_request: + # paths: + # - '.docker/**' + # - '.github/workflows/docker.yml' + +jobs: + setup: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev + + - name: install cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Upload cross + uses: actions/upload-artifact@v3 + with: + name: cross + path: '~/.cargo/bin/cross' + if-no-files-found: error + + - name: build CLI + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path ./tooling/cli/Cargo.toml + + - name: Upload CLI + uses: actions/upload-artifact@v3 + with: + name: cargo-tauri + path: tooling/cli/target/debug/cargo-tauri + if-no-files-found: error + + docker: + needs: setup + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + target: + - { name: 'aarch64-unknown-linux-gnu', filename: 'aarch64' } + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + default: true + target: ${{ matrix.target.name }} + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Download cross + uses: actions/download-artifact@v3 + with: + name: cross + path: '~/.cargo/bin' + + - name: Download CLI + uses: actions/download-artifact@v3 + with: + name: cargo-tauri + path: 'examples/api' + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and export to Docker + uses: docker/build-push-action@v3 + with: + context: .docker/cross + file: .docker/cross/${{ matrix.target.filename }}.Dockerfile + load: true + tags: ${{ matrix.target.name }}:latest + + - name: install dependencies + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev + + - name: Test + run: | + cd tooling/api + yarn && yarn build + cd ../../examples/api + yarn + . .setup-cross.sh + chmod +x cargo-tauri + chmod +x $HOME/.cargo/bin/cross + ./cargo-tauri build --runner cross --bundles deb --target ${{ matrix.target.name }} --verbose + + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: .docker/cross + file: .docker/cross/${{ matrix.target.filename }}.Dockerfile + push: true + tags: ghcr.io/${{ github.repository }}/${{ matrix.target.name }}:latest diff --git a/.github/workflows/js-lint.yml b/.github/workflows/js-lint.yml deleted file mode 100644 index da3fe8381ca1..000000000000 --- a/.github/workflows/js-lint.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: eslint check - -on: - pull_request: - paths: - - '.github/workflows/js-lint.yml' - - 'tooling/cli.rs/node/**' - - 'tooling/api/**' - - 'tooling/create-tauri-app/**' - -jobs: - eslint-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: '12' - cache: yarn - cache-dependency-path: tooling/*/yarn.lock - - name: install cli.js deps via yarn - working-directory: ./tooling/cli.rs/node/ - run: yarn - # nothing to lint - #- name: run cli.js lint - # working-directory: ./tooling/cli.rs/node/ - # run: yarn lint - - name: run cli.js format - working-directory: ./tooling/cli.rs/node/ - run: yarn format:check - - - name: install api deps via yarn - working-directory: ./tooling/api/ - run: yarn - - name: run api lint - working-directory: ./tooling/api/ - run: yarn lint - - name: run api format - working-directory: ./tooling/api/ - run: yarn format:check - - - name: install create-tauri-app deps via yarn - working-directory: ./tooling/create-tauri-app/ - run: yarn - - name: run create-tauri-app lint - working-directory: ./tooling/create-tauri-app/ - run: yarn lint - - name: run create-tauri-app format - working-directory: ./tooling/create-tauri-app/ - run: yarn format:check diff --git a/.github/workflows/lint-cli.yml b/.github/workflows/lint-cli.yml new file mode 100644 index 000000000000..297330b136ea --- /dev/null +++ b/.github/workflows/lint-cli.yml @@ -0,0 +1,70 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: lint cli + +on: + push: + branches: + - dev + - next + pull_request: + paths: + - '.github/workflows/lint-cli.yml' + - 'tooling/cli/**' + +env: + RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + fmt: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --manifest-path ./tooling/cli/Cargo.toml --all -- --check + + clippy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: clippy + + - name: install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: tooling/cli + + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --manifest-path ./tooling/cli/Cargo.toml --all-targets --all-features -- -D warnings + name: cli diff --git a/.github/workflows/lint-core.yml b/.github/workflows/lint-core.yml new file mode 100644 index 000000000000..06c62a115766 --- /dev/null +++ b/.github/workflows/lint-core.yml @@ -0,0 +1,82 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: lint core + +on: + push: + branches: + - dev + - next + pull_request: + paths: + - '.github/workflows/lint-core.yml' + - 'core/**' + - '!core/tauri/scripts/**' + - 'examples/**' + +env: + RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + fmt: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + runs-on: ubuntu-latest + strategy: + matrix: + clippy: + - { args: '', key: 'empty' } + - { + args: '--features compression,wry,linux-protocol-headers,isolation,custom-protocol,system-tray,test', + key: 'all' + } + - { args: '--features custom-protocol', key: 'custom-protocol' } + + steps: + - uses: actions/checkout@v2 + + - name: install dependencies + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: clippy + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: core -> ../target + save-if: ${{ matrix.clippy.key == 'all' }} + + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --manifest-path ./core/tauri/Cargo.toml --all-targets ${{ matrix.clippy.args }} -- -D warnings + name: ${{ matrix.clippy.key }} diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml new file mode 100644 index 000000000000..7cf54f022af0 --- /dev/null +++ b/.github/workflows/lint-js.yml @@ -0,0 +1,57 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: lint js + +on: + pull_request: + paths: + - '.github/workflows/lint-js.yml' + - 'tooling/cli/node/**' + - 'tooling/api/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + eslint-cli: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '14' + cache: yarn + cache-dependency-path: tooling/cli/node/yarn.lock + - name: install deps via yarn + working-directory: ./tooling/cli/node/ + run: yarn + # nothing to lint + #- name: run lint + # working-directory: ./tooling/cli/node/ + # run: yarn lint + - name: run format + working-directory: ./tooling/cli/node/ + run: yarn format:check + + eslint-api: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '14' + cache: yarn + cache-dependency-path: tooling/api/yarn.lock + + - name: install deps via yarn + working-directory: ./tooling/api/ + run: yarn + - name: run lint + working-directory: ./tooling/api/ + run: yarn lint + - name: run format + working-directory: ./tooling/api/ + run: yarn format:check diff --git a/.github/workflows/publish-cli-js.yml b/.github/workflows/publish-cli-js.yml new file mode 100644 index 000000000000..9dab2702317b --- /dev/null +++ b/.github/workflows/publish-cli-js.yml @@ -0,0 +1,404 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: publish `@tauri-apps/cli` +env: + DEBUG: napi:* + APP_NAME: cli + MACOSX_DEPLOYMENT_TARGET: '10.13' +on: + workflow_dispatch: + inputs: + releaseId: + description: 'ID of the `@tauri-apps/cli` release' + required: true + repository_dispatch: + types: [publish-js-cli] + +defaults: + run: + working-directory: tooling/cli/node/ + +jobs: + build: + strategy: + fail-fast: false + matrix: + settings: + - host: macos-latest + target: x86_64-apple-darwin + architecture: x64 + build: | + yarn build:release + strip -x *.node + - host: windows-latest + build: yarn build:release + target: x86_64-pc-windows-msvc + architecture: x64 + - host: windows-latest + build: yarn build:release --target i686-pc-windows-msvc + target: i686-pc-windows-msvc + architecture: x64 + - host: windows-latest + architecture: x64 + target: aarch64-pc-windows-msvc + build: yarn build:release --target aarch64-pc-windows-msvc --features native-tls-vendored --cargo-flags="--no-default-features" + - host: ubuntu-20.04 + target: x86_64-unknown-linux-gnu + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian + build: | + cd tooling/cli/node + yarn build:release --target x86_64-unknown-linux-gnu + strip *.node + - host: ubuntu-20.04 + target: x86_64-unknown-linux-musl + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + build: | + cd tooling/cli/node + yarn build:release + strip *.node + - host: macos-latest + target: aarch64-apple-darwin + build: | + yarn build:release --target=aarch64-apple-darwin + strip -x *.node + - host: ubuntu-20.04 + target: aarch64-unknown-linux-gnu + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 + build: | + cd tooling/cli/node + yarn build:release --target aarch64-unknown-linux-gnu + aarch64-unknown-linux-gnu-strip *.node + - host: ubuntu-20.04 + architecture: x64 + target: armv7-unknown-linux-gnueabihf + setup: | + sudo apt-get update + sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y + build: | + yarn build:release --target=armv7-unknown-linux-gnueabihf + arm-linux-gnueabihf-strip *.node + - host: ubuntu-20.04 + architecture: x64 + target: aarch64-unknown-linux-musl + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + build: | + cd tooling/cli/node + rustup target add aarch64-unknown-linux-musl + yarn build:release --target aarch64-unknown-linux-musl + /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node + name: stable - ${{ matrix.settings.target }} - node@16 + runs-on: ${{ matrix.settings.host }} + steps: + - uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + if: ${{ !matrix.settings.docker }} + with: + node-version: 16 + check-latest: true + cache: yarn + cache-dependency-path: 'tooling/cli/node/yarn.lock' + architecture: ${{ matrix.settings.architecture }} + - name: Install + uses: actions-rs/toolchain@v1 + if: ${{ !matrix.settings.docker }} + with: + profile: minimal + override: true + toolchain: stable + target: ${{ matrix.settings.target }} + - uses: Swatinem/rust-cache@v1 + with: + key: ${{ matrix.settings.target }} + working-directory: 'tooling/cli/' + if: ${{ matrix.settings.docker }} + - name: Setup toolchain + run: ${{ matrix.settings.setup }} + if: ${{ matrix.settings.setup }} + shell: bash + - name: Install dependencies + run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + + - name: Build in docker + uses: addnab/docker-run-action@v3 + if: ${{ matrix.settings.docker }} + with: + image: ${{ matrix.settings.docker }} + options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/root/.cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/root/.cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/root/.cargo/registry/index -v ${{ github.workspace }}:/build -w /build + run: ${{ matrix.settings.build }} + + - name: Build + run: ${{ matrix.settings.build }} + if: ${{ !matrix.settings.docker }} + shell: bash + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: bindings-${{ matrix.settings.target }} + path: tooling/cli/node/${{ env.APP_NAME }}.*.node + if-no-files-found: error + # build-freebsd: + # runs-on: macos-10.15 + # name: Build FreeBSD + # steps: + # - uses: actions/checkout@v3 + # - name: Build + # id: build + # uses: vmactions/freebsd-vm@v0.1.6 + # env: + # DEBUG: napi:* + # RUSTUP_HOME: /usr/local/rustup + # CARGO_HOME: /usr/local/cargo + # RUSTUP_IO_THREADS: 1 + # with: + # envs: DEBUG RUSTUP_HOME CARGO_HOME RUSTUP_IO_THREADS + # usesh: true + # mem: 3000 + # prepare: | + # pkg install -y curl node14 python2 + # curl -qL https://www.npmjs.com/install.sh | sh + # npm install -g yarn + # curl https://sh.rustup.rs -sSf --output rustup.sh + # sh rustup.sh -y --profile minimal --default-toolchain stable + # export PATH="/usr/local/cargo/bin:$PATH" + # echo "~~~~ rustc --version ~~~~" + # rustc --version + # echo "~~~~ node -v ~~~~" + # node -v + # echo "~~~~ yarn --version ~~~~" + # yarn --version + # run: | + # export PATH="/usr/local/cargo/bin:$PATH" + # pwd + # ls -lah + # whoami + # env + # freebsd-version + # cd ./tooling/cli/node/ + # yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + # yarn build:release + # strip -x *.node + # rm -rf node_modules + # rm -rf ../target + # - name: Upload artifact + # uses: actions/upload-artifact@v3 + # with: + # name: bindings-freebsd + # path: tooling/cli/node/${{ env.APP_NAME }}.*.node + # if-no-files-found: error + test-macOS-windows-binding: + name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + settings: + - host: macos-latest + target: 'x86_64-apple-darwin' + - host: windows-latest + target: x86_64-pc-windows-msvc + node: + - '14' + - '16' + - '18' + runs-on: ${{ matrix.settings.host }} + steps: + - uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + check-latest: true + cache: yarn + cache-dependency-path: 'tooling/cli/node/yarn.lock' + - name: Install dependencies + run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-${{ matrix.settings.target }} + path: 'tooling/cli/node/' + - name: List packages + run: ls -R . + shell: bash + - name: Test bindings + run: yarn test + test-linux-x64-gnu-binding: + name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + node: + - '14' + - '16' + - '18' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + check-latest: true + cache: yarn + cache-dependency-path: 'tooling/cli/node/yarn.lock' + - name: Install dependencies + run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-x86_64-unknown-linux-gnu + path: 'tooling/cli/node/' + - name: List packages + run: ls -R . + shell: bash + - name: install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev + - name: Test bindings + run: yarn test + test-linux-x64-musl-binding: + name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + node: + - '14' + - '16' + - '18' + runs-on: ubuntu-latest + container: + image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + steps: + - uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + check-latest: true + cache: yarn + cache-dependency-path: 'tooling/cli/node/yarn.lock' + - name: Install dependencies + run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-x86_64-unknown-linux-musl + path: 'tooling/cli/node/' + - name: List packages + run: ls -R . + shell: bash + - name: Install system dependencies + run: | + apk add openssl-dev musl-dev glib-dev cairo-dev pkgconfig gdk-pixbuf-dev webkit2gtk-dev curl gtk+3.0-dev + - name: Setup and run tests + run: | + yarn tauri --help + ls -la + # TODO: fix this test: https://github.com/tauri-apps/tauri/runs/5145729140?check_suite_focus=true#step:9:704 + #- name: Setup and run tests + # run: | + # rustup install stable + # rustup default stable + # yarn test + # ls -la + test-linux-arm-bindings: + name: Test bindings on ${{ matrix.image }} - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + node: + - '14' + - '16' + - '18' + image: + - ghcr.io/napi-rs/napi-rs/nodejs:aarch64-16 + - ghcr.io/napi-rs/napi-rs/nodejs:armhf-16 + runs-on: ubuntu-latest + steps: + - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset + working-directory: ${{ github.workspace }} + - uses: actions/checkout@v3 + - name: List packages + run: ls -R . + shell: bash + - name: Install dependencies + run: yarn install --ignore-scripts --ignore-platform --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + - name: Download aarch64-gnu artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-aarch64-unknown-linux-gnu + path: 'tooling/cli/node/' + - name: Download armv7-gnueabihf artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-armv7-unknown-linux-gnueabihf + path: 'tooling/cli/node/' + # TODO: actually run test, blocked by https://github.com/rust-lang/cargo/issues/8719 + - uses: addnab/docker-run-action@v3 + with: + image: ${{ matrix.image }} + options: '-v ${{ github.workspace }}:/build -w /build -e RUSTUP_HOME=/usr/local/rustup -e CARGO_HOME=/usr/local/cargo' + shell: bash + run: | + set -e + export PATH=/usr/local/cargo/bin/:/usr/local/fnm:$PATH + apt-get update + DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install --no-install-recommends -y unzip libayatana-appindicator3-dev + bash + curl https://sh.rustup.rs -sSf | bash -s -- -y + curl -fsSL https://fnm.vercel.app/install | bash -s -- --install-dir "/usr/local/fnm" --skip-shell + eval "$(fnm env --use-on-cd)" + fnm install ${{ matrix.node }} + fnm use ${{ matrix.node }} + cd tooling/cli/node + yarn tauri --help + ls -la + publish: + name: Publish + runs-on: ubuntu-latest + needs: + #- build-freebsd + - test-macOS-windows-binding + - test-linux-x64-gnu-binding + - test-linux-x64-musl-binding + #- test-linux-arm-bindings + steps: + - uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + check-latest: true + cache: yarn + cache-dependency-path: 'tooling/cli/node/yarn.lock' + - name: Install dependencies + run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + - name: Download all artifacts + uses: actions/download-artifact@v3 + with: + path: tooling/cli/node/artifacts + - name: Move artifacts + run: yarn artifacts + - name: List packages + run: ls -R ./npm + shell: bash + - name: Publish + run: | + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + npm publish --tag next + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.ORG_NPM_TOKEN }} + RELEASE_ID: ${{ github.event.client_payload.releaseId || inputs.releaseId }} diff --git a/.github/workflows/publish-cli-rs.yml b/.github/workflows/publish-cli-rs.yml new file mode 100644 index 000000000000..911442eeefe3 --- /dev/null +++ b/.github/workflows/publish-cli-rs.yml @@ -0,0 +1,98 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: publish `tauri-cli` +env: + MACOSX_DEPLOYMENT_TARGET: '10.13' +on: + workflow_dispatch: + repository_dispatch: + types: [publish-clirs] + +jobs: + build: + runs-on: ${{ matrix.config.os }} + + strategy: + fail-fast: false + matrix: + config: + - os: ubuntu-20.04 + rust_target: x86_64-unknown-linux-gnu + ext: '' + args: '' + - os: macos-latest + rust_target: x86_64-apple-darwin + ext: '' + args: '' + - os: macos-latest + rust_target: aarch64-apple-darwin + ext: '' + args: '' + - os: windows-latest + rust_target: x86_64-pc-windows-msvc + ext: '.exe' + args: '' + - os: windows-latest + rust_target: aarch64-pc-windows-msvc + ext: '.exe' + args: '--no-default-features --features native-tls-vendored' + + steps: + - uses: actions/checkout@v3 + + - name: 'Setup Rust' + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.config.rust_target }} + + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.config.rust_target }} + + - name: install Linux dependencies + if: matrix.config.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev + + - name: Build CLI + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path ./tooling/cli/Cargo.toml --release ${{ matrix.config.args }} + + - name: Upload CLI + uses: actions/upload-artifact@v3 + with: + name: cargo-tauri-${{ matrix.config.rust_target }}${{ matrix.config.ext }} + path: tooling/cli/target/release/cargo-tauri${{ matrix.config.ext }} + if-no-files-found: error + + upload: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Download built CLIs + uses: actions/download-artifact@v3 + with: + path: outputs + + - name: Pack archives + run: ./.scripts/ci/pack-cli.sh + + - name: Get CLI version + run: echo "CLI_VERSION=$(cat tooling/cli/metadata.json | jq '."@tauri-apps/cli".version' -r)" >> $GITHUB_ENV + + - name: Publish release + uses: softprops/action-gh-release@50195ba7f6f93d1ac97ba8332a178e008ad176aa + with: + tag_name: tauri-cli-v${{ env.CLI_VERSION }} + files: | + outputs/cargo-tauri-*.zip + outputs/cargo-tauri-*.tgz diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml deleted file mode 100644 index d9d22b947bbe..000000000000 --- a/.github/workflows/publish-cli.yml +++ /dev/null @@ -1,416 +0,0 @@ -name: publish cli.js -env: - DEBUG: napi:* - APP_NAME: cli - MACOSX_DEPLOYMENT_TARGET: '10.13' -'on': - workflow_dispatch: - repository_dispatch: - types: [publish-clijs] - -defaults: - run: - working-directory: tooling/cli.rs/node/ - -jobs: - build: - strategy: - fail-fast: false - matrix: - settings: - - host: macos-latest - target: x86_64-apple-darwin - architecture: x64 - build: | - yarn build - strip -x *.node - - host: windows-latest - build: yarn build - target: x86_64-pc-windows-msvc - architecture: x64 - - host: ubuntu-18.04 - target: x86_64-unknown-linux-gnu - architecture: x64 - docker: | - docker pull $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-debian - docker tag $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-debian builder - build: | - docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd)/../../../:/build -w /build builder sh -c "cd tooling/cli.rs/node && yarn build && strip *.node" - - host: ubuntu-18.04 - target: x86_64-unknown-linux-musl - architecture: x64 - docker: | - docker pull $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-alpine - docker tag $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-alpine builder - build: docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd)/../../../:/build -w /build builder sh -c "cd tooling/cli.rs/node && yarn build && strip *.node" - - host: macos-latest - target: aarch64-apple-darwin - build: | - yarn build --target=aarch64-apple-darwin - strip -x *.node - - host: ubuntu-18.04 - architecture: x64 - target: aarch64-unknown-linux-gnu - setup: | - sudo apt-get update - sudo apt-get install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu -y - build: | - yarn build --target=aarch64-unknown-linux-gnu - aarch64-linux-gnu-strip *.node - - host: ubuntu-18.04 - architecture: x64 - target: armv7-unknown-linux-gnueabihf - setup: | - sudo apt-get update - sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y - build: | - yarn build --target=armv7-unknown-linux-gnueabihf - arm-linux-gnueabihf-strip *.node - - host: ubuntu-18.04 - architecture: x64 - target: aarch64-unknown-linux-musl - downloadTarget: aarch64-unknown-linux-musl - docker: | - docker pull ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine - docker tag ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine builder - build: | - docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd)/../../../:/build -w /build builder sh -c "cd tooling/cli.rs/node && yarn build --target=aarch64-unknown-linux-musl && /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node" - #- host: windows-latest - # architecture: x64 - # target: aarch64-pc-windows-msvc - # build: yarn build --target aarch64-pc-windows-msvc - name: stable - ${{ matrix.settings.target }} - node@16 - runs-on: ${{ matrix.settings.host }} - steps: - - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 16 - check-latest: true - cache: yarn - cache-dependency-path: 'tooling/cli.rs/node/yarn.lock' - architecture: ${{ matrix.settings.architecture }} - - name: Install - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - toolchain: stable - target: ${{ matrix.settings.target }} - # should be committed - #- name: Generate Cargo.lock - # uses: actions-rs/cargo@v1 - # with: - # command: generate-lockfile - - uses: Swatinem/rust-cache@v1 - with: - key: ${{ matrix.settings.target }} - working-directory: 'tooling/cli.rs/' - - name: Pull latest image - run: ${{ matrix.settings.docker }} - env: - DOCKER_REGISTRY_URL: ghcr.io - if: ${{ matrix.settings.docker }} - - name: Setup toolchain - run: ${{ matrix.settings.setup }} - if: ${{ matrix.settings.setup }} - shell: bash - - name: Install dependencies - run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - - name: Build - run: ${{ matrix.settings.build }} - shell: bash - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: bindings-${{ matrix.settings.target }} - path: tooling/cli.rs/node/${{ env.APP_NAME }}.*.node - if-no-files-found: error - # build-freebsd: - # runs-on: macos-10.15 - # name: Build FreeBSD - # steps: - # - uses: actions/checkout@v2 - # - name: Build - # id: build - # uses: vmactions/freebsd-vm@v0.1.5 - # env: - # DEBUG: napi:* - # RUSTUP_HOME: /usr/local/rustup - # CARGO_HOME: /usr/local/cargo - # RUSTUP_IO_THREADS: 1 - # with: - # envs: DEBUG RUSTUP_HOME CARGO_HOME RUSTUP_IO_THREADS - # usesh: true - # mem: 3000 - # prepare: | - # pkg install -y curl node14 python2 - # curl -qL https://www.npmjs.com/install.sh | sh - # npm install -g yarn - # curl https://sh.rustup.rs -sSf --output rustup.sh - # sh rustup.sh -y --profile minimal --default-toolchain stable - # export PATH="/usr/local/cargo/bin:$PATH" - # echo "~~~~ rustc --version ~~~~" - # rustc --version - # echo "~~~~ node -v ~~~~" - # node -v - # echo "~~~~ yarn --version ~~~~" - # yarn --version - # run: | - # export PATH="/usr/local/cargo/bin:$PATH" - # pwd - # ls -lah - # whoami - # env - # freebsd-version - # cd ./tooling/cli.rs/node/ - # yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - # yarn build - # strip -x *.node - # rm -rf node_modules - # rm -rf ../target - # - name: Upload artifact - # uses: actions/upload-artifact@v2 - # with: - # name: bindings-freebsd - # path: tooling/cli.rs/node/${{ env.APP_NAME }}.*.node - # if-no-files-found: error - test-macOS-windows-binding: - name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} - needs: - - build - strategy: - fail-fast: false - matrix: - settings: - - host: windows-latest - target: x86_64-pc-windows-msvc - node: - - '12' - - '14' - - '16' - runs-on: ${{ matrix.settings.host }} - steps: - - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - check-latest: true - cache: yarn - cache-dependency-path: 'tooling/cli.rs/node/yarn.lock' - - name: Install dependencies - run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: bindings-${{ matrix.settings.target }} - path: 'tooling/cli.rs/node/' - - name: List packages - run: ls -R . - shell: bash - - name: Test bindings - run: yarn test - test-linux-x64-gnu-binding: - name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} - needs: - - build - strategy: - fail-fast: false - matrix: - node: - - '12' - - '14' - - '16' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - check-latest: true - cache: yarn - cache-dependency-path: 'tooling/cli.rs/node/yarn.lock' - - name: Install dependencies - run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: bindings-x86_64-unknown-linux-gnu - path: 'tooling/cli.rs/node/' - - name: List packages - run: ls -R . - shell: bash - - name: Test bindings - run: yarn test - test-linux-x64-musl-binding: - name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }} - needs: - - build - strategy: - fail-fast: false - matrix: - node: - - '12' - - '14' - - '16' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - check-latest: true - cache: yarn - cache-dependency-path: 'tooling/cli.rs/node/yarn.lock' - - name: Install dependencies - run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: bindings-x86_64-unknown-linux-musl - path: 'tooling/cli.rs/node/' - - name: List packages - run: ls -R . - shell: bash - - name: Test bindings - run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine sh -c "yarn test" - test-linux-aarch64-gnu-binding: - name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }} - needs: - - build - strategy: - fail-fast: false - matrix: - node: - - '12' - - '14' - - '16' - runs-on: ubuntu-latest - steps: - - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset - working-directory: ${{ github.workspace }} - - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: bindings-aarch64-unknown-linux-gnu - path: 'tooling/cli.rs/node/' - - name: List packages - run: ls -R . - shell: bash - - name: Install dependencies - run: yarn install --ignore-scripts --ignore-platform --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - - name: Setup and run tests - uses: addnab/docker-run-action@v3 - with: - image: ghcr.io/napi-rs/napi-rs/nodejs:aarch64-${{ matrix.node }} - options: '-v ${{ github.workspace }}:/build -w /build' - run: | - set -e - sh -c "cd tooling/cli.rs/node && yarn test" - ls -la - test-linux-aarch64-musl-binding: - name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }} - needs: - - build - runs-on: ubuntu-latest - steps: - - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset - working-directory: ${{ github.workspace }} - - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: bindings-aarch64-unknown-linux-musl - path: 'tooling/cli.rs/node/' - - name: List packages - run: ls -R . - shell: bash - - name: Install dependencies - run: yarn install --ignore-scripts --ignore-platform --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - - name: Setup and run tests - uses: addnab/docker-run-action@v3 - with: - image: multiarch/alpine:aarch64-latest-stable - options: '-v ${{ github.workspace }}:/build -w /build' - run: | - set -e - apk add nodejs npm yarn - sh -c "cd tooling/cli.rs/node && yarn test" - test-linux-arm-gnueabihf-binding: - name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }} - needs: - - build - strategy: - fail-fast: false - matrix: - node: - - '12' - - '14' - - '16' - runs-on: ubuntu-latest - steps: - - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset - working-directory: ${{ github.workspace }} - - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: bindings-armv7-unknown-linux-gnueabihf - path: 'tooling/cli.rs/node/' - - name: List packages - run: ls -R . - shell: bash - - name: Install dependencies - run: yarn install --ignore-scripts --ignore-platform --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - - name: Setup and run tests - uses: addnab/docker-run-action@v3 - with: - image: ghcr.io/napi-rs/napi-rs/nodejs:armhf-${{ matrix.node }} - options: '-v ${{ github.workspace }}:/build -w /build' - run: | - set -e - sh -c "cd tooling/cli.rs/node && yarn test" - ls -la - publish: - name: Publish - runs-on: ubuntu-latest - needs: - #- build-freebsd - - test-macOS-windows-binding - - test-linux-x64-gnu-binding - #- test-linux-x64-musl-binding - #- test-linux-aarch64-gnu-binding - #- test-linux-aarch64-musl-binding - #- test-linux-arm-gnueabihf-binding - steps: - - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 16 - check-latest: true - cache: yarn - cache-dependency-path: 'tooling/cli.rs/node/yarn.lock' - - name: Install dependencies - run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 - - name: Download all artifacts - uses: actions/download-artifact@v2 - with: - path: tooling/cli.rs/node/artifacts - - name: Move artifacts - run: yarn artifacts - - name: List packages - run: ls -R ./npm - shell: bash - - name: Publish - run: | - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - npm publish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/smoke-test-prod.yml b/.github/workflows/smoke-test-prod.yml deleted file mode 100644 index 0d2b88621654..000000000000 --- a/.github/workflows/smoke-test-prod.yml +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: build smoke tests with prod - -on: - release: - types: [published] - -jobs: - via-prod: - runs-on: ${{ matrix.platform }} - timeout-minutes: 40 - - strategy: - fail-fast: false - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - example: [react/gatsby-themed-site, react/create-react-app, react/next.js, vanillajs/monolith, vue/quasar-app, svelte/svelte-app] - include: - - platform: ubuntu-latest - CARGO_HOME: ~/.cargo - - platform: macos-latest - CARGO_HOME: ~/.cargo - - platform: windows-latest - CARGO_HOME: ~/.cargo - - steps: - - uses: actions/checkout@v2 - - name: install rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - - name: install webkit2gtk (ubuntu only) - if: matrix.platform == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - name: cache rust bin - id: cache_rust_bin - uses: actions/cache@v1 - with: - path: ${{ format('{0}/bin/', matrix.CARGO_HOME) }} - key: ${{ runner.OS }}-build-bin-${{ hashFiles('**/Cargo.toml') }}- - - name: cache rust registry/index - id: cache_rust_reg_index - uses: actions/cache@v1 - with: - path: ${{ format('{0}/registry/index/', matrix.CARGO_HOME) }} - key: ${{ runner.OS }}-build-reg-index-${{ hashFiles('**/Cargo.toml') }}- - - name: cache rust registry/cache - id: cache_rust_reg_cache - uses: actions/cache@v1 - with: - path: ${{ format('{0}/registry/cache/', matrix.CARGO_HOME) }} - key: ${{ runner.OS }}-build-reg-cache-${{ hashFiles('**/Cargo.toml') }}- - - name: cache node modules - uses: actions/cache@v1 - with: - path: ${{ format('examples/node/{0}/node_modules', matrix.example) }} - key: ${{ runner.OS }}-build-${{ hashFiles(format('examples/{0}/yarn.lock', matrix.example)) }} - restore-keys: ${{ runner.OS }}-build-${{ env.cache-name }}- - - name: install via yarn - run: | - cd ./node/examples/${{ matrix.example }} - yarn - - name: build example - run: | - cd ./node/examples/${{ matrix.example }} - yarn build - - name: yarn tauri build - run: | - cd ./node/examples/${{ matrix.example }} - yarn tauri:prod:build diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml new file mode 100644 index 000000000000..ea54d2617b48 --- /dev/null +++ b/.github/workflows/test-android.yml @@ -0,0 +1,100 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: test android + +on: + pull_request: + paths: + - '.github/workflows/test-android.yml' + - 'tooling/cli/templates/mobile/android/**' + - 'tooling/cli/src/mobile/**' + - '!tooling/cli/src/mobile/ios.rs' + - '!tooling/cli/src/mobile/ios/**' + - 'core/tauri-build/src/mobile.rs' + - 'core/tauri/mobile/android/**' + - 'core/tauri/mobile/android-codegen/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v3 + + - name: install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: install Linux dependencies + if: matrix.platform == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev webkit2gtk-4.1 + + - name: setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: yarn + cache-dependency-path: | + tooling/api/yarn.lock + examples/api/yarn.lock + + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + cache: gradle + + - name: Setup NDK + uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r25b + local-cache: true + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + tooling/cli + examples/api/src-tauri + + - name: build CLI + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path ./tooling/cli/Cargo.toml + + - name: build Tauri API + working-directory: ./tooling/api + run: yarn && yarn build + + - name: install API example dependencies + working-directory: ./examples/api + run: yarn + + - name: init Android Studio project + working-directory: ./examples/api + run: ../../tooling/cli/target/debug/cargo-tauri android init + env: + NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + + - name: build APK + working-directory: ./examples/api + run: ../../tooling/cli/target/debug/cargo-tauri android build + env: + NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} diff --git a/.github/workflows/test-bundler.yml b/.github/workflows/test-bundler.yml deleted file mode 100644 index 37f0c047643f..000000000000 --- a/.github/workflows/test-bundler.yml +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: test bundler - -on: - push: - branches: - - dev - - next - pull_request: - paths: - - '.github/workflows/test-bundler.yml' - - 'tooling/bundler/**' -env: - RUST_BACKTRACE: 1 - -jobs: - build-tauri-bundler: - runs-on: ${{ matrix.platform }} - - strategy: - fail-fast: false - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v2 - - name: install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - - name: install webkit2gtk (ubuntu only) - if: matrix.platform == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - if: matrix.platform == 'macos-latest' || matrix.platform == 'ubuntu-latest' - - - name: Get current date - if: matrix.platform == 'windows-latest' - run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-index- - - - name: Cache bundler cargo target - uses: actions/cache@v2 - with: - path: tooling/bundler/target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-bundler-${{ hashFiles('tooling/bundler/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-bundler-${{ hashFiles('tooling/bundler/Cargo.lock') }} - ${{ matrix.platform }}-stable-cargo-bundler- - - - name: test - run: | - cd ./tooling/bundler - cargo test - - clippy-fmt-check: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: install minimal nightly with clippy and rustfmt - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt, clippy - - - name: install webkit2gtk (ubuntu only) - if: matrix.platform == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - name: clippy check - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --manifest-path ./tooling/bundler/Cargo.toml --all-targets -- -D warnings - name: bundler - - - name: fmt check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --manifest-path ./tooling/bundler/Cargo.toml --all -- --check diff --git a/.github/workflows/test-cli-js.yml b/.github/workflows/test-cli-js.yml new file mode 100644 index 000000000000..aaeea54f6310 --- /dev/null +++ b/.github/workflows/test-cli-js.yml @@ -0,0 +1,68 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: test `@tauri-apps/cli` + +on: + push: + branches: + - dev + - next + pull_request: + paths: + - '.github/workflows/test-cli-js.yml' + # currently` @tauri-apps/cli` only tests the template + - 'tooling/cli/templates/app/**' + +env: + RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v2 + + - name: install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + cache: yarn + cache-dependency-path: tooling/cli/node/yarn.lock + + - name: install Linux dependencies + if: matrix.platform == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + tooling/cli + tooling/cli/node/test/jest/fixtures/empty/src-tauri + + - name: test + timeout-minutes: 30 + run: | + cd ./tooling/cli/node + yarn + yarn build + yarn test diff --git a/.github/workflows/test-cli-rs.yml b/.github/workflows/test-cli-rs.yml new file mode 100644 index 000000000000..c1e0a5a1b64b --- /dev/null +++ b/.github/workflows/test-cli-rs.yml @@ -0,0 +1,73 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: test `tauri-cli` + +on: + push: + branches: + - dev + - next + pull_request: + paths: + - '.github/workflows/test-cli-rs.yml' + - 'tooling/cli/**' + +env: + RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ${{ matrix.platform.os }} + + strategy: + fail-fast: false + matrix: + platform: + - { + target: x86_64-pc-windows-msvc, + os: windows-latest + } + - { + target: aarch64-pc-windows-msvc, + os: windows-latest, + args: --no-default-features --features native-tls-vendored + } + - { + target: x86_64-unknown-linux-gnu, + os: ubuntu-latest + } + - { + target: x86_64-apple-darwin, + os: macos-latest + } + + steps: + - uses: actions/checkout@v2 + + - name: 'Setup Rust' + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.platform.target }} + + - name: install Linux dependencies + if: matrix.platform.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: tooling/cli + + - name: build CLI + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path ./tooling/cli/Cargo.toml ${{ matrix.platform.args }} diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index da5a5b90c63a..6d1376684914 100644 --- a/.github/workflows/test-core.yml +++ b/.github/workflows/test-core.yml @@ -1,4 +1,4 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT @@ -13,226 +13,94 @@ on: paths: - '.github/workflows/test-core.yml' - 'core/**' - - 'examples/**' - - 'tooling/**' + - '!core/tauri/scripts/**' env: RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - test-tauri-core: - runs-on: ${{ matrix.platform }} + test: + runs-on: ${{ matrix.platform.os }} strategy: fail-fast: false matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] + platform: + - { + target: x86_64-pc-windows-msvc, + os: windows-latest, + toolchain: '1.65.0', + cross: false, + command: 'test' + } + - { + target: x86_64-unknown-linux-gnu, + os: ubuntu-latest, + toolchain: '1.65.0', + cross: false, + command: 'test' + } + - { + target: x86_64-apple-darwin, + os: macos-latest, + toolchain: '1.65.0', + cross: false, + command: 'test' + } + - { + target: aarch64-apple-ios, + os: macos-latest, + toolchain: '1.65.0', + cross: false, + command: 'build' + } + - { + target: aarch64-linux-android, + os: ubuntu-latest, + toolchain: '1.65.0', + cross: true, + command: 'build' + } + features: + - { + args: --no-default-features, + key: no-default + } + - { + args: --features compression,wry,linux-protocol-headers,isolation,custom-protocol,system-tray,test, + key: all + } steps: - uses: actions/checkout@v2 - - name: install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - name: install webkit2gtk (ubuntu only) - if: matrix.platform == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - if: matrix.platform == 'macos-latest' || matrix.platform == 'ubuntu-latest' - - - name: Get current date - if: matrix.platform == 'windows-latest' - run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-index- - - - name: Cache core cargo target - uses: actions/cache@v2 - with: - path: target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-core-${{ hashFiles('core/**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-core-${{ hashFiles('core/**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-core- - - name: test - run: | - cargo test - cargo test --features api-all - cargo test --features compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray - - test-tauri-cli: - runs-on: ${{ matrix.platform }} - - strategy: - fail-fast: false - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v2 - name: install stable uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: ${{ matrix.platform.toolchain }} + target: ${{ matrix.platform.target }} override: true + default: true - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - if: matrix.platform == 'macos-latest' || matrix.platform == 'ubuntu-latest' - - - name: Get current date - if: matrix.platform == 'windows-latest' - run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-index- - - - name: Cache CLI cargo target - uses: actions/cache@v2 - with: - path: tooling/cli.rs/target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }} - ${{ matrix.platform }}-stable-cargo-cli- - - - name: build CLI - uses: actions-rs/cargo@v1 - with: - command: build - args: --manifest-path ./tooling/cli.rs/Cargo.toml - - test-tauri-js-cli: - runs-on: ${{ matrix.platform }} - - strategy: - fail-fast: false - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v2 - - name: install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - name: setup node - uses: actions/setup-node@v2 - with: - node-version: 14 - cache: yarn - cache-dependency-path: tooling/cli.rs/node/yarn.lock - - name: install webkit2gtk (ubuntu only) - if: matrix.platform == 'ubuntu-latest' + - name: install Linux dependencies + if: contains(matrix.platform.target, 'unknown-linux') run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - if: matrix.platform == 'macos-latest' || matrix.platform == 'ubuntu-latest' + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev - - name: Get current date - if: matrix.platform == 'windows-latest' - run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 + - uses: Swatinem/rust-cache@v2 with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ${{ matrix.platform }}-stable-cargo-index- - - - name: Cache CLI cargo target - uses: actions/cache@v2 - with: - path: tooling/cli.rs/target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }} - ${{ matrix.platform }}-stable-cargo-cli- - - - name: Cache template cargo target - uses: actions/cache@v2 - with: - path: tooling/cli.rs/node/test/jest/fixtures/empty/src-tauri/target - # Add date to the cache to keep it up to date - key: ${{ matrix.platform }}-stable-template-${{ hashFiles('tooling/cli.rs/templates/app/**') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ${{ matrix.platform }}-stable-template-${{ hashFiles('tooling/cli.rs/templates/app/**') }} - ${{ matrix.platform }}-stable-template- + workspaces: core -> ../target + save-if: ${{ matrix.features.key == 'all' }} - name: test - timeout-minutes: 30 - run: | - cd ./tooling/cli.rs/node - yarn - yarn build - yarn test + uses: actions-rs/cargo@v1 + with: + use-cross: ${{ matrix.platform.cross }} + command: ${{ matrix.platform.command }} + args: --target ${{ matrix.platform.target }} ${{ matrix.features.args }} diff --git a/.github/workflows/test-cta.yml b/.github/workflows/test-cta.yml deleted file mode 100644 index 41cc5e1c9eab..000000000000 --- a/.github/workflows/test-cta.yml +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: test create-tauri-app -env: - RUST_BACKTRACE: 1 - TAURI_RECIPE: 'vanillajs,cra,vite,ngcli,solid' - -on: - workflow_dispatch: - inputs: - platform: - default: 'ubuntu' - pull_request: - paths: - - 'tooling/create-tauri-app/**' - -jobs: - create-recipe-with-npm: - name: 'node@${{ matrix.node }} + npm@${{ matrix.manager }}' - runs-on: ${{ github.event.inputs.platform || 'ubuntu' }}-latest - - strategy: - fail-fast: false - matrix: - node: ['14', '16'] - manager: ['7'] - exclude: - - node: '16' - manager: '6' - - steps: - - uses: actions/checkout@v2 - - name: install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - uses: volta-cli/action@v1 - with: - node-version: ${{ matrix.node }} - npm-version: ${{ matrix.manager }} - yarn-version: 1.22.5 - - name: install webkit2gtk - if: (github.event.inputs.platform || 'ubuntu') == 'ubuntu' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - #- run: yarn - # working-directory: tooling/cli.rs/node - #- run: yarn build - # working-directory: tooling/cli.rs/node - - run: yarn - working-directory: tooling/api - - run: yarn build - working-directory: tooling/api - - run: yarn - working-directory: tooling/create-tauri-app - - run: yarn build - working-directory: tooling/create-tauri-app - - run: yarn test - working-directory: tooling/create-tauri-app - env: - TAURI_RUN_MANAGER: 'npm' - - # create-recipe-with-yarn: - # name: "node@${{ matrix.node }} + yarn@1" - # runs-on: ${{ github.event.inputs.platform || 'ubuntu' }}-latest - - # strategy: - # fail-fast: false - # matrix: - # node: ["14", "16"] - - # steps: - # - uses: actions/checkout@v2 - # - name: install stable - # uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable - # - uses: volta-cli/action@v1 - # with: - # node-version: ${{ matrix.node }} - # yarn-version: 1.22.5 - # - name: install webkit2gtk - # if: (github.event.inputs.platform || 'ubuntu') == 'ubuntu' - # run: | - # sudo apt-get update - # sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - # - run: yarn - # working-directory: tooling/create-tauri-app - # - run: yarn build - # working-directory: tooling/create-tauri-app - # - run: yarn test - # working-directory: tooling/create-tauri-app - # env: - # TAURI_RUN_MANAGER: "yarn" diff --git a/.github/workflows/test-lint-bundler.yml b/.github/workflows/test-lint-bundler.yml new file mode 100644 index 000000000000..f53a151175da --- /dev/null +++ b/.github/workflows/test-lint-bundler.yml @@ -0,0 +1,93 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: test and lint bundler + +on: + push: + branches: + - dev + - next + pull_request: + paths: + - '.github/workflows/test-bundler.yml' + - 'tooling/bundler/**' + +env: + RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v2 + + - name: install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: tooling/bundler + + - name: test + run: | + cd ./tooling/bundler + cargo test + + fmt: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --manifest-path ./tooling/bundler/Cargo.toml --all -- --check + + clippy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: install minimal stable with clippy and rustfmt + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + default: true + components: rustfmt, clippy + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: tooling/bundler + + - name: clippy check + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --manifest-path ./tooling/bundler/Cargo.toml --all-targets -- -D warnings + name: bundler diff --git a/.github/workflows/udeps.yml b/.github/workflows/udeps.yml index 23f3014ab121..65a1ded28a87 100644 --- a/.github/workflows/udeps.yml +++ b/.github/workflows/udeps.yml @@ -1,108 +1,165 @@ -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT name: Udeps on: - pull_request: - paths: - - '.github/workflows/udeps.yml' - - 'core/**' - - 'tooling/bundler/**' - - 'tooling/cli.rs/**' + push: + branches: + - dev + - next + +env: + RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: + changes: + runs-on: ubuntu-latest + outputs: + tauri: ${{ steps.filter.outputs.tauri }} + build: ${{ steps.filter.outputs.build }} + codegen: ${{ steps.filter.outputs.codegen }} + macros: ${{ steps.filter.outputs.macros }} + runtime: ${{ steps.filter.outputs.runtime }} + wry: ${{ steps.filter.outputs.wry }} + utils: ${{ steps.filter.outputs.utils }} + bundler: ${{ steps.filter.outputs.bundler }} + cli: ${{ steps.filter.outputs.cli }} + steps: + - uses: actions/checkout@v2 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + tauri: + - 'core/tauri/**' + - '!core/tauri/scripts/**' + build: + - 'core/tauri-build/**' + codegen: + - 'core/tauri-codegen/**' + macros: + - 'core/tauri-macros/**' + runtime: + - 'core/tauri-runtime/**' + wry: + - 'core/tauri-runtime-wry/**' + utils: + - 'core/tauri-utils/**' + bundler: + - 'tooling/bundler/**' + cli: + - 'tooling/cli/**' + + setup: + runs-on: ubuntu-latest + needs: changes + if: | + needs.changes.outputs.tauri == 'true' || + needs.changes.outputs.build == 'true' || + needs.changes.outputs.codegen == 'true' || + needs.changes.outputs.macros == 'true' || + needs.changes.outputs.runtime == 'true' || + needs.changes.outputs.wry == 'true' || + needs.changes.outputs.utils == 'true' || + needs.changes.outputs.bundler == 'true' || + needs.changes.outputs.cli == 'true' + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + + - uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-udeps --locked --force + + - name: Upload udeps + uses: actions/upload-artifact@v3 + with: + name: udeps + path: '~/.cargo/bin/cargo-udeps' + if-no-files-found: error + + - name: Create udeps matrix + id: create-matrix + env: + TAURI: ${{ needs.changes.outputs.tauri == 'true' }} + BUILD: ${{ needs.changes.outputs.build == 'true' }} + CODEGEN: ${{ needs.changes.outputs.codegen == 'true' }} + MACROS: ${{ needs.changes.outputs.macros == 'true' }} + RUNTIME: ${{ needs.changes.outputs.runtime == 'true' }} + WRY: ${{ needs.changes.outputs.wry == 'true' }} + UTILS: ${{ needs.changes.outputs.utils == 'true' }} + BUNDLER: ${{ needs.changes.outputs.bundler == 'true' }} + CLI: ${{ needs.changes.outputs.cli == 'true' }} + run: | + crates=() + if [ "${TAURI}" == "true" ]; then crates[${#crates[@]}]="\"./core/tauri\""; fi + if [ "${BUILD}" == "true" ]; then crates[${#crates[@]}]="\"./core/tauri-build\""; fi + if [ "${CODEGEN}" == "true" ]; then crates[${#crates[@]}]="\"./core/tauri-codegen\""; fi + if [ "${MACROS}" == "true" ]; then crates[${#crates[@]}]="\"./core/tauri-macros\""; fi + if [ "${RUNTIME}" == "true" ]; then crates[${#crates[@]}]="\"./core/tauri-runtime\""; fi + if [ "${WRY}" == "true" ]; then crates[${#crates[@]}]="\"./core/tauri-runtime-wry\""; fi + if [ "${UTILS}" == "true" ]; then crates[${#crates[@]}]="\"./core/tauri-utils\""; fi + if [ "${BUNDLER}" == "true" ]; then crates[${#crates[@]}]="\"./tooling/bundler\""; fi + if [ "${CLI}" == "true" ]; then crates[${#crates[@]}]="\"./tooling/cli\""; fi + echo ::set-output name=matrix::[$crates] + outputs: + matrix: ${{ steps.create-matrix.outputs.matrix }} + udeps: runs-on: ubuntu-latest + needs: setup + strategy: + matrix: + path: ${{ fromJson(needs.setup.outputs.matrix) }} steps: - - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - - - name: Get current date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - - - name: Cache cargo registry - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/registry - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-registry-${{ hashFiles('**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-registry- - - - name: Cache cargo index - uses: actions/cache@v2.1.4 - with: - path: ~/.cargo/git - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-index-${{ hashFiles('**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-index- - - - name: Cache core cargo target - uses: actions/cache@v2 - with: - path: target - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-core-${{ hashFiles('core/**/Cargo.toml') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-core-${{ hashFiles('core/**/Cargo.toml') }} - ubuntu-latest-nightly-cargo-core- - - - name: Cache bundler cargo target - uses: actions/cache@v2 - with: - path: tooling/bundler/target - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-bundler-${{ hashFiles('tooling/bundler/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-bundler-${{ hashFiles('tooling/bundler/Cargo.lock') }} - ubuntu-latest-nightly-cargo-bundler- - - - name: Cache CLI cargo target - uses: actions/cache@v2 - with: - path: tooling/cli.rs/target - # Add date to the cache to keep it up to date - key: ubuntu-latest-nightly-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }}-${{ env.CURRENT_DATE }} - # Restore from outdated cache for speed - restore-keys: | - ubuntu-latest-nightly-cargo-cli-${{ hashFiles('tooling/cli.rs/Cargo.lock') }} - ubuntu-latest-nightly-cargo-cli- - - - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-udeps --locked - - - name: Install required packages - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - - - uses: actions-rs/cargo@v1 - with: - command: udeps - args: --all-targets --all-features - - - uses: actions-rs/cargo@v1 - with: - command: udeps - args: --manifest-path ./tooling/bundler/Cargo.toml --all-targets --all-features - - - uses: actions-rs/cargo@v1 - with: - command: udeps - args: --manifest-path ./tooling/cli.rs/Cargo.toml --all-targets --all-features + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + + - name: install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + core -> ../target + tooling/cli + tooling/bundler + + - name: Download udeps + uses: actions/download-artifact@v3 + with: + name: udeps + path: '~/.cargo/bin' + + - run: chmod +x $HOME/.cargo/bin/cargo-udeps + + - name: Install required packages + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev + + - uses: actions-rs/cargo@v1 + with: + command: udeps + args: --manifest-path ${{ matrix.path }}/Cargo.toml --all-targets --all-features diff --git a/.gitignore b/.gitignore index cc1a831a3e49..4fb1d3064239 100644 --- a/.gitignore +++ b/.gitignore @@ -82,4 +82,8 @@ __handlers__/ # benches gh-pages -test_video.mp4 \ No newline at end of file +test_video.mp4 + +# old cli directories +/tooling/cli.js +/tooling/cli.rs diff --git a/.husky/pre-commit b/.husky/pre-commit index 64a08ff1dca9..3050d5d43f92 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,6 @@ #!/bin/sh -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT @@ -15,19 +15,10 @@ else cd ../.. fi -if [ -z "$(git diff --name-only tooling/cli.rs/node)" ]; then - echo "skipping cli.js - no changes detected" +if [ -z "$(git diff --name-only tooling/cli/node)" ]; then + echo "skipping \`@tauri-apps/cli\` - no changes detected" else - cd tooling/cli.rs/node + cd tooling/cli/node yarn format cd ../../.. fi - -if [ -z "$(git diff --name-only tooling/create-tauri-app)" ]; then - echo "skipping create-tauri-app - no changes detected" -else - cd tooling/create-tauri-app - yarn format - yarn lint-fix - cd ../.. -fi diff --git a/.license_template b/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..fd6c5b9ba982 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,13 @@ +/.github +/.husky +/.vscode +/audits +node_modules +target +dist +/core/tauri/scripts +/tooling/cli/templates +/tooling/cli/node +/tooling/cli/schema.json +/tooling/api/docs/js-api.json +/core/tauri-config-schema/schema.json diff --git a/.prettierrc.js b/.prettierrc.js index 2be2f932702c..6641113efc2e 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,3 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + module.exports = { singleQuote: true, semi: false, diff --git a/.scripts/cargo-check.ps1 b/.scripts/cargo-check.ps1 index 30459bf41ae6..bf67d2511f9d 100755 --- a/.scripts/cargo-check.ps1 +++ b/.scripts/cargo-check.ps1 @@ -1,5 +1,6 @@ #!/usr/bin/env pwsh -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy + +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT diff --git a/.scripts/cargo-check.sh b/.scripts/cargo-check.sh index 0f6a431edd12..9c11342472a8 100755 --- a/.scripts/cargo-check.sh +++ b/.scripts/cargo-check.sh @@ -1,5 +1,6 @@ #!/usr/bin/env sh -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy + +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT diff --git a/.scripts/ci/check-change-tags.js b/.scripts/ci/check-change-tags.js new file mode 100644 index 000000000000..d49c7c54cc85 --- /dev/null +++ b/.scripts/ci/check-change-tags.js @@ -0,0 +1,79 @@ +#!/usr/bin/env node + +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const fs = require('fs') +const path = require('path') +const ignorePackages = [ + 'tauri-macros', + 'tauri-codegen', + 'tauri-runtime', + 'tauri-runtime-wry', + 'tauri-driver' +] + +const covectorConfig = JSON.parse( + fs.readFileSync('.changes/config.json', 'utf8') +) +const tags = Object.keys(covectorConfig.changeTags) + +const missingTagsFiles = {} +const unknownTagsFiles = {} + +function checkChangeFiles(changeFiles) { + for (const file of changeFiles) { + const content = fs.readFileSync(file, 'utf8') + const [frontMatter] = /^---[\s\S.]*---\n/i.exec(content) + const packages = frontMatter + .split('\n') + .filter((l) => !(l === '---' || !l)) + .map((l) => l.replace(/('|")/g, '').split(':')) + + for (const [package, _, tag] of packages) { + if (!tag) { + if (ignorePackages.includes(package)) continue + + if (!missingTagsFiles[file]) missingTagsFiles[file] = [] + missingTagsFiles[file].push(package) + } else if (!tags.includes(tag)) { + if (!unknownTagsFiles[file]) unknownTagsFiles[file] = [] + unknownTagsFiles[file].push({ package, tag }) + } + } + } + const missingTagsEntries = Object.entries(missingTagsFiles) + const unknownTagsEntries = Object.entries(unknownTagsFiles) + if (missingTagsEntries.length > 0 || unknownTagsEntries.length > 0) { + for (const [file, packages] of missingTagsEntries) { + for (const package of packages) { + console.error( + `Package \`${package}\` is missing a change tag in ${file} ` + ) + } + } + + for (const [file, packages] of unknownTagsEntries) { + for (const { package, tag } of packages) { + console.error( + `Package \`${package}\` has an uknown change tag ${tag} in ${file} ` + ) + } + } + + process.exit(1) + } +} + +const [_bin, _script, ...files] = process.argv + +if (files.length > 0) { + checkChangeFiles(files.filter((f) => f.toLowerCase() !== 'readme.md')) +} else { + const changeFiles = fs + .readdirSync('.changes') + .filter((f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md') + .map((p) => path.join('.changes', p)) + checkChangeFiles(changeFiles) +} diff --git a/.scripts/ci/check-license-header.js b/.scripts/ci/check-license-header.js new file mode 100644 index 000000000000..d0d6dc68f98f --- /dev/null +++ b/.scripts/ci/check-license-header.js @@ -0,0 +1,118 @@ +#!/usr/bin/env node + +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const fs = require('fs') +const path = require('path') +const readline = require('readline') + +const header = `Copyright 2019-2023 Tauri Programme within The Commons Conservancy +SPDX-License-Identifier: Apache-2.0 +SPDX-License-Identifier: MIT` +const bundlerLicense = + '// Copyright 2016-2019 Cargo-Bundle developers ' + +const extensions = ['.rs', '.js', '.ts', '.yml', '.swift', '.kt'] +const ignore = [ + 'target', + 'templates', + 'node_modules', + 'gen', + 'dist', + 'bundle.js', + 'bundle.global.js' +] + +async function checkFile(file) { + if (extensions.some((e) => file.endsWith(e)) && !ignore.some((i) => file.includes(`/${i}/`))) { + const fileStream = fs.createReadStream(file) + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }) + + let contents = `` + let i = 0 + for await (let line of rl) { + // ignore empty lines, allow shebang and bundler license + if ( + line.length === 0 || + line.startsWith('#!') || + line.startsWith('// swift-tools-version:') || + line === bundlerLicense + ) { + continue + } + + // strip comment marker + if (line.startsWith('// ')) { + line = line.substring(3) + } else if (line.startsWith('# ')) { + line = line.substring(2) + } + + contents += line + if (++i === 3) { + break + } + contents += '\n' + } + if (contents !== header) { + return true + } + } + return false +} + +async function check(src) { + const missingHeader = [] + + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + const p = path.join(src, entry.name) + + if (entry.isSymbolicLink() || ignore.includes(entry.name)) { + continue + } + + if (entry.isDirectory()) { + const missing = await check(p) + missingHeader.push(...missing) + } else { + const isMissing = await checkFile(p) + if (isMissing) { + missingHeader.push(p) + } + } + } + + return missingHeader +} + +const [_bin, _script, ...files] = process.argv + +if (files.length > 0) { + async function run() { + const missing = [] + for (const f of files) { + const isMissing = await checkFile(f) + if (isMissing) { + missing.push(f) + } + } + if (missing.length > 0) { + console.log(missing.join('\n')) + process.exit(1) + } + } + + run() +} else { + check('.').then((missing) => { + if (missing.length > 0) { + console.log(missing.join('\n')) + process.exit(1) + } + }) +} diff --git a/.scripts/ci/has-diff.sh b/.scripts/ci/has-diff.sh new file mode 100755 index 000000000000..30c19d4f143b --- /dev/null +++ b/.scripts/ci/has-diff.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +if git diff --quiet --ignore-submodules HEAD +then + echo "working directory is clean" +else + echo "found diff" + exit 1 +fi diff --git a/.scripts/ci/pack-cli.sh b/.scripts/ci/pack-cli.sh new file mode 100755 index 000000000000..1cef91b9da93 --- /dev/null +++ b/.scripts/ci/pack-cli.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +set -euxo pipefail + +for o in outputs/*; do + pushd "$o" + + chmod +x cargo-tauri* + cp ../../tooling/cli/LICENSE* ../../tooling/cli/README.md . + + target=$(basename "$o" | cut -d. -f1) + if grep -qE '(apple|windows)' <<< "$target"; then + zip "../${target}.zip" * + else + tar cv * | gzip -9 > "../${target}.tgz" + fi + + popd +done diff --git a/.scripts/covector/package-latest-version.js b/.scripts/covector/package-latest-version.js new file mode 100644 index 000000000000..53430e6dbdd5 --- /dev/null +++ b/.scripts/covector/package-latest-version.js @@ -0,0 +1,57 @@ +#!/usr/bin/env node + +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/* +This script is solely intended to be run as part of the `covector publish` step to +check the latest version of a crate, considering the current minor version. +*/ + +const https = require('https') + +const kind = process.argv[2] +const packageName = process.argv[3] +const packageVersion = process.argv[4] +const target = packageVersion.substring(0, packageVersion.lastIndexOf('.')) + +let url = null +switch (kind) { + case 'cargo': + url = `https://crates.io/api/v1/crates/${packageName}` + break + case 'npm': + url = `https://registry.npmjs.org/${packageName}` + break + default: + throw new Error('unexpected kind ' + kind) +} + +const options = { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'tauri (https://github.com/tauri-apps/tauri)' + } +} + +https.get(url, options, (response) => { + let chunks = [] + response.on('data', function (chunk) { + chunks.push(chunk) + }) + + response.on('end', function () { + const data = JSON.parse(chunks.join('')) + if (kind === 'cargo') { + const versions = data.versions.filter((v) => v.num.startsWith(target)) + console.log(versions.length ? versions[0].num : '0.0.0') + } else if (kind === 'npm') { + const versions = Object.keys(data.versions).filter((v) => + v.startsWith(target) + ) + console.log(versions[versions.length - 1] || '0.0.0') + } + }) +}) diff --git a/.scripts/covector/sync-cli-metadata.js b/.scripts/covector/sync-cli-metadata.js index 0ed5b9f23449..4edef073a515 100644 --- a/.scripts/covector/sync-cli-metadata.js +++ b/.scripts/covector/sync-cli-metadata.js @@ -1,30 +1,59 @@ #!/usr/bin/env node -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy + +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT /* This script is solely intended to be run as part of the `covector version` step to -keep the `../tooling/cli.rs/metadata.json` up to date with other version bumps. Long term -we should look to find a more "rusty way" to import / "pin" a version value in our cli.rs +keep the `../tooling/cli/metadata.json` up to date with other version bumps. Long term +we should look to find a more "rusty way" to import / "pin" a version value in our tauri-cli rust binaries. */ const { readFileSync, writeFileSync } = require('fs') +const { resolve } = require('path') -const filePath = `../../tooling/cli.rs/metadata.json` const packageNickname = process.argv[2] +const filePath = + packageNickname === '@tauri-apps/cli' + ? `../../../tooling/cli/metadata.json` + : `../../tooling/cli/metadata.json` const bump = process.argv[3] -if (bump !== 'prerelease') { - throw new Error( - `We don't handle anything except prerelease right now. Exiting.` - ) +let index = null + +switch (bump) { + case 'major': + case 'premajor': + index = 0 + break + case 'minor': + index = 1 + break + case 'patch': + index = 2 + break + case 'prerelease': + index = 3 + break + default: + throw new Error('unexpected bump ' + bump) } const inc = (version) => { - const v = version.split('') - const n = v.pop() - return [...v, String(Number(n) + 1)].join('') + const v = version.split('.') + for (let i = 0; i < v.length; i++) { + if (i === index) { + v[i] = String(Number(v[i]) + 1) + } else if (i > index) { + v[i] = 0 + } + } + if (bump === 'premajor') { + const pre = JSON.parse(readFileSync(resolve(filePath, '../../../.changes/pre.json'), 'utf-8')) + return `${v.join('.')}-${pre.tag}.0` + } + return v.join('.') } // read file into js object @@ -32,9 +61,9 @@ const metadata = JSON.parse(readFileSync(filePath, 'utf-8')) // set field version let version -if (packageNickname === 'cli.js') { - version = inc(metadata[packageNickname].version) - metadata[packageNickname].version = version +if (packageNickname === '@tauri-apps/cli') { + version = inc(metadata['cli.js'].version) + metadata['cli.js'].version = version } else { version = inc(metadata[packageNickname]) metadata[packageNickname] = version diff --git a/.scripts/covector/sync-prerelease.js b/.scripts/covector/sync-prerelease.js deleted file mode 100644 index 48a9cdd78443..000000000000 --- a/.scripts/covector/sync-prerelease.js +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env node -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -/* -This script is solely intended to be run as part of the `covector version` step to -keep the `tauri-runtime`, `tauri-runtime-wry` and `tauri-driver` crates version without the `beta` or `beta-rc` suffix. -*/ - -const { readFileSync, writeFileSync } = require('fs') - -const packageNickname = process.argv[2] -const bump = process.argv[3] - -let manifestPath -let dependencyManifestPaths -let changelogPath - -if (packageNickname === 'tauri-runtime') { - manifestPath = '../../core/tauri-runtime/Cargo.toml' - dependencyManifestPaths = [ - '../../core/tauri/Cargo.toml', - '../../core/tauri-runtime-wry/Cargo.toml' - ] - changelogPath = '../../core/tauri-runtime/CHANGELOG.md' -} else if (packageNickname === 'tauri-runtime-wry') { - manifestPath = '../../core/tauri-runtime-wry/Cargo.toml' - dependencyManifestPaths = ['../../core/tauri/Cargo.toml'] - changelogPath = '../../core/tauri-runtime-wry/CHANGELOG.md' -} else if (packageNickname === 'tauri-driver') { - manifestPath = '../../tooling/webdriver/Cargo.toml' - dependencyManifestPaths = [] - changelogPath = '../../tooling/webdriver/CHANGELOG.md' -} else { - throw new Error(`Unexpected package ${packageNickname}`) -} - -let manifest = readFileSync(manifestPath, 'utf-8') -manifest = manifest.replace( - /version = "(\d+\.\d+\.\d+)-[^0-9\.]+\.0"/, - 'version = "$1"' -) -writeFileSync(manifestPath, manifest) - -let changelog = readFileSync(changelogPath, 'utf-8') -changelog = changelog.replace(/(\d+\.\d+\.\d+)-[^0-9\.]+\.0/, '$1') -writeFileSync(changelogPath, changelog) - -for (const dependencyManifestPath of dependencyManifestPaths) { - let dependencyManifest = readFileSync(dependencyManifestPath, 'utf-8') - dependencyManifest = dependencyManifest.replace( - new RegExp( - packageNickname + ' = { version = "(\\d+\\.\\d+.\\d+)-[^0-9.]+.0"' - ), - `${packageNickname} = { version = "$1"` - ) - writeFileSync(dependencyManifestPath, dependencyManifest) -} diff --git a/.scripts/docker/build.sh b/.scripts/docker/build.sh new file mode 100755 index 000000000000..10f61e7f0d89 --- /dev/null +++ b/.scripts/docker/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +docker build -t aarch64-unknown-linux-gnu:latest --file .docker/cross/aarch64.Dockerfile .docker/cross diff --git a/.scripts/setup.ps1 b/.scripts/setup.ps1 index 445c64da057e..a841c5f99fb2 100644 --- a/.scripts/setup.ps1 +++ b/.scripts/setup.ps1 @@ -1,5 +1,6 @@ #!/usr/bin/env pwsh -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy + +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT @@ -9,7 +10,7 @@ yarn; yarn build cd ..\.. echo "Installing the Tauri Rust CLI..." -cd tooling\cli.rs +cd tooling\cli cargo install --path . cd ..\.. echo "Tauri Rust CLI installed. Run it with '$ cargo tauri [COMMAND]'." @@ -21,7 +22,7 @@ $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) $result = $host.ui.PromptForChoice("Node.js CLI", "Do you want to install the Node.js CLI?", $options, 1) switch ($result) { 0{ - cd tooling\cli.rs/node + cd tooling\cli\node yarn; yarn build; yarn link cd ..\.. echo "Tauri Node.js CLI installed. use `yarn link @tauri-apps/cli` and run it with '$ yarn tauri [COMMAND]'." diff --git a/.scripts/setup.sh b/.scripts/setup.sh index fbabfde6264d..5e2f9174348f 100755 --- a/.scripts/setup.sh +++ b/.scripts/setup.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy + +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT @@ -9,7 +10,7 @@ yarn && yarn build cd ../.. echo "Building the Tauri Rust CLI..." -cd tooling/cli.rs +cd tooling/cli cargo install --path . cd ../.. echo "Tauri Rust CLI installed. Run it with '$ cargo tauri [COMMAND]'." @@ -18,7 +19,7 @@ echo "Do you want to install the Node.js CLI?" select yn in "Yes" "No"; do case $yn in Yes ) - cd tooling/cli.rs/node + cd tooling/cli/node yarn && yarn build && yarn link cd ../../.. echo "Tauri Node.js CLI installed. use `yarn link @tauri-apps/cli` and run it with '$ yarn tauri [COMMAND]'." diff --git a/.scripts/update-lockfiles.sh b/.scripts/update-lockfiles.sh index 6b82bb65f048..961d0fb4c5a5 100755 --- a/.scripts/update-lockfiles.sh +++ b/.scripts/update-lockfiles.sh @@ -1,10 +1,11 @@ #!/usr/bin/env sh -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy + +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT -declare -a examples=("api" "sidecar" "updater" "resources") -declare -a tooling=("bench" "cli.rs" "webdriver") +declare -a examples=("api" "sidecar" "resources" "tauri-dynamic-lib" "workspace") +declare -a tooling=("bench" "cli" "webdriver") for example in "${examples[@]}" do @@ -21,12 +22,3 @@ do cargo build cd ../.. done - -cd tooling/bench/tests -cd cpu_intensive/src-tauri -cargo update -cargo build -cd ../../files_transfer/src-tauri -cargo update -cargo build -cd ../../../../.. diff --git a/.scripts/utils/batch_to_exe.cmd b/.scripts/utils/batch_to_exe.cmd index 44b207b4f801..3d5ffa6d8fe0 100644 --- a/.scripts/utils/batch_to_exe.cmd +++ b/.scripts/utils/batch_to_exe.cmd @@ -1,6 +1,10 @@ +: Copyright 2019-2023 Tauri Programme within The Commons Conservancy +: SPDX-License-Identifier: Apache-2.0 +: SPDX-License-Identifier: MIT + @ECHO OFF -REM Copyright 2019-2021 Tauri Programme within The Commons Conservancy +REM Copyright 2019-2023 Tauri Programme within The Commons Conservancy REM SPDX-License-Identifier: Apache-2.0 REM SPDX-License-Identifier: MIT @@ -59,4 +63,4 @@ IF EXIST %SourceFiles% DEL %SourceFiles% IF EXIST %Config% DEL %Config% IF EXIST %Source7ZFile% DEL %Source7ZFile% -ENDLOCAL \ No newline at end of file +ENDLOCAL diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000000..737a92cd4d89 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "ms-vscode-remote.vscode-remote-extensionpack", + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode" + ] +} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4af34792730b..bebaed43e01f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,5 +1,6 @@ # The Tauri Architecture -https://tauri.studio +https://tauri.app + https://github.com/tauri-apps/tauri ## Introduction @@ -38,19 +39,19 @@ This is common code that is reused in many places and offers useful utilities li ### Tauri Tooling -#### [api](https://github.com/tauri-apps/tauri/tree/dev/tooling/api) [TS -> JS] -A typescript library that creates `cjs` and `esm` Javascript endpoints for you to import into your Frontend framework so that the Webview can call and listen to backend activity. We also ship the pure typescript, because for some frameworks this is more optimal. It uses the message passing of webviews to their hosts. +#### [@tauri-apps/api](https://github.com/tauri-apps/tauri/tree/dev/tooling/api) [TS -> JS] +A TypeScript library that creates `cjs` and `esm` JavaScript endpoints for you to import into your Frontend framework so that the Webview can call and listen to backend activity. We also ship the pure TypeScript, because for some frameworks this is more optimal. It uses the message passing of webviews to their hosts. #### [bundler](https://github.com/tauri-apps/tauri/tree/dev/tooling/bundler) [RUST / SHELL] The bundler is a library that builds a Tauri App for the platform triple it detects / is told. At the moment it currently supports macOS, Windows and Linux - but in the near future will support mobile platforms as well. May be used outside of Tauri projects. -#### [cli.js](https://github.com/tauri-apps/tauri/tree/dev/tooling/cli.rs/node) [JS] -It is a wrapper around [cli.rs](https://github.com/tauri-apps/tauri/blob/dev/tooling/cli.rs) using [napi-rs](https://github.com/napi-rs/napi-rs) to produce NPM packages for each platform. +#### [@tauri-apps/cli](https://github.com/tauri-apps/tauri/tree/dev/tooling/cli/node) [JS] +It is a wrapper around [tauri-cli](https://github.com/tauri-apps/tauri/blob/dev/tooling/cli) using [napi-rs](https://github.com/napi-rs/napi-rs) to produce NPM packages for each platform. -#### [cli.rs](https://github.com/tauri-apps/tauri/tree/dev/tooling/cli.rs) [RUST] +#### [tauri-cli](https://github.com/tauri-apps/tauri/tree/dev/tooling/cli) [RUST] This rust executable provides the full interface to all of the required activities for which the CLI is required. It will run on macOS, Windows, and Linux. -#### [create-tauri-app](https://github.com/tauri-apps/tauri/tree/dev/tooling/create-tauri-app) [JS] +#### [create-tauri-app](https://github.com/tauri-apps/create-tauri-app) [JS] This is a toolkit that will enable engineering teams to rapidly scaffold out a new tauri-apps project using the frontend framework of their choice (as long as it has been configured). # External Crates @@ -64,14 +65,8 @@ Cross-platform application window creation library in Rust that supports all maj WRY is a cross-platform WebView rendering library in Rust that supports all major desktop platforms like Windows, macOS, and Linux. Tauri uses WRY as the abstract layer responsible to determine which webview is used (and how interactions are made). -## [tauri-hotkey-rs](https://github.com/tauri-apps/tauri-hotkey-rs) -We needed to fix hotkey to work on all platforms, because upstream was not being responsive. - # Additional tooling -## [binary-releases](https://github.com/tauri-apps/binary-releases) -This is the delivery mechanism for tauri prebuilt binaries: currently the cli.rs (used by cli.js) and rustup binaries (used by the deps install command of cli.js). These artifacts are automatically created on release. - ## [tauri-action](https://github.com/tauri-apps/tauri-action) This is a github workflow that builds tauri binaries for all platforms. It is not the fastest out there, but it gets the job done and is highly configurable. Even allowing you to create a (very basic) tauri app even if tauri is not setup. @@ -84,7 +79,7 @@ This plugin allows you to very quickly install tauri in a vue-cli project. ## [tauri-vscode](https://github.com/tauri-apps/tauri-vscode) This project enhances the VS Code interface with several nice-to-have features. -# Tauri Plugins [documentation](https://tauri.studio/en/docs/guides/plugin) +# Tauri Plugins [documentation](https://tauri.app/v1/guides/features/plugin/) Generally speaking, plugins are authored by third parties (even though there may be official, supported plugins). A plugin generally does 3 things: 1. It provides rust code to do "something". @@ -121,7 +116,7 @@ This will do several things: 1. start the JS Framework devserver 2. begin the long process of downloading and compiling the rust libraries 3. open an application window with devtools enabled -4. keep a long-lived console alive +4. keep a long-lived console alive If you change your HTML/CSS/TS/JS, your framework devserver should give you its best shot at instant hot module reloading and you will see the changes instantly. @@ -151,7 +146,7 @@ After some time, the process will end and you can see the results in the `./src- End users will be provided with binaries in ways that are appropriate for their systems. Whether macOS, Linux, or Windows, direct download or store installations - they will be able to follow procedures for installing and removing that they are used to. ## What does the Updating flow look like? -When a new version is ready, the developer publishes the new signed artifacts to a server (that they have configured within `tauri.conf.json`). +When a new version is ready, the developer publishes the new signed artifacts to a server (that they have configured within `tauri.conf.json`). The application can poll this server to see if there is a new release. When there is a new release, the user is prompted to update. The application update is downloaded, verified (checksum & signature), updated, closed, and restarted. diff --git a/Cargo.toml b/Cargo.toml index c95d732b27c5..e31b3c9d07d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,17 +7,31 @@ members = [ "core/tauri-macros", "core/tauri-utils", "core/tauri-build", - "core/tauri-codegen" + "core/tauri-codegen", + "core/tauri-config-schema", + + # integration tests + "core/tests/restart", ] + exclude = [ # examples that can be compiled with the tauri CLI "examples/api/src-tauri", - "examples/updater/src-tauri", "examples/resources/src-tauri", "examples/sidecar/src-tauri", - "examples/isolation/src-tauri" + "examples/web/core", + "examples/workspace", ] +[workspace.package] +authors = [ "Tauri Programme within The Commons Conservancy" ] +homepage = "https://tauri.app/" +repository = "https://github.com/tauri-apps/tauri" +categories = [ "gui", "web-programming" ] +license = "Apache-2.0 OR MIT" +edition = "2021" +rust-version = "1.65" + # default to small, optimized workspace release binaries [profile.release] panic = "abort" diff --git a/LICENSE.spdx b/LICENSE.spdx index 4a4518460ea9..7e8f2bfad23f 100644 --- a/LICENSE.spdx +++ b/LICENSE.spdx @@ -6,7 +6,7 @@ PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy PackageHomePage: https://tauri.app PackageLicenseDeclared: Apache-2.0 PackageLicenseDeclared: MIT -PackageCopyrightText: 2019-2021, The Tauri Programme in the Commons Conservancy +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy PackageSummary: Tauri is a rust project that enables developers to make secure and small desktop applications using a web frontend. @@ -17,4 +17,4 @@ Created: 2019-05-20T09:00:00Z PackageDownloadLocation: git://github.com/tauri-apps/tauri PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git -Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file +Creator: Person: Daniel Thompson-Yvetot diff --git a/README.md b/README.md index 5d3d748f351f..18903b7b5f03 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,98 @@ Tauri -[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri/tree/dev) +[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev) [![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri) -[![test library](https://img.shields.io/github/workflow/status/tauri-apps/tauri/test%20library?label=test%20library)](https://github.com/tauri-apps/tauri/actions?query=workflow%3A%22test+library%22) +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield) - -[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) -[![devto](https://img.shields.io/badge/blog-dev.to-black.svg)](https://dev.to/tauri) -[![devto](https://img.shields.io/badge/documentation-tauri.studio-purple.svg)](https://tauri.studio/docs/get-started/intro) +[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) +[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) -[![support](https://img.shields.io/badge/sponsor-open%20collective-blue.svg)](https://opencollective.com/tauri) +[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) ## Current Releases -| Component | Description | Version | Lin | Win | Mac | -| --------------------------------------------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --- | --- | --- | -| [**cli.rs**](https://github.com/tauri-apps/tauri/tree/dev/tooling/cli.rs) | create, develop and build apps | [![](https://img.shields.io/crates/v/tauri-cli.svg)](https://crates.io/crates/tauri-cli) | ✅ | ✅ | ✅ | -| [**cli.js**](https://github.com/tauri-apps/tauri/tree/dev/tooling/cli.rs/node) | Node.js CLI wrapper for cli.rs | [![](https://img.shields.io/npm/v/@tauri-apps/cli.svg)](https://www.npmjs.com/package/@tauri-apps/cli) | ✅ | ✅ | ✅ | -| [**api.js**](https://github.com/tauri-apps/tauri/tree/dev/tooling/api) | JS API for interaction with Rust backend | [![](https://img.shields.io/npm/v/@tauri-apps/api.svg)](https://www.npmjs.com/package/@tauri-apps/api) | ✅ | ✅ | ✅ | -| [**create-tauri-app**](https://github.com/tauri-apps/tauri/tree/dev/tooling/create-tauri-app) | Get started with your first Tauri app | [![](https://img.shields.io/npm/v/create-tauri-app.svg)](https://www.npmjs.com/package/create-tauri-app) | ✅ | ✅ | ✅ | -| [**vue-cli-plugin-tauri**](https://github.com/tauri-apps/vue-cli-plugin-tauri/) | Vue CLI plugin for Tauri | [![](https://img.shields.io/npm/v/vue-cli-plugin-tauri.svg)](https://www.npmjs.com/package/vue-cli-plugin-tauri) | ✅ | ✅ | ✅ | -| [**core**](https://github.com/tauri-apps/tauri/tree/dev/core/tauri) | runtime core | [![](https://img.shields.io/crates/v/tauri.svg)](https://crates.io/crates/tauri) | ✅ | ✅ | ✅ | -| [**bundler**](https://github.com/tauri-apps/tauri/tree/dev/tooling/bundler) | manufacture the final binaries | [![](https://img.shields.io/crates/v/tauri-bundler.svg)](https://crates.io/crates/tauri-bundler) | ✅ | ✅ | ✅ | +### Core + +| Component | Description | Version | Lin | Win | Mac | +| -------------------------------------------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------- | --- | --- | --- | +| [**tauri**](https://github.com/tauri-apps/tauri/tree/dev/core/tauri) | runtime core | [![](https://img.shields.io/crates/v/tauri.svg)](https://crates.io/crates/tauri) | ✅ | ✅ | ✅ | +| [**tauri-build**](https://github.com/tauri-apps/tauri/tree/dev/core/tauri-build) | applies macros at build-time | [![](https://img.shields.io/crates/v/tauri-build.svg)](https://crates.io/crates/tauri-build) | ✅ | ✅ | ✅ | +| [**tauri-codegen**](https://github.com/tauri-apps/tauri/tree/dev/core/tauri-codegen) | handles assets, parses tauri.conf.json | [![](https://img.shields.io/crates/v/tauri-codegen.svg)](https://crates.io/crates/tauri-codegen) | ✅ | ✅ | ✅ | +| [**tauri-macros**](https://github.com/tauri-apps/tauri/tree/dev/core/tauri-macros) | creates macros using tauri-codegen | [![](https://img.shields.io/crates/v/tauri-macros.svg)](https://crates.io/crates/tauri-macros) | ✅ | ✅ | ✅ | +| [**tauri-runtime**](https://github.com/tauri-apps/tauri/tree/dev/core/tauri-runtime) | layer between Tauri and webview libraries | [![](https://img.shields.io/crates/v/tauri-runtime.svg)](https://crates.io/crates/tauri-runtime) | ✅ | ✅ | ✅ | +| [**tauri-runtime-wry**](https://github.com/tauri-apps/tauri/tree/dev/core/tauri-runtime-wry) | enables system-level interaction via WRY | [![](https://img.shields.io/crates/v/tauri-runtime-wry.svg)](https://crates.io/crates/tauri-runtime-wry) | ✅ | ✅ | ✅ | +| [**tauri-utils**](https://github.com/tauri-apps/tauri/tree/dev/core/tauri-utils) | common code used across the tauri crates | [![](https://img.shields.io/crates/v/tauri-utils.svg)](https://crates.io/crates/tauri-utils) | ✅ | ✅ | ✅ | + +### Tooling + +| Component | Description | Version | Lin | Win | Mac | +| ------------------------------------------------------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------ | --- | --- | --- | +| [**bundler**](https://github.com/tauri-apps/tauri/tree/dev/tooling/bundler) | manufacture the final binaries | [![](https://img.shields.io/crates/v/tauri-bundler.svg)](https://crates.io/crates/tauri-bundler) | ✅ | ✅ | ✅ | +| [**tauri-cli**](https://github.com/tauri-apps/tauri/tree/dev/tooling/cli) | create, develop and build apps | [![](https://img.shields.io/crates/v/tauri-cli.svg)](https://crates.io/crates/tauri-cli) | ✅ | ✅ | ✅ | +| [**@tauri-apps/cli**](https://github.com/tauri-apps/tauri/tree/dev/tooling/cli/node) | Node.js CLI wrapper for `tauri-cli` | [![](https://img.shields.io/npm/v/@tauri-apps/cli.svg)](https://www.npmjs.com/package/@tauri-apps/cli) | ✅ | ✅ | ✅ | +| [**@tauri-apps/api**](https://github.com/tauri-apps/tauri/tree/dev/tooling/api) | JS API for interaction with Rust backend | [![](https://img.shields.io/npm/v/@tauri-apps/api.svg)](https://www.npmjs.com/package/@tauri-apps/api) | ✅ | ✅ | ✅ | + +### Utilities and Plugins + +| Component | Description | Version | Lin | Win | Mac | +| ------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --- | --- | --- | +| [**create-tauri-app**](https://github.com/tauri-apps/create-tauri-app) | Get started with your first Tauri app | [![](https://img.shields.io/npm/v/create-tauri-app.svg)](https://www.npmjs.com/package/create-tauri-app) | ✅ | ✅ | ✅ | +| [**vue-cli-plugin-tauri**](https://github.com/tauri-apps/vue-cli-plugin-tauri/) | Vue CLI plugin for Tauri | [![](https://img.shields.io/npm/v/vue-cli-plugin-tauri.svg)](https://www.npmjs.com/package/vue-cli-plugin-tauri) | ✅ | ✅ | ✅ | ## Introduction -Tauri is a framework for building tiny, blazing fast binaries for all major desktop platforms. Developers can integrate any front-end framework that compiles to HTML, JS and CSS for building their user interface. The backend of the application is a rust-sourced binary with an API that the front-end can interact with. + +Tauri is a framework for building tiny, blazingly fast binaries for all major desktop platforms. Developers can integrate any front-end framework that compiles to HTML, JS and CSS for building their user interface. The backend of the application is a rust-sourced binary with an API that the front-end can interact with. The user interface in Tauri apps currently leverages [`tao`](https://docs.rs/tao) as a window handling library on macOS and Windows, and [`gtk`](https://gtk-rs.org/docs/gtk/) on Linux via the **Tauri-team** incubated and maintained [WRY](https://github.com/tauri-apps/wry), which creates a unified interface to the system webview (and other goodies like Menu and Taskbar), leveraging WebKit on macOS, WebView2 on Windows and WebKitGTK on Linux. To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. ## Get Started -If you are interested in making a tauri-app, please visit the [documentation website](https://tauri.studio). This README is directed towards those who are interested in contributing to the core library. But if you just want a quick overview about where `tauri` is at in its development, here's a quick burndown: + +If you are interested in making a tauri app, please visit the [documentation website](https://tauri.app). This README is directed towards those who are interested in contributing to the core library. But if you just want a quick overview about where `tauri` is at in its development, here's a quick burndown: ### Platforms -- [x] Windows 7,8,10 -- [x] Linux -- [x] macOS -- [ ] iOS (in progress) -- [ ] android (soon) - -### App Bundles -- [x] App Icons -- [x] Build on MacOS (.app, .dmg) -- [x] Build on Linux (.deb, AppImage) -- [x] Build on Windows (.exe, .msi) -- [x] Copy Buffer -- [x] Device Notifications (toast) + +Tauri currently supports development and distribution on the following platforms: + +| Platform | Versions | +| :----------------------- | :-------------- | +| Windows | 7 and above | +| macOS | 10.15 and above | +| Linux | See below | +| iOS/iPadOS (coming soon) | | +| Android (coming soon) | | + +**Linux Support** + +For **developing** Tauri apps refer to the [Getting Started guide on tauri.app](https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux). + +For **running** Tauri apps we support the below configurations (these are automatically added as dependencies for .deb and are bundled for AppImage so that your users don't need to manually install them): + +- Debian (Ubuntu 18.04 and above or equivalent) with the following packages installed: + - `libwebkit2gtk-4.1-0`, `libgtk-3-0`, `libayatana-appindicator3-1`1 +- Arch with the following packages installed: + - `webkit2gtk`, `gtk3`, `libayatana-appindicator`1 +- Fedora (latest 2 versions) with the following packages installed: + - `webkit2gtk3`, `gtk3`, `libappindicator-gtk3`1 +- Void with the following packages installed: + - `webkit2gtk`, `gtk+3`, `libappindicator`1 + +1 `appindicator` is only required if system trays are used + +### Features + +- [x] Desktop Bundler (.app, .dmg, .deb, AppImage, .msi) - [x] Self Updater - [x] App Signing -- [x] Frameless Mode -- [x] Transparent Mode -- [x] Multiwindow Mode -- [x] Tray -- [ ] deeplink RPC (in progress) -- [ ] One-Time commands (coming soon) +- [x] Native Notifications (toast) +- [x] App Tray +- [x] Core Plugin System +- [x] Scoped Filesystem +- [x] Sidecar ### Security Features + - [x] localhost-free (:fire:) - [x] custom protocol for secure mode - [x] Dynamic ahead of Time Compilation (dAoT) with functional tree-shaking @@ -65,97 +101,69 @@ If you are interested in making a tauri-app, please visit the [documentation web - [x] CSP Injection ### Utilities + +- [x] Rust-based CLI - [x] GH Action for creating binaries for all platforms - [x] VS Code Extension -- [x] Tauri Core Plugins -- [x] Update core dependencies automatically from the command line -- [x] Rust-based CLI - -### Comparison between Tauri and Electron - -| Detail | Tauri | Electron | -| -------------------------- | ------ | -------------------- | -| Installer Size Linux | 3.1 MB | 52.1 MB | -| Memory Consumption Linux | 180 MB | 462 MB | -| Launch Time Linux | 0.39s | 0.80s | -| Interface Service Provider | WRY | Chromium | -| Backend Binding | Rust | Node.js (ECMAScript) | -| Underlying Engine | Rust | V8 (C/C++) | -| FLOSS | Yes | No | -| Multithreading | Yes | Yes | -| Bytecode Delivery | Yes | No | -| Multiple Windows | Yes | Yes | -| Auto Updater | Yes | Yes1 | -| Custom App Icon | Yes | Yes | -| Windows Binary | Yes | Yes | -| MacOS Binary | Yes | Yes | -| Linux Binary | Yes | Yes | -| iOS Binary | Soon | No | -| Android Binary | Soon | No | -| Desktop Tray | Yes | Yes | -| Sidecar Binaries | Yes | No | - -#### Notes -1. Electron has no native auto updater on Linux, but is offered by electron-packager ## Development Tauri is a system composed of a number of moving pieces: ### Infrastructure + - Git for code management - GitHub for project management - GitHub actions for CI and CD - Discord for discussions - Netlify-hosted documentation website -- DigitalOcean meilisearch instance - -### Major Runtimes -- Node.js for running the CLI (deno and pure rust are on the roadmap) -- Cargo for testing, running the dev service, building binaries and as the runtime harness for the webview - -### Major Languages -- Rust for the CLI -- EcmaScript bindings to the Rust API, written in typescript -- Rust for bindings, rust side of the API, harnesses -- Rust plugins to Tauri backend +- DigitalOcean Meilisearch instance ### Operating systems + Tauri core can be developed on Mac, Linux and Windows, but you are encouraged to use the latest possible operating systems and build tools for your OS. ### Contributing -Before you start working on something, it's best to check if there is an existing issue first. It's also is a good idea to stop by the Discord server and confirm with the team if it makes sense or if someone is already working on it. + +Before you start working on something, it's best to check if there is an existing issue first. It's also a good idea to stop by the Discord server and confirm with the team if it makes sense or if someone else is already working on it. Please make sure to read the [Contributing Guide](./.github/CONTRIBUTING.md) before making a pull request. Thank you to everyone contributing to Tauri! ### Documentation + Documentation in a polyglot system is a tricky proposition. To this end, we prefer to use inline documentation of Rust code and at JSDoc in typescript / javascript code. We autocollect these and publish them using Docusaurus v2 and netlify. Here is the hosting repository for the documentation site: https://github.com/tauri-apps/tauri-docs ### Testing & Linting + Test all the things! We have a number of test suites, but are always looking to improve our coverage: + - Rust (`cargo test`) => sourced via inline `#[cfg(test)]` declarations -- TS (`jest`) => via spec files +- Typescript (`jest`) => via spec files - Smoke Tests (run on merges to latest) - eslint, clippy ### CI/CD + We recommend you read this article to understand better how we run our pipelines: https://www.jacobbolda.com/setting-up-ci-and-cd-for-tauri/ ## Organization + Tauri aims to be a sustainable collective based on principles that guide [sustainable free and open software communities](https://sfosc.org). To this end it has become a Programme within the [Commons Conservancy](https://commonsconservancy.org/), and you can contribute financially via [Open Collective](https://opencollective.com/tauri). ## Semver + **tauri** is following [Semantic Versioning 2.0](https://semver.org/). ## Licenses + Code: (c) 2015 - 2021 - The Tauri Programme within The Commons Conservancy. MIT or MIT/Apache 2.0 where applicable. Logo: CC-BY-NC-ND -- Original Tauri Logo Designs by [Alve Larsson](https://alve.io/), [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) +- Original Tauri Logo Designs by [Alve Larsson](https://alve.io/), [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_large) diff --git a/SECURITY.md b/SECURITY.md index cbd06ded3472..0b30bfd89e48 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,7 +13,16 @@ If you have found a potential security threat, vulnerability or exploit in Tauri or one of its upstream dependencies, please DON’T create a pull-request, DON’T file an issue on GitHub, DON’T mention it on Discord and DON’T create a forum thread. -We will be adding contact information to this page very soon. +Please submit your report via the GitHub Private Vulnerability Disclosure functionality. + +Find out more about the reporting process [here](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability). + +Our team will triage your report and keep you informed about the progress. +We may ask questions or request further guidance on reproduction of the vulnerability in the comments of the advisory, which will be publicized. + +Additionally, we may ask you to independently verify our patch, which will be available in the private advisory branch. Please do not publish your vulnerability during the process or before coordinated public disclosure from our side. We try to adhere to common standards of publication within 90-Days of disclosure. + +Depending on your decision to accept or deny credit for the vulnerability, you will be publicly attributed to the vulnerability and may be mentioned in our announcements. At the current time we do not have the financial ability to reward bounties, but in extreme cases will at our discretion consider a reward. diff --git a/core/tauri-build/.license_template b/core/tauri-build/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/core/tauri-build/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/core/tauri-build/CHANGELOG.md b/core/tauri-build/CHANGELOG.md index c28eb7995adc..5eda582b7536 100644 --- a/core/tauri-build/CHANGELOG.md +++ b/core/tauri-build/CHANGELOG.md @@ -1,5 +1,242 @@ # Changelog +## \[2.0.0-alpha.5] + +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`2969d1cb`](https://www.github.com/tauri-apps/tauri/commit/2969d1cbba39301f9cc611d9f7d7051d80eef846)([#6773](https://www.github.com/tauri-apps/tauri/pull/6773)) Use absolute path to each Android plugin project instead of copying the files to enhance developer experience. +- [`cdad6e08`](https://www.github.com/tauri-apps/tauri/commit/cdad6e083728ea61bd6fc734ef93f6306056ea2e)([#6774](https://www.github.com/tauri-apps/tauri/pull/6774)) Changed how the `tauri-android` dependency is injected. This requires the `gen/android` project to be recreated. +- [`5a768d5c`](https://www.github.com/tauri-apps/tauri/commit/5a768d5ce69d6c9011c41f38a43481087c8d4921)([#6886](https://www.github.com/tauri-apps/tauri/pull/6886)) Remove `WindowsAttributes::sdk_dir`. + +## \[2.0.0-alpha.4] + +- Added `android` configuration object under `tauri > bundle`. + - Bumped due to a bump in tauri-utils. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 + +## \[2.0.0-alpha.3] + +- Read the `IPHONEOS_DEPLOYMENT_TARGET` environment variable to set the Swift iOS target version, defaults to 13. + - [4c3b9ecf](https://www.github.com/tauri-apps/tauri/commit/4c3b9ecfdcd1a4489b1e466727f11045ef34d67a) fix(build): iOS deployment target env var is IPHONEOS_DEPLOYMENT_TARGET ([#6602](https://www.github.com/tauri-apps/tauri/pull/6602)) on 2023-03-31 + +## \[2.0.0-alpha.2] + +- Add `mobile::PluginBuilder` for running build tasks related to Tauri plugins. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 + +## \[2.0.0-alpha.1] + +- Refactor mobile environment variables. + - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27 +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Removed mobile logging initialization, which will be handled by `tauri-plugin-log`. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[2.0.0-alpha.0] + +- Set environment variables used by `tauri::mobile_entry_point`. + - [98904863](https://www.github.com/tauri-apps/tauri/commit/9890486321c9c79ccfb7c547fafee85b5c3ffa71) feat(core): add `mobile_entry_point` macro ([#4983](https://www.github.com/tauri-apps/tauri/pull/4983)) on 2022-08-21 +- First mobile alpha release! + - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08 + +## \[1.4.0] + +### Enhancements + +- [`52474e47`](https://www.github.com/tauri-apps/tauri/commit/52474e479d695865299d8c8d868fb98b99731020)([#7141](https://www.github.com/tauri-apps/tauri/pull/7141)) Enhance Cargo features check. +- [`af937290`](https://www.github.com/tauri-apps/tauri/commit/af93729031565a69d1fde6cf16bea3b9b6e43a65)([#6676](https://www.github.com/tauri-apps/tauri/pull/6676)) On Windows, set `LegalCopyright` and `FileDescription` file properties on the executable from `tauri.bundle.copyright` and `tauri.bundle.shortDescription`, +- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85. + +## \[1.3.0] + +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 +- Add initial support for building `nsis` bundles on non-Windows platforms. + - [60e6f6c3](https://www.github.com/tauri-apps/tauri/commit/60e6f6c3f1605f3064b5bb177992530ff788ccf0) feat(bundler): Add support for creating NSIS bundles on unix hosts ([#5788](https://www.github.com/tauri-apps/tauri/pull/5788)) on 2023-01-19 +- Add `WindowsAttributes::app_manifest` to specify the application manifest on Windows. + - [bca09f7f](https://www.github.com/tauri-apps/tauri/commit/bca09f7f5ff1c9c5a4b51da043bdd5da668a179b) feat(tauri-build): add option to specify Windows manifest, closes [#5584](https://www.github.com/tauri-apps/tauri/pull/5584) ([#5730](https://www.github.com/tauri-apps/tauri/pull/5730)) on 2022-12-14 +- Added support for Cargo's workspace inheritance for package information. The cli now also detects inherited `tauri` and `tauri-build` dependencies and disables manifest rewrites accordingly. + - [cd8c074a](https://www.github.com/tauri-apps/tauri/commit/cd8c074ae6592303d3f6844a4fb6d262eae913b2) feat(cli): add support for Cargo's workspace inheritance for the package version, closes [#5070](https://www.github.com/tauri-apps/tauri/pull/5070) ([#5775](https://www.github.com/tauri-apps/tauri/pull/5775)) on 2022-12-14 + - [d20a7288](https://www.github.com/tauri-apps/tauri/commit/d20a728892eee1858ab525ab6216cd721f473ab5) feat: Further improve workspace inheritance, closes [#6122](https://www.github.com/tauri-apps/tauri/pull/6122), [#5070](https://www.github.com/tauri-apps/tauri/pull/5070) ([#6144](https://www.github.com/tauri-apps/tauri/pull/6144)) on 2023-01-26 +- Pin `winnow` crate to 0.4.1 to keep the 1.60 MSRV. + +## \[1.2.1] + +- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`. + - Bumped due to a bump in tauri-utils. + - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18 + +## \[1.2.0] + +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 + +## \[1.1.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - Bumped due to a bump in tauri-utils. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[1.1.0] + +- Rerun codegen if assets or icons change. + - [ff8fd761](https://www.github.com/tauri-apps/tauri/commit/ff8fd7619ae894b70f149b192d8635d842827141) fix(tauri-build): rerun if assets or icons change ([#4910](https://www.github.com/tauri-apps/tauri/pull/4910)) on 2022-08-10 +- Create the `desktop` and `mobile` cfg aliases. + - [c04d0340](https://www.github.com/tauri-apps/tauri/commit/c04d0340e2f163483f3556c7aabe35322ee71e6a) feat(core): prepare build for mobile targets ([#4830](https://www.github.com/tauri-apps/tauri/pull/4830)) on 2022-08-02 +- Added support to configuration files in TOML format (Tauri.toml file). + - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02 +- Enhance the dialog style on Windows via the manifest dependency `Microsoft.Windows.Common-Controls v6.0.0.0`. + - [5c5c42ed](https://www.github.com/tauri-apps/tauri/commit/5c5c42edb64adf4250af6891d7d226fda7d4d783) feat(build): use modern dialog styles on Windows, closes [#4709](https://www.github.com/tauri-apps/tauri/pull/4709) ([#4840](https://www.github.com/tauri-apps/tauri/pull/4840)) on 2022-08-02 +- Fix root of codegen output when using the `CodegenContext` API. + - [ed581950](https://www.github.com/tauri-apps/tauri/commit/ed581950ea6fd0afee47aa73fb63083d2483429f) fix(tauri-build): use `::tauri` as root for the CodegenContext ([#4894](https://www.github.com/tauri-apps/tauri/pull/4894)) on 2022-08-08 +- Return an error if a sidecar is configured with the same file name as the application. + - [5cc1fd0f](https://www.github.com/tauri-apps/tauri/commit/5cc1fd0f7b01b257a461d4e867ddc8cd5072ff15) feat(tauri-build): validate sidecar name, closes [#4780](https://www.github.com/tauri-apps/tauri/pull/4780) closes [#4823](https://www.github.com/tauri-apps/tauri/pull/4823) ([#4814](https://www.github.com/tauri-apps/tauri/pull/4814)) on 2022-08-02 +- Only rewrite temporary icon files when the content change, avoid needless rebuilds. + - [f957cbb5](https://www.github.com/tauri-apps/tauri/commit/f957cbb56ccbd8d1c047a978b4579946252a6fd2) fix(codegen): write output file when contents change ([#4889](https://www.github.com/tauri-apps/tauri/pull/4889)) on 2022-08-09 + +## \[1.0.4] + +- Reduce the amount of allocations when converting cases. + - [bc370e32](https://www.github.com/tauri-apps/tauri/commit/bc370e326810446e15b1f50fb962b980114ba16b) feat: reduce the amount of `heck`-related allocations ([#4634](https://www.github.com/tauri-apps/tauri/pull/4634)) on 2022-07-11 + +## \[1.0.3] + +- Improve configuration deserialization error messages. + - [9170c920](https://www.github.com/tauri-apps/tauri/commit/9170c9207044fa561535f624916dfdbaa41ff79d) feat(core): improve config deserialization error messages ([#4607](https://www.github.com/tauri-apps/tauri/pull/4607)) on 2022-07-06 +- The `TAURI_CONFIG` environment variable now represents the configuration to be merged instead of the entire JSON. + - [fa028ebf](https://www.github.com/tauri-apps/tauri/commit/fa028ebf3c8ca7b43a70d283a01dbea86217594f) refactor: do not pass entire config from CLI to core, send patch instead ([#4598](https://www.github.com/tauri-apps/tauri/pull/4598)) on 2022-07-06 + +## \[1.0.2] + +- Expose `platform::windows_version` function. + - Bumped due to a bump in tauri-utils. + - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30 + +## \[1.0.1] + +- Changed the `BundleConfig::targets` to a `BundleTarget` enum to enhance generated documentation. + - Bumped due to a bump in tauri-utils. + - [31c15cd2](https://www.github.com/tauri-apps/tauri/commit/31c15cd2bd94dbe39fb94982a15cbe02ac5d8925) docs(config): enhance documentation for bundle targets, closes [#3251](https://www.github.com/tauri-apps/tauri/pull/3251) ([#4418](https://www.github.com/tauri-apps/tauri/pull/4418)) on 2022-06-21 +- Added `platform::is_windows_7`. + - Bumped due to a bump in tauri-utils. + - [57039fb2](https://www.github.com/tauri-apps/tauri/commit/57039fb2166571de85271b014a8711a29f06be1a) fix(core): add windows 7 notification support ([#4491](https://www.github.com/tauri-apps/tauri/pull/4491)) on 2022-06-28 +- Suppress unused variable warning in release builds. + - Bumped due to a bump in tauri-utils. + - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22 +- Added webview install mode options. + - Bumped due to a bump in tauri-utils. + - [2ca762d2](https://www.github.com/tauri-apps/tauri/commit/2ca762d207030a892a6d128b519e150e2d733468) feat(bundler): extend webview2 installation options, closes [#2882](https://www.github.com/tauri-apps/tauri/pull/2882) [#2452](https://www.github.com/tauri-apps/tauri/pull/2452) ([#4466](https://www.github.com/tauri-apps/tauri/pull/4466)) on 2022-06-26 + +## \[1.0.0] + +- Upgrade to `stable`! + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[1.0.0-rc.15] + +- Read the tray icon path relatively to the config directory. + - Bumped due to a bump in tauri-codegen. + - [562e8ca2](https://www.github.com/tauri-apps/tauri/commit/562e8ca23facf1a8e5fa6c8cdf872357d3523a78) fix(codegen): tray icon path is relative to the config directory on 2022-06-15 + +## \[1.0.0-rc.14] + +- Do not copy the tray icon to the output directory on Linux since it is embedded in the binary. + - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14 + +## \[1.0.0-rc.13] + +- Copy `tauri.conf.json > tauri.bundle.windows.webview_fixed_runtime_path` as a resource to the target directory to fix development usage of a fixed Webview2 runtime path. + - [8a634895](https://www.github.com/tauri-apps/tauri/commit/8a63489567b9fa86e404ad42b5b30c64361efe85) fix(build): fixed Webview2 runtime path in development, closes [#4308](https://www.github.com/tauri-apps/tauri/pull/4308) on 2022-06-10 +- Improve usage of the GNU toolchain on Windows by copying the Webview2Loader.dll file to the target directory. + - [58a6879b](https://www.github.com/tauri-apps/tauri/commit/58a6879b82e3a82027604cdd0913caacaaab5c76) feat(tauri-build): improve Windows GNU toolchain usage, closes [#4319](https://www.github.com/tauri-apps/tauri/pull/4319) ([#4323](https://www.github.com/tauri-apps/tauri/pull/4323)) on 2022-06-12 +- Only statically link the VC runtime when the `STATIC_VCRUNTIME` environment variable is set to `true` (automatically done by the Tauri CLI). + - [d703d27a](https://www.github.com/tauri-apps/tauri/commit/d703d27a707edc028f13b35603205da1133fcc2b) fix(build): statically link VC runtime only on `tauri build` ([#4292](https://www.github.com/tauri-apps/tauri/pull/4292)) on 2022-06-07 + +## \[1.0.0-rc.12] + +- Statically link the Visual C++ runtime instead of using a merge module on the installer. + - [bb061509](https://www.github.com/tauri-apps/tauri/commit/bb061509fb674bef86ecbc1de3aa8f3e367a9907) refactor(core): statically link vcruntime, closes [#4122](https://www.github.com/tauri-apps/tauri/pull/4122) ([#4227](https://www.github.com/tauri-apps/tauri/pull/4227)) on 2022-05-27 + +## \[1.0.0-rc.11] + +- Create `dev` cfg alias. + - [9cdcf9b3](https://www.github.com/tauri-apps/tauri/commit/9cdcf9b3a8fa27612b3156c1702a4e776627e869) feat(build): create `dev` alias ([#4212](https://www.github.com/tauri-apps/tauri/pull/4212)) on 2022-05-25 + +## \[1.0.0-rc.10] + +- Delete existing sidecar before copying new one. + - [a737f25c](https://www.github.com/tauri-apps/tauri/commit/a737f25c1078083e0b0f7f338f5c958b86914323) fix(tauri-build): delete existing sidecar file, closes [#4134](https://www.github.com/tauri-apps/tauri/pull/4134) ([#4167](https://www.github.com/tauri-apps/tauri/pull/4167)) on 2022-05-18 + +## \[1.0.0-rc.9] + +- Search `tauri.conf.json > tauri > bundle > icons` for a `.ico` file for the window icon instead of simple default `icons/icon.ico` when `WindowsAttributes::window_icon_path` is not set. + - [bad85a1f](https://www.github.com/tauri-apps/tauri/commit/bad85a1f11da03421401080531275ba201480cd1) feat(build): find .ico in config instead of default `icons/icon.ico` ([#4115](https://www.github.com/tauri-apps/tauri/pull/4115)) on 2022-05-13 + +## \[1.0.0-rc.8] + +- Properly set file version information for the Windows executable. + - [1ca2dd67](https://www.github.com/tauri-apps/tauri/commit/1ca2dd677d50b4c724c55b37060c3ba64b81c54a) fix(tauri-build): properly set executable version info on Windows ([#4045](https://www.github.com/tauri-apps/tauri/pull/4045)) on 2022-05-03 + +## \[1.0.0-rc.7] + +- Rerun build script if `TAURI_CONFIG` environment variable change. + - [7ae9e252](https://www.github.com/tauri-apps/tauri/commit/7ae9e25262376529637cd3b47b1cf84809efaec9) fix(tauri-build): rerun if `TAURI_CONFIG` env var changes on 2022-04-26 + +## \[1.0.0-rc.6] + +- Copy system tray icon resource to the target directory on Linux. + - [f2a30d8b](https://www.github.com/tauri-apps/tauri/commit/f2a30d8bc54fc3ba49e16f69a413eca5f61a9b1f) refactor(core): use ayatana appindicator by default, keep option to use gtk ([#3916](https://www.github.com/tauri-apps/tauri/pull/3916)) on 2022-04-19 + +## \[1.0.0-rc.5] + +- Print error context on the `build` panic. + - [49546c52](https://www.github.com/tauri-apps/tauri/commit/49546c5269080f38d57365788eb2592bff8f6d10) feat(build): print error context ([#3644](https://www.github.com/tauri-apps/tauri/pull/3644)) on 2022-03-09 + +## \[1.0.0-rc.4] + +- Parse window icons at compile time. + - Bumped due to a bump in tauri-codegen. + - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05 + +## \[1.0.0-rc.3] + +- Automatically emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET` with the value set on `tauri.conf.json > tauri > bundle > macos > minimumSystemVersion`. + - [4bacea5b](https://www.github.com/tauri-apps/tauri/commit/4bacea5bf48ea5ca6c9bdd10e28e85e67a0c6ef9) feat(core): set `MACOSX_DEPLOYMENT_TARGET` environment variable, closes [#2732](https://www.github.com/tauri-apps/tauri/pull/2732) ([#3496](https://www.github.com/tauri-apps/tauri/pull/3496)) on 2022-02-17 + +## \[1.0.0-rc.2] + +- Rerun if sidecar or resource change. + - [afcc3ec5](https://www.github.com/tauri-apps/tauri/commit/afcc3ec50148074293350aaa26a05812207716be) fix(build): rerun if resource or sidecar change ([#3460](https://www.github.com/tauri-apps/tauri/pull/3460)) on 2022-02-14 + +## \[1.0.0-rc.1] + +- Remove `cargo:rerun-if-changed` check for non-existent file that caused projects to *always* rebuild. + - [65287cd6](https://www.github.com/tauri-apps/tauri/commit/65287cd6148feeba91df86217b261770fed34608) remove non-existent cargo rerun check ([#3412](https://www.github.com/tauri-apps/tauri/pull/3412)) on 2022-02-11 + +## \[1.0.0-rc.0] + +- Allow user to specify windows sdk path in build.rs. + - [59b6ee87](https://www.github.com/tauri-apps/tauri/commit/59b6ee87932d341433032befe3babd897ed8f7d0) fix(tauri-build): allow user to specify win sdk path (fix: [#2871](https://www.github.com/tauri-apps/tauri/pull/2871)) ([#2893](https://www.github.com/tauri-apps/tauri/pull/2893)) on 2021-11-16 +- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension. + +Here is the logic flow that determines if JSON or JSON5 will be used to parse the config: + +1. Check if `tauri.conf.json` exists + a. Parse it with `serde_json` + b. Parse it with `json5` if `serde_json` fails + c. Return original `serde_json` error if all above steps failed +2. Check if `tauri.conf.json5` exists + a. Parse it with `json5` + b. Return error if all above steps failed +3. Return error if all above steps failed + +- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03 +- Move the copying of resources and sidecars from `cli.rs` to `tauri-build` so using the Cargo CLI directly processes the files for the application execution in development. + - [5eb72c24](https://www.github.com/tauri-apps/tauri/commit/5eb72c24deddf5a01093bea96b90c0d8806afc3f) refactor: copy resources and sidecars on the Cargo build script ([#3357](https://www.github.com/tauri-apps/tauri/pull/3357)) on 2022-02-08 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 +- Validate `tauri` dependency `features` under `Cargo.toml` matching `tauri.conf.json`'s `allowlist`. + - [4de285c3](https://www.github.com/tauri-apps/tauri/commit/4de285c3967d32250d73acdd5d171a6fd332d2b3) feat(core): validate Cargo features matching allowlist \[TRI-023] on 2022-01-09 + ## \[1.0.0-beta.4] - Implement `Debug` on public API structs and enums. diff --git a/core/tauri-build/Cargo.toml b/core/tauri-build/Cargo.toml index dd58d6308b95..1804ba295e16 100644 --- a/core/tauri-build/Cargo.toml +++ b/core/tauri-build/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "tauri-build" -version = "1.0.0-beta.4" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.studio" -repository = "https://github.com/tauri-apps/tauri/tree/dev/core/tauri-build" +version = "2.0.0-alpha.5" description = "build time code to pair with https://crates.io/crates/tauri" -edition = "2021" -rust-version = "1.57" -exclude = [ ".license_template", "CHANGELOG.md", "/target" ] +exclude = [ "CHANGELOG.md", "/target" ] readme = "README.md" +# workspace defined package items +authors = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } + [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "doc_cfg" ] @@ -19,15 +21,22 @@ rustdoc-args = [ "--cfg", "doc_cfg" ] [dependencies] anyhow = "1" quote = { version = "1", optional = true } -tauri-codegen = { version = "1.0.0-beta.4", path = "../tauri-codegen", optional = true } -tauri-utils = { version = "1.0.0-beta.0", path = "../tauri-utils", features = [ "build", "resources" ] } -cargo_toml = "0.11" +tauri-codegen = { version = "2.0.0-alpha.5", path = "../tauri-codegen", optional = true } +tauri-utils = { version = "2.0.0-alpha.5", path = "../tauri-utils", features = [ "build", "resources" ] } +cargo_toml = "0.15" +serde = "1" serde_json = "1" +heck = "0.4" +json-patch = "1.0" +walkdir = "2" +tauri-winres = "0.1" +semver = "1" -[target."cfg(windows)".dependencies] -winres = "0.1" +[target."cfg(target_os = \"macos\")".dependencies] +swift-rs = { version = "1.0.4", features = [ "build" ] } [features] codegen = [ "tauri-codegen", "quote" ] -isolation = ["tauri-codegen/isolation", "tauri-utils/isolation"] +isolation = [ "tauri-codegen/isolation", "tauri-utils/isolation" ] config-json5 = [ "tauri-utils/config-json5" ] +config-toml = [ "tauri-utils/config-toml" ] diff --git a/core/tauri-build/README.md b/core/tauri-build/README.md index 06c9f7d0427a..835e9b932752 100644 --- a/core/tauri-build/README.md +++ b/core/tauri-build/README.md @@ -2,38 +2,41 @@ -[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri) -[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) -[![devto](https://img.shields.io/badge/blog-dev.to-black.svg)](https://dev.to/tauri) - -![](https://img.shields.io/github/workflow/status/tauri-apps/tauri/test%20library?label=test%20library -) -[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.studio) - +[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev) +[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri) +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield) +[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) +[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) -[![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri) +[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) -| Component | Version | -| --------- | ------------------------------------------- | -| tauri-build | [![](https://img.shields.io/crates/v/tauri-build?style=flat-square)](https://crates.io/crates/tauri-build) | +| Component | Version | +| ----------- | ---------------------------------------------------------------------------------------------------------- | +| tauri-build | [![](https://img.shields.io/crates/v/tauri-build?style=flat-square)](https://crates.io/crates/tauri-build) | ## About Tauri + Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily. Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task. ## This module + This applies the macros at build-time in order to rig some special features needed by `cargo`. To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. - ## Semver + **tauri** is following [Semantic Versioning 2.0](https://semver.org/). + ## Licenses + Code: (c) 2021 - The Tauri Programme within The Commons Conservancy. MIT or MIT/Apache 2.0 where applicable. Logo: CC-BY-NC-ND + - Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) diff --git a/core/tauri-build/src/allowlist.rs b/core/tauri-build/src/allowlist.rs new file mode 100644 index 000000000000..34654f689c37 --- /dev/null +++ b/core/tauri-build/src/allowlist.rs @@ -0,0 +1,211 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use anyhow::{anyhow, Result}; +use cargo_toml::{Dependency, Manifest}; +use tauri_utils::config::{Config, PatternKind, TauriConfig}; + +#[derive(Debug, Default, PartialEq, Eq)] +struct Diff { + remove: Vec, + add: Vec, +} + +#[derive(Debug, Clone, Copy)] +enum DependencyKind { + Build, + Normal, +} + +#[derive(Debug)] +struct AllowlistedDependency { + name: String, + alias: Option, + kind: DependencyKind, + all_cli_managed_features: Option>, + expected_features: Vec, +} + +pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> { + let dependencies = vec![ + AllowlistedDependency { + name: "tauri-build".into(), + alias: None, + kind: DependencyKind::Build, + all_cli_managed_features: Some(vec!["isolation"]), + expected_features: match config.tauri.pattern { + PatternKind::Isolation { .. } => vec!["isolation".to_string()], + _ => vec![], + }, + }, + AllowlistedDependency { + name: "tauri".into(), + alias: None, + kind: DependencyKind::Normal, + all_cli_managed_features: Some(TauriConfig::all_features()), + expected_features: config + .tauri + .features() + .into_iter() + .map(|f| f.to_string()) + .collect::>(), + }, + ]; + + for metadata in dependencies { + let mut name = metadata.name.clone(); + let mut deps = find_dependency(manifest, &metadata.name, metadata.kind); + if deps.is_empty() { + if let Some(alias) = &metadata.alias { + deps = find_dependency(manifest, alias, metadata.kind); + name = alias.clone(); + } + } + + for dep in deps { + if let Err(error) = check_features(dep, &metadata) { + return Err(anyhow!(" + The `{}` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`. + Please run `tauri dev` or `tauri build` or {}. + ", name, error)); + } + } + } + + Ok(()) +} + +fn find_dependency(manifest: &mut Manifest, name: &str, kind: DependencyKind) -> Vec { + let dep = match kind { + DependencyKind::Build => manifest.build_dependencies.remove(name), + DependencyKind::Normal => manifest.dependencies.remove(name), + }; + + if let Some(dep) = dep { + vec![dep] + } else { + let mut deps = Vec::new(); + for target in manifest.target.values_mut() { + if let Some(dep) = match kind { + DependencyKind::Build => target.build_dependencies.remove(name), + DependencyKind::Normal => target.dependencies.remove(name), + } { + deps.push(dep); + } + } + deps + } +} + +fn features_diff(current: &[String], expected: &[String]) -> Diff { + let mut remove = Vec::new(); + let mut add = Vec::new(); + for feature in current { + if !expected.contains(feature) { + remove.push(feature.clone()); + } + } + + for feature in expected { + if !current.contains(feature) { + add.push(feature.clone()); + } + } + + Diff { remove, add } +} + +fn check_features(dependency: Dependency, metadata: &AllowlistedDependency) -> Result<(), String> { + let features = match dependency { + Dependency::Simple(_) => Vec::new(), + Dependency::Detailed(dep) => dep.features, + Dependency::Inherited(dep) => dep.features, + }; + + let diff = if let Some(all_cli_managed_features) = &metadata.all_cli_managed_features { + features_diff( + &features + .into_iter() + .filter(|f| all_cli_managed_features.contains(&f.as_str())) + .collect::>(), + &metadata.expected_features, + ) + } else { + features_diff( + &features + .into_iter() + .filter(|f| f.starts_with("allow-")) + .collect::>(), + &metadata.expected_features, + ) + }; + + let mut error_message = String::new(); + if !diff.remove.is_empty() { + error_message.push_str("remove the `"); + error_message.push_str(&diff.remove.join(", ")); + error_message.push_str(if diff.remove.len() == 1 { + "` feature" + } else { + "` features" + }); + if !diff.add.is_empty() { + error_message.push_str(" and "); + } + } + if !diff.add.is_empty() { + error_message.push_str("add the `"); + error_message.push_str(&diff.add.join(", ")); + error_message.push_str(if diff.add.len() == 1 { + "` feature" + } else { + "` features" + }); + } + + if error_message.is_empty() { + Ok(()) + } else { + Err(error_message) + } +} + +#[cfg(test)] +mod tests { + use super::Diff; + + #[test] + fn array_diff() { + for (current, expected, result) in [ + (vec![], vec![], Default::default()), + ( + vec!["a".into()], + vec![], + Diff { + remove: vec!["a".into()], + add: vec![], + }, + ), + (vec!["a".into()], vec!["a".into()], Default::default()), + ( + vec!["a".into(), "b".into()], + vec!["a".into()], + Diff { + remove: vec!["b".into()], + add: vec![], + }, + ), + ( + vec!["a".into(), "b".into()], + vec!["a".into(), "c".into()], + Diff { + remove: vec!["b".into()], + add: vec!["c".into()], + }, + ), + ] { + assert_eq!(super::features_diff(¤t, &expected), result); + } + } +} diff --git a/core/tauri-build/src/codegen/context.rs b/core/tauri-build/src/codegen/context.rs index 29356b09885b..e17fe3b8f8d4 100644 --- a/core/tauri-build/src/codegen/context.rs +++ b/core/tauri-build/src/codegen/context.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -10,6 +10,7 @@ use std::{ path::PathBuf, }; use tauri_codegen::{context_codegen, ContextData}; +use tauri_utils::config::{AppUrl, WindowUrl}; // TODO docs /// A builder for generating a Tauri application context during compile time. @@ -52,13 +53,13 @@ impl CodegenContext { /// /// **Note:** This path should be relative to the `OUT_DIR`. /// - /// Don't set this if you are using [`tauri::include_codegen_context!`] as that helper macro + /// Don't set this if you are using [`tauri::tauri_build_context!`] as that helper macro /// expects the default value. This option can be useful if you are not using the helper and /// instead using [`std::include!`] on the generated code yourself. /// /// Defaults to `tauri-build-context.rs`. /// - /// [`tauri::include_codegen_context!`]: https://docs.rs/tauri/0.12/tauri/macro.include_codegen_context.html + /// [`tauri::tauri_build_context!`]: https://docs.rs/tauri/latest/tauri/macro.tauri_build_context.html #[must_use] pub fn out_file(mut self, filename: PathBuf) -> Self { self.out_file = filename; @@ -85,20 +86,60 @@ impl CodegenContext { pub fn build(self) -> PathBuf { match self.try_build() { Ok(out) => out, - Err(error) => panic!("Error found during Codegen::build: {}", error), + Err(error) => panic!("Error found during Codegen::build: {error}"), } } /// Non-panicking [`Self::build`] pub fn try_build(self) -> Result { let (config, config_parent) = tauri_codegen::get_config(&self.config_path)?; + + // rerun if changed + let app_url = if self.dev { + &config.build.dev_path + } else { + &config.build.dist_dir + }; + match app_url { + AppUrl::Url(WindowUrl::App(p)) => { + println!("cargo:rerun-if-changed={}", config_parent.join(p).display()); + } + AppUrl::Files(files) => { + for path in files { + println!( + "cargo:rerun-if-changed={}", + config_parent.join(path).display() + ); + } + } + _ => (), + } + for icon in &config.tauri.bundle.icon { + println!( + "cargo:rerun-if-changed={}", + config_parent.join(icon).display() + ); + } + if let Some(tray_icon) = config.tauri.system_tray.as_ref().map(|t| &t.icon_path) { + println!( + "cargo:rerun-if-changed={}", + config_parent.join(tray_icon).display() + ); + } + + #[cfg(target_os = "macos")] + println!( + "cargo:rerun-if-changed={}", + config_parent.join("Info.plist").display() + ); + let code = context_codegen(ContextData { dev: self.dev, config, config_parent, // it's very hard to have a build script for unit tests, so assume this is always called from // outside the tauri crate, making the ::tauri root valid. - root: quote::quote!(::tauri::Context), + root: quote::quote!(::tauri), })?; // get the full output file path @@ -120,7 +161,7 @@ impl CodegenContext { ) })?; - writeln!(file, "{}", code).with_context(|| { + writeln!(file, "{code}").with_context(|| { format!( "Unable to write tokenstream to out file during tauri-build {}", out.display() diff --git a/core/tauri-build/src/codegen/mod.rs b/core/tauri-build/src/codegen/mod.rs index 6fa87bc23988..61a7e5b297b3 100644 --- a/core/tauri-build/src/codegen/mod.rs +++ b/core/tauri-build/src/codegen/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT diff --git a/core/tauri-build/src/lib.rs b/core/tauri-build/src/lib.rs index 9f720a12a87f..35539de45f13 100644 --- a/core/tauri-build/src/lib.rs +++ b/core/tauri-build/src/lib.rs @@ -1,16 +1,30 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT #![cfg_attr(doc_cfg, feature(doc_cfg))] +use anyhow::Context; pub use anyhow::Result; -use tauri_utils::resources::{external_binaries, resource_relpath, ResourcePaths}; +use cargo_toml::Manifest; +use heck::AsShoutySnakeCase; -use std::path::{Path, PathBuf}; +use tauri_utils::{ + config::Config, + resources::{external_binaries, resource_relpath, ResourcePaths}, +}; +use std::{ + env::var_os, + path::{Path, PathBuf}, +}; + +mod allowlist; #[cfg(feature = "codegen")] mod codegen; +/// Mobile build functions. +pub mod mobile; +mod static_vcruntime; #[cfg(feature = "codegen")] #[cfg_attr(doc_cfg, doc(cfg(feature = "codegen")))] @@ -25,20 +39,38 @@ fn copy_file(from: impl AsRef, to: impl AsRef) -> Result<()> { if !from.is_file() { return Err(anyhow::anyhow!("{:?} is not a file", from)); } + let dest_dir = to.parent().expect("No data in parent"); + std::fs::create_dir_all(dest_dir)?; std::fs::copy(from, to)?; Ok(()) } -fn copy_binaries<'a>(binaries: ResourcePaths<'a>, target_triple: &str, path: &Path) -> Result<()> { +fn copy_binaries( + binaries: ResourcePaths, + target_triple: &str, + path: &Path, + package_name: Option<&String>, +) -> Result<()> { for src in binaries { let src = src?; - let dest = path.join( - src - .file_name() - .expect("failed to extract external binary filename") - .to_string_lossy() - .replace(&format!("-{}", target_triple), ""), - ); + println!("cargo:rerun-if-changed={}", src.display()); + let file_name = src + .file_name() + .expect("failed to extract external binary filename") + .to_string_lossy() + .replace(&format!("-{target_triple}"), ""); + + if package_name.map_or(false, |n| n == &file_name) { + return Err(anyhow::anyhow!( + "Cannot define a sidecar with the same name as the Cargo package name `{}`. Please change the sidecar name in the filesystem and the Tauri configuration.", + file_name + )); + } + + let dest = path.join(file_name); + if dest.exists() { + std::fs::remove_file(&dest).unwrap(); + } copy_file(&src, &dest)?; } Ok(()) @@ -48,32 +80,64 @@ fn copy_binaries<'a>(binaries: ResourcePaths<'a>, target_triple: &str, path: &Pa fn copy_resources(resources: ResourcePaths<'_>, path: &Path) -> Result<()> { for src in resources { let src = src?; + println!("cargo:rerun-if-changed={}", src.display()); let dest = path.join(resource_relpath(&src)); - copy_file(&src, &dest)?; + copy_file(&src, dest)?; } Ok(()) } -/// Attributes used on Windows. -#[allow(dead_code)] -#[derive(Debug)] -pub struct WindowsAttributes { - window_icon_path: PathBuf, - /// The path to the sdk location. This can be a absolute or relative path. If not supplied - /// this defaults to whatever `winres` crate determines is the best. See the - /// [winres documentation](https://docs.rs/winres/*/winres/struct.WindowsResource.html#method.set_toolkit_path) - sdk_dir: Option, +// checks if the given Cargo feature is enabled. +fn has_feature(feature: &str) -> bool { + // when a feature is enabled, Cargo sets the `CARGO_FEATURE_ Self { - Self { - window_icon_path: PathBuf::from("icons/icon.ico"), - sdk_dir: None, - } +// creates a cfg alias if `has_feature` is true. +// `alias` must be a snake case string. +fn cfg_alias(alias: &str, has_feature: bool) { + if has_feature { + println!("cargo:rustc-cfg={alias}"); } } +/// Attributes used on Windows. +#[allow(dead_code)] +#[derive(Debug, Default)] +pub struct WindowsAttributes { + window_icon_path: Option, + /// A string containing an [application manifest] to be included with the application on Windows. + /// + /// Defaults to: + /// ```text + #[doc = include_str!("window-app-manifest.xml")] + /// ``` + /// + /// ## Warning + /// + /// if you are using tauri's dialog APIs, you need to specify a dependency on Common Control v6 by adding the following to your custom manifest: + /// ```text + /// + /// + /// + /// + /// + /// ``` + /// + /// [application manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests + app_manifest: Option, +} + impl WindowsAttributes { /// Creates the default attribute set. pub fn new() -> Self { @@ -84,15 +148,66 @@ impl WindowsAttributes { /// It must be in `ico` format. Defaults to `icons/icon.ico`. #[must_use] pub fn window_icon_path>(mut self, window_icon_path: P) -> Self { - self.window_icon_path = window_icon_path.as_ref().into(); + self + .window_icon_path + .replace(window_icon_path.as_ref().into()); self } - /// Sets the sdk dir for windows. Currently only used on Windows. This must be a vaild UTF-8 - /// path. Defaults to whatever the `winres` crate determines is best. + /// Sets the [application manifest] to be included with the application on Windows. + /// + /// Defaults to: + /// ```text + #[doc = include_str!("window-app-manifest.xml")] + /// ``` + /// + /// ## Warning + /// + /// if you are using tauri's dialog APIs, you need to specify a dependency on Common Control v6 by adding the following to your custom manifest: + /// ```text + /// + /// + /// + /// + /// + /// ``` + /// + /// # Example + /// + /// The following manifest will brand the exe as requesting administrator privileges. + /// Thus, everytime it is executed, a Windows UAC dialog will appear. + /// + /// ```rust,no_run + /// let mut windows = tauri_build::WindowsAttributes::new(); + /// windows = windows.app_manifest(r#" + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// "#); + /// let attrs = tauri_build::Attributes::new().windows_attributes(windows); + /// tauri_build::try_build(attrs).expect("failed to run build script"); + /// ``` + /// + /// Note that you can move the manifest contents to a separate file and use `include_str!("manifest.xml")` + /// instead of the inline string. + /// + /// [manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests #[must_use] - pub fn sdk_dir>(mut self, sdk_dir: P) -> Self { - self.sdk_dir = Some(sdk_dir.as_ref().into()); + pub fn app_manifest>(mut self, manifest: S) -> Self { + self.app_manifest = Some(manifest.as_ref().to_string()); self } } @@ -139,7 +254,15 @@ impl Attributes { /// This is typically desirable when running inside a build script; see [`try_build`] for no panics. pub fn build() { if let Err(error) = try_build(Attributes::default()) { - panic!("error found during tauri-build: {}", error); + let error = format!("{error:#}"); + println!("{error}"); + if error.starts_with("unknown field") { + print!("found an unknown configuration field. This usually means that you are using a CLI version that is newer than `tauri-build` and is incompatible. "); + println!( + "Please try updating the Rust crates by running `cargo update` in the Tauri app folder." + ); + } + std::process::exit(1); } } @@ -147,78 +270,69 @@ pub fn build() { #[allow(unused_variables)] pub fn try_build(attributes: Attributes) -> Result<()> { use anyhow::anyhow; - use cargo_toml::{Dependency, Manifest}; - use tauri_utils::config::{Config, TauriConfig}; - println!("cargo:rerun-if-changed=src/Cargo.toml"); + println!("cargo:rerun-if-env-changed=TAURI_CONFIG"); println!("cargo:rerun-if-changed=tauri.conf.json"); #[cfg(feature = "config-json5")] println!("cargo:rerun-if-changed=tauri.conf.json5"); - - let config: Config = if let Ok(env) = std::env::var("TAURI_CONFIG") { - serde_json::from_str(&env)? - } else { - serde_json::from_value(tauri_utils::config::parse::read_from( - std::env::current_dir().unwrap(), - )?)? - }; - - let mut manifest = Manifest::from_path("Cargo.toml")?; - if let Some(tauri) = manifest.dependencies.remove("tauri") { - let features = match tauri { - Dependency::Simple(_) => Vec::new(), - Dependency::Detailed(dep) => dep.features, - }; - - let all_cli_managed_features = TauriConfig::all_features(); - let diff = features_diff( - &features - .into_iter() - .filter(|f| all_cli_managed_features.contains(&f.as_str())) - .collect::>(), - &config - .tauri - .features() - .into_iter() - .map(|f| f.to_string()) - .collect::>(), - ); - - let mut error_message = String::new(); - if !diff.remove.is_empty() { - error_message.push_str("remove the `"); - error_message.push_str(&diff.remove.join(", ")); - error_message.push_str(if diff.remove.len() == 1 { - "` feature" - } else { - "` features" - }); - if !diff.add.is_empty() { - error_message.push_str(" and "); - } - } - if !diff.add.is_empty() { - error_message.push_str("add the `"); - error_message.push_str(&diff.add.join(", ")); - error_message.push_str(if diff.add.len() == 1 { - "` feature" - } else { - "` features" - }); + #[cfg(feature = "config-toml")] + println!("cargo:rerun-if-changed=Tauri.toml"); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "ios" || target_os == "android"; + cfg_alias("desktop", !mobile); + cfg_alias("mobile", mobile); + + let mut config = serde_json::from_value(tauri_utils::config::parse::read_from( + std::env::current_dir().unwrap(), + )?)?; + if let Ok(env) = std::env::var("TAURI_CONFIG") { + let merge_config: serde_json::Value = serde_json::from_str(&env)?; + json_patch::merge(&mut config, &merge_config); + } + let config: Config = serde_json::from_value(config)?; + + let s = config.tauri.bundle.identifier.split('.'); + let last = s.clone().count() - 1; + let mut android_package_prefix = String::new(); + for (i, w) in s.enumerate() { + if i == 0 || i != last { + android_package_prefix.push_str(w); + android_package_prefix.push('_'); } + } + android_package_prefix.pop(); + println!("cargo:rustc-env=TAURI_ANDROID_PACKAGE_PREFIX={android_package_prefix}"); - if !error_message.is_empty() { - return Err(anyhow!(" - The `tauri` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`. - Please run `tauri dev` or `tauri build` or {}. - ", error_message)); - } + if let Some(project_dir) = var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) { + mobile::generate_gradle_files(project_dir)?; + } + + cfg_alias("dev", !has_feature("custom-protocol")); + + let ws_path = get_workspace_dir()?; + let mut manifest = + Manifest::::from_slice_with_metadata(&std::fs::read("Cargo.toml")?)?; + + if let Ok(ws_manifest) = Manifest::from_path(ws_path.join("Cargo.toml")) { + Manifest::complete_from_path_and_workspace( + &mut manifest, + Path::new("Cargo.toml"), + Some((&ws_manifest, ws_path.as_path())), + )?; + } else { + Manifest::complete_from_path(&mut manifest, Path::new("Cargo.toml"))?; } + allowlist::check(&config, &mut manifest)?; + let target_triple = std::env::var("TARGET").unwrap(); - let out_dir = std::env::var("OUT_DIR").unwrap(); + + println!("cargo:rustc-env=TAURI_TARGET_TRIPLE={target_triple}"); + + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); // TODO: far from ideal, but there's no other way to get the target dir, see - let target_dir = Path::new(&out_dir) + let target_dir = out_dir .parent() .unwrap() .parent() @@ -226,124 +340,148 @@ pub fn try_build(attributes: Attributes) -> Result<()> { .parent() .unwrap(); - if let Some(paths) = config.tauri.bundle.external_bin { + if let Some(paths) = &config.tauri.bundle.external_bin { copy_binaries( - ResourcePaths::new(external_binaries(&paths, &target_triple).as_slice(), true), + ResourcePaths::new(external_binaries(paths, &target_triple).as_slice(), true), &target_triple, target_dir, + manifest.package.as_ref().map(|p| &p.name), )?; } - if let Some(paths) = config.tauri.bundle.resources { - copy_resources(ResourcePaths::new(paths.as_slice(), true), target_dir)?; + + #[allow(unused_mut, clippy::redundant_clone)] + let mut resources = config.tauri.bundle.resources.clone().unwrap_or_default(); + if target_triple.contains("windows") { + if let Some(fixed_webview2_runtime_path) = + &config.tauri.bundle.windows.webview_fixed_runtime_path + { + resources.push(fixed_webview2_runtime_path.display().to_string()); + } } + copy_resources(ResourcePaths::new(resources.as_slice(), true), target_dir)?; - #[cfg(windows)] - { - use anyhow::Context; - use winres::WindowsResource; + if target_triple.contains("darwin") { + if let Some(version) = &config.tauri.bundle.macos.minimum_system_version { + println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}"); + } + } + + if target_triple.contains("windows") { + use semver::Version; + use tauri_winres::{VersionInfo, WindowsResource}; + + fn find_icon bool>(config: &Config, predicate: F, default: &str) -> PathBuf { + let icon_path = config + .tauri + .bundle + .icon + .iter() + .find(|i| predicate(i)) + .cloned() + .unwrap_or_else(|| default.to_string()); + icon_path.into() + } - let icon_path_string = attributes + let window_icon_path = attributes .windows_attributes .window_icon_path - .to_string_lossy() - .into_owned(); + .unwrap_or_else(|| find_icon(&config, |i| i.ends_with(".ico"), "icons/icon.ico")); - if attributes.windows_attributes.window_icon_path.exists() { - let mut res = WindowsResource::new(); - if let Some(sdk_dir) = &attributes.windows_attributes.sdk_dir { - if let Some(sdk_dir_str) = sdk_dir.to_str() { - res.set_toolkit_path(sdk_dir_str); + if target_triple.contains("windows") { + if window_icon_path.exists() { + let mut res = WindowsResource::new(); + + if let Some(manifest) = attributes.windows_attributes.app_manifest { + res.set_manifest(&manifest); } else { - return Err(anyhow!( - "sdk_dir path is not valid; only UTF-8 characters are allowed" - )); + res.set_manifest(include_str!("window-app-manifest.xml")); } + + if let Some(version_str) = &config.package.version { + if let Ok(v) = Version::parse(version_str) { + let version = v.major << 48 | v.minor << 32 | v.patch << 16; + res.set_version_info(VersionInfo::FILEVERSION, version); + res.set_version_info(VersionInfo::PRODUCTVERSION, version); + } + res.set("FileVersion", version_str); + res.set("ProductVersion", version_str); + } + if let Some(product_name) = &config.package.product_name { + res.set("ProductName", product_name); + } + if let Some(short_description) = &config.tauri.bundle.short_description { + res.set("FileDescription", short_description); + } + if let Some(copyright) = &config.tauri.bundle.copyright { + res.set("LegalCopyright", copyright); + } + res.set_icon_with_id(&window_icon_path.display().to_string(), "32512"); + res.compile().with_context(|| { + format!( + "failed to compile `{}` into a Windows Resource file during tauri-build", + window_icon_path.display() + ) + })?; + } else { + return Err(anyhow!(format!( + "`{}` not found; required for generating a Windows Resource file during tauri-build", + window_icon_path.display() + ))); } - if let Some(version) = &config.package.version { - res.set("FileVersion", version); - res.set("ProductVersion", version); + } + + let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); + match target_env.as_str() { + "gnu" => { + let target_arch = match std::env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() { + "x86_64" => Some("x64"), + "x86" => Some("x86"), + "aarch64" => Some("arm64"), + arch => None, + }; + if let Some(target_arch) = target_arch { + for entry in std::fs::read_dir(target_dir.join("build"))? { + let path = entry?.path(); + let webview2_loader_path = path + .join("out") + .join(target_arch) + .join("WebView2Loader.dll"); + if path.to_string_lossy().contains("webview2-com-sys") && webview2_loader_path.exists() + { + std::fs::copy(webview2_loader_path, target_dir.join("WebView2Loader.dll"))?; + break; + } + } + } } - if let Some(product_name) = &config.package.product_name { - res.set("ProductName", product_name); - res.set("FileDescription", product_name); + "msvc" => { + if std::env::var("STATIC_VCRUNTIME").map_or(false, |v| v == "true") { + static_vcruntime::build(); + } } - res.set_icon_with_id(&icon_path_string, "32512"); - res.compile().with_context(|| { - format!( - "failed to compile `{}` into a Windows Resource file during tauri-build", - icon_path_string - ) - })?; - } else { - return Err(anyhow!(format!( - "`{}` not found; required for generating a Windows Resource file during tauri-build", - icon_path_string - ))); + _ => (), } } Ok(()) } -#[derive(Debug, Default, PartialEq, Eq)] -struct Diff { - remove: Vec, - add: Vec, +#[derive(serde::Deserialize)] +struct CargoMetadata { + workspace_root: PathBuf, } -fn features_diff(current: &[String], expected: &[String]) -> Diff { - let mut remove = Vec::new(); - let mut add = Vec::new(); - for feature in current { - if !expected.contains(feature) { - remove.push(feature.clone()); - } - } +fn get_workspace_dir() -> Result { + let output = std::process::Command::new("cargo") + .args(["metadata", "--no-deps", "--format-version", "1"]) + .output()?; - for feature in expected { - if !current.contains(feature) { - add.push(feature.clone()); - } + if !output.status.success() { + return Err(anyhow::anyhow!( + "cargo metadata command exited with a non zero exit code: {}", + String::from_utf8(output.stderr)? + )); } - Diff { remove, add } -} - -#[cfg(test)] -mod tests { - use super::Diff; - - #[test] - fn array_diff() { - for (current, expected, result) in [ - (vec![], vec![], Default::default()), - ( - vec!["a".into()], - vec![], - Diff { - remove: vec!["a".into()], - add: vec![], - }, - ), - (vec!["a".into()], vec!["a".into()], Default::default()), - ( - vec!["a".into(), "b".into()], - vec!["a".into()], - Diff { - remove: vec!["b".into()], - add: vec![], - }, - ), - ( - vec!["a".into(), "b".into()], - vec!["a".into(), "c".into()], - Diff { - remove: vec!["b".into()], - add: vec!["c".into()], - }, - ), - ] { - assert_eq!(super::features_diff(¤t, &expected), result); - } - } + Ok(serde_json::from_slice::(&output.stdout)?.workspace_root) } diff --git a/core/tauri-build/src/mobile.rs b/core/tauri-build/src/mobile.rs new file mode 100644 index 000000000000..9b4e0b87e812 --- /dev/null +++ b/core/tauri-build/src/mobile.rs @@ -0,0 +1,200 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + env::{var, var_os}, + fs::{copy, create_dir, create_dir_all, remove_dir_all, write}, + path::{Path, PathBuf}, +}; + +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Eq, PartialEq)] +pub(crate) struct PluginMetadata { + pub path: PathBuf, +} + +#[derive(Default)] +pub struct PluginBuilder { + android_path: Option, + ios_path: Option, +} + +impl PluginBuilder { + /// Creates a new builder for mobile plugin functionality. + pub fn new() -> Self { + Self::default() + } + + /// Sets the Android project path. + pub fn android_path>(mut self, android_path: P) -> Self { + self.android_path.replace(android_path.into()); + self + } + + /// Sets the iOS project path. + pub fn ios_path>(mut self, ios_path: P) -> Self { + self.ios_path.replace(ios_path.into()); + self + } + + /// Injects the mobile templates in the given path relative to the manifest root. + pub fn run(self) -> Result<()> { + let target_os = var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "android" || target_os == "ios"; + crate::cfg_alias("mobile", mobile); + crate::cfg_alias("desktop", !mobile); + + match target_os.as_str() { + "android" => { + if let Some(path) = self.android_path { + let manifest_dir = var_os("CARGO_MANIFEST_DIR").map(PathBuf::from).unwrap(); + let source = manifest_dir.join(path); + + let tauri_library_path = std::env::var("DEP_TAURI_ANDROID_LIBRARY_PATH") + .expect("missing `DEP_TAURI_ANDROID_LIBRARY_PATH` environment variable. Make sure `tauri` is a dependency of the plugin."); + println!("cargo:rerun-if-env-changed=DEP_TAURI_ANDROID_LIBRARY_PATH"); + + create_dir_all(source.join(".tauri")).context("failed to create .tauri directory")?; + copy_folder( + Path::new(&tauri_library_path), + &source.join(".tauri").join("tauri-api"), + &[], + ) + .context("failed to copy tauri-api to the plugin project")?; + + println!("cargo:android_library_path={}", source.display()); + } + } + #[cfg(target_os = "macos")] + "ios" => { + if let Some(path) = self.ios_path { + let manifest_dir = var_os("CARGO_MANIFEST_DIR").map(PathBuf::from).unwrap(); + let tauri_library_path = std::env::var("DEP_TAURI_IOS_LIBRARY_PATH") + .expect("missing `DEP_TAURI_IOS_LIBRARY_PATH` environment variable. Make sure `tauri` is a dependency of the plugin."); + + let tauri_dep_path = path.parent().unwrap().join(".tauri"); + create_dir_all(&tauri_dep_path).context("failed to create .tauri directory")?; + copy_folder( + Path::new(&tauri_library_path), + &tauri_dep_path.join("tauri-api"), + &[".build", "Package.resolved", "Tests"], + ) + .context("failed to copy tauri-api to the plugin project")?; + link_swift_library(&var("CARGO_PKG_NAME").unwrap(), manifest_dir.join(path)); + } + } + _ => (), + } + + Ok(()) + } +} + +#[cfg(target_os = "macos")] +#[doc(hidden)] +pub fn link_swift_library(name: &str, source: impl AsRef) { + let source = source.as_ref(); + + let sdk_root = std::env::var_os("SDKROOT"); + std::env::remove_var("SDKROOT"); + + swift_rs::SwiftLinker::new( + &std::env::var("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|_| "10.13".into()), + ) + .with_ios(&std::env::var("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or_else(|_| "13.0".into())) + .with_package(name, source) + .link(); + + if let Some(root) = sdk_root { + std::env::set_var("SDKROOT", root); + } +} + +fn copy_folder(source: &Path, target: &Path, ignore_paths: &[&str]) -> Result<()> { + let _ = remove_dir_all(target); + + for entry in walkdir::WalkDir::new(source) { + let entry = entry?; + let rel_path = entry.path().strip_prefix(source)?; + let rel_path_str = rel_path.to_string_lossy(); + if ignore_paths + .iter() + .any(|path| rel_path_str.starts_with(path)) + { + continue; + } + let dest_path = target.join(rel_path); + + if entry.file_type().is_dir() { + create_dir(&dest_path) + .with_context(|| format!("failed to create directory {}", dest_path.display()))?; + } else { + copy(entry.path(), &dest_path).with_context(|| { + format!( + "failed to copy {} to {}", + entry.path().display(), + dest_path.display() + ) + })?; + println!("cargo:rerun-if-changed={}", entry.path().display()); + } + } + + Ok(()) +} + +pub(crate) fn generate_gradle_files(project_dir: PathBuf) -> Result<()> { + let gradle_settings_path = project_dir.join("tauri.settings.gradle"); + let app_build_gradle_path = project_dir.join("app").join("tauri.build.gradle.kts"); + + let mut gradle_settings = + "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n".to_string(); + let mut app_build_gradle = "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +val implementation by configurations +dependencies {" + .to_string(); + + for (env, value) in std::env::vars_os() { + let env = env.to_string_lossy(); + if env.starts_with("DEP_") && env.ends_with("_ANDROID_LIBRARY_PATH") { + let name_len = env.len() - "DEP_".len() - "_ANDROID_LIBRARY_PATH".len(); + let mut plugin_name = env + .chars() + .skip("DEP_".len()) + .take(name_len) + .collect::() + .to_lowercase() + .replace('_', "-"); + if plugin_name == "tauri" { + plugin_name = "tauri-android".into(); + } + let plugin_path = PathBuf::from(value); + + gradle_settings.push_str(&format!("include ':{plugin_name}'")); + gradle_settings.push('\n'); + gradle_settings.push_str(&format!( + "project(':{plugin_name}').projectDir = new File({:?})", + tauri_utils::display_path(plugin_path) + )); + gradle_settings.push('\n'); + + app_build_gradle.push('\n'); + app_build_gradle.push_str(&format!(r#" implementation(project(":{plugin_name}"))"#)); + } + } + + app_build_gradle.push_str("\n}"); + + write(&gradle_settings_path, gradle_settings).context("failed to write tauri.settings.gradle")?; + + write(&app_build_gradle_path, app_build_gradle) + .context("failed to write tauri.build.gradle.kts")?; + + println!("cargo:rerun-if-changed={}", gradle_settings_path.display()); + println!("cargo:rerun-if-changed={}", app_build_gradle_path.display()); + + Ok(()) +} diff --git a/core/tauri-build/src/static_vcruntime.rs b/core/tauri-build/src/static_vcruntime.rs new file mode 100644 index 000000000000..0d95f9a6d07e --- /dev/null +++ b/core/tauri-build/src/static_vcruntime.rs @@ -0,0 +1,58 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// taken from https://github.com/ChrisDenton/static_vcruntime/ +// we're not using static_vcruntime directly because we want this for debug builds too + +use std::{env, fs, io::Write, path::Path}; + +pub fn build() { + override_msvcrt_lib(); + + // Disable conflicting libraries that aren't hard coded by Rust + println!("cargo:rustc-link-arg=/NODEFAULTLIB:libvcruntimed.lib"); + println!("cargo:rustc-link-arg=/NODEFAULTLIB:vcruntime.lib"); + println!("cargo:rustc-link-arg=/NODEFAULTLIB:vcruntimed.lib"); + println!("cargo:rustc-link-arg=/NODEFAULTLIB:libcmtd.lib"); + println!("cargo:rustc-link-arg=/NODEFAULTLIB:msvcrt.lib"); + println!("cargo:rustc-link-arg=/NODEFAULTLIB:msvcrtd.lib"); + println!("cargo:rustc-link-arg=/NODEFAULTLIB:libucrt.lib"); + println!("cargo:rustc-link-arg=/NODEFAULTLIB:libucrtd.lib"); + // Set the libraries we want. + println!("cargo:rustc-link-arg=/DEFAULTLIB:libcmt.lib"); + println!("cargo:rustc-link-arg=/DEFAULTLIB:libvcruntime.lib"); + println!("cargo:rustc-link-arg=/DEFAULTLIB:ucrt.lib"); +} + +/// Override the hard-coded msvcrt.lib by replacing it with a (mostly) empty object file. +fn override_msvcrt_lib() { + // Get the right machine type for the empty library. + let arch = std::env::var("CARGO_CFG_TARGET_ARCH"); + let machine: &[u8] = if arch.as_deref() == Ok("x86_64") { + &[0x64, 0x86] + } else if arch.as_deref() == Ok("x86") { + &[0x4C, 0x01] + } else { + return; + }; + let bytes: &[u8] = &[ + 1, 0, 94, 3, 96, 98, 60, 0, 0, 0, 1, 0, 0, 0, 0, 0, 132, 1, 46, 100, 114, 101, 99, 116, 118, + 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 10, 16, 0, 46, 100, 114, 101, 99, 116, 118, 101, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 4, 0, 0, 0, + ]; + + // Write the empty "msvcrt.lib" to the output directory. + let out_dir = env::var("OUT_DIR").unwrap(); + let path = Path::new(&out_dir).join("msvcrt.lib"); + let f = fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(path); + if let Ok(mut f) = f { + f.write_all(machine).unwrap(); + f.write_all(bytes).unwrap(); + } + // Add the output directory to the native library path. + println!("cargo:rustc-link-search=native={out_dir}"); +} diff --git a/core/tauri-build/src/window-app-manifest.xml b/core/tauri-build/src/window-app-manifest.xml new file mode 100644 index 000000000000..2d510ed314a2 --- /dev/null +++ b/core/tauri-build/src/window-app-manifest.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/core/tauri-codegen/.license_template b/core/tauri-codegen/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/core/tauri-codegen/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/core/tauri-codegen/CHANGELOG.md b/core/tauri-codegen/CHANGELOG.md index 4eef1d17a1c4..37ed4158f359 100644 --- a/core/tauri-codegen/CHANGELOG.md +++ b/core/tauri-codegen/CHANGELOG.md @@ -1,5 +1,212 @@ # Changelog +## \[2.0.0-alpha.5] + +- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `shell` functionality to its own plugin in the plugins-workspace repository. +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`ae102980`](https://www.github.com/tauri-apps/tauri/commit/ae102980fcdde3f55effdc0623ea425b48d07dd1)([#6719](https://www.github.com/tauri-apps/tauri/pull/6719)) Refactor the `Context` conditional fields and only parse the tray icon on desktop. + +## \[2.0.0-alpha.4] + +- Added `android` configuration object under `tauri > bundle`. + - Bumped due to a bump in tauri-utils. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 + +## \[2.0.0-alpha.3] + +- Pull changes from Tauri 1.3 release. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[2.0.0-alpha.2] + +- Return `bool` in the invoke handler. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 + +## \[2.0.0-alpha.1] + +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Added `crate_name` field on `PackageInfo`. + - [630a7f4b](https://www.github.com/tauri-apps/tauri/commit/630a7f4b18cef169bfd48673609306fec434e397) refactor: remove mobile log initialization, ref [#6049](https://www.github.com/tauri-apps/tauri/pull/6049) ([#6081](https://www.github.com/tauri-apps/tauri/pull/6081)) on 2023-01-17 + +## \[2.0.0-alpha.0] + +- Change `devPath` URL to use the local IP address on iOS and Android. + - [6f061504](https://www.github.com/tauri-apps/tauri/commit/6f0615044d09ec58393a7ebca5e45bb175e20db3) feat(cli): add `android dev` and `ios dev` commands ([#4982](https://www.github.com/tauri-apps/tauri/pull/4982)) on 2022-08-20 +- First mobile alpha release! + - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08 + +## \[1.4.0] + +### Enhancements + +- [`17d5a4f5`](https://www.github.com/tauri-apps/tauri/commit/17d5a4f51f244d3ff42014b5d1b075fad7c636a5)([#6706](https://www.github.com/tauri-apps/tauri/pull/6706)) Early panic if the PNG icon is not RGBA. +- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85. + +## \[1.3.0] + +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 +- Pin `time` to `0.3.15`. + - [3d16461b](https://www.github.com/tauri-apps/tauri/commit/3d16461b68583ba7db037fbc217786e79b46ddf2) fix(core): pin time to 0.3.15 ([#6312](https://www.github.com/tauri-apps/tauri/pull/6312)) on 2023-02-19 + +## \[1.2.1] + +- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`. + - Bumped due to a bump in tauri-utils. + - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18 + +## \[1.2.0] + +- Properly serialize HTML template tags. + - [aec5537d](https://www.github.com/tauri-apps/tauri/commit/aec5537de0205f62b2ae5c89da04d21930a6fc2e) fix(codegen): serialize template tags, closes [#4410](https://www.github.com/tauri-apps/tauri/pull/4410) ([#5247](https://www.github.com/tauri-apps/tauri/pull/5247)) on 2022-09-28 +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 + +## \[1.1.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - Bumped due to a bump in tauri-utils. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[1.1.0] + +- Use `TARGET` environment variable for code generation inside build scripts. + - [6ba99689](https://www.github.com/tauri-apps/tauri/commit/6ba99689aa7ed0ffa9072a1c8ab5db12c9bf95af) feat(codegen): use TARGET environment variable if set ([#4921](https://www.github.com/tauri-apps/tauri/pull/4921)) on 2022-08-12 +- Added support to configuration files in TOML format (Tauri.toml file). + - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02 +- Improve tray icon read error message. + - [52f0c8bb](https://www.github.com/tauri-apps/tauri/commit/52f0c8bb836c6d50b7ce2393161394f4ce78f5ae) feat(core): improve tray icon read error messages ([#4850](https://www.github.com/tauri-apps/tauri/pull/4850)) on 2022-08-03 +- Fix relative paths in `version` field of `tauri.config.json` not being correctly parsed by `generate_context!()`. + - [accbc5e8](https://www.github.com/tauri-apps/tauri/commit/accbc5e8806a32efc555d019634fc2fa14d17f0a) fix(codegen): fix relative paths in `version` field of `tauri.config.json`, closes [#4723](https://www.github.com/tauri-apps/tauri/pull/4723) ([#4725](https://www.github.com/tauri-apps/tauri/pull/4725)) on 2022-07-24 +- Only rewrite temporary icon files when the content change, avoid needless rebuilds. + - [f957cbb5](https://www.github.com/tauri-apps/tauri/commit/f957cbb56ccbd8d1c047a978b4579946252a6fd2) fix(codegen): write output file when contents change ([#4889](https://www.github.com/tauri-apps/tauri/pull/4889)) on 2022-08-09 + +## \[1.0.4] + +- Validate `__TAURI_ISOLATION_HOOK__` being set by a file in the isolation application. + - [3b4ed970](https://www.github.com/tauri-apps/tauri/commit/3b4ed970e663f5bffbfe0358610f9c3f157c513f) feat(codegen): validate `__TAURI_ISOLATION_HOOK__` is referenced ([#4631](https://www.github.com/tauri-apps/tauri/pull/4631)) on 2022-07-11 + +## \[1.0.3] + +- The `TAURI_CONFIG` environment variable now represents the configuration to be merged instead of the entire JSON. + - [fa028ebf](https://www.github.com/tauri-apps/tauri/commit/fa028ebf3c8ca7b43a70d283a01dbea86217594f) refactor: do not pass entire config from CLI to core, send patch instead ([#4598](https://www.github.com/tauri-apps/tauri/pull/4598)) on 2022-07-06 + +## \[1.0.2] + +- Expose `platform::windows_version` function. + - Bumped due to a bump in tauri-utils. + - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30 + +## \[1.0.1] + +- Set the bundle name and app metadata in the Info.plist file in development mode. + - [38f5db6e](https://www.github.com/tauri-apps/tauri/commit/38f5db6e6a8b496b50d486db6fd8204266de3a69) feat(codegen): fill app metadata in development Info.plist on 2022-06-21 +- Set the application icon in development mode on macOS. + - [307c2ebf](https://www.github.com/tauri-apps/tauri/commit/307c2ebfb68238dacab6088f9c6ba310c727c68c) feat(core): set macOS app icon in development ([#4385](https://www.github.com/tauri-apps/tauri/pull/4385)) on 2022-06-19 + +## \[1.0.0] + +- Upgrade to `stable`! + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[1.0.0-rc.11] + +- Read the tray icon path relatively to the config directory. + - [562e8ca2](https://www.github.com/tauri-apps/tauri/commit/562e8ca23facf1a8e5fa6c8cdf872357d3523a78) fix(codegen): tray icon path is relative to the config directory on 2022-06-15 + +## \[1.0.0-rc.10] + +- **Breaking change:** The `TrayIcon` enum has been removed and now `Icon` is used instead. + This allows you to use more image formats and use embedded icons on Linux. + - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14 + +## \[1.0.0-rc.9] + +- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`. + - Bumped due to a bump in tauri-utils. + - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10 + +## \[1.0.0-rc.8] + +- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`. + - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25 + - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27 + +## \[1.0.0-rc.7] + +- Allow configuring the display options for the MSI execution allowing quieter updates. + - Bumped due to a bump in tauri-utils. + - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15 + +## \[1.0.0-rc.6] + +- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable. + - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02 + +## \[1.0.0-rc.5] + +- Read platform-specific configuration files when generating code without the `TAURI_CONFIG` env var. + - [edf85bc1](https://www.github.com/tauri-apps/tauri/commit/edf85bc1d18450c92aee17f7f99c163abe432ebd) fix(codegen): read platform-specific config file ([#3966](https://www.github.com/tauri-apps/tauri/pull/3966)) on 2022-04-25 + +## \[1.0.0-rc.4] + +- Added an option to disable the CSP injection of distributable assets nonces and hashes. + - [f6e32ee1](https://www.github.com/tauri-apps/tauri/commit/f6e32ee1880eb364ed76beb937c9d12e14d54910) feat(core): add dangerous option to disable compile time CSP injection ([#3775](https://www.github.com/tauri-apps/tauri/pull/3775)) on 2022-03-28 + +- Replace multiple dependencies who's C code compiled concurrently and caused + the other ones to bloat compile time significantly. + +- `zstd` -> `brotli` + +- `blake3` -> a vendored version of the blake3 reference + +- `ring` -> `getrandom` + +See https://github.com/tauri-apps/tauri/pull/3773 for more information about +these specific choices. + +- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27 + +## \[1.0.0-rc.3] + +- Parse window icons at compile time. + - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05 + +## \[1.0.0-rc.2] + +- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`. + - Bumped due to a bump in tauri-utils. + - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17 + +## \[1.0.0-rc.1] + +- Change default value for the `freezePrototype` configuration to `false`. + - Bumped due to a bump in tauri-utils. + - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12 + +## \[1.0.0-rc.0] + +- Apply `nonce` to `script` and `style` tags and set them on the `CSP` (`script-src` and `style-src` fetch directives). + - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09 +- Added the `isolation` pattern. + - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) Co-authored-by: Lucas Fernandes Nogueira on 2022-01-17 +- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension. + +Here is the logic flow that determines if JSON or JSON5 will be used to parse the config: + +1. Check if `tauri.conf.json` exists + a. Parse it with `serde_json` + b. Parse it with `json5` if `serde_json` fails + c. Return original `serde_json` error if all above steps failed +2. Check if `tauri.conf.json5` exists + a. Parse it with `json5` + b. Return error if all above steps failed +3. Return error if all above steps failed + +- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 + ## \[1.0.0-beta.4] - Embed Info.plist file contents on binary on dev. diff --git a/core/tauri-codegen/Cargo.toml b/core/tauri-codegen/Cargo.toml index 11f2fa4ba303..291b0c118fd1 100644 --- a/core/tauri-codegen/Cargo.toml +++ b/core/tauri-codegen/Cargo.toml @@ -1,35 +1,44 @@ [package] name = "tauri-codegen" -version = "1.0.0-beta.4" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.studio" -repository = "https://github.com/tauri-apps/tauri/tree/dev/core/tauri-codegen" +version = "2.0.0-alpha.5" description = "code generation meant to be consumed inside of `tauri` through `tauri-build` or `tauri-macros`" -edition = "2021" -rust-version = "1.57" -exclude = [ ".license_template", "CHANGELOG.md", "/target" ] +exclude = [ "CHANGELOG.md", "/target" ] readme = "README.md" +# workspace defined package items +authors = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } + [dependencies] sha2 = "0.10" -base64 = "0.13" -blake3 = { version = "1.3", features = [ "rayon" ] } +base64 = "0.21" proc-macro2 = "1" quote = "1" serde = { version = "1", features = [ "derive" ] } serde_json = "1" -tauri-utils = { version = "1.0.0-beta.3", path = "../tauri-utils", features = [ "build" ] } +tauri-utils = { version = "2.0.0-alpha.5", path = "../tauri-utils", features = [ "build" ] } thiserror = "1" walkdir = "2" -zstd = { version = "0.10", optional = true } -regex = { version = "1", optional = true } -uuid = { version = "0.8", features = [ "v4" ] } +brotli = { version = "3", optional = true, default-features = false, features = [ "std" ] } +uuid = { version = "1", features = [ "v4" ] } +semver = "1" +ico = "0.3" +png = "0.17" +json-patch = "1.0" +url = "2" + +[target."cfg(target_os = \"macos\")".dependencies] +plist = "1" +time = { version = "0.3", features = [ "parsing", "formatting" ] } [features] default = [ "compression" ] -compression = [ "zstd", "tauri-utils/compression" ] -isolation = ["tauri-utils/isolation"] -shell-scope = ["regex"] +compression = [ "brotli", "tauri-utils/compression" ] +isolation = [ "tauri-utils/isolation" ] config-json5 = [ "tauri-utils/config-json5" ] +config-toml = [ "tauri-utils/config-toml" ] diff --git a/core/tauri-codegen/README.md b/core/tauri-codegen/README.md index 82ac38888df8..3f3e15990f28 100644 --- a/core/tauri-codegen/README.md +++ b/core/tauri-codegen/README.md @@ -2,38 +2,43 @@ -[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri) +[![status](https://img.shields.io/badge/Status-Stable-green.svg)](https://github.com/tauri-apps/tauri) [![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) [![devto](https://img.shields.io/badge/blog-dev.to-black.svg)](https://dev.to/tauri) -![](https://img.shields.io/github/workflow/status/tauri-apps/tauri/test%20library?label=test%20library -) -[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.studio) +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) +[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.app) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) [![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri) -| Component | Version | -| --------- | ------------------------------------------- | -| tauri-codegen | [![](https://img.shields.io/crates/v/tauri-codegen?style=flat-square)](https://crates.io/crates/tauri-codegen) | +| Component | Version | +| ------------- | -------------------------------------------------------------------------------------------------------------- | +| tauri-codegen | [![](https://img.shields.io/crates/v/tauri-codegen?style=flat-square)](https://crates.io/crates/tauri-codegen) | ## About Tauri + Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily. Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task. ## This module + - Embed, hash, and compress assets, including icons for the app as well as the system-tray. - Parse `tauri.conf.json` at compile time and generate the Config struct. To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. ## Semver + **tauri** is following [Semantic Versioning 2.0](https://semver.org/). + ## Licenses + Code: (c) 2021 - The Tauri Programme within The Commons Conservancy. MIT or MIT/Apache 2.0 where applicable. Logo: CC-BY-NC-ND + - Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index 9e1762e2e283..cf556e5120cc 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -1,20 +1,20 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::ffi::OsStr; use std::path::{Path, PathBuf}; +use std::{ffi::OsStr, str::FromStr}; +use base64::Engine; use proc_macro2::TokenStream; use quote::quote; use sha2::{Digest, Sha256}; use tauri_utils::assets::AssetKey; use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl}; -use tauri_utils::html::{inject_nonce_token, parse as parse_html, NodeRef}; - -#[cfg(feature = "shell-scope")] -use tauri_utils::config::{ShellAllowedArg, ShellAllowedArgs, ShellAllowlistScope}; +use tauri_utils::html::{ + inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, +}; use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError}; @@ -26,53 +26,64 @@ pub struct ContextData { pub root: TokenStream, } -fn load_csp(document: &mut NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) { - #[cfg(target_os = "linux")] - ::tauri_utils::html::inject_csp_token(document); - inject_nonce_token(document); - if let Ok(inline_script_elements) = document.select("script:not(empty)") { - let mut scripts = Vec::new(); - for inline_script_el in inline_script_elements { - let script = inline_script_el.as_node().text_contents(); - let mut hasher = Sha256::new(); - hasher.update(&script); - let hash = hasher.finalize(); - scripts.push(format!("'sha256-{}'", base64::encode(&hash))); - } - csp_hashes - .inline_scripts - .entry(key.clone().into()) - .or_default() - .append(&mut scripts); - } -} - fn map_core_assets( options: &AssetOptions, + target: Target, ) -> impl Fn(&AssetKey, &Path, &mut Vec, &mut CspHashes) -> Result<(), EmbeddedAssetsError> { #[cfg(feature = "isolation")] let pattern = tauri_utils::html::PatternObject::from(&options.pattern); let csp = options.csp; + let dangerous_disable_asset_csp_modification = + options.dangerous_disable_asset_csp_modification.clone(); move |key, path, input, csp_hashes| { if path.extension() == Some(OsStr::new("html")) { - let mut document = parse_html(String::from_utf8_lossy(input).into_owned()); - + #[allow(clippy::collapsible_if)] if csp { - load_csp(&mut document, key, csp_hashes); + let mut document = parse_html(String::from_utf8_lossy(input).into_owned()); + + if target == Target::Linux { + ::tauri_utils::html::inject_csp_token(&mut document); + } + + inject_nonce_token(&mut document, &dangerous_disable_asset_csp_modification); + + if dangerous_disable_asset_csp_modification.can_modify("script-src") { + if let Ok(inline_script_elements) = document.select("script:not(empty)") { + let mut scripts = Vec::new(); + for inline_script_el in inline_script_elements { + let script = inline_script_el.as_node().text_contents(); + let mut hasher = Sha256::new(); + hasher.update(&script); + let hash = hasher.finalize(); + scripts.push(format!( + "'sha256-{}'", + base64::engine::general_purpose::STANDARD.encode(hash) + )); + } + csp_hashes + .inline_scripts + .entry(key.clone().into()) + .or_default() + .append(&mut scripts); + } + } #[cfg(feature = "isolation")] - if let tauri_utils::html::PatternObject::Isolation { .. } = &pattern { - // create the csp for the isolation iframe styling now, to make the runtime less complex - let mut hasher = Sha256::new(); - hasher.update(tauri_utils::pattern::isolation::IFRAME_STYLE); - let hash = hasher.finalize(); - csp_hashes - .styles - .push(format!("'sha256-{}'", base64::encode(&hash))); + if dangerous_disable_asset_csp_modification.can_modify("style-src") { + if let tauri_utils::html::PatternObject::Isolation { .. } = &pattern { + // create the csp for the isolation iframe styling now, to make the runtime less complex + let mut hasher = Sha256::new(); + hasher.update(tauri_utils::pattern::isolation::IFRAME_STYLE); + let hash = hasher.finalize(); + csp_hashes.styles.push(format!( + "'sha256-{}'", + base64::engine::general_purpose::STANDARD.encode(hash) + )); + } } - } - *input = document.to_string().as_bytes().to_vec(); + *input = serialize_html_node(&document); + } } Ok(()) } @@ -101,6 +112,26 @@ fn map_isolation( } } +#[derive(PartialEq, Eq, Clone, Copy)] +enum Target { + Linux, + Windows, + Darwin, + Android, + // iOS. + Ios, +} + +impl Target { + fn is_mobile(&self) -> bool { + matches!(self, Target::Android | Target::Ios) + } + + fn is_desktop(&self) -> bool { + !self.is_mobile() + } +} + /// Build a `tauri::Context` for including in application code. pub fn context_codegen(data: ContextData) -> Result { let ContextData { @@ -110,17 +141,53 @@ pub fn context_codegen(data: ContextData) -> Result Result unimplemented!(), }, @@ -159,30 +226,76 @@ pub fn context_codegen(data: ContextData) -> Result>(), - map_core_assets(&options), + &options, + map_core_assets(&options, target), )?, _ => unimplemented!(), }; - // handle default window icons for Windows targets - let default_window_icon = if cfg!(windows) { - let icon_path = find_icon( - &config, - &config_parent, - |i| i.ends_with(".ico"), - "icons/icon.ico", - ); - quote!(Some(include_bytes!(#icon_path).to_vec())) - } else if cfg!(target_os = "linux") { - let icon_path = find_icon( + let out_dir = { + let out_dir = std::env::var("OUT_DIR") + .map_err(|_| EmbeddedAssetsError::OutDir) + .map(PathBuf::from) + .and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))?; + + // make sure that our output directory is created + std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?; + + out_dir + }; + + let default_window_icon = { + if target == Target::Windows { + // handle default window icons for Windows targets + let icon_path = find_icon( + &config, + &config_parent, + |i| i.ends_with(".ico"), + "icons/icon.ico", + ); + if icon_path.exists() { + ico_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))? + } else { + let icon_path = find_icon( + &config, + &config_parent, + |i| i.ends_with(".png"), + "icons/icon.png", + ); + png_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))? + } + } else if target == Target::Linux { + // handle default window icons for Linux targets + let icon_path = find_icon( + &config, + &config_parent, + |i| i.ends_with(".png"), + "icons/icon.png", + ); + png_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))? + } else { + quote!(::std::option::Option::None) + } + }; + + let app_icon = if target == Target::Darwin && dev { + let mut icon_path = find_icon( &config, &config_parent, - |i| i.ends_with(".png"), + |i| i.ends_with(".icns"), "icons/icon.png", ); - quote!(Some(include_bytes!(#icon_path).to_vec())) + if !icon_path.exists() { + icon_path = find_icon( + &config, + &config_parent, + |i| i.ends_with(".png"), + "icons/icon.png", + ); + } + raw_icon(&out_dir, icon_path)? } else { - quote!(None) + quote!(::std::option::Option::None) }; let package_name = if let Some(product_name) = &config.package.product_name { @@ -191,6 +304,7 @@ pub fn context_codegen(data: ContextData) -> Result Result quote!(#root::Pattern::Brownfield(std::marker::PhantomData)), + #[cfg(not(feature = "isolation"))] + PatternKind::Isolation { dir: _ } => { + quote!(#root::Pattern::Brownfield(std::marker::PhantomData)) + } #[cfg(feature = "isolation")] PatternKind::Isolation { dir } => { let dir = config_parent.join(dir); if !dir.exists() { - panic!( - "The isolation dir configuration is set to `{:?}` but this path doesn't exist", - dir - ) + panic!("The isolation application path is set to `{dir:?}` but it does not exist") } + let mut sets_isolation_hook = false; + let key = uuid::Uuid::new_v4().to_string(); - let assets = EmbeddedAssets::new(dir.clone(), map_isolation(&options, dir))?; + let map_isolation = map_isolation(&options, dir.clone()); + let assets = EmbeddedAssets::new(dir, &options, |key, path, input, csp_hashes| { + // we check if `__TAURI_ISOLATION_HOOK__` exists in the isolation code + // before modifying the files since we inject our own `__TAURI_ISOLATION_HOOK__` reference in HTML files + if String::from_utf8_lossy(input).contains("__TAURI_ISOLATION_HOOK__") { + sets_isolation_hook = true; + } + map_isolation(key, path, input, csp_hashes) + })?; + + if !sets_isolation_hook { + panic!("The isolation application does not contain a file setting the `window.__TAURI_ISOLATION_HOOK__` value."); + } + let schema = options.isolation_schema; quote!(#root::Pattern::Isolation { @@ -291,50 +421,125 @@ pub fn context_codegen(data: ContextData) -> Result>( + root: &TokenStream, + out_dir: &Path, + path: P, +) -> Result { + let path = path.as_ref(); + let bytes = std::fs::read(path) + .unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e)) + .to_vec(); + let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes)) + .unwrap_or_else(|e| panic!("failed to parse icon {}: {}", path.display(), e)); + let entry = &icon_dir.entries()[0]; + let rgba = entry + .decode() + .unwrap_or_else(|e| panic!("failed to decode icon {}: {}", path.display(), e)) + .rgba_data() + .to_vec(); + let width = entry.width(); + let height = entry.height(); + + let out_path = out_dir.join(path.file_name().unwrap()); + write_if_changed(&out_path, &rgba).map_err(|error| EmbeddedAssetsError::AssetWrite { + path: path.to_owned(), + error, + })?; + + let out_path = out_path.display().to_string(); + + let icon = quote!(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height }); + Ok(icon) +} - let shell_scope_open = match &config.tauri.allowlist.shell.open { - ShellAllowlistOpen::Flag(false) => quote!(::std::option::Option::None), - ShellAllowlistOpen::Flag(true) => { - quote!(::std::option::Option::Some(#root::regex::Regex::new("^https?://").unwrap())) - } - ShellAllowlistOpen::Validate(regex) => match Regex::new(regex) { - Ok(_) => quote!(::std::option::Option::Some(#root::regex::Regex::new(#regex).unwrap())), - Err(error) => { - let error = error.to_string(); - quote!({ - compile_error!(#error); - ::std::option::Option::Some(#root::regex::Regex::new(#regex).unwrap()) - }) - } - }, - _ => panic!("unknown shell open format, unable to prepare"), - }; +fn raw_icon>(out_dir: &Path, path: P) -> Result { + let path = path.as_ref(); + let bytes = std::fs::read(path) + .unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e)) + .to_vec(); - quote!(#root::ShellScopeConfig { - open: #shell_scope_open, - scopes: #shell_scopes - }) - }; + let out_path = out_dir.join(path.file_name().unwrap()); + write_if_changed(&out_path, &bytes).map_err(|error| EmbeddedAssetsError::AssetWrite { + path: path.to_owned(), + error, + })?; + + let out_path = out_path.display().to_string(); + + let icon = quote!(::std::option::Option::Some( + include_bytes!(#out_path).to_vec() + )); + Ok(icon) +} + +fn png_icon>( + root: &TokenStream, + out_dir: &Path, + path: P, +) -> Result { + let path = path.as_ref(); + let bytes = std::fs::read(path) + .unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e)) + .to_vec(); + let decoder = png::Decoder::new(std::io::Cursor::new(bytes)); + let mut reader = decoder + .read_info() + .unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e)); + + let (color_type, _) = reader.output_color_type(); + + if color_type != png::ColorType::Rgba { + panic!("icon {} is not RGBA", path.display()); + } + + let mut buffer: Vec = Vec::new(); + while let Ok(Some(row)) = reader.next_row() { + buffer.extend(row.data()); + } + let width = reader.info().width; + let height = reader.info().height; + + let out_path = out_dir.join(path.file_name().unwrap()); + write_if_changed(&out_path, &buffer).map_err(|error| EmbeddedAssetsError::AssetWrite { + path: path.to_owned(), + error, + })?; - #[cfg(not(feature = "shell-scope"))] - let shell_scope_config = quote!(); - - Ok(quote!(#root::Context::new( - #config, - ::std::sync::Arc::new(#assets), - #default_window_icon, - #system_tray_icon, - #package_info, - #info_plist, - #pattern, - #shell_scope_config - ))) + let out_path = out_path.display().to_string(); + + let icon = quote!(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height }); + Ok(icon) +} + +fn write_if_changed(out_path: &Path, data: &[u8]) -> std::io::Result<()> { + use std::fs::File; + use std::io::Write; + + if let Ok(curr) = std::fs::read(out_path) { + if curr == data { + return Ok(()); + } + } + + let mut out_file = File::create(out_path)?; + out_file.write_all(data) } fn find_icon bool>( @@ -342,7 +547,7 @@ fn find_icon bool>( config_parent: &Path, predicate: F, default: &str, -) -> String { +) -> PathBuf { let icon_path = config .tauri .bundle @@ -351,80 +556,5 @@ fn find_icon bool>( .find(|i| predicate(i)) .cloned() .unwrap_or_else(|| default.to_string()); - config_parent.join(icon_path).display().to_string() -} - -#[cfg(feature = "shell-scope")] -fn get_allowed_clis(root: &TokenStream, scope: &ShellAllowlistScope) -> TokenStream { - let commands = scope - .0 - .iter() - .map(|scope| { - let sidecar = &scope.sidecar; - - let name = &scope.name; - let name = quote!(#name.into()); - - let command = scope.command.to_string_lossy(); - let command = quote!(::std::path::PathBuf::from(#command)); - - let args = match &scope.args { - ShellAllowedArgs::Flag(true) => quote!(::std::option::Option::None), - ShellAllowedArgs::Flag(false) => quote!(::std::option::Option::Some(::std::vec![])), - ShellAllowedArgs::List(list) => { - let list = list.iter().map(|arg| match arg { - ShellAllowedArg::Fixed(fixed) => { - quote!(#root::scope::ShellScopeAllowedArg::Fixed(#fixed.into())) - } - ShellAllowedArg::Var { validator } => { - let validator = match regex::Regex::new(validator) { - Ok(regex) => { - let regex = regex.as_str(); - quote!(#root::regex::Regex::new(#regex).unwrap()) - } - Err(error) => { - let error = error.to_string(); - quote!({ - compile_error!(#error); - #root::regex::Regex::new(#validator).unwrap() - }) - } - }; - - quote!(#root::scope::ShellScopeAllowedArg::Var { validator: #validator }) - } - _ => panic!("unknown shell scope arg, unable to prepare"), - }); - - quote!(::std::option::Option::Some(::std::vec![#(#list),*])) - } - _ => panic!("unknown shell scope command, unable to prepare"), - }; - - ( - quote!(#name), - quote!( - #root::scope::ShellScopeAllowedCommand { - command: #command, - args: #args, - sidecar: #sidecar, - } - ), - ) - }) - .collect::>(); - - if commands.is_empty() { - quote!(::std::collections::HashMap::new()) - } else { - let insertions = commands - .iter() - .map(|(name, value)| quote!(hashmap.insert(#name, #value);)); - - quote!({ - let mut hashmap = ::std::collections::HashMap::new(); - #(#insertions)* - hashmap - }) - } + config_parent.join(icon_path) } diff --git a/core/tauri-codegen/src/embedded_assets.rs b/core/tauri-codegen/src/embedded_assets.rs index 5f29057b56d1..ce9e0fcb472f 100644 --- a/core/tauri-codegen/src/embedded_assets.rs +++ b/core/tauri-codegen/src/embedded_assets.rs @@ -1,26 +1,28 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use base64::Engine; use proc_macro2::TokenStream; use quote::{quote, ToTokens, TokenStreamExt}; use sha2::{Digest, Sha256}; use std::{ collections::HashMap, + fmt::Write, fs::File, path::{Path, PathBuf}, }; -use tauri_utils::assets::AssetKey; use tauri_utils::config::PatternKind; +use tauri_utils::{assets::AssetKey, config::DisabledCspModificationKind}; use thiserror::Error; use walkdir::{DirEntry, WalkDir}; +#[cfg(feature = "compression")] +use brotli::enc::backward_references::BrotliEncoderParams; + /// The subdirectory inside the target directory we want to place assets. const TARGET_PATH: &str = "tauri-codegen-assets"; -/// The minimum size needed for the hasher to use multiple threads. -const MULTI_HASH_SIZE_LIMIT: usize = 131_072; // 128KiB - /// (key, (original filepath, compressed bytes)) type Asset = (AssetKey, (PathBuf, PathBuf)); @@ -40,6 +42,9 @@ pub enum EmbeddedAssetsError { error: std::io::Error, }, + #[error("failed to create hex from bytes because {0}")] + Hex(std::fmt::Error), + #[error("invalid prefix {prefix} used while including path {path}")] PrefixInvalid { prefix: PathBuf, path: PathBuf }, @@ -51,6 +56,9 @@ pub enum EmbeddedAssetsError { #[error("OUT_DIR env var is not set, do you have a build script?")] OutDir, + + #[error("version error: {0}")] + Version(#[from] semver::Error), } /// Represent a directory of assets that are compressed and embedded. @@ -90,7 +98,7 @@ struct RawEmbeddedAssets { impl RawEmbeddedAssets { /// Creates a new list of (prefix, entry) from a collection of inputs. - fn new(input: EmbeddedAssetsInput) -> Result { + fn new(input: EmbeddedAssetsInput, options: &AssetOptions) -> Result { let mut csp_hashes = CspHashes::default(); input @@ -119,7 +127,9 @@ impl RawEmbeddedAssets { // compress all files encountered Ok(entry) => { - if let Err(error) = csp_hashes.add_if_applicable(&entry) { + if let Err(error) = csp_hashes + .add_if_applicable(&entry, &options.dangerous_disable_asset_csp_modification) + { Some(Err(error)) } else { Some(Ok((prefix, entry))) @@ -154,22 +164,29 @@ impl CspHashes { /// /// Note: this only checks the file extension, much like how a browser will assume a .js file is /// a JavaScript file unless HTTP headers tell it otherwise. - pub fn add_if_applicable(&mut self, entry: &DirEntry) -> Result<(), EmbeddedAssetsError> { + pub fn add_if_applicable( + &mut self, + entry: &DirEntry, + dangerous_disable_asset_csp_modification: &DisabledCspModificationKind, + ) -> Result<(), EmbeddedAssetsError> { let path = entry.path(); // we only hash JavaScript files for now, may expand to other CSP hashable types in the future if let Some("js") | Some("mjs") = path.extension().and_then(|os| os.to_str()) { - let mut hasher = Sha256::new(); - hasher.update( - &std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead { - path: path.to_path_buf(), - error, - })?, - ); - let hash = hasher.finalize(); - self - .scripts - .push(format!("'sha256-{}'", base64::encode(hash))) + if dangerous_disable_asset_csp_modification.can_modify("script-src") { + let mut hasher = Sha256::new(); + hasher.update( + &std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead { + path: path.to_path_buf(), + error, + })?, + ); + let hash = hasher.finalize(); + self.scripts.push(format!( + "'sha256-{}'", + base64::engine::general_purpose::STANDARD.encode(hash) + )); + } } Ok(()) @@ -182,6 +199,7 @@ pub struct AssetOptions { pub(crate) csp: bool, pub(crate) pattern: PatternKind, pub(crate) freeze_prototype: bool, + pub(crate) dangerous_disable_asset_csp_modification: DisabledCspModificationKind, #[cfg(feature = "isolation")] pub(crate) isolation_schema: String, } @@ -192,13 +210,14 @@ impl AssetOptions { Self { csp: false, pattern, - freeze_prototype: true, + freeze_prototype: false, + dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false), #[cfg(feature = "isolation")] isolation_schema: format!("isolation-{}", uuid::Uuid::new_v4()), } } - /// Instruct the asset handler to inject the CSP token to HTML files. + /// Instruct the asset handler to inject the CSP token to HTML files (Linux only) and add asset nonces and hashes to the policy. #[must_use] pub fn with_csp(mut self) -> Self { self.csp = true; @@ -211,6 +230,15 @@ impl AssetOptions { self.freeze_prototype = freeze; self } + + /// Instruct the asset handler to **NOT** modify the CSP. This is **NOT** recommended. + pub fn dangerous_disable_asset_csp_modification( + mut self, + dangerous_disable_asset_csp_modification: DisabledCspModificationKind, + ) -> Self { + self.dangerous_disable_asset_csp_modification = dangerous_disable_asset_csp_modification; + self + } } impl EmbeddedAssets { @@ -219,10 +247,16 @@ impl EmbeddedAssets { /// [`Assets`]: tauri_utils::assets::Assets pub fn new( input: impl Into, - map: impl Fn(&AssetKey, &Path, &mut Vec, &mut CspHashes) -> Result<(), EmbeddedAssetsError>, + options: &AssetOptions, + mut map: impl FnMut( + &AssetKey, + &Path, + &mut Vec, + &mut CspHashes, + ) -> Result<(), EmbeddedAssetsError>, ) -> Result { // we need to pre-compute all files now, so that we can inject data from all files into a few - let RawEmbeddedAssets { paths, csp_hashes } = RawEmbeddedAssets::new(input.into())?; + let RawEmbeddedAssets { paths, csp_hashes } = RawEmbeddedAssets::new(input.into(), options)?; struct CompressState { csp_hashes: CspHashes, @@ -235,9 +269,10 @@ impl EmbeddedAssets { assets: HashMap::new(), }, move |mut state, (prefix, entry)| { - let (key, asset) = Self::compress_file(&prefix, entry.path(), &map, &mut state.csp_hashes)?; + let (key, asset) = + Self::compress_file(&prefix, entry.path(), &mut map, &mut state.csp_hashes)?; state.assets.insert(key, asset); - Ok(state) + Result::<_, EmbeddedAssetsError>::Ok(state) }, )?; @@ -246,20 +281,31 @@ impl EmbeddedAssets { /// Use highest compression level for release, the fastest one for everything else #[cfg(feature = "compression")] - fn compression_level() -> i32 { - let levels = zstd::compression_level_range(); + fn compression_settings() -> BrotliEncoderParams { + let mut settings = BrotliEncoderParams::default(); + + // the following compression levels are hand-picked and are not min-maxed. + // they have a good balance of runtime vs size for the respective profile goals. + // see the "brotli" section of this comment https://github.com/tauri-apps/tauri/issues/3571#issuecomment-1054847558 if cfg!(debug_assertions) { - *levels.start() + settings.quality = 2 } else { - *levels.end() + settings.quality = 9 } + + settings } /// Compress a file and spit out the information in a [`HashMap`] friendly form. fn compress_file( prefix: &Path, path: &Path, - map: &impl Fn(&AssetKey, &Path, &mut Vec, &mut CspHashes) -> Result<(), EmbeddedAssetsError>, + map: &mut impl FnMut( + &AssetKey, + &Path, + &mut Vec, + &mut CspHashes, + ) -> Result<(), EmbeddedAssetsError>, csp_hashes: &mut CspHashes, ) -> Result { let mut input = std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead { @@ -291,20 +337,24 @@ impl EmbeddedAssets { // get a hash of the input - allows for caching existing files let hash = { - let mut hasher = blake3::Hasher::new(); - if input.len() < MULTI_HASH_SIZE_LIMIT { - hasher.update(&input); - } else { - hasher.update_rayon(&input); + let mut hasher = crate::vendor::blake3_reference::Hasher::default(); + hasher.update(&input); + + let mut bytes = [0u8; 32]; + hasher.finalize(&mut bytes); + + let mut hex = String::with_capacity(2 * bytes.len()); + for b in bytes { + write!(hex, "{b:02x}").map_err(EmbeddedAssetsError::Hex)?; } - hasher.finalize().to_hex() + hex }; // use the content hash to determine filename, keep extensions that exist let out_path = if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - out_dir.join(format!("{}.{}", hash, ext)) + out_dir.join(format!("{hash}.{ext}")) } else { - out_dir.join(hash.to_string()) + out_dir.join(hash) }; // only compress and write to the file if it doesn't already exist. @@ -328,13 +378,16 @@ impl EmbeddedAssets { } #[cfg(feature = "compression")] - // entirely write input to the output file path with compression - zstd::stream::copy_encode(&*input, out_file, Self::compression_level()).map_err(|error| { - EmbeddedAssetsError::AssetWrite { - path: path.to_owned(), - error, - } - })?; + { + let mut input = std::io::Cursor::new(input); + // entirely write input to the output file path with compression + brotli::BrotliCompress(&mut input, &mut out_file, &Self::compression_settings()).map_err( + |error| EmbeddedAssetsError::AssetWrite { + path: path.to_owned(), + error, + }, + )?; + } } Ok((key, (path.into(), out_path))) @@ -380,6 +433,7 @@ impl ToTokens for EmbeddedAssets { // we expect phf related items to be in path when generating the path code tokens.append_all(quote! {{ + #[allow(unused_imports)] use ::tauri::utils::assets::{CspHash, EmbeddedAssets, phf, phf::phf_map}; EmbeddedAssets::new(phf_map! { #assets }, &[#global_hashes], phf_map! { #html_hashes }) }}); diff --git a/core/tauri-codegen/src/lib.rs b/core/tauri-codegen/src/lib.rs index 4eedaa1434ee..7e7395a946a2 100644 --- a/core/tauri-codegen/src/lib.rs +++ b/core/tauri-codegen/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -11,6 +11,8 @@ pub use tauri_utils::config::{parse::ConfigError, Config}; mod context; pub mod embedded_assets; +#[doc(hidden)] +pub mod vendor; /// Represents all the errors that can happen while reading the config during codegen. #[derive(Debug, thiserror::Error)] @@ -26,6 +28,9 @@ pub enum CodegenConfigError { #[error("unable to parse inline JSON TAURI_CONFIG env var: {0}")] FormatInline(serde_json::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error("{0}")] ConfigError(#[from] ConfigError), } @@ -42,21 +47,31 @@ pub fn get_config(path: &Path) -> Result<(Config, PathBuf), CodegenConfigError> Cow::Borrowed(path) }; - // in the future we may want to find a way to not need the TAURI_CONFIG env var so that - // it is impossible for the content of two separate configs to get mixed up. The chances are - // already unlikely unless the developer goes out of their way to run the cli on a different - // project than the target crate. - let config = if let Ok(env) = std::env::var("TAURI_CONFIG") { - serde_json::from_str(&env).map_err(CodegenConfigError::FormatInline)? - } else { - tauri_utils::config::parse(path.to_path_buf())? - }; - // this should be impossible because of the use of `current_dir()` above, but handle it anyways let parent = path .parent() .map(ToOwned::to_owned) .ok_or_else(|| CodegenConfigError::Parent(path.into_owned()))?; + // in the future we may want to find a way to not need the TAURI_CONFIG env var so that + // it is impossible for the content of two separate configs to get mixed up. The chances are + // already unlikely unless the developer goes out of their way to run the cli on a different + // project than the target crate. + let mut config = serde_json::from_value(tauri_utils::config::parse::read_from(parent.clone())?)?; + if let Ok(env) = std::env::var("TAURI_CONFIG") { + let merge_config: serde_json::Value = + serde_json::from_str(&env).map_err(CodegenConfigError::FormatInline)?; + json_patch::merge(&mut config, &merge_config); + } + + let old_cwd = std::env::current_dir().map_err(CodegenConfigError::CurrentDir)?; + // Set working directory to where `tauri.config.json` is, so that relative paths in it are parsed correctly. + std::env::set_current_dir(parent.clone()).map_err(CodegenConfigError::CurrentDir)?; + + let config = serde_json::from_value(config)?; + + // Reset working directory. + std::env::set_current_dir(old_cwd).map_err(CodegenConfigError::CurrentDir)?; + Ok((config, parent)) } diff --git a/core/tauri-codegen/src/vendor/blake3_reference.rs b/core/tauri-codegen/src/vendor/blake3_reference.rs new file mode 100644 index 000000000000..c984af280673 --- /dev/null +++ b/core/tauri-codegen/src/vendor/blake3_reference.rs @@ -0,0 +1,377 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! This is a lightly modified version of the BLAKE3 reference implementation. +//! The changes applied are to remove unused item warnings due to using it +//! vendored along with some minor clippy suggestions. No logic changes. I +//! suggest diffing against the original to find all the changes. +//! +//! ## Original Header +//! This is the reference implementation of BLAKE3. It is used for testing and +//! as a readable example of the algorithms involved. Section 5.1 of [the BLAKE3 +//! spec](https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf) +//! discusses this implementation. You can render docs for this implementation +//! by running `cargo doc --open` in this directory. +//! +//! # Example +//! +//! ``` +//! let mut hasher = tauri_codegen::vendor::blake3_reference::Hasher::new(); +//! hasher.update(b"abc"); +//! hasher.update(b"def"); +//! let mut hash = [0; 32]; +//! hasher.finalize(&mut hash); +//! let mut extended_hash = [0; 500]; +//! hasher.finalize(&mut extended_hash); +//! assert_eq!(hash, extended_hash[..32]); +//! ``` +//! +//! CC0-1.0 OR Apache-2.0 + +use core::cmp::min; +use core::convert::TryInto; + +const OUT_LEN: usize = 32; +const BLOCK_LEN: usize = 64; +const CHUNK_LEN: usize = 1024; + +const CHUNK_START: u32 = 1 << 0; +const CHUNK_END: u32 = 1 << 1; +const PARENT: u32 = 1 << 2; +const ROOT: u32 = 1 << 3; + +const IV: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +const MSG_PERMUTATION: [usize; 16] = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8]; + +// The mixing function, G, which mixes either a column or a diagonal. +fn g(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, mx: u32, my: u32) { + state[a] = state[a].wrapping_add(state[b]).wrapping_add(mx); + state[d] = (state[d] ^ state[a]).rotate_right(16); + state[c] = state[c].wrapping_add(state[d]); + state[b] = (state[b] ^ state[c]).rotate_right(12); + state[a] = state[a].wrapping_add(state[b]).wrapping_add(my); + state[d] = (state[d] ^ state[a]).rotate_right(8); + state[c] = state[c].wrapping_add(state[d]); + state[b] = (state[b] ^ state[c]).rotate_right(7); +} + +fn round(state: &mut [u32; 16], m: &[u32; 16]) { + // Mix the columns. + g(state, 0, 4, 8, 12, m[0], m[1]); + g(state, 1, 5, 9, 13, m[2], m[3]); + g(state, 2, 6, 10, 14, m[4], m[5]); + g(state, 3, 7, 11, 15, m[6], m[7]); + // Mix the diagonals. + g(state, 0, 5, 10, 15, m[8], m[9]); + g(state, 1, 6, 11, 12, m[10], m[11]); + g(state, 2, 7, 8, 13, m[12], m[13]); + g(state, 3, 4, 9, 14, m[14], m[15]); +} + +fn permute(m: &mut [u32; 16]) { + let mut permuted = [0; 16]; + for i in 0..16 { + permuted[i] = m[MSG_PERMUTATION[i]]; + } + *m = permuted; +} + +fn compress( + chaining_value: &[u32; 8], + block_words: &[u32; 16], + counter: u64, + block_len: u32, + flags: u32, +) -> [u32; 16] { + let mut state = [ + chaining_value[0], + chaining_value[1], + chaining_value[2], + chaining_value[3], + chaining_value[4], + chaining_value[5], + chaining_value[6], + chaining_value[7], + IV[0], + IV[1], + IV[2], + IV[3], + counter as u32, + (counter >> 32) as u32, + block_len, + flags, + ]; + let mut block = *block_words; + + round(&mut state, &block); // round 1 + permute(&mut block); + round(&mut state, &block); // round 2 + permute(&mut block); + round(&mut state, &block); // round 3 + permute(&mut block); + round(&mut state, &block); // round 4 + permute(&mut block); + round(&mut state, &block); // round 5 + permute(&mut block); + round(&mut state, &block); // round 6 + permute(&mut block); + round(&mut state, &block); // round 7 + + for i in 0..8 { + state[i] ^= state[i + 8]; + state[i + 8] ^= chaining_value[i]; + } + state +} + +fn first_8_words(compression_output: [u32; 16]) -> [u32; 8] { + compression_output[0..8].try_into().unwrap() +} + +fn words_from_little_endian_bytes(bytes: &[u8], words: &mut [u32]) { + debug_assert_eq!(bytes.len(), 4 * words.len()); + for (four_bytes, word) in bytes.chunks_exact(4).zip(words) { + *word = u32::from_le_bytes(four_bytes.try_into().unwrap()); + } +} + +// Each chunk or parent node can produce either an 8-word chaining value or, by +// setting the ROOT flag, any number of final output bytes. The Output struct +// captures the state just prior to choosing between those two possibilities. +struct Output { + input_chaining_value: [u32; 8], + block_words: [u32; 16], + counter: u64, + block_len: u32, + flags: u32, +} + +impl Output { + fn chaining_value(&self) -> [u32; 8] { + first_8_words(compress( + &self.input_chaining_value, + &self.block_words, + self.counter, + self.block_len, + self.flags, + )) + } + + fn root_output_bytes(&self, out_slice: &mut [u8]) { + for (output_block_counter, out_block) in (0u64..).zip(out_slice.chunks_mut(2 * OUT_LEN)) { + let words = compress( + &self.input_chaining_value, + &self.block_words, + output_block_counter, + self.block_len, + self.flags | ROOT, + ); + // The output length might not be a multiple of 4. + for (word, out_word) in words.iter().zip(out_block.chunks_mut(4)) { + out_word.copy_from_slice(&word.to_le_bytes()[..out_word.len()]); + } + } + } +} + +struct ChunkState { + chaining_value: [u32; 8], + chunk_counter: u64, + block: [u8; BLOCK_LEN], + block_len: u8, + blocks_compressed: u8, + flags: u32, +} + +impl ChunkState { + fn new(key_words: [u32; 8], chunk_counter: u64, flags: u32) -> Self { + Self { + chaining_value: key_words, + chunk_counter, + block: [0; BLOCK_LEN], + block_len: 0, + blocks_compressed: 0, + flags, + } + } + + fn len(&self) -> usize { + BLOCK_LEN * self.blocks_compressed as usize + self.block_len as usize + } + + fn start_flag(&self) -> u32 { + if self.blocks_compressed == 0 { + CHUNK_START + } else { + 0 + } + } + + fn update(&mut self, mut input: &[u8]) { + while !input.is_empty() { + // If the block buffer is full, compress it and clear it. More + // input is coming, so this compression is not CHUNK_END. + if self.block_len as usize == BLOCK_LEN { + let mut block_words = [0; 16]; + words_from_little_endian_bytes(&self.block, &mut block_words); + self.chaining_value = first_8_words(compress( + &self.chaining_value, + &block_words, + self.chunk_counter, + BLOCK_LEN as u32, + self.flags | self.start_flag(), + )); + self.blocks_compressed += 1; + self.block = [0; BLOCK_LEN]; + self.block_len = 0; + } + + // Copy input bytes into the block buffer. + let want = BLOCK_LEN - self.block_len as usize; + let take = min(want, input.len()); + self.block[self.block_len as usize..][..take].copy_from_slice(&input[..take]); + self.block_len += take as u8; + input = &input[take..]; + } + } + + fn output(&self) -> Output { + let mut block_words = [0; 16]; + words_from_little_endian_bytes(&self.block, &mut block_words); + Output { + input_chaining_value: self.chaining_value, + block_words, + counter: self.chunk_counter, + block_len: self.block_len as u32, + flags: self.flags | self.start_flag() | CHUNK_END, + } + } +} + +fn parent_output( + left_child_cv: [u32; 8], + right_child_cv: [u32; 8], + key_words: [u32; 8], + flags: u32, +) -> Output { + let mut block_words = [0; 16]; + block_words[..8].copy_from_slice(&left_child_cv); + block_words[8..].copy_from_slice(&right_child_cv); + Output { + input_chaining_value: key_words, + block_words, + counter: 0, // Always 0 for parent nodes. + block_len: BLOCK_LEN as u32, // Always BLOCK_LEN (64) for parent nodes. + flags: PARENT | flags, + } +} + +fn parent_cv( + left_child_cv: [u32; 8], + right_child_cv: [u32; 8], + key_words: [u32; 8], + flags: u32, +) -> [u32; 8] { + parent_output(left_child_cv, right_child_cv, key_words, flags).chaining_value() +} + +/// An incremental hasher that can accept any number of writes. +pub struct Hasher { + chunk_state: ChunkState, + key_words: [u32; 8], + cv_stack: [[u32; 8]; 54], // Space for 54 subtree chaining values: + cv_stack_len: u8, // 2^54 * CHUNK_LEN = 2^64 + flags: u32, +} + +impl Hasher { + fn new_internal(key_words: [u32; 8], flags: u32) -> Self { + Self { + chunk_state: ChunkState::new(key_words, 0, flags), + key_words, + cv_stack: [[0; 8]; 54], + cv_stack_len: 0, + flags, + } + } + + /// Construct a new `Hasher` for the regular hash function. + pub fn new() -> Self { + Self::new_internal(IV, 0) + } + + fn push_stack(&mut self, cv: [u32; 8]) { + self.cv_stack[self.cv_stack_len as usize] = cv; + self.cv_stack_len += 1; + } + + fn pop_stack(&mut self) -> [u32; 8] { + self.cv_stack_len -= 1; + self.cv_stack[self.cv_stack_len as usize] + } + + // Section 5.1.2 of the BLAKE3 spec explains this algorithm in more detail. + fn add_chunk_chaining_value(&mut self, mut new_cv: [u32; 8], mut total_chunks: u64) { + // This chunk might complete some subtrees. For each completed subtree, + // its left child will be the current top entry in the CV stack, and + // its right child will be the current value of `new_cv`. Pop each left + // child off the stack, merge it with `new_cv`, and overwrite `new_cv` + // with the result. After all these merges, push the final value of + // `new_cv` onto the stack. The number of completed subtrees is given + // by the number of trailing 0-bits in the new total number of chunks. + while total_chunks & 1 == 0 { + new_cv = parent_cv(self.pop_stack(), new_cv, self.key_words, self.flags); + total_chunks >>= 1; + } + self.push_stack(new_cv); + } + + /// Add input to the hash state. This can be called any number of times. + pub fn update(&mut self, mut input: &[u8]) { + while !input.is_empty() { + // If the current chunk is complete, finalize it and reset the + // chunk state. More input is coming, so this chunk is not ROOT. + if self.chunk_state.len() == CHUNK_LEN { + let chunk_cv = self.chunk_state.output().chaining_value(); + let total_chunks = self.chunk_state.chunk_counter + 1; + self.add_chunk_chaining_value(chunk_cv, total_chunks); + self.chunk_state = ChunkState::new(self.key_words, total_chunks, self.flags); + } + + // Compress input bytes into the current chunk state. + let want = CHUNK_LEN - self.chunk_state.len(); + let take = min(want, input.len()); + self.chunk_state.update(&input[..take]); + input = &input[take..]; + } + } + + /// Finalize the hash and write any number of output bytes. + pub fn finalize(&self, out_slice: &mut [u8]) { + // Starting with the Output from the current chunk, compute all the + // parent chaining values along the right edge of the tree, until we + // have the root Output. + let mut output = self.chunk_state.output(); + let mut parent_nodes_remaining = self.cv_stack_len as usize; + while parent_nodes_remaining > 0 { + parent_nodes_remaining -= 1; + output = parent_output( + self.cv_stack[parent_nodes_remaining], + output.chaining_value(), + self.key_words, + self.flags, + ); + } + output.root_output_bytes(out_slice); + } +} + +impl Default for Hasher { + fn default() -> Self { + Self::new() + } +} diff --git a/core/tauri-codegen/src/vendor/mod.rs b/core/tauri-codegen/src/vendor/mod.rs new file mode 100644 index 000000000000..98c696d05a43 --- /dev/null +++ b/core/tauri-codegen/src/vendor/mod.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Manual vendored dependencies - NOT STABLE. +//! +//! This module and all submodules are not considered part of the public +//! api. They can and will change at any time for any reason in any +//! version. + +pub mod blake3_reference; diff --git a/core/tauri-config-schema/Cargo.toml b/core/tauri-config-schema/Cargo.toml new file mode 100644 index 000000000000..64188842f46c --- /dev/null +++ b/core/tauri-config-schema/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tauri-config-schema" +version = "0.0.0" +edition = "2021" +publish = false + +[build-dependencies] +tauri-utils = { version = "2.0.0-alpha.4", features = [ + "schema", +], path = "../tauri-utils" } +schemars = { version = "0.8", features = ["url", "preserve_order"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +url = { version = "2.3", features = ["serde"] } diff --git a/core/tauri-config-schema/build.rs b/core/tauri-config-schema/build.rs new file mode 100644 index 000000000000..1c867b0ba4aa --- /dev/null +++ b/core/tauri-config-schema/build.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + error::Error, + fs::File, + io::{BufWriter, Write}, + path::PathBuf, +}; + +pub fn main() -> Result<(), Box> { + let schema = schemars::schema_for!(tauri_utils::config::Config); + let schema_str = serde_json::to_string_pretty(&schema).unwrap(); + let crate_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?); + for file in [ + crate_dir.join("schema.json"), + crate_dir.join("../../tooling/cli/schema.json"), + ] { + let mut schema_file = BufWriter::new(File::create(file)?); + write!(schema_file, "{schema_str}")?; + } + + Ok(()) +} diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json new file mode 100644 index 000000000000..475211b88406 --- /dev/null +++ b/core/tauri-config-schema/schema.json @@ -0,0 +1,2148 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler and define a system tray.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"bundle\": {}, \"security\": { \"csp\": null }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```", + "type": "object", + "properties": { + "$schema": { + "description": "The JSON schema for the Tauri config.", + "type": [ + "string", + "null" + ] + }, + "package": { + "description": "Package settings.", + "default": { + "productName": null, + "version": null + }, + "allOf": [ + { + "$ref": "#/definitions/PackageConfig" + } + ] + }, + "tauri": { + "description": "The Tauri configuration.", + "default": { + "bundle": { + "active": false, + "android": { + "minSdkVersion": 24 + }, + "appimage": { + "bundleMediaFramework": false + }, + "deb": { + "files": {} + }, + "iOS": {}, + "icon": [], + "identifier": "", + "macOS": { + "minimumSystemVersion": "10.13" + }, + "targets": "all", + "updater": { + "active": false, + "pubkey": "", + "windows": { + "installMode": "passive" + } + }, + "windows": { + "allowDowngrades": true, + "certificateThumbprint": null, + "digestAlgorithm": null, + "nsis": null, + "timestampUrl": null, + "tsp": false, + "webviewFixedRuntimePath": null, + "webviewInstallMode": { + "silent": true, + "type": "downloadBootstrapper" + }, + "wix": null + } + }, + "macOSPrivateApi": false, + "pattern": { + "use": "brownfield" + }, + "security": { + "assetProtocol": { + "enable": false, + "scope": [] + }, + "dangerousDisableAssetCspModification": false, + "dangerousRemoteDomainIpcAccess": [], + "freezePrototype": false + }, + "windows": [] + }, + "allOf": [ + { + "$ref": "#/definitions/TauriConfig" + } + ] + }, + "build": { + "description": "The build configuration.", + "default": { + "devPath": "http://localhost:8080/", + "distDir": "../dist", + "withGlobalTauri": false + }, + "allOf": [ + { + "$ref": "#/definitions/BuildConfig" + } + ] + }, + "plugins": { + "description": "The plugins config.", + "default": {}, + "allOf": [ + { + "$ref": "#/definitions/PluginConfig" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "PackageConfig": { + "description": "The package configuration.\n\nSee more: https://tauri.app/v1/api/config#packageconfig", + "type": "object", + "properties": { + "productName": { + "description": "App name.", + "type": [ + "string", + "null" + ], + "pattern": "^[^/\\:*?\"<>|]+$" + }, + "version": { + "description": "App version. It is a semver version number or a path to a `package.json` file containing the `version` field. If removed the version number from `Cargo.toml` is used.", + "default": null, + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "TauriConfig": { + "description": "The Tauri configuration object.\n\nSee more: https://tauri.app/v1/api/config#tauriconfig", + "type": "object", + "properties": { + "pattern": { + "description": "The pattern to use.", + "default": { + "use": "brownfield" + }, + "allOf": [ + { + "$ref": "#/definitions/PatternKind" + } + ] + }, + "windows": { + "description": "The windows configuration.", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/WindowConfig" + } + }, + "bundle": { + "description": "The bundler configuration.", + "default": { + "active": false, + "android": { + "minSdkVersion": 24 + }, + "appimage": { + "bundleMediaFramework": false + }, + "deb": { + "files": {} + }, + "iOS": {}, + "icon": [], + "identifier": "", + "macOS": { + "minimumSystemVersion": "10.13" + }, + "targets": "all", + "updater": { + "active": false, + "pubkey": "", + "windows": { + "installMode": "passive" + } + }, + "windows": { + "allowDowngrades": true, + "certificateThumbprint": null, + "digestAlgorithm": null, + "nsis": null, + "timestampUrl": null, + "tsp": false, + "webviewFixedRuntimePath": null, + "webviewInstallMode": { + "silent": true, + "type": "downloadBootstrapper" + }, + "wix": null + } + }, + "allOf": [ + { + "$ref": "#/definitions/BundleConfig" + } + ] + }, + "security": { + "description": "Security configuration.", + "default": { + "assetProtocol": { + "enable": false, + "scope": [] + }, + "dangerousDisableAssetCspModification": false, + "dangerousRemoteDomainIpcAccess": [], + "freezePrototype": false + }, + "allOf": [ + { + "$ref": "#/definitions/SecurityConfig" + } + ] + }, + "systemTray": { + "description": "Configuration for app system tray.", + "anyOf": [ + { + "$ref": "#/definitions/SystemTrayConfig" + }, + { + "type": "null" + } + ] + }, + "macOSPrivateApi": { + "description": "MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "PatternKind": { + "description": "The application pattern.", + "oneOf": [ + { + "description": "Brownfield pattern.", + "type": "object", + "required": [ + "use" + ], + "properties": { + "use": { + "type": "string", + "enum": [ + "brownfield" + ] + } + } + }, + { + "description": "Isolation pattern. Recommended for security purposes.", + "type": "object", + "required": [ + "options", + "use" + ], + "properties": { + "use": { + "type": "string", + "enum": [ + "isolation" + ] + }, + "options": { + "type": "object", + "required": [ + "dir" + ], + "properties": { + "dir": { + "description": "The dir containing the index.html file that contains the secure isolation application.", + "type": "string" + } + } + } + } + } + ] + }, + "WindowConfig": { + "description": "The window configuration object.\n\nSee more: https://tauri.app/v1/api/config#windowconfig", + "type": "object", + "properties": { + "label": { + "description": "The window identifier. It must be alphanumeric.", + "default": "main", + "type": "string" + }, + "url": { + "description": "The window webview URL.", + "default": "index.html", + "allOf": [ + { + "$ref": "#/definitions/WindowUrl" + } + ] + }, + "userAgent": { + "description": "The user agent for the webview", + "type": [ + "string", + "null" + ] + }, + "fileDropEnabled": { + "description": "Whether the file drop is enabled or not on the webview. By default it is enabled.\n\nDisabling it is required to use drag and drop on the frontend on Windows.", + "default": true, + "type": "boolean" + }, + "center": { + "description": "Whether or not the window starts centered or not.", + "default": false, + "type": "boolean" + }, + "x": { + "description": "The horizontal position of the window's top left corner", + "type": [ + "number", + "null" + ], + "format": "double" + }, + "y": { + "description": "The vertical position of the window's top left corner", + "type": [ + "number", + "null" + ], + "format": "double" + }, + "width": { + "description": "The window width.", + "default": 800.0, + "type": "number", + "format": "double" + }, + "height": { + "description": "The window height.", + "default": 600.0, + "type": "number", + "format": "double" + }, + "minWidth": { + "description": "The min window width.", + "type": [ + "number", + "null" + ], + "format": "double" + }, + "minHeight": { + "description": "The min window height.", + "type": [ + "number", + "null" + ], + "format": "double" + }, + "maxWidth": { + "description": "The max window width.", + "type": [ + "number", + "null" + ], + "format": "double" + }, + "maxHeight": { + "description": "The max window height.", + "type": [ + "number", + "null" + ], + "format": "double" + }, + "resizable": { + "description": "Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.", + "default": true, + "type": "boolean" + }, + "maximizable": { + "description": "Whether the window's native maximize button is enabled or not. If resizable is set to false, this setting is ignored.\n\n## Platform-specific\n\n- **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode. - **Linux / iOS / Android:** Unsupported.", + "default": true, + "type": "boolean" + }, + "minimizable": { + "description": "Whether the window's native minimize button is enabled or not.\n\n## Platform-specific\n\n- **Linux / iOS / Android:** Unsupported.", + "default": true, + "type": "boolean" + }, + "closable": { + "description": "Whether the window's native close button is enabled or not.\n\n## Platform-specific\n\n- **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button. Depending on the system, this function may not have any effect when called on a window that is already visible\" - **iOS / Android:** Unsupported.", + "default": true, + "type": "boolean" + }, + "title": { + "description": "The window title.", + "default": "Tauri App", + "type": "string" + }, + "fullscreen": { + "description": "Whether the window starts as fullscreen or not.", + "default": false, + "type": "boolean" + }, + "focus": { + "description": "Whether the window will be initially focused or not.", + "default": true, + "type": "boolean" + }, + "transparent": { + "description": "Whether the window is transparent or not.\n\nNote that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`. WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.", + "default": false, + "type": "boolean" + }, + "maximized": { + "description": "Whether the window is maximized or not.", + "default": false, + "type": "boolean" + }, + "visible": { + "description": "Whether the window is visible or not.", + "default": true, + "type": "boolean" + }, + "decorations": { + "description": "Whether the window should have borders and bars.", + "default": true, + "type": "boolean" + }, + "alwaysOnTop": { + "description": "Whether the window should always be on top of other windows.", + "default": false, + "type": "boolean" + }, + "contentProtected": { + "description": "Prevents the window contents from being captured by other apps.", + "default": false, + "type": "boolean" + }, + "skipTaskbar": { + "description": "If `true`, hides the window icon from the taskbar on Windows and Linux.", + "default": false, + "type": "boolean" + }, + "theme": { + "description": "The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.", + "anyOf": [ + { + "$ref": "#/definitions/Theme" + }, + { + "type": "null" + } + ] + }, + "titleBarStyle": { + "description": "The style of the macOS title bar.", + "default": "Visible", + "allOf": [ + { + "$ref": "#/definitions/TitleBarStyle" + } + ] + }, + "hiddenTitle": { + "description": "If `true`, sets the window title to be hidden on macOS.", + "default": false, + "type": "boolean" + }, + "acceptFirstMouse": { + "description": "Whether clicking an inactive window also clicks through to the webview on macOS.", + "default": false, + "type": "boolean" + }, + "tabbingIdentifier": { + "description": "Defines the window [tabbing identifier] for macOS.\n\nWindows with matching tabbing identifiers will be grouped together. If the tabbing identifier is not set, automatic tabbing will be disabled.\n\n[tabbing identifier]: ", + "type": [ + "string", + "null" + ] + }, + "additionalBrowserArgs": { + "description": "Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection` so if you use this method, you also need to disable these components by yourself if you want.", + "type": [ + "string", + "null" + ] + }, + "shadow": { + "description": "Whether or not the window has shadow.\n\n## Platform-specific\n\n- **Windows:** - `false` has no effect on decorated window, shadow are always ON. - `true` will make ndecorated window have a 1px white border, and on Windows 11, it will have a rounded corners. - **Linux:** Unsupported.", + "default": true, + "type": "boolean" + }, + "windowEffects": { + "description": "Window effects.\n\nRequires the window to be transparent.\n\n## Platform-specific:\n\n- **Windows**: If using decorations or shadows, you may want to try this workaround https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891 - **Linux**: Unsupported", + "anyOf": [ + { + "$ref": "#/definitions/WindowEffectsConfig" + }, + { + "type": "null" + } + ] + }, + "incognito": { + "description": "Whether or not the webview should be launched in incognito mode.\n\n## Platform-specific:\n\n- **Android**: Unsupported.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "WindowUrl": { + "description": "An URL to open on a Tauri webview window.", + "anyOf": [ + { + "description": "An external URL.", + "type": "string", + "format": "uri" + }, + { + "description": "The path portion of an app URL. For instance, to load `tauri://localhost/users/john`, you can simply provide `users/john` in this configuration.", + "type": "string" + } + ] + }, + "Theme": { + "description": "System theme.", + "oneOf": [ + { + "description": "Light theme.", + "type": "string", + "enum": [ + "Light" + ] + }, + { + "description": "Dark theme.", + "type": "string", + "enum": [ + "Dark" + ] + } + ] + }, + "TitleBarStyle": { + "description": "How the window title bar should be displayed on macOS.", + "oneOf": [ + { + "description": "A normal title bar.", + "type": "string", + "enum": [ + "Visible" + ] + }, + { + "description": "Makes the title bar transparent, so the window background color is shown instead.\n\nUseful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.", + "type": "string", + "enum": [ + "Transparent" + ] + }, + { + "description": "Shows the title bar as a transparent overlay over the window's content.\n\nKeep in mind: - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect. - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus . - The color of the window title depends on the system theme.", + "type": "string", + "enum": [ + "Overlay" + ] + } + ] + }, + "WindowEffectsConfig": { + "description": "The window effects configuration object", + "type": "object", + "required": [ + "effects" + ], + "properties": { + "effects": { + "description": "List of Window effects to apply to the Window. Conflicting effects will apply the first one and ignore the rest.", + "type": "array", + "items": { + "$ref": "#/definitions/WindowEffect" + } + }, + "state": { + "description": "Window effect state **macOS Only**", + "anyOf": [ + { + "$ref": "#/definitions/WindowEffectState" + }, + { + "type": "null" + } + ] + }, + "radius": { + "description": "Window effect corner radius **macOS Only**", + "type": [ + "number", + "null" + ], + "format": "double" + }, + "color": { + "description": "Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.", + "anyOf": [ + { + "$ref": "#/definitions/Color" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "WindowEffect": { + "description": "Platform-specific window effects", + "oneOf": [ + { + "description": "A default material appropriate for the view's effectiveAppearance. **macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "appearanceBased" + ] + }, + { + "description": "*macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "light" + ] + }, + { + "description": "*macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "dark" + ] + }, + { + "description": "*macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "mediumLight" + ] + }, + { + "description": "*macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "ultraDark" + ] + }, + { + "description": "*macOS 10.10+**", + "type": "string", + "enum": [ + "titlebar" + ] + }, + { + "description": "*macOS 10.10+**", + "type": "string", + "enum": [ + "selection" + ] + }, + { + "description": "*macOS 10.11+**", + "type": "string", + "enum": [ + "menu" + ] + }, + { + "description": "*macOS 10.11+**", + "type": "string", + "enum": [ + "popover" + ] + }, + { + "description": "*macOS 10.11+**", + "type": "string", + "enum": [ + "sidebar" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "headerView" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "sheet" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "windowBackground" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "hudWindow" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "fullScreenUI" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "tooltip" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "contentBackground" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "underWindowBackground" + ] + }, + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "underPageBackground" + ] + }, + { + "description": "*Windows 11 Only**", + "type": "string", + "enum": [ + "mica" + ] + }, + { + "description": "**Windows 7/10/11(22H1) Only**\n\n## Notes\n\nThis effect has bad performance when resizing/dragging the window on Windows 11 build 22621.", + "type": "string", + "enum": [ + "blur" + ] + }, + { + "description": "**Windows 10/11 Only**\n\n## Notes\n\nThis effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.", + "type": "string", + "enum": [ + "acrylic" + ] + } + ] + }, + "WindowEffectState": { + "description": "Window effect state **macOS only**\n\n", + "oneOf": [ + { + "description": "Make window effect state follow the window's active state", + "type": "string", + "enum": [ + "followsWindowActiveState" + ] + }, + { + "description": "Make window effect state always active", + "type": "string", + "enum": [ + "active" + ] + }, + { + "description": "Make window effect state always inactive", + "type": "string", + "enum": [ + "inactive" + ] + } + ] + }, + "Color": { + "description": "a tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.", + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + ], + "maxItems": 4, + "minItems": 4 + }, + "BundleConfig": { + "description": "Configuration for tauri-bundler.\n\nSee more: https://tauri.app/v1/api/config#bundleconfig", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "active": { + "description": "Whether Tauri should bundle your application or just output the executable.", + "default": false, + "type": "boolean" + }, + "targets": { + "description": "The bundle targets, currently supports [\"deb\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\", \"updater\"] or \"all\".", + "default": "all", + "allOf": [ + { + "$ref": "#/definitions/BundleTarget" + } + ] + }, + "identifier": { + "description": "The application identifier in reverse domain name notation (e.g. `com.tauri.example`). This string must be unique across applications since it is used in system configurations like the bundle ID and path to the webview data directory. This string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-), and periods (.).", + "type": "string" + }, + "publisher": { + "description": "The application's publisher. Defaults to the second element in the identifier string. Currently maps to the Manufacturer property of the Windows Installer.", + "type": [ + "string", + "null" + ] + }, + "icon": { + "description": "The app's icons", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "resources": { + "description": "App resources to bundle. Each resource is a path to a file or directory. Glob patterns are supported.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "copyright": { + "description": "A copyright string associated with your application.", + "type": [ + "string", + "null" + ] + }, + "category": { + "description": "The application kind.\n\nShould be one of the following: Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.", + "type": [ + "string", + "null" + ] + }, + "shortDescription": { + "description": "A short description of your application.", + "type": [ + "string", + "null" + ] + }, + "longDescription": { + "description": "A longer, multi-line description of the application.", + "type": [ + "string", + "null" + ] + }, + "appimage": { + "description": "Configuration for the AppImage bundle.", + "default": { + "bundleMediaFramework": false + }, + "allOf": [ + { + "$ref": "#/definitions/AppImageConfig" + } + ] + }, + "deb": { + "description": "Configuration for the Debian bundle.", + "default": { + "files": {} + }, + "allOf": [ + { + "$ref": "#/definitions/DebConfig" + } + ] + }, + "macOS": { + "description": "Configuration for the macOS bundles.", + "default": { + "minimumSystemVersion": "10.13" + }, + "allOf": [ + { + "$ref": "#/definitions/MacConfig" + } + ] + }, + "externalBin": { + "description": "A list of—either absolute or relative—paths to binaries to embed with your application.\n\nNote that Tauri will look for system-specific binaries following the pattern \"binary-name{-target-triple}{.system-extension}\".\n\nE.g. for the external binary \"my-binary\", Tauri looks for:\n\n- \"my-binary-x86_64-pc-windows-msvc.exe\" for Windows - \"my-binary-x86_64-apple-darwin\" for macOS - \"my-binary-x86_64-unknown-linux-gnu\" for Linux\n\nso don't forget to provide binaries for all targeted platforms.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "windows": { + "description": "Configuration for the Windows bundle.", + "default": { + "allowDowngrades": true, + "certificateThumbprint": null, + "digestAlgorithm": null, + "nsis": null, + "timestampUrl": null, + "tsp": false, + "webviewFixedRuntimePath": null, + "webviewInstallMode": { + "silent": true, + "type": "downloadBootstrapper" + }, + "wix": null + }, + "allOf": [ + { + "$ref": "#/definitions/WindowsConfig" + } + ] + }, + "iOS": { + "description": "iOS configuration.", + "default": {}, + "allOf": [ + { + "$ref": "#/definitions/IosConfig" + } + ] + }, + "android": { + "description": "Android configuration.", + "default": { + "minSdkVersion": 24 + }, + "allOf": [ + { + "$ref": "#/definitions/AndroidConfig" + } + ] + }, + "updater": { + "description": "The updater configuration.", + "default": { + "active": false, + "pubkey": "", + "windows": { + "installMode": "passive" + } + }, + "allOf": [ + { + "$ref": "#/definitions/UpdaterConfig" + } + ] + } + }, + "additionalProperties": false + }, + "BundleTarget": { + "description": "Targets to bundle. Each value is case insensitive.", + "anyOf": [ + { + "description": "Bundle all targets.", + "enum": [ + "all" + ] + }, + { + "description": "A list of bundle targets.", + "type": "array", + "items": { + "$ref": "#/definitions/BundleType" + } + }, + { + "description": "A single bundle target.", + "allOf": [ + { + "$ref": "#/definitions/BundleType" + } + ] + } + ] + }, + "BundleType": { + "description": "A bundle referenced by tauri-bundler.", + "oneOf": [ + { + "description": "The debian bundle (.deb).", + "type": "string", + "enum": [ + "deb" + ] + }, + { + "description": "The AppImage bundle (.appimage).", + "type": "string", + "enum": [ + "appimage" + ] + }, + { + "description": "The Microsoft Installer bundle (.msi).", + "type": "string", + "enum": [ + "msi" + ] + }, + { + "description": "The NSIS bundle (.exe).", + "type": "string", + "enum": [ + "nsis" + ] + }, + { + "description": "The macOS application bundle (.app).", + "type": "string", + "enum": [ + "app" + ] + }, + { + "description": "The Apple Disk Image bundle (.dmg).", + "type": "string", + "enum": [ + "dmg" + ] + }, + { + "description": "The Tauri updater bundle.", + "type": "string", + "enum": [ + "updater" + ] + } + ] + }, + "AppImageConfig": { + "description": "Configuration for AppImage bundles.\n\nSee more: https://tauri.app/v1/api/config#appimageconfig", + "type": "object", + "properties": { + "bundleMediaFramework": { + "description": "Include additional gstreamer dependencies needed for audio and video playback. This increases the bundle size by ~15-35MB depending on your build system.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DebConfig": { + "description": "Configuration for Debian (.deb) bundles.\n\nSee more: https://tauri.app/v1/api/config#debconfig", + "type": "object", + "properties": { + "depends": { + "description": "The list of deb dependencies your application relies on.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "files": { + "description": "The files to include on the package.", + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "desktopTemplate": { + "description": "Path to a custom desktop file Handlebars template.\n\nAvailable variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "MacConfig": { + "description": "Configuration for the macOS bundles.\n\nSee more: https://tauri.app/v1/api/config#macconfig", + "type": "object", + "properties": { + "frameworks": { + "description": "A list of strings indicating any macOS X frameworks that need to be bundled with the application.\n\nIf a name is used, \".framework\" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "minimumSystemVersion": { + "description": "A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n\nSetting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist` and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n\nAn empty string is considered an invalid value so the default value is used.", + "default": "10.13", + "type": [ + "string", + "null" + ] + }, + "exceptionDomain": { + "description": "Allows your application to communicate with the outside world. It should be a lowercase, without port and protocol domain name.", + "type": [ + "string", + "null" + ] + }, + "license": { + "description": "The path to the license file to add to the DMG bundle.", + "type": [ + "string", + "null" + ] + }, + "signingIdentity": { + "description": "Identity to use for code signing.", + "type": [ + "string", + "null" + ] + }, + "providerShortName": { + "description": "Provider short name for notarization.", + "type": [ + "string", + "null" + ] + }, + "entitlements": { + "description": "Path to the entitlements file.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "WindowsConfig": { + "description": "Windows bundler configuration.\n\nSee more: https://tauri.app/v1/api/config#windowsconfig", + "type": "object", + "properties": { + "digestAlgorithm": { + "description": "Specifies the file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.", + "type": [ + "string", + "null" + ] + }, + "certificateThumbprint": { + "description": "Specifies the SHA1 hash of the signing certificate.", + "type": [ + "string", + "null" + ] + }, + "timestampUrl": { + "description": "Server to use during timestamping.", + "type": [ + "string", + "null" + ] + }, + "tsp": { + "description": "Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.", + "default": false, + "type": "boolean" + }, + "webviewInstallMode": { + "description": "The installation mode for the Webview2 runtime.", + "default": { + "silent": true, + "type": "downloadBootstrapper" + }, + "allOf": [ + { + "$ref": "#/definitions/WebviewInstallMode" + } + ] + }, + "webviewFixedRuntimePath": { + "description": "Path to the webview fixed runtime to use. Overwrites [`Self::webview_install_mode`] if set.\n\nWill be removed in v2, prefer the [`Self::webview_install_mode`] option.\n\nThe fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section). The `.cab` file must be extracted to a folder and this folder path must be defined on this field.", + "type": [ + "string", + "null" + ] + }, + "allowDowngrades": { + "description": "Validates a second app installation, blocking the user from installing an older version if set to `false`.\n\nFor instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n\nThe default value of this flag is `true`.", + "default": true, + "type": "boolean" + }, + "wix": { + "description": "Configuration for the MSI generated with WiX.", + "anyOf": [ + { + "$ref": "#/definitions/WixConfig" + }, + { + "type": "null" + } + ] + }, + "nsis": { + "description": "Configuration for the installer generated with NSIS.", + "anyOf": [ + { + "$ref": "#/definitions/NsisConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "WebviewInstallMode": { + "description": "Install modes for the Webview2 runtime. Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.\n\nFor more information see .", + "oneOf": [ + { + "description": "Do not install the Webview2 as part of the Windows Installer.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "skip" + ] + } + }, + "additionalProperties": false + }, + { + "description": "Download the bootstrapper and run it. Requires an internet connection. Results in a smaller installer size, but is not recommended on Windows 7.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "downloadBootstrapper" + ] + }, + "silent": { + "description": "Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + { + "description": "Embed the bootstrapper and run it. Requires an internet connection. Increases the installer size by around 1.8MB, but offers better support on Windows 7.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "embedBootstrapper" + ] + }, + "silent": { + "description": "Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + { + "description": "Embed the offline installer and run it. Does not require an internet connection. Increases the installer size by around 127MB.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "offlineInstaller" + ] + }, + "silent": { + "description": "Instructs the installer to run the installer in silent mode. Defaults to `true`.", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + { + "description": "Embed a fixed webview2 version and use it at runtime. Increases the installer size by around 180MB.", + "type": "object", + "required": [ + "path", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "fixedRuntime" + ] + }, + "path": { + "description": "The path to the fixed runtime to use.\n\nThe fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section). The `.cab` file must be extracted to a folder and this folder path must be defined on this field.", + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "WixConfig": { + "description": "Configuration for the MSI bundle using WiX.\n\nSee more: https://tauri.app/v1/api/config#wixconfig", + "type": "object", + "properties": { + "language": { + "description": "The installer languages to build. See .", + "default": "en-US", + "allOf": [ + { + "$ref": "#/definitions/WixLanguage" + } + ] + }, + "template": { + "description": "A custom .wxs template to use.", + "type": [ + "string", + "null" + ] + }, + "fragmentPaths": { + "description": "A list of paths to .wxs files with WiX fragments to use.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "componentGroupRefs": { + "description": "The ComponentGroup element ids you want to reference from the fragments.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "componentRefs": { + "description": "The Component element ids you want to reference from the fragments.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "featureGroupRefs": { + "description": "The FeatureGroup element ids you want to reference from the fragments.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "featureRefs": { + "description": "The Feature element ids you want to reference from the fragments.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "mergeRefs": { + "description": "The Merge element ids you want to reference from the fragments.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "skipWebviewInstall": { + "description": "Disables the Webview2 runtime installation after app install.\n\nWill be removed in v2, prefer the [`WindowsConfig::webview_install_mode`] option.", + "default": false, + "type": "boolean" + }, + "license": { + "description": "The path to the license file to render on the installer.\n\nMust be an RTF file, so if a different extension is provided, we convert it to the RTF format.", + "type": [ + "string", + "null" + ] + }, + "enableElevatedUpdateTask": { + "description": "Create an elevated update task within Windows Task Scheduler.", + "default": false, + "type": "boolean" + }, + "bannerPath": { + "description": "Path to a bitmap file to use as the installation user interface banner. This bitmap will appear at the top of all but the first page of the installer.\n\nThe required dimensions are 493px × 58px.", + "type": [ + "string", + "null" + ] + }, + "dialogImagePath": { + "description": "Path to a bitmap file to use on the installation user interface dialogs. It is used on the welcome and completion dialogs. The required dimensions are 493px × 312px.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "WixLanguage": { + "description": "The languages to build using WiX.", + "anyOf": [ + { + "description": "A single language to build, without configuration.", + "type": "string" + }, + { + "description": "A list of languages to build, without configuration.", + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "A map of languages and its configuration.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/WixLanguageConfig" + } + } + ] + }, + "WixLanguageConfig": { + "description": "Configuration for a target language for the WiX build.\n\nSee more: https://tauri.app/v1/api/config#wixlanguageconfig", + "type": "object", + "properties": { + "localePath": { + "description": "The path to a locale (`.wxl`) file. See .", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "NsisConfig": { + "description": "Configuration for the Installer bundle using NSIS.", + "type": "object", + "properties": { + "template": { + "description": "A custom .nsi template to use.", + "type": [ + "string", + "null" + ] + }, + "license": { + "description": "The path to the license file to render on the installer.", + "type": [ + "string", + "null" + ] + }, + "headerImage": { + "description": "The path to a bitmap file to display on the header of installers pages.\n\nThe recommended dimensions are 150px x 57px.", + "type": [ + "string", + "null" + ] + }, + "sidebarImage": { + "description": "The path to a bitmap file for the Welcome page and the Finish page.\n\nThe recommended dimensions are 164px x 314px.", + "type": [ + "string", + "null" + ] + }, + "installerIcon": { + "description": "The path to an icon file used as the installer icon.", + "type": [ + "string", + "null" + ] + }, + "installMode": { + "description": "Whether the installation will be for all users or just the current user.", + "default": "currentUser", + "allOf": [ + { + "$ref": "#/definitions/NSISInstallerMode" + } + ] + }, + "languages": { + "description": "A list of installer languages. By default the OS language is used. If the OS language is not in the list of languages, the first language will be used. To allow the user to select the language, set `display_language_selector` to `true`.\n\nSee for the complete list of languages.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "customLanguageFiles": { + "description": "A key-value pair where the key is the language and the value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\n\nSee for an example `.nsh` file.\n\n**Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "displayLanguageSelector": { + "description": "Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "NSISInstallerMode": { + "description": "Install Modes for the NSIS installer.", + "oneOf": [ + { + "description": "Default mode for the installer.\n\nInstall the app by default in a directory that doesn't require Administrator access.\n\nInstaller metadata will be saved under the `HKCU` registry path.", + "type": "string", + "enum": [ + "currentUser" + ] + }, + { + "description": "Install the app by default in the `Program Files` folder directory requires Administrator access for the installation.\n\nInstaller metadata will be saved under the `HKLM` registry path.", + "type": "string", + "enum": [ + "perMachine" + ] + }, + { + "description": "Combines both modes and allows the user to choose at install time whether to install for the current user or per machine. Note that this mode will require Administrator access even if the user wants to install it for the current user only.\n\nInstaller metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.", + "type": "string", + "enum": [ + "both" + ] + } + ] + }, + "IosConfig": { + "description": "General configuration for the iOS target.", + "type": "object", + "properties": { + "developmentTeam": { + "description": "The development team. This value is required for iOS development because code signing is enforced. The `TAURI_APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "AndroidConfig": { + "description": "General configuration for the iOS target.", + "type": "object", + "properties": { + "minSdkVersion": { + "description": "The minimum API level required for the application to run. The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.", + "default": 24, + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "UpdaterConfig": { + "description": "The Updater configuration object.\n\nSee more: https://tauri.app/v1/api/config#updaterconfig", + "type": "object", + "properties": { + "active": { + "description": "Whether the updater is active or not.", + "default": false, + "type": "boolean" + }, + "pubkey": { + "description": "Signature public key.", + "default": "", + "type": "string" + }, + "windows": { + "description": "The Windows configuration for the updater.", + "default": { + "installMode": "passive" + }, + "allOf": [ + { + "$ref": "#/definitions/UpdaterWindowsConfig" + } + ] + } + }, + "additionalProperties": false + }, + "UpdaterWindowsConfig": { + "description": "The updater configuration for Windows.\n\nSee more: https://tauri.app/v1/api/config#updaterwindowsconfig", + "type": "object", + "properties": { + "installMode": { + "description": "The installation mode for the update on Windows. Defaults to `passive`.", + "default": "passive", + "allOf": [ + { + "$ref": "#/definitions/WindowsUpdateInstallMode" + } + ] + } + }, + "additionalProperties": false + }, + "WindowsUpdateInstallMode": { + "description": "Install modes for the Windows update.", + "oneOf": [ + { + "description": "Specifies there's a basic UI during the installation process, including a final dialog box at the end.", + "type": "string", + "enum": [ + "basicUi" + ] + }, + { + "description": "The quiet mode means there's no user interaction required. Requires admin privileges if the installer does.", + "type": "string", + "enum": [ + "quiet" + ] + }, + { + "description": "Specifies unattended mode, which means the installation only shows a progress bar.", + "type": "string", + "enum": [ + "passive" + ] + } + ] + }, + "SecurityConfig": { + "description": "Security configuration.\n\nSee more: https://tauri.app/v1/api/config#securityconfig", + "type": "object", + "properties": { + "csp": { + "description": "The Content Security Policy that will be injected on all HTML files on the built application. If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.\n\nThis is a really important part of the configuration since it helps you ensure your WebView is secured. See .", + "anyOf": [ + { + "$ref": "#/definitions/Csp" + }, + { + "type": "null" + } + ] + }, + "devCsp": { + "description": "The Content Security Policy that will be injected on all HTML files on development.\n\nThis is a really important part of the configuration since it helps you ensure your WebView is secured. See .", + "anyOf": [ + { + "$ref": "#/definitions/Csp" + }, + { + "type": "null" + } + ] + }, + "freezePrototype": { + "description": "Freeze the `Object.prototype` when using the custom protocol.", + "default": false, + "type": "boolean" + }, + "dangerousDisableAssetCspModification": { + "description": "Disables the Tauri-injected CSP sources.\n\nAt compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy to only allow loading of your own scripts and styles by injecting nonce and hash sources. This stricts your CSP, which may introduce issues when using along with other flexing sources.\n\nThis configuration option allows both a boolean and a list of strings as value. A boolean instructs Tauri to disable the injection for all CSP injections, and a list of strings indicates the CSP directives that Tauri cannot inject.\n\n**WARNING:** Only disable this if you know what you are doing and have properly configured the CSP. Your application might be vulnerable to XSS attacks without this Tauri protection.", + "default": false, + "allOf": [ + { + "$ref": "#/definitions/DisabledCspModificationKind" + } + ] + }, + "dangerousRemoteDomainIpcAccess": { + "description": "Allow external domains to send command to Tauri.\n\nBy default, external domains do not have access to `window.__TAURI__`, which means they cannot communicate with the commands defined in Rust. This prevents attacks where an externally loaded malicious or compromised sites could start executing commands on the user's device.\n\nThis configuration allows a set of external domains to have access to the Tauri commands. When you configure a domain to be allowed to access the IPC, all subpaths are allowed. Subdomains are not allowed.\n\n**WARNING:** Only use this option if you either have internal checks against malicious external sites or you can trust the allowed external sites. You application might be vulnerable to dangerous Tauri command related attacks otherwise.", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/RemoteDomainAccessScope" + } + }, + "assetProtocol": { + "description": "Custom protocol config.", + "default": { + "enable": false, + "scope": [] + }, + "allOf": [ + { + "$ref": "#/definitions/AssetProtocolConfig" + } + ] + } + }, + "additionalProperties": false + }, + "Csp": { + "description": "A Content-Security-Policy definition. See .", + "anyOf": [ + { + "description": "The entire CSP policy in a single text string.", + "type": "string" + }, + { + "description": "An object mapping a directive with its sources values as a list of strings.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/CspDirectiveSources" + } + } + ] + }, + "CspDirectiveSources": { + "description": "A Content-Security-Policy directive source list. See .", + "anyOf": [ + { + "description": "An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.", + "type": "string" + }, + { + "description": "A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "DisabledCspModificationKind": { + "description": "The possible values for the `dangerous_disable_asset_csp_modification` config option.", + "anyOf": [ + { + "description": "If `true`, disables all CSP modification. `false` is the default value and it configures Tauri to control the CSP.", + "type": "boolean" + }, + { + "description": "Disables the given list of CSP directives modifications.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "RemoteDomainAccessScope": { + "description": "External command access definition.", + "type": "object", + "required": [ + "domain", + "windows" + ], + "properties": { + "scheme": { + "description": "The URL scheme to allow. By default, all schemas are allowed.", + "type": [ + "string", + "null" + ] + }, + "domain": { + "description": "The domain to allow.", + "type": "string" + }, + "windows": { + "description": "The list of window labels this scope applies to.", + "type": "array", + "items": { + "type": "string" + } + }, + "plugins": { + "description": "The list of plugins that are allowed in this scope.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "AssetProtocolConfig": { + "description": "Config for the asset custom protocol.\n\nSee more: https://tauri.app/v1/api/config#assetprotocolconfig", + "type": "object", + "properties": { + "scope": { + "description": "The access scope for the asset protocol.", + "default": [], + "allOf": [ + { + "$ref": "#/definitions/FsScope" + } + ] + }, + "enable": { + "description": "Enables the asset protocol.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "FsScope": { + "description": "Protocol scope definition. It is a list of glob patterns that restrict the API access from the webview.\n\nEach 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`.", + "anyOf": [ + { + "description": "A list of paths that are allowed by this scope.", + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "A complete scope configuration.", + "type": "object", + "properties": { + "allow": { + "description": "A list of paths that are allowed by this scope.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "A list of paths that are not allowed by this scope. This gets precedence over the [`Self::Scope::allow`] list.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "requireLiteralLeadingDot": { + "description": "Whether or not paths that contain components that start with a `.` will require that `.` appears literally in the pattern; `*`, `?`, `**`, or `[...]` will not match. This is useful because such files are conventionally considered hidden on Unix systems and it might be desirable to skip them when listing files.\n\nDefaults to `false` on Unix systems and `true` on Windows", + "type": [ + "boolean", + "null" + ] + } + } + } + ] + }, + "SystemTrayConfig": { + "description": "Configuration for application system tray icon.\n\nSee more: https://tauri.app/v1/api/config#systemtrayconfig", + "type": "object", + "required": [ + "iconPath" + ], + "properties": { + "iconPath": { + "description": "Path to the default icon to use on the system tray.", + "type": "string" + }, + "iconAsTemplate": { + "description": "A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.", + "default": false, + "type": "boolean" + }, + "menuOnLeftClick": { + "description": "A Boolean value that determines whether the menu should appear when the tray icon receives a left click on macOS.", + "default": true, + "type": "boolean" + }, + "title": { + "description": "Title for MacOS tray", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "BuildConfig": { + "description": "The Build configuration object.\n\nSee more: https://tauri.app/v1/api/config#buildconfig", + "type": "object", + "properties": { + "runner": { + "description": "The binary used to build and run the application.", + "type": [ + "string", + "null" + ] + }, + "devPath": { + "description": "The path to the application assets or URL to load in development.\n\nThis is usually an URL to a dev server, which serves your application assets with live reloading. Most modern JavaScript bundlers provides a way to start a dev server by default.\n\nSee [vite](https://vitejs.dev/guide/), [Webpack DevServer](https://webpack.js.org/configuration/dev-server/) and [sirv](https://github.com/lukeed/sirv) for examples on how to set up a dev server.", + "default": "http://localhost:8080/", + "allOf": [ + { + "$ref": "#/definitions/AppUrl" + } + ] + }, + "distDir": { + "description": "The path to the application assets or URL to load in production.\n\nWhen a path relative to the configuration file is provided, it is read recursively and all files are embedded in the application binary. Tauri then looks for an `index.html` file unless you provide a custom window URL.\n\nYou can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary. In this case, all files are added to the root and you must reference it that way in your HTML files.\n\nWhen an URL is provided, the application won't have bundled assets and the application will load that URL by default.", + "default": "../dist", + "allOf": [ + { + "$ref": "#/definitions/AppUrl" + } + ] + }, + "beforeDevCommand": { + "description": "A shell command to run before `tauri dev` kicks in.\n\nThe TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.", + "anyOf": [ + { + "$ref": "#/definitions/BeforeDevCommand" + }, + { + "type": "null" + } + ] + }, + "beforeBuildCommand": { + "description": "A shell command to run before `tauri build` kicks in.\n\nThe TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.", + "anyOf": [ + { + "$ref": "#/definitions/HookCommand" + }, + { + "type": "null" + } + ] + }, + "beforeBundleCommand": { + "description": "A shell command to run before the bundling phase in `tauri build` kicks in.\n\nThe TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.", + "anyOf": [ + { + "$ref": "#/definitions/HookCommand" + }, + { + "type": "null" + } + ] + }, + "features": { + "description": "Features passed to `cargo` commands.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "withGlobalTauri": { + "description": "Whether we should inject the Tauri API on `window.__TAURI__` or not.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "AppUrl": { + "description": "Defines the URL or assets to embed in the application.", + "anyOf": [ + { + "description": "The app's external URL, or the path to the directory containing the app assets.", + "allOf": [ + { + "$ref": "#/definitions/WindowUrl" + } + ] + }, + { + "description": "An array of files to embed on the app.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "BeforeDevCommand": { + "description": "Describes the shell command to run before `tauri dev`.", + "anyOf": [ + { + "description": "Run the given script with the default options.", + "type": "string" + }, + { + "description": "Run the given script with custom options.", + "type": "object", + "required": [ + "script" + ], + "properties": { + "script": { + "description": "The script to execute.", + "type": "string" + }, + "cwd": { + "description": "The current working directory.", + "type": [ + "string", + "null" + ] + }, + "wait": { + "description": "Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.", + "default": false, + "type": "boolean" + } + } + } + ] + }, + "HookCommand": { + "description": "Describes a shell command to be executed when a CLI hook is triggered.", + "anyOf": [ + { + "description": "Run the given script with the default options.", + "type": "string" + }, + { + "description": "Run the given script with custom options.", + "type": "object", + "required": [ + "script" + ], + "properties": { + "script": { + "description": "The script to execute.", + "type": "string" + }, + "cwd": { + "description": "The current working directory.", + "type": [ + "string", + "null" + ] + } + } + } + ] + }, + "PluginConfig": { + "description": "The plugin configs holds a HashMap mapping a plugin name to its configuration object.\n\nSee more: https://tauri.app/v1/api/config#pluginconfig", + "type": "object", + "additionalProperties": true + } + } +} \ No newline at end of file diff --git a/core/tauri-config-schema/src/main.rs b/core/tauri-config-schema/src/main.rs new file mode 100644 index 000000000000..e6c5244c75c7 --- /dev/null +++ b/core/tauri-config-schema/src/main.rs @@ -0,0 +1,5 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +fn main() {} diff --git a/core/tauri-macros/.license_template b/core/tauri-macros/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/core/tauri-macros/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/core/tauri-macros/CHANGELOG.md b/core/tauri-macros/CHANGELOG.md index 44eb2c9c2978..acda17970000 100644 --- a/core/tauri-macros/CHANGELOG.md +++ b/core/tauri-macros/CHANGELOG.md @@ -1,5 +1,210 @@ # Changelog +## \[2.0.0-alpha.5] + +- [`7a4b1fb9`](https://www.github.com/tauri-apps/tauri/commit/7a4b1fb96da475053c61960f362bbecf18cd00d4)([#6839](https://www.github.com/tauri-apps/tauri/pull/6839)) Added support to attibutes for each command path in the `generate_handler` macro. +- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `shell` functionality to its own plugin in the plugins-workspace repository. +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Removed the module command macros. + +## \[2.0.0-alpha.4] + +- Added `android` configuration object under `tauri > bundle`. + - Bumped due to a bump in tauri-utils. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 + +## \[2.0.0-alpha.3] + +- Pull changes from Tauri 1.3 release. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[2.0.0-alpha.2] + +- Resolve Android package name from single word bundle identifiers. + - [60a8b07d](https://www.github.com/tauri-apps/tauri/commit/60a8b07dc7c56c9c45331cb57d9afb410e7eadf3) fix: handle single word bundle identifier when resolving Android domain ([#6313](https://www.github.com/tauri-apps/tauri/pull/6313)) on 2023-02-19 +- Return `bool` in the invoke handler. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 +- Refactored the implementation of the `mobile_entry_point` macro. + - [9feab904](https://www.github.com/tauri-apps/tauri/commit/9feab904bf08b5c168d4779c21d0419409a68d30) feat(core): add API to call Android plugin ([#6239](https://www.github.com/tauri-apps/tauri/pull/6239)) on 2023-02-10 + +## \[2.0.0-alpha.1] + +- Refactor mobile environment variables. + - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27 +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Removed mobile logging initialization, which will be handled by `tauri-plugin-log`. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[2.0.0-alpha.0] + +- Added the `mobile_entry_point` macro. + - [98904863](https://www.github.com/tauri-apps/tauri/commit/9890486321c9c79ccfb7c547fafee85b5c3ffa71) feat(core): add `mobile_entry_point` macro ([#4983](https://www.github.com/tauri-apps/tauri/pull/4983)) on 2022-08-21 +- First mobile alpha release! + - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08 + +## \[1.4.0] + +### Enhancements + +- [`d68a25e3`](https://www.github.com/tauri-apps/tauri/commit/d68a25e32e012e57a9e5225b589b9ecbea70a887)([#6124](https://www.github.com/tauri-apps/tauri/pull/6124)) Improve compiler error message when generating an async command that has a reference input and don't return a Result. + +## \[1.3.0] + +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 + +## \[1.2.1] + +- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`. + - Bumped due to a bump in tauri-utils. + - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18 + +## \[1.2.0] + +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 + +## \[1.1.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - Bumped due to a bump in tauri-utils. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[1.1.0] + +- Added support to configuration files in TOML format (Tauri.toml file). + - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02 + +## \[1.0.4] + +- Adjust command imports to fix `items_after_statements` Clippy warning. + - [d3e19e34](https://www.github.com/tauri-apps/tauri/commit/d3e19e3420a023cddc46173a2d1f1e6c5a390a1b) fix(macros): `items_after_statements` Clippy warning, closes [#4639](https://www.github.com/tauri-apps/tauri/pull/4639) on 2022-07-11 +- Remove raw identifier (`r#`) prefix from command arguments. + - [ac72800f](https://www.github.com/tauri-apps/tauri/commit/ac72800fb630738e2502569935ecdc83e3e78055) fix(macros): strip `r#` from command arguments, closes [#4654](https://www.github.com/tauri-apps/tauri/pull/4654) ([#4657](https://www.github.com/tauri-apps/tauri/pull/4657)) on 2022-07-12 + +## \[1.0.3] + +- Add `#[doc(hidden)]` attribute to the `#[command]` generated macro. + - [d4cdf807](https://www.github.com/tauri-apps/tauri/commit/d4cdf80781a7955ac620fe6d394e82d067178c4d) feat(macros): hide command macro from docs, closes [#4550](https://www.github.com/tauri-apps/tauri/pull/4550) ([#4556](https://www.github.com/tauri-apps/tauri/pull/4556)) on 2022-07-01 + +## \[1.0.2] + +- Expose `platform::windows_version` function. + - Bumped due to a bump in tauri-utils. + - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30 + +## \[1.0.1] + +- Set the bundle name and app metadata in the Info.plist file in development mode. + - [38f5db6e](https://www.github.com/tauri-apps/tauri/commit/38f5db6e6a8b496b50d486db6fd8204266de3a69) feat(codegen): fill app metadata in development Info.plist on 2022-06-21 +- Set the application icon in development mode on macOS. + - [307c2ebf](https://www.github.com/tauri-apps/tauri/commit/307c2ebfb68238dacab6088f9c6ba310c727c68c) feat(core): set macOS app icon in development ([#4385](https://www.github.com/tauri-apps/tauri/pull/4385)) on 2022-06-19 + +## \[1.0.0] + +- Upgrade to `stable`! + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[1.0.0-rc.11] + +- Read the tray icon path relatively to the config directory. + - Bumped due to a bump in tauri-codegen. + - [562e8ca2](https://www.github.com/tauri-apps/tauri/commit/562e8ca23facf1a8e5fa6c8cdf872357d3523a78) fix(codegen): tray icon path is relative to the config directory on 2022-06-15 + +## \[1.0.0-rc.10] + +- **Breaking change:** The `TrayIcon` enum has been removed and now `Icon` is used instead. + This allows you to use more image formats and use embedded icons on Linux. + - Bumped due to a bump in tauri-codegen. + - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14 + +## \[1.0.0-rc.9] + +- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`. + - Bumped due to a bump in tauri-utils. + - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10 + +## \[1.0.0-rc.8] + +- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`. + - Bumped due to a bump in tauri-utils. + - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25 + - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27 + +## \[1.0.0-rc.7] + +- Allow configuring the display options for the MSI execution allowing quieter updates. + - Bumped due to a bump in tauri-utils. + - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15 + +## \[1.0.0-rc.6] + +- Added `$schema` support to `tauri.conf.json`. + - Bumped due to a bump in tauri-utils. + - [715cbde3](https://www.github.com/tauri-apps/tauri/commit/715cbde3842a916c4ebeab2cab348e1774b5c192) feat(config): add `$schema` to `tauri.conf.json`, closes [#3464](https://www.github.com/tauri-apps/tauri/pull/3464) ([#4031](https://www.github.com/tauri-apps/tauri/pull/4031)) on 2022-05-03 +- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable. + - Bumped due to a bump in tauri-utils. + - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02 + +## \[1.0.0-rc.5] + +- Read platform-specific configuration files when generating code without the `TAURI_CONFIG` env var. + - Bumped due to a bump in tauri-codegen. + - [edf85bc1](https://www.github.com/tauri-apps/tauri/commit/edf85bc1d18450c92aee17f7f99c163abe432ebd) fix(codegen): read platform-specific config file ([#3966](https://www.github.com/tauri-apps/tauri/pull/3966)) on 2022-04-25 + +## \[1.0.0-rc.4] + +- Replace multiple dependencies who's C code compiled concurrently and caused + the other ones to bloat compile time significantly. + +- `zstd` -> `brotli` + +- `blake3` -> a vendored version of the blake3 reference + +- `ring` -> `getrandom` + +See https://github.com/tauri-apps/tauri/pull/3773 for more information about +these specific choices. + +- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27 + +## \[1.0.0-rc.3] + +- Parse window icons at compile time. + - Bumped due to a bump in tauri-codegen. + - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05 + +## \[1.0.0-rc.2] + +- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`. + - Bumped due to a bump in tauri-utils. + - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17 + +## \[1.0.0-rc.1] + +- Change default value for the `freezePrototype` configuration to `false`. + - Bumped due to a bump in tauri-utils. + - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12 + +## \[1.0.0-rc.0] + +- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension. + +Here is the logic flow that determines if JSON or JSON5 will be used to parse the config: + +1. Check if `tauri.conf.json` exists + a. Parse it with `serde_json` + b. Parse it with `json5` if `serde_json` fails + c. Return original `serde_json` error if all above steps failed +2. Check if `tauri.conf.json5` exists + a. Parse it with `json5` + b. Return error if all above steps failed +3. Return error if all above steps failed + +- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 + ## \[1.0.0-beta.5] - Embed Info.plist file contents on binary on dev. @@ -75,7 +280,7 @@ - Fixes a name collision when the command function is named `invoke`. - [7862ec5](https://www.github.com/tauri-apps/tauri/commit/7862ec562fa70e3733263ce1f690d6cd2943c0b4) fix(macros): change invoke binding in generate handler ([#1804](https://www.github.com/tauri-apps/tauri/pull/1804)) on 2021-05-12 - Fixes a name collision when the command function is named `message` or `resolver`. - - [0b87532](https://www.github.com/tauri-apps/tauri/commit/0b875327067ca825ff6f6f26c9b2ce6fcb001e79) fix(macros): fix rest of command collisons ([#1805](https://www.github.com/tauri-apps/tauri/pull/1805)) on 2021-05-12 + - [0b87532](https://www.github.com/tauri-apps/tauri/commit/0b875327067ca825ff6f6f26c9b2ce6fcb001e79) fix(macros): fix rest of command collisions ([#1805](https://www.github.com/tauri-apps/tauri/pull/1805)) on 2021-05-12 - Fixes a name collision when the command function is named `cmd`. - [d36b726](https://www.github.com/tauri-apps/tauri/commit/d36b7269261d329dd7d7fcd4d5098f3fca167364) fix(macros): collision when command is named `cmd` ([#1802](https://www.github.com/tauri-apps/tauri/pull/1802)) on 2021-05-12 diff --git a/core/tauri-macros/Cargo.toml b/core/tauri-macros/Cargo.toml index ed99b79a9be4..7035d5b5b0af 100644 --- a/core/tauri-macros/Cargo.toml +++ b/core/tauri-macros/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "tauri-macros" -version = "1.0.0-beta.5" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "os", "filesystem", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.studio" -repository = "https://github.com/tauri-apps/tauri" +version = "2.0.0-alpha.5" description = "Macros for the tauri crate." -edition = "2021" -rust-version = "1.57" -exclude = [ ".license_template", "CHANGELOG.md", "/target" ] +exclude = [ "CHANGELOG.md", "/target" ] readme = "README.md" +# workspace defined package items +authors = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } + [lib] proc-macro = true @@ -20,12 +22,12 @@ proc-macro2 = "1" quote = "1" syn = { version = "1", features = [ "full" ] } heck = "0.4" -tauri-codegen = { version = "1.0.0-beta.4", default-features = false, path = "../tauri-codegen" } -tauri-utils = { version = "1.0.0-beta.3", path = "../tauri-utils" } +tauri-codegen = { version = "2.0.0-alpha.5", default-features = false, path = "../tauri-codegen" } +tauri-utils = { version = "2.0.0-alpha.5", path = "../tauri-utils" } [features] custom-protocol = [ ] compression = [ "tauri-codegen/compression" ] -isolation = ["tauri-codegen/isolation"] -shell-scope = ["tauri-codegen/shell-scope"] +isolation = [ "tauri-codegen/isolation" ] config-json5 = [ "tauri-codegen/config-json5", "tauri-utils/config-json5" ] +config-toml = [ "tauri-codegen/config-toml", "tauri-utils/config-toml" ] diff --git a/core/tauri-macros/README.md b/core/tauri-macros/README.md index a66ba454cbb1..64eb561239fa 100644 --- a/core/tauri-macros/README.md +++ b/core/tauri-macros/README.md @@ -2,37 +2,41 @@ -[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri) -[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) -[![devto](https://img.shields.io/badge/blog-dev.to-black.svg)](https://dev.to/tauri) - -![](https://img.shields.io/github/workflow/status/tauri-apps/tauri/test%20library?label=test%20library -) -[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.studio) - +[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev) +[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri) +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield) +[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) +[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) -[![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri) +[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) -| Component | Version | -| --------- | ------------------------------------------- | -| tauri-macros | [![](https://img.shields.io/crates/v/tauri-macros?style=flat-square)](https://crates.io/crates/tauri-macros) | +| Component | Version | +| ------------ | ------------------------------------------------------------------------------------------------------------ | +| tauri-macros | [![](https://img.shields.io/crates/v/tauri-macros?style=flat-square)](https://crates.io/crates/tauri-macros) | ## About Tauri + Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily. Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task. ## This module + Create macros for the context, handler, and commands by leveraging the `tauri-codegen` crate. To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. ## Semver + **tauri** is following [Semantic Versioning 2.0](https://semver.org/). + ## Licenses + Code: (c) 2021 - The Tauri Programme within The Commons Conservancy. MIT or MIT/Apache 2.0 where applicable. Logo: CC-BY-NC-ND + - Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) diff --git a/core/tauri-macros/src/command/handler.rs b/core/tauri-macros/src/command/handler.rs index 75e882442527..62a9ace45840 100644 --- a/core/tauri-macros/src/command/handler.rs +++ b/core/tauri-macros/src/command/handler.rs @@ -1,29 +1,43 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use quote::format_ident; use syn::{ - parse::{Parse, ParseBuffer}, - Ident, Path, Token, + parse::{Parse, ParseBuffer, ParseStream}, + Attribute, Ident, Path, Token, }; +struct CommandDef { + path: Path, + attrs: Vec, +} + +impl Parse for CommandDef { + fn parse(input: ParseStream) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; + let path = input.parse()?; + + Ok(CommandDef { path, attrs }) + } +} + /// The items parsed from [`generate_handle!`](crate::generate_handle). pub struct Handler { - paths: Vec, + command_defs: Vec, commands: Vec, wrappers: Vec, } impl Parse for Handler { fn parse(input: &ParseBuffer<'_>) -> syn::Result { - let paths = input.parse_terminated::(Path::parse)?; + let command_defs = input.parse_terminated::(CommandDef::parse)?; // parse the command names and wrappers from the passed paths - let (commands, wrappers) = paths + let (commands, wrappers) = command_defs .iter() - .map(|path| { - let mut wrapper = path.clone(); + .map(|command_def| { + let mut wrapper = command_def.path.clone(); let last = super::path_to_command(&mut wrapper); // the name of the actual command function @@ -37,7 +51,7 @@ impl Parse for Handler { .unzip(); Ok(Self { - paths: paths.into_iter().collect(), // remove punctuation separators + command_defs: command_defs.into_iter().collect(), // remove punctuation separators commands, wrappers, }) @@ -47,19 +61,23 @@ impl Parse for Handler { impl From for proc_macro::TokenStream { fn from( Handler { - paths, + command_defs, commands, wrappers, }: Handler, ) -> Self { let cmd = format_ident!("__tauri_cmd__"); let invoke = format_ident!("__tauri_invoke__"); + let (paths, attrs): (Vec, Vec>) = command_defs + .into_iter() + .map(|def| (def.path, def.attrs)) + .unzip(); quote::quote!(move |#invoke| { let #cmd = #invoke.message.command(); match #cmd { - #(stringify!(#commands) => #wrappers!(#paths, #invoke),)* + #(#(#attrs)* stringify!(#commands) => #wrappers!(#paths, #invoke),)* _ => { - #invoke.resolver.reject(format!("command {} not found", #cmd)) + return false; }, } }) diff --git a/core/tauri-macros/src/command/mod.rs b/core/tauri-macros/src/command/mod.rs index 9acbb0bda519..35c4661c34cf 100644 --- a/core/tauri-macros/src/command/mod.rs +++ b/core/tauri-macros/src/command/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT diff --git a/core/tauri-macros/src/command/wrapper.rs b/core/tauri-macros/src/command/wrapper.rs index 9d27962feb45..19dbb2c379d8 100644 --- a/core/tauri-macros/src/command/wrapper.rs +++ b/core/tauri-macros/src/command/wrapper.rs @@ -1,39 +1,96 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use heck::{ToLowerCamelCase, ToSnakeCase}; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::{format_ident, quote, quote_spanned}; use syn::{ - parse::{Parse, ParseBuffer}, + ext::IdentExt, + parse::{Parse, ParseStream}, parse_macro_input, spanned::Spanned, - FnArg, Ident, ItemFn, Pat, Token, Visibility, + FnArg, ItemFn, Lit, Meta, Pat, Token, Visibility, }; +struct WrapperAttributes { + root: TokenStream2, + execution_context: ExecutionContext, + argument_case: ArgumentCase, +} + +impl Parse for WrapperAttributes { + fn parse(input: ParseStream) -> syn::Result { + let mut wrapper_attributes = WrapperAttributes { + root: quote!(::tauri), + execution_context: ExecutionContext::Blocking, + argument_case: ArgumentCase::Camel, + }; + + loop { + match input.parse::() { + Ok(Meta::List(_)) => {} + Ok(Meta::NameValue(v)) => { + if v.path.is_ident("rename_all") { + if let Lit::Str(s) = v.lit { + wrapper_attributes.argument_case = match s.value().as_str() { + "snake_case" => ArgumentCase::Snake, + "camelCase" => ArgumentCase::Camel, + _ => { + return Err(syn::Error::new( + s.span(), + "expected \"camelCase\" or \"snake_case\"", + )) + } + }; + } + } else if v.path.is_ident("root") { + if let Lit::Str(s) = v.lit { + let lit = s.value(); + + wrapper_attributes.root = if lit == "crate" { + quote!($crate) + } else { + let ident = Ident::new(&lit, Span::call_site()); + quote!(#ident) + }; + } + } + } + Ok(Meta::Path(p)) => { + if p.is_ident("async") { + wrapper_attributes.execution_context = ExecutionContext::Async; + } else { + return Err(syn::Error::new(p.span(), "expected `async`")); + } + } + Err(_e) => { + break; + } + } + + let lookahead = input.lookahead1(); + if lookahead.peek(Token![,]) { + input.parse::()?; + } + } + + Ok(wrapper_attributes) + } +} + /// The execution context of the command. enum ExecutionContext { Async, Blocking, } -impl Parse for ExecutionContext { - fn parse(input: &ParseBuffer<'_>) -> syn::Result { - if input.is_empty() { - return Ok(Self::Blocking); - } - - input - .parse::() - .map(|_| Self::Async) - .map_err(|_| { - syn::Error::new( - input.span(), - "only a single item `async` is currently allowed", - ) - }) - } +/// The case of each argument name. +#[derive(Copy, Clone)] +enum ArgumentCase { + Snake, + Camel, } /// The bindings we attach to `tauri::Invoke`. @@ -59,31 +116,102 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { resolver: format_ident!("__tauri_resolver__"), }; + // Tauri currently doesn't support async commands that take a reference as input and don't return + // a result. See: https://github.com/tauri-apps/tauri/issues/2533 + // + // For now, we provide an informative error message to the user in that case. Once #2533 is + // resolved, this check can be removed. + let mut async_command_check = TokenStream2::new(); + if function.sig.asyncness.is_some() { + // This check won't catch all possible problems but it should catch the most common ones. + let mut ref_argument_span = None; + + for arg in &function.sig.inputs { + if let syn::FnArg::Typed(pat) = arg { + match &*pat.ty { + syn::Type::Reference(_) => { + ref_argument_span = Some(pat.span()); + } + syn::Type::Path(path) => { + // Check if the type contains a lifetime argument + let last = path.path.segments.last().unwrap(); + if let syn::PathArguments::AngleBracketed(args) = &last.arguments { + if args + .args + .iter() + .any(|arg| matches!(arg, syn::GenericArgument::Lifetime(_))) + { + ref_argument_span = Some(pat.span()); + } + } + } + _ => {} + } + + if let Some(span) = ref_argument_span { + if let syn::ReturnType::Type(_, return_type) = &function.sig.output { + // To check if the return type is `Result` we require it to check a trait that is + // only implemented by `Result`. That way we don't exclude renamed result types + // which we wouldn't otherwise be able to detect purely from the token stream. + // The "error message" displayed to the user is simply the trait name. + async_command_check = quote_spanned! {return_type.span() => + #[allow(unreachable_code, clippy::diverging_sub_expression)] + const _: () = if false { + trait AsyncCommandMustReturnResult {} + impl AsyncCommandMustReturnResult for ::std::result::Result {} + let _check: #return_type = unreachable!(); + let _: &dyn AsyncCommandMustReturnResult = &_check; + }; + }; + } else { + return quote_spanned! { + span => compile_error!("async commands that contain references as inputs must return a `Result`"); + }.into(); + } + } + } + } + } + // body to the command wrapper or a `compile_error!` of an error occurred while parsing it. - let body = syn::parse::(attributes) - .map(|context| match function.sig.asyncness { - Some(_) => ExecutionContext::Async, - None => context, + let (body, attributes) = syn::parse::(attributes) + .map(|mut attrs| { + if function.sig.asyncness.is_some() { + attrs.execution_context = ExecutionContext::Async; + } + attrs }) - .and_then(|context| match context { - ExecutionContext::Async => body_async(&function, &invoke), - ExecutionContext::Blocking => body_blocking(&function, &invoke), + .and_then(|attrs| { + let body = match attrs.execution_context { + ExecutionContext::Async => body_async(&function, &invoke, &attrs), + ExecutionContext::Blocking => body_blocking(&function, &invoke, &attrs), + }; + body.map(|b| (b, Some(attrs))) }) - .unwrap_or_else(syn::Error::into_compile_error); + .unwrap_or_else(|e| (syn::Error::into_compile_error(e), None)); let Invoke { message, resolver } = invoke; + let root = attributes + .map(|a| a.root) + .unwrap_or_else(|| quote!(::tauri)); + // Rely on rust 2018 edition to allow importing a macro from a path. quote!( + #async_command_check + #function #maybe_macro_export + #[doc(hidden)] macro_rules! #wrapper { // double braces because the item is expected to be a block expression ($path:path, $invoke:ident) => {{ + #[allow(unused_imports)] + use #root::command::private::*; // prevent warnings when the body is a `compile_error!` or if the command has no arguments #[allow(unused_variables)] - let ::tauri::Invoke { message: #message, resolver: #resolver } = $invoke; + let #root::Invoke { message: #message, resolver: #resolver } = $invoke; #body }}; @@ -101,18 +229,20 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { /// See the [`tauri::command`] module for all the items and traits that make this possible. /// /// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html -fn body_async(function: &ItemFn, invoke: &Invoke) -> syn::Result { +fn body_async( + function: &ItemFn, + invoke: &Invoke, + attributes: &WrapperAttributes, +) -> syn::Result { let Invoke { message, resolver } = invoke; - parse_args(function, message).map(|args| { + parse_args(function, message, attributes).map(|args| { quote! { - #[allow(unused_imports)] - use ::tauri::command::private::*; - #resolver.respond_async_serialized(async move { let result = $path(#(#args?),*); let kind = (&result).async_kind(); kind.future(result).await }); + return true; } }) } @@ -122,38 +252,49 @@ fn body_async(function: &ItemFn, invoke: &Invoke) -> syn::Result { /// See the [`tauri::command`] module for all the items and traits that make this possible. /// /// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html -fn body_blocking(function: &ItemFn, invoke: &Invoke) -> syn::Result { +fn body_blocking( + function: &ItemFn, + invoke: &Invoke, + attributes: &WrapperAttributes, +) -> syn::Result { let Invoke { message, resolver } = invoke; - let args = parse_args(function, message)?; + let args = parse_args(function, message, attributes)?; // the body of a `match` to early return any argument that wasn't successful in parsing. let match_body = quote!({ Ok(arg) => arg, - Err(err) => return #resolver.invoke_error(err), + Err(err) => { #resolver.invoke_error(err); return true }, }); Ok(quote! { - #[allow(unused_imports)] - use ::tauri::command::private::*; - let result = $path(#(match #args #match_body),*); let kind = (&result).blocking_kind(); kind.block(result, #resolver); + return true; }) } /// Parse all arguments for the command wrapper to use from the signature of the command function. -fn parse_args(function: &ItemFn, message: &Ident) -> syn::Result> { +fn parse_args( + function: &ItemFn, + message: &Ident, + attributes: &WrapperAttributes, +) -> syn::Result> { function .sig .inputs .iter() - .map(|arg| parse_arg(&function.sig.ident, arg, message)) + .map(|arg| parse_arg(&function.sig.ident, arg, message, attributes)) .collect() } /// Transform a [`FnArg`] into a command argument. -fn parse_arg(command: &Ident, arg: &FnArg, message: &Ident) -> syn::Result { +fn parse_arg( + command: &Ident, + arg: &FnArg, + message: &Ident, + attributes: &WrapperAttributes, +) -> syn::Result { // we have no use for self arguments let mut arg = match arg { FnArg::Typed(arg) => arg.pat.as_ref().clone(), @@ -167,7 +308,7 @@ fn parse_arg(command: &Ident, arg: &FnArg, message: &Ident) -> syn::Result arg.ident.to_string(), + Pat::Ident(arg) => arg.ident.unraw().to_string(), Pat::Wild(_) => "".into(), // we always convert to camelCase, so "_" will end up empty anyways Pat::Struct(s) => super::path_to_command(&mut s.path).ident.to_string(), Pat::TupleStruct(s) => super::path_to_command(&mut s.path).ident.to_string(), @@ -187,32 +328,22 @@ fn parse_arg(command: &Ident, arg: &FnArg, message: &Ident) -> syn::Result camelCase - if key.as_str().contains('_') { - key = snake_case_to_camel_case(key.as_str()); + match attributes.argument_case { + ArgumentCase::Camel => { + key = key.to_lower_camel_case(); + } + ArgumentCase::Snake => { + key = key.to_snake_case(); + } } - Ok(quote!(::tauri::command::CommandArg::from_command( - ::tauri::command::CommandItem { + let root = &attributes.root; + + Ok(quote!(#root::command::CommandArg::from_command( + #root::command::CommandItem { name: stringify!(#command), key: #key, message: &#message, } ))) } - -/// Convert a snake_case string into camelCase, no underscores will be left. -fn snake_case_to_camel_case(key: &str) -> String { - let mut camel = String::with_capacity(key.len()); - let mut to_upper = false; - - for c in key.chars() { - match c { - '_' => to_upper = true, - c if std::mem::take(&mut to_upper) => camel.push(c.to_ascii_uppercase()), - c => camel.push(c), - } - } - - camel -} diff --git a/core/tauri-macros/src/command_module.rs b/core/tauri-macros/src/command_module.rs deleted file mode 100644 index a8301a8a421d..000000000000 --- a/core/tauri-macros/src/command_module.rs +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use heck::ToSnakeCase; -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree}; - -use quote::{format_ident, quote, quote_spanned}; -use syn::{ - parse::{Parse, ParseStream}, - spanned::Spanned, - Data, DeriveInput, Error, Fields, FnArg, Ident, ItemFn, LitStr, Pat, Token, -}; - -pub fn generate_run_fn(input: DeriveInput) -> TokenStream { - let name = &input.ident; - let data = &input.data; - - let mut is_async = false; - let attrs = input.attrs; - for attr in attrs { - if attr.path.is_ident("cmd") { - let _ = attr.parse_args_with(|input: ParseStream| { - while let Some(token) = input.parse()? { - if let TokenTree::Ident(ident) = token { - is_async |= ident == "async"; - } - } - Ok(()) - }); - } - } - let maybe_await = if is_async { quote!(.await) } else { quote!() }; - let maybe_async = if is_async { quote!(async) } else { quote!() }; - - let mut matcher; - - match data { - Data::Enum(data_enum) => { - matcher = TokenStream2::new(); - - for variant in &data_enum.variants { - let variant_name = &variant.ident; - - let (fields_in_variant, variables) = match &variant.fields { - Fields::Unit => (quote_spanned! { variant.span() => }, quote!()), - Fields::Unnamed(fields) => { - let mut variables = TokenStream2::new(); - for i in 0..fields.unnamed.len() { - let variable_name = format_ident!("value{}", i); - variables.extend(quote!(#variable_name,)); - } - (quote_spanned! { variant.span() => (#variables) }, variables) - } - Fields::Named(fields) => { - let mut variables = TokenStream2::new(); - for field in &fields.named { - let ident = field.ident.as_ref().unwrap(); - variables.extend(quote!(#ident,)); - } - ( - quote_spanned! { variant.span() => { #variables } }, - variables, - ) - } - }; - - let mut variant_execute_function_name = format_ident!( - "{}", - variant_name.to_string().to_snake_case().to_lowercase() - ); - variant_execute_function_name.set_span(variant_name.span()); - - matcher.extend(quote_spanned! { - variant.span() => #name::#variant_name #fields_in_variant => #name::#variant_execute_function_name(context, #variables)#maybe_await.map(Into::into), - }); - } - } - _ => { - return Error::new( - Span::call_site(), - "CommandModule is only implemented for enums", - ) - .to_compile_error() - .into() - } - }; - - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let expanded = quote! { - impl #impl_generics #name #ty_generics #where_clause { - pub #maybe_async fn run(self, context: crate::endpoints::InvokeContext) -> crate::Result { - match self { - #matcher - } - } - } - }; - - TokenStream::from(expanded) -} - -/// Attributes for the module enum variant handler. -pub struct HandlerAttributes { - allowlist: Ident, - error_message: String, -} - -impl Parse for HandlerAttributes { - fn parse(input: ParseStream) -> syn::Result { - let allowlist = input.parse()?; - input.parse::()?; - let raw: LitStr = input.parse()?; - let error_message = raw.value(); - Ok(Self { - allowlist, - error_message, - }) - } -} - -pub struct HandlerTestAttributes { - allowlist: Ident, - error_message: String, - is_async: bool, -} - -impl Parse for HandlerTestAttributes { - fn parse(input: ParseStream) -> syn::Result { - let allowlist = input.parse()?; - input.parse::()?; - let error_message_raw: LitStr = input.parse()?; - let error_message = error_message_raw.value(); - let _ = input.parse::(); - let is_async = input - .parse::() - .map(|i| i == "async") - .unwrap_or_default(); - - Ok(Self { - allowlist, - error_message, - is_async, - }) - } -} - -pub fn command_handler(attributes: HandlerAttributes, function: ItemFn) -> TokenStream2 { - let allowlist = attributes.allowlist; - let error_message = attributes.error_message.as_str(); - let signature = function.sig.clone(); - - quote!( - #[cfg(#allowlist)] - #function - - #[cfg(not(#allowlist))] - #[allow(unused_variables)] - #[allow(unused_mut)] - #signature { - Err(crate::Error::ApiNotAllowlisted( - #error_message.to_string(), - )) - } - ) -} - -pub fn command_test(attributes: HandlerTestAttributes, function: ItemFn) -> TokenStream2 { - let allowlist = attributes.allowlist; - let is_async = attributes.is_async; - let error_message = attributes.error_message.as_str(); - let signature = function.sig.clone(); - let test_name = function.sig.ident.clone(); - let mut args = quote!(); - for arg in &function.sig.inputs { - if let FnArg::Typed(t) = arg { - if let Pat::Ident(i) = &*t.pat { - let ident = &i.ident; - args.extend(quote!(#ident,)) - } - } - } - - let response = if is_async { - quote!(crate::async_runtime::block_on( - super::Cmd::#test_name(crate::test::mock_invoke_context(), #args) - )) - } else { - quote!(super::Cmd::#test_name(crate::test::mock_invoke_context(), #args)) - }; - - quote!( - #[cfg(#allowlist)] - #function - - #[cfg(not(#allowlist))] - #[quickcheck_macros::quickcheck] - #signature { - if let Err(crate::Error::ApiNotAllowlisted(e)) = #response { - assert_eq!(e, #error_message); - } else { - panic!("unexpected response"); - } - } - ) -} diff --git a/core/tauri-macros/src/context.rs b/core/tauri-macros/src/context.rs index 337fe3a6098e..c2855eebb29d 100644 --- a/core/tauri-macros/src/context.rs +++ b/core/tauri-macros/src/context.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -11,7 +11,7 @@ use syn::{ LitStr, PathArguments, PathSegment, Token, }; use tauri_codegen::{context_codegen, get_config, ContextData}; -use tauri_utils::config::parse::does_supported_extension_exist; +use tauri_utils::config::parse::does_supported_file_name_exist; pub(crate) struct ContextItems { config_file: PathBuf, @@ -36,7 +36,7 @@ impl Parse for ContextItems { VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into(), }) .and_then(|path| { - if does_supported_extension_exist(&path) { + if does_supported_file_name_exist(&path) { Ok(path) } else { Err(format!( diff --git a/core/tauri-macros/src/lib.rs b/core/tauri-macros/src/lib.rs index b431b39ba31c..9d5616a8e5f9 100644 --- a/core/tauri-macros/src/lib.rs +++ b/core/tauri-macros/src/lib.rs @@ -1,13 +1,13 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use crate::context::ContextItems; use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput, ItemFn}; +use syn::{parse_macro_input, DeriveInput}; mod command; -mod command_module; +mod mobile; mod runtime; #[macro_use] @@ -24,9 +24,14 @@ pub fn command(attributes: TokenStream, item: TokenStream) -> TokenStream { command::wrapper(attributes, item) } +#[proc_macro_attribute] +pub fn mobile_entry_point(attributes: TokenStream, item: TokenStream) -> TokenStream { + mobile::entry_point(attributes, item) +} + /// Accepts a list of commands functions. Creates a handler that allows commands to be called from JS with invoke(). /// -/// # Example +/// # Examples /// ```rust,ignore /// use tauri_macros::{command, generate_handler}; /// #[command] @@ -75,32 +80,3 @@ pub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStre let input = parse_macro_input!(input as DeriveInput); runtime::default_runtime(attributes, input).into() } - -/// Adds a `run` method to an enum (one of the tauri endpoint modules). -/// The `run` method takes a `tauri::endpoints::InvokeContext` -/// and returns a `tauri::Result`. -/// It matches on each enum variant and call a method with name equal to the variant name, lowercased and snake_cased, -/// passing the the context and the variant's fields as arguments. -/// That function must also return the same `Result`. -#[doc(hidden)] -#[proc_macro_derive(CommandModule, attributes(cmd))] -pub fn derive_command_module(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - command_module::generate_run_fn(input) -} - -#[doc(hidden)] -#[proc_macro_attribute] -pub fn module_command_handler(attributes: TokenStream, input: TokenStream) -> TokenStream { - let attributes = parse_macro_input!(attributes as command_module::HandlerAttributes); - let input = parse_macro_input!(input as ItemFn); - command_module::command_handler(attributes, input).into() -} - -#[doc(hidden)] -#[proc_macro_attribute] -pub fn module_command_test(attributes: TokenStream, input: TokenStream) -> TokenStream { - let attributes = parse_macro_input!(attributes as command_module::HandlerTestAttributes); - let input = parse_macro_input!(input as ItemFn); - command_module::command_test(attributes, input).into() -} diff --git a/core/tauri-macros/src/mobile.rs b/core/tauri-macros/src/mobile.rs new file mode 100644 index 000000000000..eae55fa68ca9 --- /dev/null +++ b/core/tauri-macros/src/mobile.rs @@ -0,0 +1,83 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use std::env::var; +use syn::{parse_macro_input, spanned::Spanned, ItemFn}; + +fn get_env_var String>( + name: &str, + replacer: R, + error: &mut Option, + function: &ItemFn, +) -> TokenStream2 { + match var(name) { + Ok(value) => { + let ident = format_ident!("{}", replacer(value)); + quote!(#ident) + } + Err(_) => { + error.replace( + syn::Error::new( + function.span(), + format!("`{name}` env var not set, do you have a build script with tauri-build?",), + ) + .into_compile_error(), + ); + quote!() + } + } +} + +pub fn entry_point(_attributes: TokenStream, item: TokenStream) -> TokenStream { + let function = parse_macro_input!(item as ItemFn); + let function_name = &function.sig.ident; + + let mut error = None; + let domain = get_env_var("TAURI_ANDROID_PACKAGE_PREFIX", |r| r, &mut error, &function); + let app_name = get_env_var( + "CARGO_PKG_NAME", + |r| r.replace('-', "_"), + &mut error, + &function, + ); + + if let Some(e) = error { + quote!(#e).into() + } else { + quote!( + fn stop_unwind T, T>(f: F) -> T { + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) { + Ok(t) => t, + Err(err) => { + eprintln!("attempt to unwind out of `rust` with err: {:?}", err); + std::process::abort() + } + } + } + + #function + + fn _start_app() { + #[cfg(target_os = "ios")] + ::tauri::log_stdout(); + #[cfg(target_os = "android")] + { + ::tauri::android_binding!(#domain, #app_name, _start_app, ::tauri::wry); + } + stop_unwind(#function_name); + } + + #[cfg(not(target_os = "android"))] + #[no_mangle] + #[inline(never)] + pub extern "C" fn start_app() { + _start_app() + } + ) + .into() + } +} diff --git a/core/tauri-macros/src/runtime.rs b/core/tauri-macros/src/runtime.rs index 3fe0f0d2b005..800e6beb399a 100644 --- a/core/tauri-macros/src/runtime.rs +++ b/core/tauri-macros/src/runtime.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT diff --git a/core/tauri-runtime-wry/.license_template b/core/tauri-runtime-wry/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/core/tauri-runtime-wry/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/core/tauri-runtime-wry/CHANGELOG.md b/core/tauri-runtime-wry/CHANGELOG.md index bbcca2f137fc..ced4eff9033e 100644 --- a/core/tauri-runtime-wry/CHANGELOG.md +++ b/core/tauri-runtime-wry/CHANGELOG.md @@ -1,5 +1,391 @@ # Changelog +## \[0.13.0-alpha.5] + +- [`39f1b04f`](https://www.github.com/tauri-apps/tauri/commit/39f1b04f7be4966488484829cd54c8ce72a04200)([#6943](https://www.github.com/tauri-apps/tauri/pull/6943)) Moved the `event` JS APIs to a plugin. +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`cebd7526`](https://www.github.com/tauri-apps/tauri/commit/cebd75261ac71b98976314a450cb292eeeec1515)([#6728](https://www.github.com/tauri-apps/tauri/pull/6728)) Moved the `clipboard` feature to its own plugin in the plugins-workspace repository. +- [`3f17ee82`](https://www.github.com/tauri-apps/tauri/commit/3f17ee82f6ff21108806edb7b00500b8512b8dc7)([#6737](https://www.github.com/tauri-apps/tauri/pull/6737)) Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository. +- [`31444ac1`](https://www.github.com/tauri-apps/tauri/commit/31444ac196add770f2ad18012d7c18bce7538f22)([#6725](https://www.github.com/tauri-apps/tauri/pull/6725)) Update `wry` to `0.28` + +## \[0.13.0-alpha.4] + +- Added `android` configuration object under `tauri > bundle`. + - Bumped due to a bump in tauri-utils. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 + +## \[0.13.0-alpha.3] + +- Pull changes from Tauri 1.3 release. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[0.13.0-alpha.2] + +- Add `find_class`, `run_on_android_context` on `RuntimeHandle`. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 +- Allow a wry plugin to be registered at runtime. + - [ae296f3d](https://www.github.com/tauri-apps/tauri/commit/ae296f3de16fb6a8badbad5555075a5861681fe5) refactor(tauri-runtime-wry): register runtime plugin after run() ([#6478](https://www.github.com/tauri-apps/tauri/pull/6478)) on 2023-03-17 +- Added the `shadow` option when creating a window and `Window::set_shadow`. + - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08 +- Implemented `with_webview` on Android and iOS. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 + +## \[0.13.0-alpha.1] + +- Update gtk to 0.16. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Update wry to 0.26. + - [f0a1d9cd](https://www.github.com/tauri-apps/tauri/commit/f0a1d9cdbcfb645ce1c5f1cdd597f764991772cd) chore: update rfd and wry versions ([#6174](https://www.github.com/tauri-apps/tauri/pull/6174)) on 2023-02-03 + +## \[0.13.0-alpha.0] + +- Support `with_webview` for Android platform alowing execution of JNI code in context. + - [8ea87e9c](https://www.github.com/tauri-apps/tauri/commit/8ea87e9c9ca8ba4c7017c8281f78aacd08f45785) feat(android): with_webview access for jni execution ([#5148](https://www.github.com/tauri-apps/tauri/pull/5148)) on 2022-09-08 + +## \[0.14.0] + +### New Features + +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` methods to `WindowBuilder`. +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods to the `Dispatch` trait. +- [`000104bc`](https://www.github.com/tauri-apps/tauri/commit/000104bc3bc0c9ff3d20558ab9cf2080f126e9e0)([#6472](https://www.github.com/tauri-apps/tauri/pull/6472)) Add `Window::is_focused` getter. + +### Enhancements + +- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85. + +### Bug Fixes + +- [`b41b57eb`](https://www.github.com/tauri-apps/tauri/commit/b41b57ebb27befd366db5befaafb6043c18fdfef)([#7105](https://www.github.com/tauri-apps/tauri/pull/7105)) Fix panics when registering an invalid global shortcuts or checking it is registered and return proper errors instead. + +### What's Changed + +- [`076e1a81`](https://www.github.com/tauri-apps/tauri/commit/076e1a81a50468e3dfb34ae9ca7e77c5e1758daa)([#7119](https://www.github.com/tauri-apps/tauri/pull/7119)) Use `u32` instead of `u64` for js event listener ids + +## \[0.13.0] + +- Added the `additional_browser_args` option when creating a window. + - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14 +- Added the `content_protected` option when creating a window and `Window::set_content_protected` to change it at runtime. + - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13 +- Added `Builder::device_event_filter` and `App::set_device_event_filter` methods. + - [73fd60ee](https://www.github.com/tauri-apps/tauri/commit/73fd60eef2b60f5dc84525ef9c315f4d80c4414f) expose set_device_event_filter in tauri ([#5562](https://www.github.com/tauri-apps/tauri/pull/5562)) on 2022-12-13 +- Fixes tray events not being delivered. + - [138cb8d7](https://www.github.com/tauri-apps/tauri/commit/138cb8d739b15bccdb388e555c20f17ffe16318c) fix(tauri-runtime-wry): tray event listener not registered ([#6270](https://www.github.com/tauri-apps/tauri/pull/6270)) on 2023-02-14 +- Add `is_minimized()` window method. + - [62144ef3](https://www.github.com/tauri-apps/tauri/commit/62144ef3be63b237869e511826edfb938e2c7174) feat: add is_minimized (fix [#3878](https://www.github.com/tauri-apps/tauri/pull/3878)) ([#5618](https://www.github.com/tauri-apps/tauri/pull/5618)) on 2022-12-13 +- Disable cursor mouse events on Linux. + - [8c842a54](https://www.github.com/tauri-apps/tauri/commit/8c842a54a6f3dc5327b4d737df7123dcddaa5769) feature: disable mouse event when building windows on Linux, closes [#5913](https://www.github.com/tauri-apps/tauri/pull/5913) ([#6025](https://www.github.com/tauri-apps/tauri/pull/6025)) on 2023-01-16 +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 +- Pin raw-window-handle to 0.5.0 to keep MSRV. + - [c46c09f3](https://www.github.com/tauri-apps/tauri/commit/c46c09f31d9f5169ca8a7e62406a9ea170e3a5c5) fix(deps): pin raw-window-handle to 0.5.0 ([#6480](https://www.github.com/tauri-apps/tauri/pull/6480)) on 2023-03-17 +- Add `title` getter on window. + - [233e43b0](https://www.github.com/tauri-apps/tauri/commit/233e43b0c34fada1ca025378533a0b76931a6540) feat: add `title` getter on window, closes [#5023](https://www.github.com/tauri-apps/tauri/pull/5023) ([#5515](https://www.github.com/tauri-apps/tauri/pull/5515)) on 2022-12-13 +- Added `TrayHandle::set_tooltip` and `SystemTray::with_tooltip`. + - [2265e097](https://www.github.com/tauri-apps/tauri/commit/2265e09718f6ebfeb1d200f11e1e1e069075af6e) feat(windows): implement `with_tooltip` ([#5938](https://www.github.com/tauri-apps/tauri/pull/5938)) on 2023-01-01 +- Added window's `url()` getter. + - [d17027e1](https://www.github.com/tauri-apps/tauri/commit/d17027e1a0db3e8c5ae81fc4f472c5918fbce611) feat: expose url method ([#5914](https://www.github.com/tauri-apps/tauri/pull/5914)) on 2022-12-26 +- On Windows, change webview theme based on Window theme for more accurate `prefers-color-scheme` support. + - [7a8d570d](https://www.github.com/tauri-apps/tauri/commit/7a8d570db72667367eb24b75ddc5dd07a968f7c0) fix: sync webview theme with window theme on Windows, closes [#5802](https://www.github.com/tauri-apps/tauri/pull/5802) ([#5874](https://www.github.com/tauri-apps/tauri/pull/5874)) on 2022-12-27 +- On Windows, Fix missing `WindowEvent::Focused` in `App::run` callback. + - [ff4ea1ea](https://www.github.com/tauri-apps/tauri/commit/ff4ea1eabbf2874b113c6b4698002929bbac737a) fix: dispatch focus event to app.run on Windows, closes [#6460](https://www.github.com/tauri-apps/tauri/pull/6460) ([#6504](https://www.github.com/tauri-apps/tauri/pull/6504)) on 2023-03-31 +- Implement the webview navigation handler. + - [3f35b452](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42) Expose wry navigation_handler via WindowBuilder closes [#4080](https://www.github.com/tauri-apps/tauri/pull/4080) ([#5686](https://www.github.com/tauri-apps/tauri/pull/5686)) on 2022-12-27 + +## \[0.12.3] + +- Block remote URLs from accessing the IPC. + - [9c0593c33](https://www.github.com/tauri-apps/tauri/commit/9c0593c33af52cd9e00ec784d15f63efebdf039c) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.12.2] + +- Fix compatibility with older Linux distributions. + - [b490308c](https://www.github.com/tauri-apps/tauri/commit/b490308c8897b893292951754607c2253abbc6e1) fix(core): compilation error on older Linux versions, fixes [#5684](https://www.github.com/tauri-apps/tauri/pull/5684) ([#5697](https://www.github.com/tauri-apps/tauri/pull/5697)) on 2022-11-28 +- Update wry to 0.23. + - [fdcd7733](https://www.github.com/tauri-apps/tauri/commit/fdcd77338c1a3a7ef8a8ea1907351c5c350ea7ba) chore(deps): update wry to 0.23 on 2022-12-08 + +## \[0.12.1] + +- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`. + - Bumped due to a bump in tauri-utils. + - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18 + +## \[0.12.0] + +- Add `accept_first_mouse` option for macOS windows. + - [95f467ad](https://www.github.com/tauri-apps/tauri/commit/95f467add51448319983c54e2f382c7c09fb72d6) feat(core): add window `accept_first_mouse` option, closes [#5347](https://www.github.com/tauri-apps/tauri/pull/5347) ([#5374](https://www.github.com/tauri-apps/tauri/pull/5374)) on 2022-10-17 +- Disable automatic window tabbing on macOS when the `tabbing_identifier` option is not defined, the window is transparent or does not have decorations. + - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19 +- Drop the WebContext when the WebView is dropped. + - [9d8b3774](https://www.github.com/tauri-apps/tauri/commit/9d8b377481abf975dc37f9050d2ac7b63ce353e9) feat(tauri-runtime-wry): drop the WebContext on WebView drop ([#5240](https://www.github.com/tauri-apps/tauri/pull/5240)) on 2022-10-19 +- Readd the option to create an unfocused window via the `focused` method. The `focus` function has been deprecated. + - [4036e15f](https://www.github.com/tauri-apps/tauri/commit/4036e15f5af933bdc0d0913508b5103958afc143) feat(core): reimplement window initial focus flag, closes [#5120](https://www.github.com/tauri-apps/tauri/pull/5120) ([#5338](https://www.github.com/tauri-apps/tauri/pull/5338)) on 2022-10-08 +- Add `hidden_title` option for macOS windows. + - [321f3fed](https://www.github.com/tauri-apps/tauri/commit/321f3fed19df40c1223099bce953332b7f00f7a9) feat(macos): `title_bar_style` and `hidden_title` window options, closes [#2663](https://www.github.com/tauri-apps/tauri/pull/2663) ([#3965](https://www.github.com/tauri-apps/tauri/pull/3965)) on 2022-09-30 +- Custom protocol headers are now implemented on Linux when running on webkit2gtk 2.36 or above. + - [357480f4](https://www.github.com/tauri-apps/tauri/commit/357480f4ae43aa8da99f7ba61ae2ee51b4552c60) feat(core): custom protocol headers on Linux, closes [#4496](https://www.github.com/tauri-apps/tauri/pull/4496) ([#5421](https://www.github.com/tauri-apps/tauri/pull/5421)) on 2022-10-17 +- Added `Runtime::show()`, `RuntimeHandle::show()`, `Runtime::hide()`, `RuntimeHandle::hide()` for hiding/showing the entire application on macOS. + - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03 +- Fix regression in `SystemTray::with_menu_on_left_click` + - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05 + - [7bbf167c](https://www.github.com/tauri-apps/tauri/commit/7bbf167c1c84493ea6e2353f720edafd7daa47e4) Apply Version Updates From Current Changes ([#4560](https://www.github.com/tauri-apps/tauri/pull/4560)) on 2022-07-06 + - [63011ca8](https://www.github.com/tauri-apps/tauri/commit/63011ca84e7a22c8c0d8bd1c1be6592140f93ff2) fix(macos): fix regression in `with_menu_on_left_click`, closes [#5220](https://www.github.com/tauri-apps/tauri/pull/5220) ([#5235](https://www.github.com/tauri-apps/tauri/pull/5235)) on 2022-09-30 +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 +- Added `tabbing_identifier` to the window builder on macOS. + - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19 +- Add `title_bar_style` option for macOS windows. + - [321f3fed](https://www.github.com/tauri-apps/tauri/commit/321f3fed19df40c1223099bce953332b7f00f7a9) feat(macos): `title_bar_style` and `hidden_title` window options, closes [#2663](https://www.github.com/tauri-apps/tauri/pull/2663) ([#3965](https://www.github.com/tauri-apps/tauri/pull/3965)) on 2022-09-30 +- Fix regression introduce in tauri@1.1 which prevented removing tray icon when the app exits on Windows. + - [f756cd5e](https://www.github.com/tauri-apps/tauri/commit/f756cd5e7ecc86f178f8d602eded1e1b6ecb51f3) fix(core): wait for tray cleanup before exiting app, closes [#5244](https://www.github.com/tauri-apps/tauri/pull/5244) ([#5245](https://www.github.com/tauri-apps/tauri/pull/5245)) on 2022-10-04 +- Added methods to set the system tray title on macOS. + - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30 +- Added the `user_agent` option when creating a window. + - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02 + +## \[0.11.2] + +- Block remote URLs from accessing the IPC. + - [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.11.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - Bumped due to a bump in tauri-utils. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[0.11.0] + +- Ignore window events with unknown IDs. + - [0668dd42](https://www.github.com/tauri-apps/tauri/commit/0668dd42204b163f11aaf31f45106c8551f15942) fix(tauri-runtime-wry): ignore events on unknown windows on 2022-08-29 +- Implement theme APIs for Linux. + - [f21cbecd](https://www.github.com/tauri-apps/tauri/commit/f21cbecdeb3571ac4ad971b9a865ff62a131a176) feat(core): implement theme APIs for Linux ([#4808](https://www.github.com/tauri-apps/tauri/pull/4808)) on 2022-08-02 +- Changed `windows` map to be stored in a `RefCell` instead of a `Mutex`. + - [64546cb9](https://www.github.com/tauri-apps/tauri/commit/64546cb9cca2fe56cf81cfc4aaf85c4e1d58877c) refactor: use RefCell instead of Mutex for windows map, closes [#4870](https://www.github.com/tauri-apps/tauri/pull/4870) ([#4909](https://www.github.com/tauri-apps/tauri/pull/4909)) on 2022-08-10 +- Added APIs to create a system tray at runtime. + - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09 +- Update windows to 0.39.0 and webview2-com to 0.19.1. + - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03 + +## \[0.10.3] + +- Block remote URLs from accessing the IPC. + - [fa90214b0](https://www.github.com/tauri-apps/tauri/commit/fa90214b052b1a5d38d54fbf1ca422b4c37cfd1f) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.10.2] + +- Disable drag-n-drop of tao based on `fileDropEnabled` value. + - [a1d569bb](https://www.github.com/tauri-apps/tauri/commit/a1d569bbc9cfdd58258916df594911e1c512a75e) fix(core): disable tao's drag-n-drop based on `fileDropEnabled`, closes [#4580](https://www.github.com/tauri-apps/tauri/pull/4580) ([#4592](https://www.github.com/tauri-apps/tauri/pull/4592)) on 2022-07-05 +- Added option to disable tray menu on left click on macOS. + - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05 + +## \[0.10.1] + +- Fixes a deadlock on the file drop handler. + - [23a48007](https://www.github.com/tauri-apps/tauri/commit/23a48007c0df7346fa45c76dfaf9235a157f59ec) fix(tauri-runtime-wry): deadlock on file drop, closes [#4527](https://www.github.com/tauri-apps/tauri/pull/4527) ([#4535](https://www.github.com/tauri-apps/tauri/pull/4535)) on 2022-06-30 +- Send theme value only once on the getter function implementation on macOS. + - [63841c10](https://www.github.com/tauri-apps/tauri/commit/63841c10609c3d7337ba6cd68ae126b18987014d) fix(tauri-runtime-wry): do not send theme twice on macOS, closes [#4532](https://www.github.com/tauri-apps/tauri/pull/4532) ([#4540](https://www.github.com/tauri-apps/tauri/pull/4540)) on 2022-06-30 +- Fixes a deadlock when the window focus change on Windows. + - [185b0e31](https://www.github.com/tauri-apps/tauri/commit/185b0e314ece9563cd7c83a16466b2b8b9167eb3) fix(tauri-runtime-wry): deadlock when window focus change, closes [#4533](https://www.github.com/tauri-apps/tauri/pull/4533) ([#4539](https://www.github.com/tauri-apps/tauri/pull/4539)) on 2022-06-30 + +## \[0.10.0] + +- Implement `raw_window_handle::HasRawWindowHandle` on Linux. + - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26 +- Moved the window and menu event listeners to the window struct. + - [46196fe9](https://www.github.com/tauri-apps/tauri/commit/46196fe922f4f1b38057155c6113236cfa4b3597) refactor(tauri-runtime-wry): move window and menu listeners to window ([#4485](https://www.github.com/tauri-apps/tauri/pull/4485)) on 2022-06-27 +- Refactored the `tauri-runtime-wry` plugin interface. + - [e39e2999](https://www.github.com/tauri-apps/tauri/commit/e39e2999e0ab1843a8195ba83aea3d6de705c3d8) refactor(tauri-runtime-wry): enhance plugin interface ([#4476](https://www.github.com/tauri-apps/tauri/pull/4476)) on 2022-06-27 +- Removed the `hwnd` and `ns_window` functions from `Dispatch` in favor of `raw_window_handle`. + - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26 +- The theme API is now implemented on macOS 10.14+. + - [6d94ce42](https://www.github.com/tauri-apps/tauri/commit/6d94ce42353204a02fe9c82ed397d349439f75ef) feat(core): theme is now implemented on macOS ([#4380](https://www.github.com/tauri-apps/tauri/pull/4380)) on 2022-06-17 +- Suppress unused variable warning in release builds. + - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22 +- Update tao to 0.12 and wry to 0.19. + - [f6edc6df](https://www.github.com/tauri-apps/tauri/commit/f6edc6df29b1c45b483fa87c481a3b95730b131b) chore(deps): update tao to 0.12, wry to 0.19, closes [#3220](https://www.github.com/tauri-apps/tauri/pull/3220) ([#4502](https://www.github.com/tauri-apps/tauri/pull/4502)) on 2022-06-28 +- Fixes deadlocks when using window setters in the main thread. + - [123f6e69](https://www.github.com/tauri-apps/tauri/commit/123f6e69f60ca6d4b2fd738ca3ff5cf016d8e814) fix(tauri-runtime-wry): release windows lock immediately, closes [#4390](https://www.github.com/tauri-apps/tauri/pull/4390) ([#4392](https://www.github.com/tauri-apps/tauri/pull/4392)) on 2022-06-19 + +## \[0.9.0] + +- Upgrade to `stable`! + - Bumped due to a bump in tauri-utils. + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[0.8.1] + +- Add `Menu::os_default` which will create a menu filled with default menu items and submenus. + - Bumped due to a bump in tauri-runtime. + - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15 + +## \[0.8.0] + +- Removed `TrayIcon` and renamed `WindowIcon` to `Icon`, a shared type for both icons. + - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14 + +## \[0.7.0] + +- **Breaking change**: Removed the `gtk-tray` and `ayatana-tray` Cargo features. + - [6216eb49](https://www.github.com/tauri-apps/tauri/commit/6216eb49e72863bfb6d4c9edb8827b21406ac393) refactor(core): drop `ayatana-tray` and `gtk-tray` Cargo features ([#4247](https://www.github.com/tauri-apps/tauri/pull/4247)) on 2022-06-02 + +## \[0.6.0] + +- Account the monitor position when centering a window. + - [a7a9fde1](https://www.github.com/tauri-apps/tauri/commit/a7a9fde16fb7c35d48d4f97e83ff95b8baf9e090) fix(core): account for monitor position when centering window ([#4166](https://www.github.com/tauri-apps/tauri/pull/4166)) on 2022-05-21 +- Update `windows-rs` to `0.37.0`, which requires Rust 1.61.0+. + - [2326be39](https://www.github.com/tauri-apps/tauri/commit/2326be39821890cdd4de76e7029a531424dcb26f) feat(core): update windows-rs to 0.37.0 ([#4199](https://www.github.com/tauri-apps/tauri/pull/4199)) on 2022-05-24 + +## \[0.5.2] + +- Use the event loop proxy to create a window so it doesn't deadlock on Windows. + - [61e37652](https://www.github.com/tauri-apps/tauri/commit/61e37652b931520424d6a93a134e67893703d992) fix(core): deadlock when creating window from IPC handler, closes [#4121](https://www.github.com/tauri-apps/tauri/pull/4121) ([#4123](https://www.github.com/tauri-apps/tauri/pull/4123)) on 2022-05-13 + +## \[0.5.1] + +- Added the `plugin` method to the `Wry` runtime, allowing extensions to the event loop. + - [c8e0e5b9](https://www.github.com/tauri-apps/tauri/commit/c8e0e5b97d542e549b37be08b545515c862af0e5) feat(tauri-runtime-wry): add plugin API ([#4094](https://www.github.com/tauri-apps/tauri/pull/4094)) on 2022-05-10 +- Update wry to 0.16.2 and webkit2gtk to 0.18.0. + - [71a553b7](https://www.github.com/tauri-apps/tauri/commit/71a553b715312e2bcceb963c83e42cffca7a63bc) chore(deps): update wry to 0.16.2, webkit2gtk to 0.18.0 ([#4099](https://www.github.com/tauri-apps/tauri/pull/4099)) on 2022-05-10 + +## \[0.5.0] + +- The file drop event payloads are now percent-decoded. + - [a0ecd81a](https://www.github.com/tauri-apps/tauri/commit/a0ecd81a934e1aa8935151a74cad686786054204) fix(core): percent decode file drop payloads, closes [#4034](https://www.github.com/tauri-apps/tauri/pull/4034) ([#4035](https://www.github.com/tauri-apps/tauri/pull/4035)) on 2022-05-03 +- Fixes a crash when using the menu with the inspector window focused on macOS. In this case the `window_id` will be the id of the first app window. + - [891eb748](https://www.github.com/tauri-apps/tauri/commit/891eb748cf590895dc3f1666f8dbd6082b21e04e) fix(tauri-runtime-wry): menu even panic on macOS inspector, closes [#3875](https://www.github.com/tauri-apps/tauri/pull/3875) ([#4027](https://www.github.com/tauri-apps/tauri/pull/4027)) on 2022-05-02 +- Fixes a freeze when calling `set_size` in the main thread on Windows. + - [8f259f4e](https://www.github.com/tauri-apps/tauri/commit/8f259f4ef89be3da11b57222c8b66af9487ab736) fix(core): use EventLoopProxy to prevent set_size freeze closes [#3990](https://www.github.com/tauri-apps/tauri/pull/3990) ([#4014](https://www.github.com/tauri-apps/tauri/pull/4014)) on 2022-04-30 +- Expose methods to access the underlying native handles of the webview. + - [c82b4761](https://www.github.com/tauri-apps/tauri/commit/c82b4761e1660592472dc55308ad69d9efc5855b) feat(core): expose `with_webview` API to access the platform webview ([#4058](https://www.github.com/tauri-apps/tauri/pull/4058)) on 2022-05-04 + +## \[0.4.0] + +- \**Breaking change::* Added the `clipboard` Cargo feature. + - [24e4ff20](https://www.github.com/tauri-apps/tauri/commit/24e4ff208ee0fe1a4cc5b10667ea0922ac63dfb5) refactor(core): add clipboard Cargo feature, enhancing binary size ([#3957](https://www.github.com/tauri-apps/tauri/pull/3957)) on 2022-04-24 +- Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`. + - [c54ddfe9](https://www.github.com/tauri-apps/tauri/commit/c54ddfe9338e7eb90b4d5b02dfde687d432d5bc1) feat: expose window cursor APIs, closes [#3888](https://www.github.com/tauri-apps/tauri/pull/3888) [#3890](https://www.github.com/tauri-apps/tauri/pull/3890) ([#3935](https://www.github.com/tauri-apps/tauri/pull/3935)) on 2022-04-21 +- Fixes a panic when using the `create_tao_window` API. + - [320329a9](https://www.github.com/tauri-apps/tauri/commit/320329a9a7d8a249c0fc9dee6db5669057ca8b39) fix(core): insert to webview_id_map on tao window creation, closes [#3883](https://www.github.com/tauri-apps/tauri/pull/3883) ([#3932](https://www.github.com/tauri-apps/tauri/pull/3932)) on 2022-04-21 +- Fixes a panic when a menu event is triggered when all windows are minimized on macOS. + - [70ff55c1](https://www.github.com/tauri-apps/tauri/commit/70ff55c1aa69ed59cd2a78d865e1cb398ef2a4ba) fix(core): panic on menu event with minimized windows, closes [#3902](https://www.github.com/tauri-apps/tauri/pull/3902) ([#3918](https://www.github.com/tauri-apps/tauri/pull/3918)) on 2022-04-20 +- Fixes a rendering issue when resizing the window with the devtools open. + - [80b714af](https://www.github.com/tauri-apps/tauri/commit/80b714af6b31365b9026bc92f8631b1721950447) fix: rendering issue when resizing with devtools open closes [#3914](https://www.github.com/tauri-apps/tauri/pull/3914) [#3814](https://www.github.com/tauri-apps/tauri/pull/3814) ([#3915](https://www.github.com/tauri-apps/tauri/pull/3915)) on 2022-04-19 +- \**Breaking change::* Added the `global-shortcut` Cargo feature. + - [e11878bc](https://www.github.com/tauri-apps/tauri/commit/e11878bcf7174b261a1fa146fc7d564d12e6312a) refactor(core): add global-shortcut Cargo feature, enhancing binary size ([#3956](https://www.github.com/tauri-apps/tauri/pull/3956)) on 2022-04-24 +- Added `WindowEvent::ThemeChanged(theme)`. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Added `theme` getter on `Window`. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Added `theme` setter to the WindowBuilder. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Create webview immediately when executed in the main thread. + - [fa2baba7](https://www.github.com/tauri-apps/tauri/commit/fa2baba76c8f59c81f2a2f7139033a09d14d89da) feat(core): create webview immediately when running in main thread ([#3891](https://www.github.com/tauri-apps/tauri/pull/3891)) on 2022-04-12 + +## \[0.3.5] + +- Fixes `WindowEvent::Destroyed` not firing. + - [169b5035](https://www.github.com/tauri-apps/tauri/commit/169b5035a93e3f33a420d4b2b0f8943e6404e07f) fix(core): actually fire `WindowEvent::Destroyed` ([#3797](https://www.github.com/tauri-apps/tauri/pull/3797)) on 2022-03-28 + +## \[0.3.4] + +- Added `close_devtools` and `is_devtools_open` APIs to the `Dispatch` trait. + - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28 +- Emit `RunEvent::Exit` on `tao::event::Event::LoopDestroyed` instead of after `RunEvent::ExitRequested`. + - [3c4ee7c9](https://www.github.com/tauri-apps/tauri/commit/3c4ee7c997fa3ff696bcfd5b8c82fecaca16bf49) refactor(wry): emit `RunEvent::Exit` on `Event::LoopDestroyed` ([#3785](https://www.github.com/tauri-apps/tauri/pull/3785)) on 2022-03-27 +- **Breaking change:** The `MenuItem::About` variant is now associated with a tuple value `(String, AboutMetadata)`. + - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28 +- Support window parenting on macOS + - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23 +- The file drop event is now part of the `WindowEvent` enum instead of a having a dedicated handler. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 +- **Breaking change:** Use the dedicated `WindowEvent` enum on `RunEvent`. + - [edad9f4f](https://www.github.com/tauri-apps/tauri/commit/edad9f4f55dcc69a06cd9d6d5a5068c94ecb77dd) refactor(core): add `RunEvent::WindowEvent` ([#3793](https://www.github.com/tauri-apps/tauri/pull/3793)) on 2022-03-28 +- Added `create_proxy` to the `Runtime` and `RuntimeHandle` traits. + - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15 +- Allow specifying a user event type for the event loop message. + - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15 +- Use a random window id instead of `tao::window::WindowId` to not block the thread waiting for the event loop to process the window creation. + - [7cd39c70](https://www.github.com/tauri-apps/tauri/commit/7cd39c70c9ecd62cc9b60d0ab93f10ce0a6dd8b4) refactor(core): use random window id to simplify window creation, closes [#3645](https://www.github.com/tauri-apps/tauri/pull/3645) [#3597](https://www.github.com/tauri-apps/tauri/pull/3597) ([#3684](https://www.github.com/tauri-apps/tauri/pull/3684)) on 2022-03-15 +- Update `wry` to `0.14` and `tao` to `0.7`. + - [f2d24ef2](https://www.github.com/tauri-apps/tauri/commit/f2d24ef2fbd95ec7d3433ba651964f4aa3b7f48c) chore(deps): update wry ([#1482](https://www.github.com/tauri-apps/tauri/pull/1482)) on 2021-04-14 + - [e267ebf1](https://www.github.com/tauri-apps/tauri/commit/e267ebf1f1009b99829e0a7d71519925f5792f9f) Apply Version Updates From Current Changes ([#1486](https://www.github.com/tauri-apps/tauri/pull/1486)) on 2021-04-14 + - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28 +- Added the `WindowEvent::FileDrop` variant. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 + +## \[0.3.3] + +- Fixes a deadlock on the `Focused` event when the window is not visible. + - [c08cc6d5](https://www.github.com/tauri-apps/tauri/commit/c08cc6d50041ec887d3070c41bb2c793dbac5155) fix(core): deadlock on focus events with invisible window,[#3534](https://www.github.com/tauri-apps/tauri/pull/3534) ([#3622](https://www.github.com/tauri-apps/tauri/pull/3622)) on 2022-03-06 +- **Breaking change:** Move `ico` and `png` parsing behind `icon-ico` and `icon-png` Cargo features. + - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05 +- Print a warning to stderr if the window transparency has been set to true but `macos-private-api` is not enabled. + - [080755b5](https://www.github.com/tauri-apps/tauri/commit/080755b5377a3c0a17adf1d03e63555350422f0a) feat(core): warn if private APIs are not enabled, closes [#3481](https://www.github.com/tauri-apps/tauri/pull/3481) ([#3511](https://www.github.com/tauri-apps/tauri/pull/3511)) on 2022-02-19 + +## \[0.3.2] + +- Fix requirements for `RuntimeHandle`, `ClipboardManager`, `GlobalShortcutHandle` and `TrayHandle`. + - Bumped due to a bump in tauri-runtime. + - [84895a9c](https://www.github.com/tauri-apps/tauri/commit/84895a9cd270fc743e236d0f4d4cd6210b24a30f) fix(runtime): trait requirements ([#3489](https://www.github.com/tauri-apps/tauri/pull/3489)) on 2022-02-17 + +## \[0.3.1] + +- Change default value for the `freezePrototype` configuration to `false`. + - Bumped due to a bump in tauri-utils. + - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12 + +## \[0.3.0] + +- Fix `window.center` panic when window size is bigger than screen size. + - [76ce9f61](https://www.github.com/tauri-apps/tauri/commit/76ce9f61dd3c5bdd589c7557543894e1f770dd16) fix(core): fix `window.center` panic when window size > screen, closes [#2978](https://www.github.com/tauri-apps/tauri/pull/2978) ([#3002](https://www.github.com/tauri-apps/tauri/pull/3002)) on 2021-12-09 +- Enable non-session cookie persistence on Linux. + - [d7c02a30](https://www.github.com/tauri-apps/tauri/commit/d7c02a30a56de79100804969138b379e703f0e07) feat(core): persist non-session cookies on Linux ([#3052](https://www.github.com/tauri-apps/tauri/pull/3052)) on 2021-12-09 +- Fixes a deadlock when creating a window from a menu event handler. + - [9c82006b](https://www.github.com/tauri-apps/tauri/commit/9c82006b2fe166d20510183e36cee099bf96e8d9) fix(core): deadlock when creating window from menu handler, closes [#3110](https://www.github.com/tauri-apps/tauri/pull/3110) ([#3126](https://www.github.com/tauri-apps/tauri/pull/3126)) on 2021-12-28 +- Fixes `WindowEvent::Focus` and `WindowEvent::Blur` events not firing. + - [3b33d67a](https://www.github.com/tauri-apps/tauri/commit/3b33d67aa4f48dcf4e32b3b8a5f45e83808efc2d) fix: re-adding focus/blur events for linux and macos (fix [#2485](https://www.github.com/tauri-apps/tauri/pull/2485)) ([#2489](https://www.github.com/tauri-apps/tauri/pull/2489)) on 2021-08-24 +- Use webview's inner_size instead of window's value to get the correct size on macOS. + - [4c0c780e](https://www.github.com/tauri-apps/tauri/commit/4c0c780e00d8851be38cb1c22f636d9e4ed34a23) fix(core): window's inner_size usage, closes [#2187](https://www.github.com/tauri-apps/tauri/pull/2187) ([#2690](https://www.github.com/tauri-apps/tauri/pull/2690)) on 2021-09-29 +- Reimplement `remove_system_tray` on Windows to drop the `SystemTray` to run its cleanup code. + - [a03b8554](https://www.github.com/tauri-apps/tauri/commit/a03b85545a4b0b61a598a43eabe96e03565dcaf0) fix(core): tray not closing on Windows ([#3351](https://www.github.com/tauri-apps/tauri/pull/3351)) on 2022-02-07 +- Replace `WindowBuilder`'s `has_menu` with `get_menu`. + - [ac37b56e](https://www.github.com/tauri-apps/tauri/commit/ac37b56ef43c9e97039967a5fd99f0d2dccb5b5a) fix(core): menu id map not reflecting the current window menu ([#2726](https://www.github.com/tauri-apps/tauri/pull/2726)) on 2021-10-08 +- Fix empty header from CORS on Linux. + - [b48487e6](https://www.github.com/tauri-apps/tauri/commit/b48487e6a7b33f5a352e542fae21a2efd53ce295) Fix empty header from CORS on Linux, closes [#2327](https://www.github.com/tauri-apps/tauri/pull/2327) ([#2762](https://www.github.com/tauri-apps/tauri/pull/2762)) on 2021-10-18 +- The `run_return` API is now available on Linux. + - [8483fde9](https://www.github.com/tauri-apps/tauri/commit/8483fde975aac8833d2ce426e42fb40aeaeecba9) feat(core): expose `run_return` on Linux ([#3352](https://www.github.com/tauri-apps/tauri/pull/3352)) on 2022-02-07 +- Allow window, global shortcut and clipboard APIs to be called on the main thread. + - [2812c446](https://www.github.com/tauri-apps/tauri/commit/2812c4464b93a365ab955935d05b5cea8cb03aab) feat(core): window, shortcut and clipboard API calls on main thread ([#2659](https://www.github.com/tauri-apps/tauri/pull/2659)) on 2021-09-26 + - [d24fd8d1](https://www.github.com/tauri-apps/tauri/commit/d24fd8d10242da3da143a971d976b42ec4de6079) feat(tauri-runtime-wry): allow window creation and closing on the main thread ([#2668](https://www.github.com/tauri-apps/tauri/pull/2668)) on 2021-09-27 +- Change event loop callbacks definition to allow callers to move in mutable values. + - [bdbf905e](https://www.github.com/tauri-apps/tauri/commit/bdbf905e5d802b58693d2bd27582ce4269faf79c) Transformed event-loop callback to FnMut to allow mutable values ([#2667](https://www.github.com/tauri-apps/tauri/pull/2667)) on 2021-09-27 +- **Breaking change:** Add `macos-private-api` feature flag, enabled via `tauri.conf.json > tauri > macOSPrivateApi`. + - [6ac21b3c](https://www.github.com/tauri-apps/tauri/commit/6ac21b3cef7f14358df38cc69ea3d277011accaf) feat: add private api feature flag ([#7](https://www.github.com/tauri-apps/tauri/pull/7)) on 2022-01-09 +- Refactor `create_tao_window` API to return `Weak` instead of `Arc`. + - [c1494b35](https://www.github.com/tauri-apps/tauri/commit/c1494b353233c6a9552d7ace962fdf8d5b1f199a) refactor: return Weak on create_tao_window on 2021-08-31 +- Added `any_thread` constructor on the `Runtime` trait (only possible on Linux and Windows). + - [af44bf81](https://www.github.com/tauri-apps/tauri/commit/af44bf8168310cf77fbe102a53e7c433f11641a3) feat(core): allow app run on any thread on Linux & Windows, closes [#3172](https://www.github.com/tauri-apps/tauri/pull/3172) ([#3353](https://www.github.com/tauri-apps/tauri/pull/3353)) on 2022-02-07 +- Added `run_on_main_thread` API on `RuntimeHandle`. + - [53fdfe52](https://www.github.com/tauri-apps/tauri/commit/53fdfe52bb30d52653c72ca9f42506c3863dcf4a) feat(core): expose `run_on_main_thread` API ([#2711](https://www.github.com/tauri-apps/tauri/pull/2711)) on 2021-10-04 +- **Breaking change:** Renamed the `RPC` interface to `IPC`. + - [3420aa50](https://www.github.com/tauri-apps/tauri/commit/3420aa5031b3274a95c6c5fa0f8683ca13213396) refactor: IPC handler \[TRI-019] ([#9](https://www.github.com/tauri-apps/tauri/pull/9)) on 2022-01-09 +- Added `open_devtools` to the `Dispatcher` trait. + - [55aa22de](https://www.github.com/tauri-apps/tauri/commit/55aa22de80c3de873e29bcffcb5b2fe236a637a6) feat(core): add `Window#open_devtools` API, closes [#1213](https://www.github.com/tauri-apps/tauri/pull/1213) ([#3350](https://www.github.com/tauri-apps/tauri/pull/3350)) on 2022-02-07 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 +- Replace all of the `winapi` crate references with the `windows` crate, and replace `webview2` and `webview2-sys` with `webview2-com` and `webview2-com-sys` built with the `windows` crate. This goes along with updates to the TAO and WRY `next` branches. + - [bb00d5bd](https://www.github.com/tauri-apps/tauri/commit/bb00d5bd6c9dfcb6bdd0d308dadb70e6c6aafe5c) Replace winapi with windows crate and use webview2-com instead of webview2 ([#2615](https://www.github.com/tauri-apps/tauri/pull/2615)) on 2021-09-24 +- Update the `windows` crate to 0.25.0, which comes with pre-built libraries. WRY and Tao can both reference the same types directly from the `windows` crate instead of sharing bindings in `webview2-com-sys`. + - [34be6cf3](https://www.github.com/tauri-apps/tauri/commit/34be6cf37a98ee7cbd66623ebddae08e5a6520fd) Update webview2-com and windows crates ([#2875](https://www.github.com/tauri-apps/tauri/pull/2875)) on 2021-11-11 +- This is a temporary fix of null pointer crash on `get_content` of web resource request. + We will switch it back once upstream is updated. + - [84f6e3e8](https://www.github.com/tauri-apps/tauri/commit/84f6e3e84a34b01b7fa04f5c4719acb921ef4263) Switch to next branch of wry ([#2574](https://www.github.com/tauri-apps/tauri/pull/2574)) on 2021-09-10 +- Update wry to 0.13. + - [343ea3e2](https://www.github.com/tauri-apps/tauri/commit/343ea3e2e8d51bac63ab651289295c26fcc841d8) Update wry to 0.13 ([#3336](https://www.github.com/tauri-apps/tauri/pull/3336)) on 2022-02-06 + ## \[0.2.1] - Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing. diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index db22ed8bf01f..736479fc84b5 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -1,40 +1,53 @@ [package] name = "tauri-runtime-wry" -version = "0.2.1" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.studio" -repository = "https://github.com/tauri-apps/tauri" +version = "0.13.0-alpha.5" description = "Wry bindings to the Tauri runtime" -edition = "2021" -rust-version = "1.57" -exclude = [ ".license_template", "CHANGELOG.md", "/target" ] +exclude = [ "CHANGELOG.md", "/target" ] readme = "README.md" +# workspace defined package items +authors = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } + [dependencies] -wry = { version = "0.13.1", default-features = false, features = [ "file-drop", "protocol" ] } -tauri-runtime = { version = "0.2.1", path = "../tauri-runtime" } -tauri-utils = { version = "1.0.0-beta.3", path = "../tauri-utils" } -uuid = { version = "0.8.2", features = [ "v4" ] } -infer = "0.4" +wry = { version = "0.28.3", default-features = false, features = [ "file-drop", "protocol" ] } +tauri-runtime = { version = "0.13.0-alpha.5", path = "../tauri-runtime" } +tauri-utils = { version = "2.0.0-alpha.5", path = "../tauri-utils" } +uuid = { version = "1", features = [ "v4" ] } +rand = "0.8" +raw-window-handle = "0.5" [target."cfg(windows)".dependencies] -ico = "0.1" -webview2-com = "0.11.0" +webview2-com = "0.22" -[target."cfg(windows)".dependencies.windows] -version = "0.30.0" -features = [ - "Win32_Foundation", -] + [target."cfg(windows)".dependencies.windows] + version = "0.44" + features = [ "Win32_Foundation" ] [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] -png = "0.16" -gtk = { version = "0.15", features = [ "v3_20" ] } +gtk = { version = "0.16", features = [ "v3_24" ] } +webkit2gtk = { version = "0.19.1", features = [ "v2_38" ] } +percent-encoding = "2.1" + +[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies] +cocoa = "0.24" + +[target."cfg(target_os = \"android\")".dependencies] +jni = "0.20" [features] dox = [ "wry/dox" ] -devtools = [ "wry/devtool", "tauri-runtime/devtools" ] -system-tray = [ "wry/tray", "tauri-runtime/system-tray" ] -macos-private-api = [ "wry/fullscreen", "wry/transparent", "tauri-runtime/macos-private-api" ] +devtools = [ "wry/devtools", "tauri-runtime/devtools" ] +system-tray = [ "tauri-runtime/system-tray", "wry/tray" ] +macos-private-api = [ + "wry/fullscreen", + "wry/transparent", + "tauri-runtime/macos-private-api" +] +objc-exception = [ "wry/objc-exception" ] +linux-headers = [ "wry/linux-headers", "webkit2gtk/v2_36" ] diff --git a/core/tauri-runtime-wry/README.md b/core/tauri-runtime-wry/README.md index ef86b1794b5d..0b1c2c5c09b5 100644 --- a/core/tauri-runtime-wry/README.md +++ b/core/tauri-runtime-wry/README.md @@ -6,9 +6,8 @@ [![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) [![devto](https://img.shields.io/badge/blog-dev.to-black.svg)](https://dev.to/tauri) -![](https://img.shields.io/github/workflow/status/tauri-apps/tauri/test%20library?label=test%20library -) -[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.studio) +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) +[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.app) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) [![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri) diff --git a/core/tauri-runtime-wry/build.rs b/core/tauri-runtime-wry/build.rs new file mode 100644 index 000000000000..2ccb32962447 --- /dev/null +++ b/core/tauri-runtime-wry/build.rs @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// creates a cfg alias if `has_feature` is true. +// `alias` must be a snake case string. +fn alias(alias: &str, has_feature: bool) { + if has_feature { + println!("cargo:rustc-cfg={alias}"); + } +} + +fn main() { + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "ios" || target_os == "android"; + alias("desktop", !mobile); + alias("mobile", mobile); +} diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 809b4c4413fc..dfb672577503 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -1,74 +1,83 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! The [`wry`] Tauri [`Runtime`]. +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle}; use tauri_runtime::{ - http::{ - Request as HttpRequest, RequestParts as HttpRequestParts, Response as HttpResponse, - ResponseParts as HttpResponseParts, - }, - menu::{CustomMenuItem, Menu, MenuEntry, MenuHash, MenuId, MenuItem, MenuUpdate}, + http::{header::CONTENT_TYPE, Request as HttpRequest, RequestParts, Response as HttpResponse}, + menu::{AboutMetadata, CustomMenuItem, Menu, MenuEntry, MenuHash, MenuId, MenuItem, MenuUpdate}, monitor::Monitor, - webview::{FileDropEvent, FileDropHandler, WebviewIpcHandler, WindowBuilder, WindowBuilderBase}, + webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase}, window::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, WindowEvent, }, - ClipboardManager, Dispatch, Error, ExitRequestedEventAction, GlobalShortcutManager, Icon, Result, - RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType, + DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, + RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType, UserEvent, }; use tauri_runtime::window::MenuEvent; -#[cfg(feature = "system-tray")] +#[cfg(all(desktop, feature = "system-tray"))] use tauri_runtime::{SystemTray, SystemTrayEvent}; #[cfg(windows)] use webview2_com::FocusChangedEventHandler; #[cfg(windows)] use windows::Win32::{Foundation::HWND, System::WinRT::EventRegistrationToken}; -#[cfg(all(feature = "system-tray", target_os = "macos"))] -use wry::application::platform::macos::{SystemTrayBuilderExtMacOS, SystemTrayExtMacOS}; +#[cfg(target_os = "macos")] +use wry::application::platform::macos::EventLoopWindowTargetExtMacOS; +#[cfg(target_os = "macos")] +use wry::application::platform::macos::WindowBuilderExtMacOS; #[cfg(target_os = "linux")] use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}; #[cfg(windows)] use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWindows}; +#[cfg(windows)] +use wry::webview::WebViewBuilderExtWindows; -#[cfg(feature = "system-tray")] -use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder}; - -use tauri_utils::config::WindowConfig; +#[cfg(target_os = "macos")] +use tauri_utils::TitleBarStyle; +use tauri_utils::{config::WindowConfig, debug_eprintln, Theme}; use uuid::Uuid; use wry::{ application::{ - accelerator::{Accelerator, AcceleratorId}, - clipboard::Clipboard, dpi::{ LogicalPosition as WryLogicalPosition, LogicalSize as WryLogicalSize, PhysicalPosition as WryPhysicalPosition, PhysicalSize as WryPhysicalSize, Position as WryPosition, Size as WrySize, }, event::{Event, StartCause, WindowEvent as WryWindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget}, - global_shortcut::{GlobalShortcut, ShortcutManager as WryShortcutManager}, + event_loop::{ + ControlFlow, DeviceEventFilter as WryDeviceEventFilter, EventLoop, + EventLoopProxy as WryEventLoopProxy, EventLoopWindowTarget, + }, menu::{ - CustomMenuItem as WryCustomMenuItem, MenuBar, MenuId as WryMenuId, MenuItem as WryMenuItem, - MenuItemAttributes as WryMenuItemAttributes, MenuType, + AboutMetadata as WryAboutMetadata, CustomMenuItem as WryCustomMenuItem, MenuBar, + MenuId as WryMenuId, MenuItem as WryMenuItem, MenuItemAttributes as WryMenuItemAttributes, + MenuType, }, monitor::MonitorHandle, - window::{Fullscreen, Icon as WindowIcon, UserAttentionType as WryUserAttentionType}, - }, - http::{ - Request as WryHttpRequest, RequestParts as WryRequestParts, Response as WryHttpResponse, - ResponseParts as WryResponseParts, + window::{ + CursorIcon as WryCursorIcon, Fullscreen, Icon as WryWindowIcon, Theme as WryTheme, + UserAttentionType as WryUserAttentionType, + }, }, - webview::{FileDropEvent as WryFileDropEvent, WebContext, WebView, WebViewBuilder}, + http::{Request as WryRequest, Response as WryResponse}, + webview::{FileDropEvent as WryFileDropEvent, Url, WebContext, WebView, WebViewBuilder}, }; +pub use wry; pub use wry::application::window::{Window, WindowBuilder as WryWindowBuilder, WindowId}; +pub use wry::webview::webview_version; -#[cfg(target_os = "windows")] +#[cfg(windows)] use wry::webview::WebviewExtWindows; +#[cfg(target_os = "android")] +use wry::webview::{ + prelude::{dispatch, find_class}, + WebViewBuilderExtAndroid, WebviewExtAndroid, +}; #[cfg(target_os = "macos")] use tauri_runtime::{menu::NativeImage, ActivationPolicy}; @@ -79,42 +88,64 @@ pub use wry::application::platform::macos::{ }; use std::{ + borrow::Cow, + cell::RefCell, collections::{ hash_map::Entry::{Occupied, Vacant}, - HashMap, HashSet, + HashMap, }, fmt, - fs::read, ops::Deref, path::PathBuf, sync::{ mpsc::{channel, Sender}, - Arc, Mutex, MutexGuard, Weak, + Arc, Mutex, Weak, }, thread::{current as current_thread, ThreadId}, }; -#[cfg(feature = "system-tray")] +pub type WebviewId = u64; +type IpcHandler = dyn Fn(&Window, String) + 'static; +type FileDropHandler = dyn Fn(&Window, WryFileDropEvent) -> bool + 'static; +#[cfg(all(desktop, feature = "system-tray"))] +pub use tauri_runtime::TrayId; + +mod webview; +pub use webview::Webview; + +#[cfg(all(desktop, feature = "system-tray"))] mod system_tray; -#[cfg(feature = "system-tray")] +#[cfg(all(desktop, feature = "system-tray"))] use system_tray::*; -type WebContextStore = Arc, WebContext>>>; +pub type WebContextStore = Arc, WebContext>>>; // window -type WindowEventHandler = Box; -type WindowEventListenersMap = Arc>>; -type WindowEventListeners = Arc>>; -// global shortcut -type GlobalShortcutListeners = Arc>>>; +pub type WindowEventHandler = Box; +pub type WindowEventListeners = Arc>>; // menu pub type MenuEventHandler = Box; -pub type MenuEventListeners = Arc>>; pub type WindowMenuEventListeners = Arc>>; +#[derive(Debug, Clone, Default)] +pub struct WebviewIdStore(Arc>>); + +impl WebviewIdStore { + pub fn insert(&self, w: WindowId, id: WebviewId) { + self.0.lock().unwrap().insert(w, id); + } + + fn get(&self, w: &WindowId) -> Option { + self.0.lock().unwrap().get(w).copied() + } +} + +#[macro_export] macro_rules! getter { ($self: ident, $rx: expr, $message: expr) => {{ - send_user_message(&$self.context, $message)?; - $rx.recv().map_err(|_| Error::FailedToReceiveMessage) + $crate::send_user_message(&$self.context, $message)?; + $rx + .recv() + .map_err(|_| $crate::Error::FailedToReceiveMessage) }}; } @@ -125,19 +156,19 @@ macro_rules! window_getter { }}; } -fn send_user_message(context: &Context, message: Message) -> Result<()> { +pub(crate) fn send_user_message( + context: &Context, + message: Message, +) -> Result<()> { if current_thread().id() == context.main_thread_id { handle_user_message( &context.main_thread.window_target, message, UserMessageContext { - window_event_listeners: &context.window_event_listeners, - global_shortcut_manager: context.main_thread.global_shortcut_manager.clone(), - clipboard_manager: context.main_thread.clipboard_manager.clone(), - menu_event_listeners: &context.menu_event_listeners, + webview_id_map: context.webview_id_map.clone(), windows: context.main_thread.windows.clone(), - #[cfg(feature = "system-tray")] - tray_context: &context.main_thread.tray_context, + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager: context.main_thread.system_tray_manager.clone(), }, &context.main_thread.web_context, ); @@ -151,30 +182,74 @@ fn send_user_message(context: &Context, message: Message) -> Result<()> { } #[derive(Clone)] -struct Context { +pub struct Context { + pub webview_id_map: WebviewIdStore, main_thread_id: ThreadId, - proxy: EventLoopProxy, - window_event_listeners: WindowEventListeners, - menu_event_listeners: MenuEventListeners, - main_thread: DispatcherMainThreadContext, + pub proxy: WryEventLoopProxy>, + main_thread: DispatcherMainThreadContext, + plugins: Arc + Send>>>>, +} + +impl Context { + pub fn run_threaded(&self, f: F) -> R + where + F: FnOnce(Option<&DispatcherMainThreadContext>) -> R, + { + f(if current_thread().id() == self.main_thread_id { + Some(&self.main_thread) + } else { + None + }) + } +} + +impl Context { + fn create_webview(&self, pending: PendingWindow>) -> Result>> { + let label = pending.label.clone(); + let menu_ids = pending.menu_ids.clone(); + let context = self.clone(); + let window_id = rand::random(); + + send_user_message( + self, + Message::CreateWebview( + window_id, + Box::new(move |event_loop, web_context| { + create_webview(window_id, event_loop, web_context, context, pending) + }), + ), + )?; + + let dispatcher = WryDispatcher { + window_id, + context: self.clone(), + }; + Ok(DetachedWindow { + label, + dispatcher, + menu_ids, + }) + } } #[derive(Debug, Clone)] -struct DispatcherMainThreadContext { - window_target: EventLoopWindowTarget, - web_context: WebContextStore, - global_shortcut_manager: Arc>, - clipboard_manager: Arc>, - windows: Arc>>, - #[cfg(feature = "system-tray")] - tray_context: TrayContext, +pub struct DispatcherMainThreadContext { + pub window_target: EventLoopWindowTarget>, + pub web_context: WebContextStore, + pub windows: Arc>>, + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager: SystemTrayManager, } // SAFETY: we ensure this type is only used on the main thread. #[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for DispatcherMainThreadContext {} +unsafe impl Send for DispatcherMainThreadContext {} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for DispatcherMainThreadContext {} -impl fmt::Debug for Context { +impl fmt::Debug for Context { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Context") .field("main_thread_id", &self.main_thread_id) @@ -184,69 +259,36 @@ impl fmt::Debug for Context { } } -struct HttpRequestPartsWrapper(HttpRequestParts); - -impl From for HttpRequestParts { - fn from(parts: HttpRequestPartsWrapper) -> Self { - Self { - method: parts.0.method, - uri: parts.0.uri, - headers: parts.0.headers, - } - } -} - -impl From for HttpRequestPartsWrapper { - fn from(request: HttpRequestParts) -> Self { - Self(HttpRequestParts { - method: request.method, - uri: request.uri, - headers: request.headers, - }) - } -} - -impl From for HttpRequestPartsWrapper { - fn from(request: WryRequestParts) -> Self { - Self(HttpRequestParts { - method: request.method, - uri: request.uri, - headers: request.headers, - }) - } -} - struct HttpRequestWrapper(HttpRequest); -impl From<&WryHttpRequest> for HttpRequestWrapper { - fn from(req: &WryHttpRequest) -> Self { - Self(HttpRequest { - body: req.body.clone(), - head: HttpRequestPartsWrapper::from(req.head.clone()).0, - }) +impl From<&WryRequest>> for HttpRequestWrapper { + fn from(req: &WryRequest>) -> Self { + let parts = RequestParts { + uri: req.uri().to_string(), + method: req.method().clone(), + headers: req.headers().clone(), + }; + Self(HttpRequest::new_internal(parts, req.body().clone())) } } // response -struct HttpResponsePartsWrapper(WryResponseParts); -impl From for HttpResponsePartsWrapper { - fn from(response: HttpResponseParts) -> Self { - Self(WryResponseParts { - mimetype: response.mimetype, - status: response.status, - version: response.version, - headers: response.headers, - }) - } -} - -struct HttpResponseWrapper(WryHttpResponse); +struct HttpResponseWrapper(WryResponse>); impl From for HttpResponseWrapper { fn from(response: HttpResponse) -> Self { - Self(WryHttpResponse { - body: response.body, - head: HttpResponsePartsWrapper::from(response.head).0, - }) + let (parts, body) = response.into_parts(); + let mut res_builder = WryResponse::builder() + .status(parts.status) + .version(parts.version); + if let Some(mime) = parts.mimetype { + res_builder = res_builder.header(CONTENT_TYPE, mime); + } + for (name, val) in parts.headers.iter() { + res_builder = res_builder.header(name, val); + } + + let res = res_builder.body(body).unwrap(); + Self(res) } } @@ -265,12 +307,31 @@ impl<'a> From<&'a CustomMenuItem> for MenuItemAttributesWrapper<'a> { } } +pub struct AboutMetadataWrapper(pub WryAboutMetadata); + +impl From for AboutMetadataWrapper { + fn from(metadata: AboutMetadata) -> Self { + Self(WryAboutMetadata { + version: metadata.version, + authors: metadata.authors, + comments: metadata.comments, + copyright: metadata.copyright, + license: metadata.license, + website: metadata.website, + website_label: metadata.website_label, + }) + } +} + pub struct MenuItemWrapper(pub WryMenuItem); impl From for MenuItemWrapper { fn from(item: MenuItem) -> Self { match item { - MenuItem::About(v) => Self(WryMenuItem::About(v)), + MenuItem::About(name, metadata) => Self(WryMenuItem::About( + name, + AboutMetadataWrapper::from(metadata).0, + )), MenuItem::Hide => Self(WryMenuItem::Hide), MenuItem::Services => Self(WryMenuItem::Services), MenuItem::HideOthers => Self(WryMenuItem::HideOthers), @@ -292,6 +353,18 @@ impl From for MenuItemWrapper { } } +pub struct DeviceEventFilterWrapper(pub WryDeviceEventFilter); + +impl From for DeviceEventFilterWrapper { + fn from(item: DeviceEventFilter) -> Self { + match item { + DeviceEventFilter::Always => Self(WryDeviceEventFilter::Always), + DeviceEventFilter::Never => Self(WryDeviceEventFilter::Never), + DeviceEventFilter::Unfocused => Self(WryDeviceEventFilter::Unfocused), + } + } +} + #[cfg(target_os = "macos")] pub struct NativeImageWrapper(pub WryNativeImage); @@ -368,184 +441,51 @@ impl From for NativeImageWrapper { } } -#[derive(Debug, Clone)] -pub struct GlobalShortcutWrapper(GlobalShortcut); - -// SAFETY: usage outside of main thread is guarded, we use the event loop on such cases. -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for GlobalShortcutWrapper {} - -/// Wrapper around [`WryShortcutManager`]. -#[derive(Clone)] -pub struct GlobalShortcutManagerHandle { - context: Context, - shortcuts: Arc>>, - listeners: GlobalShortcutListeners, -} - -// SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`. -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Sync for GlobalShortcutManagerHandle {} - -impl fmt::Debug for GlobalShortcutManagerHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("GlobalShortcutManagerHandle") - .field("context", &self.context) - .field("shortcuts", &self.shortcuts) - .finish() - } -} - -impl GlobalShortcutManager for GlobalShortcutManagerHandle { - fn is_registered(&self, accelerator: &str) -> Result { - let (tx, rx) = channel(); - getter!( - self, - rx, - Message::GlobalShortcut(GlobalShortcutMessage::IsRegistered( - accelerator.parse().expect("invalid accelerator"), - tx - )) - ) - } - - fn register(&mut self, accelerator: &str, handler: F) -> Result<()> { - let wry_accelerator: Accelerator = accelerator.parse().expect("invalid accelerator"); - let id = wry_accelerator.clone().id(); - let (tx, rx) = channel(); - let shortcut = getter!( - self, - rx, - Message::GlobalShortcut(GlobalShortcutMessage::Register(wry_accelerator, tx)) - )??; - - self.listeners.lock().unwrap().insert(id, Box::new(handler)); - self - .shortcuts - .lock() - .unwrap() - .insert(accelerator.into(), (id, shortcut)); - - Ok(()) - } - - fn unregister_all(&mut self) -> Result<()> { - let (tx, rx) = channel(); - getter!( - self, - rx, - Message::GlobalShortcut(GlobalShortcutMessage::UnregisterAll(tx)) - )??; - self.listeners.lock().unwrap().clear(); - self.shortcuts.lock().unwrap().clear(); - Ok(()) - } - - fn unregister(&mut self, accelerator: &str) -> Result<()> { - if let Some((accelerator_id, shortcut)) = self.shortcuts.lock().unwrap().remove(accelerator) { - let (tx, rx) = channel(); - getter!( - self, - rx, - Message::GlobalShortcut(GlobalShortcutMessage::Unregister(shortcut, tx)) - )??; - self.listeners.lock().unwrap().remove(&accelerator_id); - } - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub struct ClipboardManagerWrapper { - context: Context, -} - -// SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`. -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Sync for ClipboardManagerWrapper {} - -impl ClipboardManager for ClipboardManagerWrapper { - fn read_text(&self) -> Result> { - let (tx, rx) = channel(); - getter!(self, rx, Message::Clipboard(ClipboardMessage::ReadText(tx))) - } - - fn write_text>(&mut self, text: T) -> Result<()> { - let (tx, rx) = channel(); - getter!( - self, - rx, - Message::Clipboard(ClipboardMessage::WriteText(text.into(), tx)) - )?; - Ok(()) - } -} - /// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`Icon`]. -pub struct WryIcon(WindowIcon); +pub struct WryIcon(pub WryWindowIcon); -fn icon_err(e: E) -> Error { +fn icon_err(e: E) -> Error { Error::InvalidIcon(Box::new(e)) } impl TryFrom for WryIcon { type Error = Error; fn try_from(icon: Icon) -> std::result::Result { - let image_bytes = match icon { - Icon::File(path) => read(path).map_err(icon_err)?, - Icon::Raw(raw) => raw, - _ => unimplemented!(), - }; - let extension = infer::get(&image_bytes) - .expect("could not determine icon extension") - .extension(); - match extension { - #[cfg(windows)] - "ico" => { - let icon_dir = ico::IconDir::read(std::io::Cursor::new(image_bytes)).map_err(icon_err)?; - let entry = &icon_dir.entries()[0]; - let icon = WindowIcon::from_rgba( - entry.decode().map_err(icon_err)?.rgba_data().to_vec(), - entry.width(), - entry.height(), - ) - .map_err(icon_err)?; - Ok(Self(icon)) - } - #[cfg(target_os = "linux")] - "png" => { - let decoder = png::Decoder::new(std::io::Cursor::new(image_bytes)); - let (info, mut reader) = decoder.read_info().map_err(icon_err)?; - let mut buffer = Vec::new(); - while let Ok(Some(row)) = reader.next_row() { - buffer.extend(row); - } - let icon = WindowIcon::from_rgba(buffer, info.width, info.height).map_err(icon_err)?; - Ok(Self(icon)) - } - _ => panic!( - "image `{}` extension not supported; please file a Tauri feature request", - extension - ), - } + WryWindowIcon::from_rgba(icon.rgba, icon.width, icon.height) + .map(Self) + .map_err(icon_err) } } -struct WindowEventWrapper(Option); +pub struct WindowEventWrapper(pub Option); impl WindowEventWrapper { - fn parse(webview: &WindowHandle, event: &WryWindowEvent<'_>) -> Self { + fn parse(webview: &Option, event: &WryWindowEvent<'_>) -> Self { match event { // resized event from tao doesn't include a reliable size on macOS // because wry replaces the NSView - WryWindowEvent::Resized(_) => Self(Some(WindowEvent::Resized( - PhysicalSizeWrapper(webview.inner_size()).into(), - ))), + WryWindowEvent::Resized(_) => { + if let Some(webview) = webview { + Self(Some(WindowEvent::Resized( + PhysicalSizeWrapper(webview.inner_size()).into(), + ))) + } else { + Self(None) + } + } e => e.into(), } } } +pub fn map_theme(theme: &WryTheme) -> Theme { + match theme { + WryTheme::Light => Theme::Light, + WryTheme::Dark => Theme::Dark, + _ => Theme::Light, + } +} + impl<'a> From<&WryWindowEvent<'a>> for WindowEventWrapper { fn from(event: &WryWindowEvent<'a>) -> Self { let event = match event { @@ -563,6 +503,7 @@ impl<'a> From<&WryWindowEvent<'a>> for WindowEventWrapper { }, #[cfg(any(target_os = "linux", target_os = "macos"))] WryWindowEvent::Focused(focused) => WindowEvent::Focused(*focused), + WryWindowEvent::ThemeChanged(theme) => WindowEvent::ThemeChanged(map_theme(theme)), _ => return Self(None), }; Self(Some(event)) @@ -578,7 +519,7 @@ impl From<&WebviewEvent> for WindowEventWrapper { } } -pub struct MonitorHandleWrapper(MonitorHandle); +pub struct MonitorHandleWrapper(pub MonitorHandle); impl From for Monitor { fn from(monitor: MonitorHandleWrapper) -> Monitor { @@ -591,7 +532,7 @@ impl From for Monitor { } } -struct PhysicalPositionWrapper(WryPhysicalPosition); +pub struct PhysicalPositionWrapper(pub WryPhysicalPosition); impl From> for PhysicalPosition { fn from(position: PhysicalPositionWrapper) -> Self { @@ -622,7 +563,7 @@ impl From> for LogicalPositionWrapper { } } -struct PhysicalSizeWrapper(WryPhysicalSize); +pub struct PhysicalSizeWrapper(pub WryPhysicalSize); impl From> for PhysicalSize { fn from(size: PhysicalSizeWrapper) -> Self { @@ -653,7 +594,7 @@ impl From> for LogicalSizeWrapper { } } -struct SizeWrapper(WrySize); +pub struct SizeWrapper(pub WrySize); impl From for SizeWrapper { fn from(size: Size) -> Self { @@ -664,7 +605,7 @@ impl From for SizeWrapper { } } -struct PositionWrapper(WryPosition); +pub struct PositionWrapper(pub WryPosition); impl From for PositionWrapper { fn from(position: Position) -> Self { @@ -676,10 +617,10 @@ impl From for PositionWrapper { } #[derive(Debug, Clone)] -pub struct UserAttentionTypeWrapper(WryUserAttentionType); +pub struct UserAttentionTypeWrapper(pub WryUserAttentionType); impl From for UserAttentionTypeWrapper { - fn from(request_type: UserAttentionType) -> UserAttentionTypeWrapper { + fn from(request_type: UserAttentionType) -> Self { let o = match request_type { UserAttentionType::Critical => WryUserAttentionType::Critical, UserAttentionType::Informational => WryUserAttentionType::Informational, @@ -688,10 +629,60 @@ impl From for UserAttentionTypeWrapper { } } +#[derive(Debug)] +pub struct CursorIconWrapper(pub WryCursorIcon); + +impl From for CursorIconWrapper { + fn from(icon: CursorIcon) -> Self { + use CursorIcon::*; + let i = match icon { + Default => WryCursorIcon::Default, + Crosshair => WryCursorIcon::Crosshair, + Hand => WryCursorIcon::Hand, + Arrow => WryCursorIcon::Arrow, + Move => WryCursorIcon::Move, + Text => WryCursorIcon::Text, + Wait => WryCursorIcon::Wait, + Help => WryCursorIcon::Help, + Progress => WryCursorIcon::Progress, + NotAllowed => WryCursorIcon::NotAllowed, + ContextMenu => WryCursorIcon::ContextMenu, + Cell => WryCursorIcon::Cell, + VerticalText => WryCursorIcon::VerticalText, + Alias => WryCursorIcon::Alias, + Copy => WryCursorIcon::Copy, + NoDrop => WryCursorIcon::NoDrop, + Grab => WryCursorIcon::Grab, + Grabbing => WryCursorIcon::Grabbing, + AllScroll => WryCursorIcon::AllScroll, + ZoomIn => WryCursorIcon::ZoomIn, + ZoomOut => WryCursorIcon::ZoomOut, + EResize => WryCursorIcon::EResize, + NResize => WryCursorIcon::NResize, + NeResize => WryCursorIcon::NeResize, + NwResize => WryCursorIcon::NwResize, + SResize => WryCursorIcon::SResize, + SeResize => WryCursorIcon::SeResize, + SwResize => WryCursorIcon::SwResize, + WResize => WryCursorIcon::WResize, + EwResize => WryCursorIcon::EwResize, + NsResize => WryCursorIcon::NsResize, + NeswResize => WryCursorIcon::NeswResize, + NwseResize => WryCursorIcon::NwseResize, + ColResize => WryCursorIcon::ColResize, + RowResize => WryCursorIcon::RowResize, + _ => WryCursorIcon::Default, + }; + Self(i) + } +} + #[derive(Debug, Clone, Default)] pub struct WindowBuilderWrapper { inner: WryWindowBuilder, center: bool, + #[cfg(target_os = "macos")] + tabbing_identifier: Option, menu: Option

, } @@ -702,38 +693,73 @@ unsafe impl Send for WindowBuilderWrapper {} impl WindowBuilderBase for WindowBuilderWrapper {} impl WindowBuilder for WindowBuilderWrapper { fn new() -> Self { - Default::default() + Self::default().focused(true) } fn with_config(config: WindowConfig) -> Self { - let mut window = WindowBuilderWrapper::new() - .title(config.title.to_string()) - .inner_size(config.width, config.height) - .visible(config.visible) - .resizable(config.resizable) - .fullscreen(config.fullscreen) - .decorations(config.decorations) - .maximized(config.maximized) - .always_on_top(config.always_on_top) - .skip_taskbar(config.skip_taskbar); + let mut window = WindowBuilderWrapper::new(); - #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + #[cfg(target_os = "macos")] { - window = window.transparent(config.transparent); + window = window + .hidden_title(config.hidden_title) + .title_bar_style(config.title_bar_style); + if let Some(identifier) = &config.tabbing_identifier { + window = window.tabbing_identifier(identifier); + } } - if let (Some(min_width), Some(min_height)) = (config.min_width, config.min_height) { - window = window.min_inner_size(min_width, min_height); + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + { + window = window.transparent(config.transparent); } - if let (Some(max_width), Some(max_height)) = (config.max_width, config.max_height) { - window = window.max_inner_size(max_width, max_height); + #[cfg(all( + target_os = "macos", + not(feature = "macos-private-api"), + debug_assertions + ))] + if config.transparent { + eprintln!( + "The window is set to be transparent but the `macos-private-api` is not enabled. + This can be enabled via the `tauri.macOSPrivateApi` configuration property + "); } - if let (Some(x), Some(y)) = (config.x, config.y) { - window = window.position(x, y); + + #[cfg(target_os = "linux")] + { + // Mouse event is disabled on Linux since sudden event bursts could block event loop. + window.inner = window.inner.with_cursor_moved_event(false); } - if config.center { - window = window.center(); + #[cfg(desktop)] + { + window = window + .title(config.title.to_string()) + .inner_size(config.width, config.height) + .visible(config.visible) + .resizable(config.resizable) + .fullscreen(config.fullscreen) + .decorations(config.decorations) + .maximized(config.maximized) + .always_on_top(config.always_on_top) + .content_protected(config.content_protected) + .skip_taskbar(config.skip_taskbar) + .theme(config.theme) + .shadow(config.shadow); + + if let (Some(min_width), Some(min_height)) = (config.min_width, config.min_height) { + window = window.min_inner_size(min_width, min_height); + } + if let (Some(max_width), Some(max_height)) = (config.max_width, config.max_height) { + window = window.max_inner_size(max_width, max_height); + } + if let (Some(x), Some(y)) = (config.x, config.y) { + window = window.position(x, y); + } + + if config.center { + window = window.center(); + } } window @@ -780,6 +806,21 @@ impl WindowBuilder for WindowBuilderWrapper { self } + fn maximizable(mut self, maximizable: bool) -> Self { + self.inner = self.inner.with_maximizable(maximizable); + self + } + + fn minimizable(mut self, minimizable: bool) -> Self { + self.inner = self.inner.with_minimizable(minimizable); + self + } + + fn closable(mut self, closable: bool) -> Self { + self.inner = self.inner.with_closable(closable); + self + } + fn title>(mut self, title: S) -> Self { self.inner = self.inner.with_title(title.into()); self @@ -796,9 +837,8 @@ impl WindowBuilder for WindowBuilderWrapper { self } - /// Deprecated since 0.1.4 (noop) - /// Windows is automatically focused when created. - fn focus(self) -> Self { + fn focused(mut self, focused: bool) -> Self { + self.inner = self.inner.with_focused(focused); self } @@ -828,18 +868,74 @@ impl WindowBuilder for WindowBuilderWrapper { self } + fn content_protected(mut self, protected: bool) -> Self { + self.inner = self.inner.with_content_protection(protected); + self + } + + fn shadow(#[allow(unused_mut)] mut self, _enable: bool) -> Self { + #[cfg(windows)] + { + self.inner = self.inner.with_undecorated_shadow(_enable); + } + #[cfg(target_os = "macos")] + { + self.inner = self.inner.with_has_shadow(_enable); + } + self + } + #[cfg(windows)] fn parent_window(mut self, parent: HWND) -> Self { self.inner = self.inner.with_parent_window(parent); self } + #[cfg(target_os = "macos")] + fn parent_window(mut self, parent: *mut std::ffi::c_void) -> Self { + self.inner = self.inner.with_parent_window(parent); + self + } + #[cfg(windows)] fn owner_window(mut self, owner: HWND) -> Self { self.inner = self.inner.with_owner_window(owner); self } + #[cfg(target_os = "macos")] + fn title_bar_style(mut self, style: TitleBarStyle) -> Self { + match style { + TitleBarStyle::Visible => { + self.inner = self.inner.with_titlebar_transparent(false); + // Fixes rendering issue when resizing window with devtools open (https://github.com/tauri-apps/tauri/issues/3914) + self.inner = self.inner.with_fullsize_content_view(true); + } + TitleBarStyle::Transparent => { + self.inner = self.inner.with_titlebar_transparent(true); + self.inner = self.inner.with_fullsize_content_view(false); + } + TitleBarStyle::Overlay => { + self.inner = self.inner.with_titlebar_transparent(true); + self.inner = self.inner.with_fullsize_content_view(true); + } + } + self + } + + #[cfg(target_os = "macos")] + fn hidden_title(mut self, hidden: bool) -> Self { + self.inner = self.inner.with_title_hidden(hidden); + self + } + + #[cfg(target_os = "macos")] + fn tabbing_identifier(mut self, identifier: &str) -> Self { + self.inner = self.inner.with_tabbing_identifier(identifier); + self.tabbing_identifier.replace(identifier.into()); + self + } + fn icon(mut self, icon: Icon) -> Result { self.inner = self .inner @@ -847,17 +943,31 @@ impl WindowBuilder for WindowBuilderWrapper { Ok(self) } - #[cfg(any(target_os = "windows", target_os = "linux"))] + #[cfg(any(windows, target_os = "linux"))] fn skip_taskbar(mut self, skip: bool) -> Self { self.inner = self.inner.with_skip_taskbar(skip); self } - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))] fn skip_taskbar(self, _skip: bool) -> Self { self } + #[allow(unused_variables, unused_mut)] + fn theme(mut self, theme: Option) -> Self { + self.inner = self.inner.with_theme(if let Some(t) = theme { + match t { + Theme::Dark => Some(WryTheme::Dark), + _ => Some(WryTheme::Light), + } + } else { + None + }); + + self + } + fn has_icon(&self) -> bool { self.inner.window.window_icon.is_some() } @@ -869,30 +979,7 @@ impl WindowBuilder for WindowBuilderWrapper { pub struct FileDropEventWrapper(WryFileDropEvent); -impl From for FileDropEvent { - fn from(event: FileDropEventWrapper) -> Self { - match event.0 { - WryFileDropEvent::Hovered(paths) => FileDropEvent::Hovered(paths), - WryFileDropEvent::Dropped(paths) => FileDropEvent::Dropped(paths), - // default to cancelled - // FIXME(maybe): Add `FileDropEvent::Unknown` event? - _ => FileDropEvent::Cancelled, - } - } -} - -#[cfg(target_os = "macos")] -pub struct NSWindow(*mut std::ffi::c_void); -#[cfg(target_os = "macos")] -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for NSWindow {} - -#[cfg(windows)] -pub struct Hwnd(HWND); -#[cfg(windows)] -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for Hwnd {} - +// on Linux, the paths are percent-encoded #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -900,40 +987,102 @@ unsafe impl Send for Hwnd {} target_os = "netbsd", target_os = "openbsd" ))] -pub struct GtkWindow(gtk::ApplicationWindow); -#[cfg(any( +fn decode_path(path: PathBuf) -> PathBuf { + percent_encoding::percent_decode(path.display().to_string().as_bytes()) + .decode_utf8_lossy() + .into_owned() + .into() +} + +// on Windows and macOS, we do not need to decode the path +#[cfg(not(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" -))] -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for GtkWindow {} +)))] +fn decode_path(path: PathBuf) -> PathBuf { + path +} +impl From for FileDropEvent { + fn from(event: FileDropEventWrapper) -> Self { + match event.0 { + WryFileDropEvent::Hovered { paths, position: _ } => { + FileDropEvent::Hovered(paths.into_iter().map(decode_path).collect()) + } + WryFileDropEvent::Dropped { paths, position: _ } => { + FileDropEvent::Dropped(paths.into_iter().map(decode_path).collect()) + } + // default to cancelled + // FIXME(maybe): Add `FileDropEvent::Unknown` event? + _ => FileDropEvent::Cancelled, + } + } +} + +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +pub struct GtkWindow(pub gtk::ApplicationWindow); +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for GtkWindow {} + +pub struct RawWindowHandle(pub raw_window_handle::RawWindowHandle); +unsafe impl Send for RawWindowHandle {} + +#[cfg(target_os = "macos")] #[derive(Debug, Clone)] +pub enum ApplicationMessage { + Show, + Hide, +} + pub enum WindowMessage { + WithWebview(Box), + AddEventListener(Uuid, Box), + AddMenuEventListener(Uuid, Box), + // Devtools #[cfg(any(debug_assertions, feature = "devtools"))] OpenDevTools, + #[cfg(any(debug_assertions, feature = "devtools"))] + CloseDevTools, + #[cfg(any(debug_assertions, feature = "devtools"))] + IsDevToolsOpen(Sender), // Getters + Url(Sender), ScaleFactor(Sender), InnerPosition(Sender>>), OuterPosition(Sender>>), InnerSize(Sender>), OuterSize(Sender>), IsFullscreen(Sender), + IsMinimized(Sender), IsMaximized(Sender), + IsFocused(Sender), IsDecorated(Sender), IsResizable(Sender), + IsMaximizable(Sender), + IsMinimizable(Sender), + IsClosable(Sender), IsVisible(Sender), + Title(Sender), IsMenuVisible(Sender), CurrentMonitor(Sender>), PrimaryMonitor(Sender>), AvailableMonitors(Sender>), - #[cfg(target_os = "macos")] - NSWindow(Sender), - #[cfg(windows)] - Hwnd(Sender), #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -942,10 +1091,15 @@ pub enum WindowMessage { target_os = "openbsd" ))] GtkWindow(Sender), + RawWindowHandle(Sender), + Theme(Sender), // Setters - Center(Sender>), + Center, RequestUserAttention(Option), SetResizable(bool), + SetMaximizable(bool), + SetMinimizable(bool), + SetClosable(bool), SetTitle(String), Maximize, Unmaximize, @@ -957,15 +1111,22 @@ pub enum WindowMessage { Hide, Close, SetDecorations(bool), + SetShadow(bool), SetAlwaysOnTop(bool), + SetContentProtected(bool), SetSize(Size), SetMinSize(Option), SetMaxSize(Option), SetPosition(Position), SetFullscreen(bool), SetFocus, - SetIcon(WindowIcon), + SetIcon(WryWindowIcon), SetSkipTaskbar(bool), + SetCursorGrab(bool), + SetCursorVisible(bool), + SetCursorIcon(CursorIcon), + SetCursorPosition(Position), + SetIgnoreCursorEvents(bool), DragWindow, UpdateMenuItem(u16, MenuUpdate), RequestRedraw, @@ -985,7 +1146,7 @@ pub enum WebviewEvent { Focused(bool), } -#[cfg(feature = "system-tray")] +#[cfg(all(desktop, feature = "system-tray"))] #[derive(Debug, Clone)] pub enum TrayMessage { UpdateItem(u16, MenuUpdate), @@ -993,52 +1154,41 @@ pub enum TrayMessage { UpdateIcon(Icon), #[cfg(target_os = "macos")] UpdateIconAsTemplate(bool), - Close, -} - -#[derive(Debug, Clone)] -pub enum GlobalShortcutMessage { - IsRegistered(Accelerator, Sender), - Register(Accelerator, Sender>), - Unregister(GlobalShortcutWrapper, Sender>), - UnregisterAll(Sender>), + #[cfg(target_os = "macos")] + UpdateTitle(String), + UpdateTooltip(String), + Create(SystemTray, Sender>), + Destroy(Sender>), } -#[derive(Debug, Clone)] -pub enum ClipboardMessage { - WriteText(String, Sender<()>), - ReadText(Sender>), -} +pub type CreateWebviewClosure = Box< + dyn FnOnce(&EventLoopWindowTarget>, &WebContextStore) -> Result + Send, +>; -pub enum Message { +pub enum Message { Task(Box), - Window(WindowId, WindowMessage), - Webview(WindowId, WebviewMessage), - #[cfg(feature = "system-tray")] - Tray(TrayMessage), - CreateWebview( - Box< - dyn FnOnce(&EventLoopWindowTarget, &WebContextStore) -> Result + Send, - >, - Sender, - ), + #[cfg(target_os = "macos")] + Application(ApplicationMessage), + Window(WebviewId, WindowMessage), + Webview(WebviewId, WebviewMessage), + #[cfg(all(desktop, feature = "system-tray"))] + Tray(TrayId, TrayMessage), + CreateWebview(WebviewId, CreateWebviewClosure), CreateWindow( + WebviewId, Box (String, WryWindowBuilder) + Send>, Sender>>, ), - GlobalShortcut(GlobalShortcutMessage), - Clipboard(ClipboardMessage), + UserEvent(T), } -impl Clone for Message { +impl Clone for Message { fn clone(&self) -> Self { match self { - Self::Window(i, m) => Self::Window(*i, m.clone()), Self::Webview(i, m) => Self::Webview(*i, m.clone()), - #[cfg(feature = "system-tray")] - Self::Tray(m) => Self::Tray(m.clone()), - Self::GlobalShortcut(m) => Self::GlobalShortcut(m.clone()), - Self::Clipboard(m) => Self::Clipboard(m.clone()), + #[cfg(all(desktop, feature = "system-tray"))] + Self::Tray(i, m) => Self::Tray(*i, m.clone()), + Self::UserEvent(t) => Self::UserEvent(t.clone()), _ => unimplemented!(), } } @@ -1046,17 +1196,17 @@ impl Clone for Message { /// The Tauri [`Dispatch`] for [`Wry`]. #[derive(Debug, Clone)] -pub struct WryDispatcher { - window_id: WindowId, - context: Context, +pub struct WryDispatcher { + window_id: WebviewId, + context: Context, } // SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`. #[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Sync for WryDispatcher {} +unsafe impl Sync for WryDispatcher {} -impl Dispatch for WryDispatcher { - type Runtime = Wry; +impl Dispatch for WryDispatcher { + type Runtime = Wry; type WindowBuilder = WindowBuilderWrapper; fn run_on_main_thread(&self, f: F) -> Result<()> { @@ -1065,34 +1215,32 @@ impl Dispatch for WryDispatcher { fn on_window_event(&self, f: F) -> Uuid { let id = Uuid::new_v4(); - self - .context - .window_event_listeners - .lock() - .unwrap() - .get(&self.window_id) - .unwrap() - .lock() - .unwrap() - .insert(id, Box::new(f)); + let _ = self.context.proxy.send_event(Message::Window( + self.window_id, + WindowMessage::AddEventListener(id, Box::new(f)), + )); id } fn on_menu_event(&self, f: F) -> Uuid { let id = Uuid::new_v4(); - self - .context - .menu_event_listeners - .lock() - .unwrap() - .get(&self.window_id) - .unwrap() - .lock() - .unwrap() - .insert(id, Box::new(f)); + let _ = self.context.proxy.send_event(Message::Window( + self.window_id, + WindowMessage::AddMenuEventListener(id, Box::new(f)), + )); id } + fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { + send_user_message( + &self.context, + Message::Window( + self.window_id, + WindowMessage::WithWebview(Box::new(move |webview| f(Box::new(webview)))), + ), + ) + } + #[cfg(any(debug_assertions, feature = "devtools"))] fn open_devtools(&self) { let _ = send_user_message( @@ -1101,8 +1249,26 @@ impl Dispatch for WryDispatcher { ); } + #[cfg(any(debug_assertions, feature = "devtools"))] + fn close_devtools(&self) { + let _ = send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::CloseDevTools), + ); + } + + /// Gets the devtools window's current open state. + #[cfg(any(debug_assertions, feature = "devtools"))] + fn is_devtools_open(&self) -> Result { + window_getter!(self, WindowMessage::IsDevToolsOpen) + } + // Getters + fn url(&self) -> Result { + window_getter!(self, WindowMessage::Url) + } + fn scale_factor(&self) -> Result { window_getter!(self, WindowMessage::ScaleFactor) } @@ -1127,10 +1293,18 @@ impl Dispatch for WryDispatcher { window_getter!(self, WindowMessage::IsFullscreen) } + fn is_minimized(&self) -> Result { + window_getter!(self, WindowMessage::IsMinimized) + } + fn is_maximized(&self) -> Result { window_getter!(self, WindowMessage::IsMaximized) } + fn is_focused(&self) -> Result { + window_getter!(self, WindowMessage::IsFocused) + } + /// Gets the window’s current decoration state. fn is_decorated(&self) -> Result { window_getter!(self, WindowMessage::IsDecorated) @@ -1141,10 +1315,29 @@ impl Dispatch for WryDispatcher { window_getter!(self, WindowMessage::IsResizable) } + /// Gets the current native window's maximize button state + fn is_maximizable(&self) -> Result { + window_getter!(self, WindowMessage::IsMaximizable) + } + + /// Gets the current native window's minimize button state + fn is_minimizable(&self) -> Result { + window_getter!(self, WindowMessage::IsMinimizable) + } + + /// Gets the current native window's close button state + fn is_closable(&self) -> Result { + window_getter!(self, WindowMessage::IsClosable) + } + fn is_visible(&self) -> Result { window_getter!(self, WindowMessage::IsVisible) } + fn title(&self) -> Result { + window_getter!(self, WindowMessage::Title) + } + fn is_menu_visible(&self) -> Result { window_getter!(self, WindowMessage::IsMenuVisible) } @@ -1166,17 +1359,11 @@ impl Dispatch for WryDispatcher { ) } - #[cfg(target_os = "macos")] - fn ns_window(&self) -> Result<*mut std::ffi::c_void> { - window_getter!(self, WindowMessage::NSWindow).map(|w| w.0) - } - - #[cfg(windows)] - fn hwnd(&self) -> Result { - window_getter!(self, WindowMessage::Hwnd).map(|w| w.0) + fn theme(&self) -> Result { + window_getter!(self, WindowMessage::Theme) } - /// Returns the `ApplicatonWindow` from gtk crate that is used by this window. + /// Returns the `ApplicationWindow` from gtk crate that is used by this window. #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -1188,10 +1375,17 @@ impl Dispatch for WryDispatcher { window_getter!(self, WindowMessage::GtkWindow).map(|w| w.0) } + fn raw_window_handle(&self) -> Result { + window_getter!(self, WindowMessage::RawWindowHandle).map(|w| w.0) + } + // Setters fn center(&self) -> Result<()> { - window_getter!(self, WindowMessage::Center)? + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::Center), + ) } fn print(&self) -> Result<()> { @@ -1215,41 +1409,36 @@ impl Dispatch for WryDispatcher { // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. fn create_window( &mut self, - pending: PendingWindow, - ) -> Result> { - let (tx, rx) = channel(); - let label = pending.label.clone(); - let menu_ids = pending.menu_ids.clone(); - let js_event_listeners = pending.js_event_listeners.clone(); - let context = self.context.clone(); + pending: PendingWindow, + ) -> Result> { + self.context.create_webview(pending) + } + fn set_resizable(&self, resizable: bool) -> Result<()> { send_user_message( &self.context, - Message::CreateWebview( - Box::new(move |event_loop, web_context| { - create_webview(event_loop, web_context, context, pending) - }), - tx, - ), - )?; - let window_id = rx.recv().unwrap(); + Message::Window(self.window_id, WindowMessage::SetResizable(resizable)), + ) + } - let dispatcher = WryDispatcher { - window_id, - context: self.context.clone(), - }; - Ok(DetachedWindow { - label, - dispatcher, - menu_ids, - js_event_listeners, - }) + fn set_maximizable(&self, maximizable: bool) -> Result<()> { + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::SetMaximizable(maximizable)), + ) } - fn set_resizable(&self, resizable: bool) -> Result<()> { + fn set_minimizable(&self, minimizable: bool) -> Result<()> { send_user_message( &self.context, - Message::Window(self.window_id, WindowMessage::SetResizable(resizable)), + Message::Window(self.window_id, WindowMessage::SetMinimizable(minimizable)), + ) + } + + fn set_closable(&self, closable: bool) -> Result<()> { + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::SetClosable(closable)), ) } @@ -1332,6 +1521,13 @@ impl Dispatch for WryDispatcher { ) } + fn set_shadow(&self, enable: bool) -> Result<()> { + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::SetShadow(enable)), + ) + } + fn set_always_on_top(&self, always_on_top: bool) -> Result<()> { send_user_message( &self.context, @@ -1339,6 +1535,16 @@ impl Dispatch for WryDispatcher { ) } + fn set_content_protected(&self, protected: bool) -> Result<()> { + send_user_message( + &self.context, + Message::Window( + self.window_id, + WindowMessage::SetContentProtected(protected), + ), + ) + } + fn set_size(&self, size: Size) -> Result<()> { send_user_message( &self.context, @@ -1398,6 +1604,44 @@ impl Dispatch for WryDispatcher { ) } + fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> { + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::SetCursorGrab(grab)), + ) + } + + fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> { + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::SetCursorVisible(visible)), + ) + } + + fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> { + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::SetCursorIcon(icon)), + ) + } + + fn set_cursor_position>(&self, position: Pos) -> crate::Result<()> { + send_user_message( + &self.context, + Message::Window( + self.window_id, + WindowMessage::SetCursorPosition(position.into()), + ), + ) + } + + fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> { + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::SetIgnoreCursorEvents(ignore)), + ) + } + fn start_dragging(&self) -> Result<()> { send_user_message( &self.context, @@ -1423,96 +1667,172 @@ impl Dispatch for WryDispatcher { } } -#[cfg(feature = "system-tray")] -#[derive(Clone, Default)] -struct TrayContext { - tray: Arc>>>>, - listeners: SystemTrayEventListeners, - items: SystemTrayItems, +#[derive(Clone)] +enum WindowHandle { + Webview { + inner: Arc, + context_store: WebContextStore, + // the key of the WebContext if it's not shared + context_key: Option, + }, + Window(Arc), } -#[cfg(feature = "system-tray")] -impl fmt::Debug for TrayContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TrayContext") - .field("items", &self.items) - .finish() +impl Drop for WindowHandle { + fn drop(&mut self) { + if let Self::Webview { + inner, + context_store, + context_key, + } = self + { + if Arc::get_mut(inner).is_some() { + context_store.lock().unwrap().remove(context_key); + } + } } } -enum WindowHandle { - Webview(WebView), - Window(Arc), -} - impl fmt::Debug for WindowHandle { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { Ok(()) } } -impl WindowHandle { - fn window(&self) -> &Window { +impl Deref for WindowHandle { + type Target = Window; + + #[inline(always)] + fn deref(&self) -> &Window { match self { - Self::Webview(w) => w.window(), + Self::Webview { inner, .. } => inner.window(), Self::Window(w) => w, } } +} +impl WindowHandle { fn inner_size(&self) -> WryPhysicalSize { match self { WindowHandle::Window(w) => w.inner_size(), - WindowHandle::Webview(w) => w.inner_size(), + WindowHandle::Webview { inner, .. } => inner.inner_size(), } } } -#[derive(Debug)] pub struct WindowWrapper { label: String, - inner: WindowHandle, + inner: Option, menu_items: Option>, + window_event_listeners: WindowEventListeners, + menu_event_listeners: WindowMenuEventListeners, +} + +impl fmt::Debug for WindowWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WindowWrapper") + .field("label", &self.label) + .field("inner", &self.inner) + .field("menu_items", &self.menu_items) + .finish() + } +} + +#[derive(Debug, Clone)] +pub struct EventProxy(WryEventLoopProxy>); + +#[cfg(target_os = "ios")] +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for EventProxy {} + +impl EventLoopProxy for EventProxy { + fn send_event(&self, event: T) -> Result<()> { + self + .0 + .send_event(Message::UserEvent(event)) + .map_err(|_| Error::EventLoopClosed) + } +} + +pub trait PluginBuilder { + type Plugin: Plugin; + fn build(self, context: Context) -> Self::Plugin; +} + +pub trait Plugin { + fn on_event( + &mut self, + event: &Event>, + event_loop: &EventLoopWindowTarget>, + proxy: &WryEventLoopProxy>, + control_flow: &mut ControlFlow, + context: EventLoopIterationContext<'_, T>, + web_context: &WebContextStore, + ) -> bool; } /// A Tauri [`Runtime`] wrapper around wry. -pub struct Wry { - main_thread_id: ThreadId, - global_shortcut_manager: Arc>, - global_shortcut_manager_handle: GlobalShortcutManagerHandle, - clipboard_manager: Arc>, - clipboard_manager_handle: ClipboardManagerWrapper, - event_loop: EventLoop, - windows: Arc>>, - web_context: WebContextStore, - window_event_listeners: WindowEventListeners, - menu_event_listeners: MenuEventListeners, - #[cfg(feature = "system-tray")] - tray_context: TrayContext, +pub struct Wry { + context: Context, + event_loop: EventLoop>, +} + +impl fmt::Debug for Wry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("Wry"); + d.field("main_thread_id", &self.context.main_thread_id) + .field("event_loop", &self.event_loop) + .field("windows", &self.context.main_thread.windows) + .field("web_context", &self.context.main_thread.web_context); + + #[cfg(all(desktop, feature = "system-tray"))] + d.field( + "system_tray_manager", + &self.context.main_thread.system_tray_manager, + ); + + d.finish() + } } /// A handle to the Wry runtime. #[derive(Debug, Clone)] -pub struct WryHandle { - context: Context, +pub struct WryHandle { + context: Context, } // SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`. #[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Sync for WryHandle {} +unsafe impl Sync for WryHandle {} -impl WryHandle { +impl WryHandle { /// Creates a new tao window using a callback, and returns its window id. pub fn create_tao_window (String, WryWindowBuilder) + Send + 'static>( &self, f: F, ) -> Result> { let (tx, rx) = channel(); - send_user_message(&self.context, Message::CreateWindow(Box::new(f), tx))?; + send_user_message( + &self.context, + Message::CreateWindow(rand::random(), Box::new(f), tx), + )?; rx.recv().unwrap() } + /// Gets the [`WebviewId'] associated with the given [`WindowId`]. + pub fn window_id(&self, window_id: WindowId) -> WebviewId { + *self + .context + .webview_id_map + .0 + .lock() + .unwrap() + .get(&window_id) + .unwrap() + } + /// Send a message to the event loop. - pub fn send_event(&self, message: Message) -> Result<()> { + pub fn send_event(&self, message: Message) -> Result<()> { self .context .proxy @@ -1520,122 +1840,143 @@ impl WryHandle { .map_err(|_| Error::FailedToSendMessage)?; Ok(()) } + + pub fn plugin + 'static>(&mut self, plugin: P) + where +

>::Plugin: Send, + { + self + .context + .plugins + .lock() + .unwrap() + .push(Box::new(plugin.build(self.context.clone()))); + } } -impl RuntimeHandle for WryHandle { - type Runtime = Wry; +impl RuntimeHandle for WryHandle { + type Runtime = Wry; + + fn create_proxy(&self) -> EventProxy { + EventProxy(self.context.proxy.clone()) + } // Creates a window by dispatching a message to the event loop. // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. fn create_window( &self, - pending: PendingWindow, - ) -> Result> { + pending: PendingWindow, + ) -> Result> { + self.context.create_webview(pending) + } + + fn run_on_main_thread(&self, f: F) -> Result<()> { + send_user_message(&self.context, Message::Task(Box::new(f))) + } + + #[cfg(all(desktop, feature = "system-tray"))] + fn system_tray( + &self, + system_tray: SystemTray, + ) -> Result<>::TrayHandler> { + let id = system_tray.id; let (tx, rx) = channel(); - let label = pending.label.clone(); - let menu_ids = pending.menu_ids.clone(); - let js_event_listeners = pending.js_event_listeners.clone(); - let context = self.context.clone(); send_user_message( &self.context, - Message::CreateWebview( - Box::new(move |event_loop, web_context| { - create_webview(event_loop, web_context, context, pending) - }), - tx, - ), + Message::Tray(id, TrayMessage::Create(system_tray, tx)), )?; - let window_id = rx.recv().unwrap(); - - let dispatcher = WryDispatcher { - window_id, + rx.recv().unwrap()?; + Ok(SystemTrayHandle { context: self.context.clone(), - }; - Ok(DetachedWindow { - label, - dispatcher, - menu_ids, - js_event_listeners, + id, + proxy: self.context.proxy.clone(), }) } - fn run_on_main_thread(&self, f: F) -> Result<()> { - send_user_message(&self.context, Message::Task(Box::new(f))) + fn raw_display_handle(&self) -> RawDisplayHandle { + self.context.main_thread.window_target.raw_display_handle() + } + + #[cfg(target_os = "macos")] + fn show(&self) -> tauri_runtime::Result<()> { + send_user_message( + &self.context, + Message::Application(ApplicationMessage::Show), + ) + } + + #[cfg(target_os = "macos")] + fn hide(&self) -> tauri_runtime::Result<()> { + send_user_message( + &self.context, + Message::Application(ApplicationMessage::Hide), + ) } - #[cfg(all(windows, feature = "system-tray"))] - /// Deprecated. (not needed anymore) - fn remove_system_tray(&self) -> Result<()> { - send_user_message(&self.context, Message::Tray(TrayMessage::Close)) + #[cfg(target_os = "android")] + fn find_class<'a>( + &'a self, + env: jni::JNIEnv<'a>, + activity: jni::objects::JObject<'a>, + name: impl Into, + ) -> std::result::Result, jni::errors::Error> { + find_class(env, activity, name.into()) + } + + #[cfg(target_os = "android")] + fn run_on_android_context(&self, f: F) + where + F: FnOnce(jni::JNIEnv<'_>, jni::objects::JObject<'_>, jni::objects::JObject<'_>) + + Send + + 'static, + { + dispatch(f) } } -impl Wry { - fn init(event_loop: EventLoop) -> Result { - let proxy = event_loop.create_proxy(); +impl Wry { + fn init(event_loop: EventLoop>) -> Result { let main_thread_id = current_thread().id(); let web_context = WebContextStore::default(); - let global_shortcut_manager = Arc::new(Mutex::new(WryShortcutManager::new(&event_loop))); - let clipboard_manager = Arc::new(Mutex::new(Clipboard::new())); - let windows = Arc::new(Mutex::new(HashMap::default())); - let window_event_listeners = WindowEventListeners::default(); - let menu_event_listeners = MenuEventListeners::default(); - #[cfg(feature = "system-tray")] - let tray_context = TrayContext::default(); + let windows = Arc::new(RefCell::new(HashMap::default())); + let webview_id_map = WebviewIdStore::default(); - let event_loop_context = Context { + #[cfg(all(desktop, feature = "system-tray"))] + let system_tray_manager = Default::default(); + + let context = Context { + webview_id_map, main_thread_id, - proxy, - window_event_listeners: window_event_listeners.clone(), - menu_event_listeners: menu_event_listeners.clone(), + proxy: event_loop.create_proxy(), main_thread: DispatcherMainThreadContext { window_target: event_loop.deref().clone(), - web_context: web_context.clone(), - global_shortcut_manager: global_shortcut_manager.clone(), - clipboard_manager: clipboard_manager.clone(), - windows: windows.clone(), - #[cfg(feature = "system-tray")] - tray_context: tray_context.clone(), + web_context, + windows, + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager, }, - }; - - let global_shortcut_listeners = GlobalShortcutListeners::default(); - let clipboard_manager_handle = ClipboardManagerWrapper { - context: event_loop_context.clone(), + plugins: Default::default(), }; Ok(Self { - main_thread_id, - global_shortcut_manager, - global_shortcut_manager_handle: GlobalShortcutManagerHandle { - context: event_loop_context, - shortcuts: Default::default(), - listeners: global_shortcut_listeners, - }, - clipboard_manager, - clipboard_manager_handle, + context, event_loop, - windows, - web_context, - window_event_listeners, - menu_event_listeners, - #[cfg(feature = "system-tray")] - tray_context, }) } } -impl Runtime for Wry { - type Dispatcher = WryDispatcher; - type Handle = WryHandle; - type GlobalShortcutManager = GlobalShortcutManagerHandle; - type ClipboardManager = ClipboardManagerWrapper; - #[cfg(feature = "system-tray")] - type TrayHandler = SystemTrayHandle; +impl Runtime for Wry { + type Dispatcher = WryDispatcher; + type Handle = WryHandle; + + #[cfg(all(desktop, feature = "system-tray"))] + type TrayHandler = SystemTrayHandle; + + type EventLoopProxy = EventProxy; fn new() -> Result { - let event_loop = EventLoop::::with_user_event(); + let event_loop = EventLoop::>::with_user_event(); Self::init(event_loop) } @@ -1645,183 +1986,93 @@ impl Runtime for Wry { use wry::application::platform::unix::EventLoopExtUnix; #[cfg(windows)] use wry::application::platform::windows::EventLoopExtWindows; - let event_loop = EventLoop::::new_any_thread(); + let event_loop = EventLoop::>::new_any_thread(); Self::init(event_loop) } + fn create_proxy(&self) -> EventProxy { + EventProxy(self.event_loop.create_proxy()) + } + fn handle(&self) -> Self::Handle { WryHandle { - context: Context { - main_thread_id: self.main_thread_id, - proxy: self.event_loop.create_proxy(), - window_event_listeners: self.window_event_listeners.clone(), - menu_event_listeners: self.menu_event_listeners.clone(), - main_thread: DispatcherMainThreadContext { - window_target: self.event_loop.deref().clone(), - web_context: self.web_context.clone(), - global_shortcut_manager: self.global_shortcut_manager.clone(), - clipboard_manager: self.clipboard_manager.clone(), - windows: self.windows.clone(), - #[cfg(feature = "system-tray")] - tray_context: self.tray_context.clone(), - }, - }, + context: self.context.clone(), } } - fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager { - self.global_shortcut_manager_handle.clone() - } - - fn clipboard_manager(&self) -> Self::ClipboardManager { - self.clipboard_manager_handle.clone() - } - - fn create_window(&self, pending: PendingWindow) -> Result> { + fn create_window(&self, pending: PendingWindow) -> Result> { let label = pending.label.clone(); let menu_ids = pending.menu_ids.clone(); - let js_event_listeners = pending.js_event_listeners.clone(); - let proxy = self.event_loop.create_proxy(); + let window_id = rand::random(); + let webview = create_webview( + window_id, &self.event_loop, - &self.web_context, - Context { - main_thread_id: self.main_thread_id, - proxy: proxy.clone(), - window_event_listeners: self.window_event_listeners.clone(), - menu_event_listeners: self.menu_event_listeners.clone(), - main_thread: DispatcherMainThreadContext { - window_target: self.event_loop.deref().clone(), - web_context: self.web_context.clone(), - global_shortcut_manager: self.global_shortcut_manager.clone(), - clipboard_manager: self.clipboard_manager.clone(), - windows: self.windows.clone(), - #[cfg(feature = "system-tray")] - tray_context: self.tray_context.clone(), - }, - }, + &self.context.main_thread.web_context, + self.context.clone(), pending, )?; - #[cfg(target_os = "windows")] - { - let id = webview.inner.window().id(); - if let WindowHandle::Webview(ref webview) = webview.inner { - if let Some(controller) = webview.controller() { - let proxy = self.event_loop.create_proxy(); - let mut token = EventRegistrationToken::default(); - unsafe { - controller.GotFocus( - FocusChangedEventHandler::create(Box::new(move |_, _| { - let _ = proxy.send_event(Message::Webview( - id, - WebviewMessage::WebviewEvent(WebviewEvent::Focused(true)), - )); - Ok(()) - })), - &mut token, - ) - } - .unwrap(); - let proxy = self.event_loop.create_proxy(); - unsafe { - controller.LostFocus( - FocusChangedEventHandler::create(Box::new(move |_, _| { - let _ = proxy.send_event(Message::Webview( - id, - WebviewMessage::WebviewEvent(WebviewEvent::Focused(false)), - )); - Ok(()) - })), - &mut token, - ) - } - .unwrap(); - } - } - } - let dispatcher = WryDispatcher { - window_id: webview.inner.window().id(), - context: Context { - main_thread_id: self.main_thread_id, - proxy, - window_event_listeners: self.window_event_listeners.clone(), - menu_event_listeners: self.menu_event_listeners.clone(), - main_thread: DispatcherMainThreadContext { - window_target: self.event_loop.deref().clone(), - web_context: self.web_context.clone(), - global_shortcut_manager: self.global_shortcut_manager.clone(), - clipboard_manager: self.clipboard_manager.clone(), - windows: self.windows.clone(), - #[cfg(feature = "system-tray")] - tray_context: self.tray_context.clone(), - }, - }, + window_id, + context: self.context.clone(), }; self + .context + .main_thread .windows - .lock() - .unwrap() - .insert(webview.inner.window().id(), webview); + .borrow_mut() + .insert(window_id, webview); Ok(DetachedWindow { label, dispatcher, menu_ids, - js_event_listeners, }) } - #[cfg(feature = "system-tray")] - fn system_tray(&self, system_tray: SystemTray) -> Result { - let icon = system_tray - .icon - .expect("tray icon not set") - .into_tray_icon(); - - let mut items = HashMap::new(); - - #[cfg(target_os = "macos")] - let tray = SystemTrayBuilder::new( - icon, - system_tray - .menu - .map(|menu| to_wry_context_menu(&mut items, menu)), - ) - .with_icon_as_template(system_tray.icon_as_template) - .build(&self.event_loop) - .map_err(|e| Error::SystemTray(Box::new(e)))?; - - #[cfg(not(target_os = "macos"))] - let tray = SystemTrayBuilder::new( - icon, - system_tray - .menu - .map(|menu| to_wry_context_menu(&mut items, menu)), - ) - .build(&self.event_loop) - .map_err(|e| Error::SystemTray(Box::new(e)))?; - - *self.tray_context.items.lock().unwrap() = items; - *self.tray_context.tray.lock().unwrap() = Some(Arc::new(Mutex::new(tray))); + #[cfg(all(desktop, feature = "system-tray"))] + fn system_tray(&self, mut system_tray: SystemTray) -> Result { + let id = system_tray.id; + let mut listeners = Vec::new(); + if let Some(l) = system_tray.on_event.take() { + listeners.push(Arc::new(l)); + } + let (tray, items) = create_tray(WryTrayId(id), system_tray, &self.event_loop)?; + self + .context + .main_thread + .system_tray_manager + .trays + .lock() + .unwrap() + .insert( + id, + TrayContext { + tray: Arc::new(Mutex::new(Some(tray))), + listeners: Arc::new(Mutex::new(listeners)), + items: Arc::new(Mutex::new(items)), + }, + ); Ok(SystemTrayHandle { + context: self.context.clone(), + id, proxy: self.event_loop.create_proxy(), }) } - #[cfg(feature = "system-tray")] - fn on_system_tray_event(&mut self, f: F) -> Uuid { - let id = Uuid::new_v4(); + #[cfg(all(desktop, feature = "system-tray"))] + fn on_system_tray_event(&mut self, f: F) { self - .tray_context - .listeners + .context + .main_thread + .system_tray_manager + .global_listeners .lock() .unwrap() - .insert(id, Box::new(f)); - id + .push(Arc::new(Box::new(f))); } #[cfg(target_os = "macos")] @@ -1836,19 +2087,36 @@ impl Runtime for Wry { }); } - fn run_iteration(&mut self, mut callback: F) -> RunIteration { + #[cfg(target_os = "macos")] + fn show(&self) { + self.event_loop.show_application(); + } + + #[cfg(target_os = "macos")] + fn hide(&self) { + self.event_loop.hide_application(); + } + + fn set_device_event_filter(&mut self, filter: DeviceEventFilter) { + self + .event_loop + .set_device_event_filter(DeviceEventFilterWrapper::from(filter).0); + } + + #[cfg(desktop)] + fn run_iteration) + 'static>(&mut self, mut callback: F) -> RunIteration { use wry::application::platform::run_return::EventLoopExtRunReturn; - let windows = self.windows.clone(); - let web_context = &self.web_context; - let window_event_listeners = self.window_event_listeners.clone(); - let menu_event_listeners = self.menu_event_listeners.clone(); - #[cfg(feature = "system-tray")] - let tray_context = self.tray_context.clone(); - let global_shortcut_manager = self.global_shortcut_manager.clone(); - let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone(); - let clipboard_manager = self.clipboard_manager.clone(); + let windows = self.context.main_thread.windows.clone(); + let webview_id_map = self.context.webview_id_map.clone(); + let web_context = &self.context.main_thread.web_context; + let plugins = self.context.plugins.clone(); + #[cfg(all(desktop, feature = "system-tray"))] + let system_tray_manager = self.context.main_thread.system_tray_manager.clone(); + let mut iteration = RunIteration::default(); + let proxy = self.event_loop.create_proxy(); + self .event_loop .run_return(|event, event_loop, control_flow| { @@ -1857,6 +2125,26 @@ impl Runtime for Wry { *control_flow = ControlFlow::Exit; } + for p in plugins.lock().unwrap().iter_mut() { + let prevent_default = p.on_event( + &event, + event_loop, + &proxy, + control_flow, + EventLoopIterationContext { + callback: &mut callback, + webview_id_map: webview_id_map.clone(), + windows: windows.clone(), + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager: system_tray_manager.clone(), + }, + web_context, + ); + if prevent_default { + return; + } + } + iteration = handle_event_loop( event, event_loop, @@ -1864,13 +2152,9 @@ impl Runtime for Wry { EventLoopIterationContext { callback: &mut callback, windows: windows.clone(), - window_event_listeners: &window_event_listeners, - global_shortcut_manager: global_shortcut_manager.clone(), - global_shortcut_manager_handle: &global_shortcut_manager_handle, - clipboard_manager: clipboard_manager.clone(), - menu_event_listeners: &menu_event_listeners, - #[cfg(feature = "system-tray")] - tray_context: &tray_context, + webview_id_map: webview_id_map.clone(), + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager: system_tray_manager.clone(), }, web_context, ); @@ -1879,32 +2163,47 @@ impl Runtime for Wry { iteration } - fn run(self, mut callback: F) { - let windows = self.windows.clone(); - let web_context = self.web_context; - let window_event_listeners = self.window_event_listeners.clone(); - let menu_event_listeners = self.menu_event_listeners.clone(); - #[cfg(feature = "system-tray")] - let tray_context = self.tray_context; - let global_shortcut_manager = self.global_shortcut_manager.clone(); - let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone(); - let clipboard_manager = self.clipboard_manager.clone(); + fn run) + 'static>(self, mut callback: F) { + let windows = self.context.main_thread.windows.clone(); + let webview_id_map = self.context.webview_id_map.clone(); + let web_context = self.context.main_thread.web_context; + let plugins = self.context.plugins.clone(); + + #[cfg(all(desktop, feature = "system-tray"))] + let system_tray_manager = self.context.main_thread.system_tray_manager; + + let proxy = self.event_loop.create_proxy(); self.event_loop.run(move |event, event_loop, control_flow| { + for p in plugins.lock().unwrap().iter_mut() { + let prevent_default = p.on_event( + &event, + event_loop, + &proxy, + control_flow, + EventLoopIterationContext { + callback: &mut callback, + webview_id_map: webview_id_map.clone(), + windows: windows.clone(), + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager: system_tray_manager.clone(), + }, + &web_context, + ); + if prevent_default { + return; + } + } handle_event_loop( event, event_loop, control_flow, EventLoopIterationContext { callback: &mut callback, + webview_id_map: webview_id_map.clone(), windows: windows.clone(), - window_event_listeners: &window_event_listeners, - global_shortcut_manager: global_shortcut_manager.clone(), - global_shortcut_manager_handle: &global_shortcut_manager_handle, - clipboard_manager: clipboard_manager.clone(), - menu_event_listeners: &menu_event_listeners, - #[cfg(feature = "system-tray")] - tray_context: &tray_context, + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager: system_tray_manager.clone(), }, &web_context, ); @@ -1912,256 +2211,339 @@ impl Runtime for Wry { } } -pub struct EventLoopIterationContext<'a> { - callback: &'a mut (dyn FnMut(RunEvent) + 'static), - windows: Arc>>, - window_event_listeners: &'a WindowEventListeners, - global_shortcut_manager: Arc>, - global_shortcut_manager_handle: &'a GlobalShortcutManagerHandle, - clipboard_manager: Arc>, - menu_event_listeners: &'a MenuEventListeners, - #[cfg(feature = "system-tray")] - tray_context: &'a TrayContext, +pub struct EventLoopIterationContext<'a, T: UserEvent> { + pub callback: &'a mut (dyn FnMut(RunEvent) + 'static), + pub webview_id_map: WebviewIdStore, + pub windows: Arc>>, + #[cfg(all(desktop, feature = "system-tray"))] + pub system_tray_manager: SystemTrayManager, } -struct UserMessageContext<'a> { - window_event_listeners: &'a WindowEventListeners, - global_shortcut_manager: Arc>, - clipboard_manager: Arc>, - menu_event_listeners: &'a MenuEventListeners, - windows: Arc>>, - #[cfg(feature = "system-tray")] - tray_context: &'a TrayContext, +struct UserMessageContext { + windows: Arc>>, + webview_id_map: WebviewIdStore, + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager: SystemTrayManager, } -fn handle_user_message( - event_loop: &EventLoopWindowTarget, - message: Message, - context: UserMessageContext<'_>, +fn handle_user_message( + event_loop: &EventLoopWindowTarget>, + message: Message, + context: UserMessageContext, web_context: &WebContextStore, ) -> RunIteration { let UserMessageContext { - window_event_listeners, - menu_event_listeners, - global_shortcut_manager, - clipboard_manager, + webview_id_map, windows, - #[cfg(feature = "system-tray")] - tray_context, + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager, } = context; match message { Message::Task(task) => task(), + #[cfg(target_os = "macos")] + Message::Application(application_message) => match application_message { + ApplicationMessage::Show => { + event_loop.show_application(); + } + ApplicationMessage::Hide => { + event_loop.hide_application(); + } + }, Message::Window(id, window_message) => { - if let Some(webview) = windows - .lock() - .expect("poisoned webview collection") - .get_mut(&id) - { - let window = webview.inner.window(); - match window_message { - #[cfg(any(debug_assertions, feature = "devtools"))] - WindowMessage::OpenDevTools => { - if let WindowHandle::Webview(w) = &webview.inner { - w.devtool(); - } - } - // Getters - WindowMessage::ScaleFactor(tx) => tx.send(window.scale_factor()).unwrap(), - WindowMessage::InnerPosition(tx) => tx - .send( - window - .inner_position() - .map(|p| PhysicalPositionWrapper(p).into()) - .map_err(|_| Error::FailedToSendMessage), - ) - .unwrap(), - WindowMessage::OuterPosition(tx) => tx - .send( - window - .outer_position() - .map(|p| PhysicalPositionWrapper(p).into()) - .map_err(|_| Error::FailedToSendMessage), - ) - .unwrap(), - WindowMessage::InnerSize(tx) => tx - .send(PhysicalSizeWrapper(webview.inner.inner_size()).into()) - .unwrap(), - WindowMessage::OuterSize(tx) => tx - .send(PhysicalSizeWrapper(window.outer_size()).into()) - .unwrap(), - WindowMessage::IsFullscreen(tx) => tx.send(window.fullscreen().is_some()).unwrap(), - WindowMessage::IsMaximized(tx) => tx.send(window.is_maximized()).unwrap(), - WindowMessage::IsDecorated(tx) => tx.send(window.is_decorated()).unwrap(), - WindowMessage::IsResizable(tx) => tx.send(window.is_resizable()).unwrap(), - WindowMessage::IsVisible(tx) => tx.send(window.is_visible()).unwrap(), - WindowMessage::IsMenuVisible(tx) => tx.send(window.is_menu_visible()).unwrap(), - WindowMessage::CurrentMonitor(tx) => tx.send(window.current_monitor()).unwrap(), - WindowMessage::PrimaryMonitor(tx) => tx.send(window.primary_monitor()).unwrap(), - WindowMessage::AvailableMonitors(tx) => { - tx.send(window.available_monitors().collect()).unwrap() - } - #[cfg(target_os = "macos")] - WindowMessage::NSWindow(tx) => tx.send(NSWindow(window.ns_window())).unwrap(), - #[cfg(windows)] - WindowMessage::Hwnd(tx) => tx.send(Hwnd(HWND(window.hwnd() as _))).unwrap(), - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - WindowMessage::GtkWindow(tx) => tx.send(GtkWindow(window.gtk_window().clone())).unwrap(), - // Setters - WindowMessage::Center(tx) => { - tx.send(center_window(window, webview.inner.inner_size())) - .unwrap(); - } - WindowMessage::RequestUserAttention(request_type) => { - window.request_user_attention(request_type.map(|r| r.0)); - } - WindowMessage::SetResizable(resizable) => window.set_resizable(resizable), - WindowMessage::SetTitle(title) => window.set_title(&title), - WindowMessage::Maximize => window.set_maximized(true), - WindowMessage::Unmaximize => window.set_maximized(false), - WindowMessage::Minimize => window.set_minimized(true), - WindowMessage::Unminimize => window.set_minimized(false), - WindowMessage::ShowMenu => window.show_menu(), - WindowMessage::HideMenu => window.hide_menu(), - WindowMessage::Show => window.set_visible(true), - WindowMessage::Hide => window.set_visible(false), - WindowMessage::Close => panic!("cannot handle `WindowMessage::Close` on the main thread"), - WindowMessage::SetDecorations(decorations) => window.set_decorations(decorations), - WindowMessage::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top), - WindowMessage::SetSize(size) => { - window.set_inner_size(SizeWrapper::from(size).0); - } - WindowMessage::SetMinSize(size) => { - window.set_min_inner_size(size.map(|s| SizeWrapper::from(s).0)); - } - WindowMessage::SetMaxSize(size) => { - window.set_max_inner_size(size.map(|s| SizeWrapper::from(s).0)); - } - WindowMessage::SetPosition(position) => { - window.set_outer_position(PositionWrapper::from(position).0) - } - WindowMessage::SetFullscreen(fullscreen) => { - if fullscreen { - window.set_fullscreen(Some(Fullscreen::Borderless(None))) - } else { - window.set_fullscreen(None) + if let WindowMessage::UpdateMenuItem(item_id, update) = window_message { + if let Some(menu_items) = windows.borrow_mut().get_mut(&id).map(|w| &mut w.menu_items) { + if let Some(menu_items) = menu_items.as_mut() { + let item = menu_items.get_mut(&item_id).expect("menu item not found"); + match update { + MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled), + MenuUpdate::SetTitle(title) => item.set_title(&title), + MenuUpdate::SetSelected(selected) => item.set_selected(selected), + #[cfg(target_os = "macos")] + MenuUpdate::SetNativeImage(image) => { + item.set_native_image(NativeImageWrapper::from(image).0) + } } } - WindowMessage::SetFocus => { - window.set_focus(); - } - WindowMessage::SetIcon(icon) => { - window.set_window_icon(Some(icon)); - } - WindowMessage::SetSkipTaskbar(_skip) => { - #[cfg(any(target_os = "windows", target_os = "linux"))] - window.set_skip_taskbar(_skip); - } - WindowMessage::DragWindow => { - let _ = window.drag_window(); - } - WindowMessage::UpdateMenuItem(id, update) => { - if let Some(menu_items) = webview.menu_items.as_mut() { - let item = menu_items.get_mut(&id).expect("menu item not found"); - match update { - MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled), - MenuUpdate::SetTitle(title) => item.set_title(&title), - MenuUpdate::SetSelected(selected) => item.set_selected(selected), + } + } else { + let w = windows.borrow().get(&id).map(|w| { + ( + w.inner.clone(), + w.window_event_listeners.clone(), + w.menu_event_listeners.clone(), + ) + }); + if let Some((Some(window), window_event_listeners, menu_event_listeners)) = w { + match window_message { + WindowMessage::WithWebview(f) => { + if let WindowHandle::Webview { inner: w, .. } = &window { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + use wry::webview::WebviewExtUnix; + f(w.webview()); + } #[cfg(target_os = "macos")] - MenuUpdate::SetNativeImage(image) => { - item.set_native_image(NativeImageWrapper::from(image).0) + { + use wry::webview::WebviewExtMacOS; + f(Webview { + webview: w.webview(), + manager: w.manager(), + ns_window: w.ns_window(), + }); + } + #[cfg(target_os = "ios")] + { + use wry::{application::platform::ios::WindowExtIOS, webview::WebviewExtIOS}; + + f(Webview { + webview: w.webview(), + manager: w.manager(), + view_controller: w.window().ui_view_controller() as cocoa::base::id, + }); + } + #[cfg(windows)] + { + f(Webview { + controller: w.controller(), + }); + } + #[cfg(target_os = "android")] + { + f(w.handle()) } } } - } - WindowMessage::RequestRedraw => { - window.request_redraw(); + + WindowMessage::AddEventListener(id, listener) => { + window_event_listeners.lock().unwrap().insert(id, listener); + } + + WindowMessage::AddMenuEventListener(id, listener) => { + menu_event_listeners.lock().unwrap().insert(id, listener); + } + + #[cfg(any(debug_assertions, feature = "devtools"))] + WindowMessage::OpenDevTools => { + if let WindowHandle::Webview { inner: w, .. } = &window { + w.open_devtools(); + } + } + #[cfg(any(debug_assertions, feature = "devtools"))] + WindowMessage::CloseDevTools => { + if let WindowHandle::Webview { inner: w, .. } = &window { + w.close_devtools(); + } + } + #[cfg(any(debug_assertions, feature = "devtools"))] + WindowMessage::IsDevToolsOpen(tx) => { + if let WindowHandle::Webview { inner: w, .. } = &window { + tx.send(w.is_devtools_open()).unwrap(); + } else { + tx.send(false).unwrap(); + } + } + // Getters + WindowMessage::Url(tx) => { + if let WindowHandle::Webview { inner: w, .. } = &window { + tx.send(w.url()).unwrap(); + } + } + WindowMessage::ScaleFactor(tx) => tx.send(window.scale_factor()).unwrap(), + WindowMessage::InnerPosition(tx) => tx + .send( + window + .inner_position() + .map(|p| PhysicalPositionWrapper(p).into()) + .map_err(|_| Error::FailedToSendMessage), + ) + .unwrap(), + WindowMessage::OuterPosition(tx) => tx + .send( + window + .outer_position() + .map(|p| PhysicalPositionWrapper(p).into()) + .map_err(|_| Error::FailedToSendMessage), + ) + .unwrap(), + WindowMessage::InnerSize(tx) => tx + .send(PhysicalSizeWrapper(window.inner_size()).into()) + .unwrap(), + WindowMessage::OuterSize(tx) => tx + .send(PhysicalSizeWrapper(window.outer_size()).into()) + .unwrap(), + WindowMessage::IsFullscreen(tx) => tx.send(window.fullscreen().is_some()).unwrap(), + WindowMessage::IsMinimized(tx) => tx.send(window.is_minimized()).unwrap(), + WindowMessage::IsMaximized(tx) => tx.send(window.is_maximized()).unwrap(), + WindowMessage::IsFocused(tx) => tx.send(window.is_focused()).unwrap(), + WindowMessage::IsDecorated(tx) => tx.send(window.is_decorated()).unwrap(), + WindowMessage::IsResizable(tx) => tx.send(window.is_resizable()).unwrap(), + WindowMessage::IsMaximizable(tx) => tx.send(window.is_maximizable()).unwrap(), + WindowMessage::IsMinimizable(tx) => tx.send(window.is_minimizable()).unwrap(), + WindowMessage::IsClosable(tx) => tx.send(window.is_closable()).unwrap(), + WindowMessage::IsVisible(tx) => tx.send(window.is_visible()).unwrap(), + WindowMessage::Title(tx) => tx.send(window.title()).unwrap(), + WindowMessage::IsMenuVisible(tx) => tx.send(window.is_menu_visible()).unwrap(), + WindowMessage::CurrentMonitor(tx) => tx.send(window.current_monitor()).unwrap(), + WindowMessage::PrimaryMonitor(tx) => tx.send(window.primary_monitor()).unwrap(), + WindowMessage::AvailableMonitors(tx) => { + tx.send(window.available_monitors().collect()).unwrap() + } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + WindowMessage::GtkWindow(tx) => { + tx.send(GtkWindow(window.gtk_window().clone())).unwrap() + } + WindowMessage::RawWindowHandle(tx) => tx + .send(RawWindowHandle(window.raw_window_handle())) + .unwrap(), + WindowMessage::Theme(tx) => { + tx.send(map_theme(&window.theme())).unwrap(); + } + // Setters + WindowMessage::Center => { + let _ = center_window(&window, window.inner_size()); + } + WindowMessage::RequestUserAttention(request_type) => { + window.request_user_attention(request_type.map(|r| r.0)); + } + WindowMessage::SetResizable(resizable) => window.set_resizable(resizable), + WindowMessage::SetMaximizable(maximizable) => window.set_maximizable(maximizable), + WindowMessage::SetMinimizable(minimizable) => window.set_minimizable(minimizable), + WindowMessage::SetClosable(closable) => window.set_closable(closable), + WindowMessage::SetTitle(title) => window.set_title(&title), + WindowMessage::Maximize => window.set_maximized(true), + WindowMessage::Unmaximize => window.set_maximized(false), + WindowMessage::Minimize => window.set_minimized(true), + WindowMessage::Unminimize => window.set_minimized(false), + WindowMessage::ShowMenu => window.show_menu(), + WindowMessage::HideMenu => window.hide_menu(), + WindowMessage::Show => window.set_visible(true), + WindowMessage::Hide => window.set_visible(false), + WindowMessage::Close => { + panic!("cannot handle `WindowMessage::Close` on the main thread") + } + WindowMessage::SetDecorations(decorations) => window.set_decorations(decorations), + WindowMessage::SetShadow(_enable) => { + #[cfg(windows)] + window.set_undecorated_shadow(_enable); + #[cfg(target_os = "macos")] + window.set_has_shadow(_enable); + } + WindowMessage::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top), + WindowMessage::SetContentProtected(protected) => { + window.set_content_protection(protected) + } + WindowMessage::SetSize(size) => { + window.set_inner_size(SizeWrapper::from(size).0); + } + WindowMessage::SetMinSize(size) => { + window.set_min_inner_size(size.map(|s| SizeWrapper::from(s).0)); + } + WindowMessage::SetMaxSize(size) => { + window.set_max_inner_size(size.map(|s| SizeWrapper::from(s).0)); + } + WindowMessage::SetPosition(position) => { + window.set_outer_position(PositionWrapper::from(position).0) + } + WindowMessage::SetFullscreen(fullscreen) => { + if fullscreen { + window.set_fullscreen(Some(Fullscreen::Borderless(None))) + } else { + window.set_fullscreen(None) + } + } + WindowMessage::SetFocus => { + window.set_focus(); + } + WindowMessage::SetIcon(icon) => { + window.set_window_icon(Some(icon)); + } + #[allow(unused_variables)] + WindowMessage::SetSkipTaskbar(skip) => { + #[cfg(any(windows, target_os = "linux"))] + window.set_skip_taskbar(skip); + } + WindowMessage::SetCursorGrab(grab) => { + let _ = window.set_cursor_grab(grab); + } + WindowMessage::SetCursorVisible(visible) => { + window.set_cursor_visible(visible); + } + WindowMessage::SetCursorIcon(icon) => { + window.set_cursor_icon(CursorIconWrapper::from(icon).0); + } + WindowMessage::SetCursorPosition(position) => { + let _ = window.set_cursor_position(PositionWrapper::from(position).0); + } + WindowMessage::SetIgnoreCursorEvents(ignore) => { + let _ = window.set_ignore_cursor_events(ignore); + } + WindowMessage::DragWindow => { + let _ = window.drag_window(); + } + WindowMessage::UpdateMenuItem(_id, _update) => { + // already handled + } + WindowMessage::RequestRedraw => { + window.request_redraw(); + } } } } } Message::Webview(id, webview_message) => match webview_message { WebviewMessage::EvaluateScript(script) => { - if let Some(WindowHandle::Webview(webview)) = windows - .lock() - .expect("poisoned webview collection") - .get(&id) - .map(|w| &w.inner) + if let Some(WindowHandle::Webview { inner: webview, .. }) = + windows.borrow().get(&id).and_then(|w| w.inner.as_ref()) { if let Err(e) = webview.evaluate_script(&script) { - #[cfg(debug_assertions)] - eprintln!("{}", e); + debug_eprintln!("{}", e); } } } WebviewMessage::Print => { - if let Some(WindowHandle::Webview(webview)) = windows - .lock() - .expect("poisoned webview collection") - .get(&id) - .map(|w| &w.inner) + if let Some(WindowHandle::Webview { inner: webview, .. }) = + windows.borrow().get(&id).and_then(|w| w.inner.as_ref()) { let _ = webview.print(); } } - WebviewMessage::WebviewEvent(event) => { - if let Some(event) = WindowEventWrapper::from(&event).0 { - for handler in window_event_listeners - .lock() - .unwrap() - .get(&id) - .unwrap() - .lock() - .unwrap() - .values() - { - handler(&event); - } - } - } + WebviewMessage::WebviewEvent(_event) => { /* already handled */ } }, - Message::CreateWebview(handler, sender) => match handler(event_loop, web_context) { + Message::CreateWebview(window_id, handler) => match handler(event_loop, web_context) { Ok(webview) => { - let window_id = webview.inner.window().id(); - windows - .lock() - .expect("poisoned webview collection") - .insert(window_id, webview); - sender.send(window_id).unwrap(); + windows.borrow_mut().insert(window_id, webview); } Err(e) => { - #[cfg(debug_assertions)] - eprintln!("{}", e); + debug_eprintln!("{}", e); } }, - Message::CreateWindow(handler, sender) => { + Message::CreateWindow(window_id, handler, sender) => { let (label, builder) = handler(); if let Ok(window) = builder.build(event_loop) { - let window_id = window.id(); - - window_event_listeners - .lock() - .unwrap() - .insert(window.id(), WindowEventListenersMap::default()); - - menu_event_listeners - .lock() - .unwrap() - .insert(window.id(), WindowMenuEventListeners::default()); + webview_id_map.insert(window.id(), window_id); let w = Arc::new(window); - windows.lock().expect("poisoned webview collection").insert( + windows.borrow_mut().insert( window_id, WindowWrapper { label, - inner: WindowHandle::Window(w.clone()), + inner: Some(WindowHandle::Window(w.clone())), menu_items: Default::default(), + window_event_listeners: Default::default(), + menu_event_listeners: Default::default(), }, ); sender.send(Ok(Arc::downgrade(&w))).unwrap(); @@ -2170,129 +2552,118 @@ fn handle_user_message( } } - #[cfg(feature = "system-tray")] - Message::Tray(tray_message) => match tray_message { - TrayMessage::UpdateItem(menu_id, update) => { - let mut tray = tray_context.items.as_ref().lock().unwrap(); - let item = tray.get_mut(&menu_id).expect("menu item not found"); - match update { - MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled), - MenuUpdate::SetTitle(title) => item.set_title(&title), - MenuUpdate::SetSelected(selected) => item.set_selected(selected), - #[cfg(target_os = "macos")] - MenuUpdate::SetNativeImage(image) => { - item.set_native_image(NativeImageWrapper::from(image).0) - } - } - } - TrayMessage::UpdateMenu(menu) => { - if let Some(tray) = &*tray_context.tray.lock().unwrap() { - let mut items = HashMap::new(); - tray - .lock() - .unwrap() - .set_menu(&to_wry_context_menu(&mut items, menu)); - *tray_context.items.lock().unwrap() = items; + #[cfg(all(desktop, feature = "system-tray"))] + Message::Tray(tray_id, tray_message) => { + let mut trays = system_tray_manager.trays.lock().unwrap(); + + if let TrayMessage::Create(mut tray, tx) = tray_message { + let mut listeners = Vec::new(); + if let Some(l) = tray.on_event.take() { + listeners.push(Arc::new(l)); } - } - TrayMessage::UpdateIcon(icon) => { - if let Some(tray) = &*tray_context.tray.lock().unwrap() { - tray.lock().unwrap().set_icon(icon.into_tray_icon()); + match create_tray(WryTrayId(tray_id), tray, event_loop) { + Ok((tray, items)) => { + trays.insert( + tray_id, + TrayContext { + tray: Arc::new(Mutex::new(Some(tray))), + listeners: Arc::new(Mutex::new(listeners)), + items: Arc::new(Mutex::new(items)), + }, + ); + + tx.send(Ok(())).unwrap(); + } + + Err(e) => { + tx.send(Err(e)).unwrap(); + } } - } - #[cfg(target_os = "macos")] - TrayMessage::UpdateIconAsTemplate(is_template) => { - if let Some(tray) = &*tray_context.tray.lock().unwrap() { - tray.lock().unwrap().set_icon_as_template(is_template); + } else if let Some(tray_context) = trays.get(&tray_id) { + match tray_message { + TrayMessage::UpdateItem(menu_id, update) => { + let mut tray = tray_context.items.as_ref().lock().unwrap(); + let item = tray.get_mut(&menu_id).expect("menu item not found"); + match update { + MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled), + MenuUpdate::SetTitle(title) => item.set_title(&title), + MenuUpdate::SetSelected(selected) => item.set_selected(selected), + #[cfg(target_os = "macos")] + MenuUpdate::SetNativeImage(image) => { + item.set_native_image(NativeImageWrapper::from(image).0) + } + } + } + TrayMessage::UpdateMenu(menu) => { + if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { + let mut items = HashMap::new(); + tray.set_menu(&to_wry_context_menu(&mut items, menu)); + *tray_context.items.lock().unwrap() = items; + } + } + TrayMessage::UpdateIcon(icon) => { + if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { + if let Ok(icon) = TrayIcon::try_from(icon) { + tray.set_icon(icon.0); + } + } + } + #[cfg(target_os = "macos")] + TrayMessage::UpdateIconAsTemplate(is_template) => { + if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { + tray.set_icon_as_template(is_template); + } + } + #[cfg(target_os = "macos")] + TrayMessage::UpdateTitle(title) => { + if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { + tray.set_title(&title); + } + } + TrayMessage::UpdateTooltip(tooltip) => { + if let Some(tray) = &mut *tray_context.tray.lock().unwrap() { + tray.set_tooltip(&tooltip); + } + } + TrayMessage::Create(_tray, _tx) => { + // already handled + } + TrayMessage::Destroy(tx) => { + *tray_context.tray.lock().unwrap() = None; + tray_context.listeners.lock().unwrap().clear(); + tray_context.items.lock().unwrap().clear(); + tx.send(Ok(())).unwrap(); + } } } - TrayMessage::Close => { - *tray_context.tray.lock().unwrap() = None; - tray_context.listeners.lock().unwrap().clear(); - tray_context.items.lock().unwrap().clear(); - } - }, - Message::GlobalShortcut(message) => match message { - GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx - .send( - global_shortcut_manager - .lock() - .unwrap() - .is_registered(&accelerator), - ) - .unwrap(), - GlobalShortcutMessage::Register(accelerator, tx) => tx - .send( - global_shortcut_manager - .lock() - .unwrap() - .register(accelerator) - .map(GlobalShortcutWrapper) - .map_err(|e| Error::GlobalShortcut(Box::new(e))), - ) - .unwrap(), - GlobalShortcutMessage::Unregister(shortcut, tx) => tx - .send( - global_shortcut_manager - .lock() - .unwrap() - .unregister(shortcut.0) - .map_err(|e| Error::GlobalShortcut(Box::new(e))), - ) - .unwrap(), - GlobalShortcutMessage::UnregisterAll(tx) => tx - .send( - global_shortcut_manager - .lock() - .unwrap() - .unregister_all() - .map_err(|e| Error::GlobalShortcut(Box::new(e))), - ) - .unwrap(), - }, - Message::Clipboard(message) => match message { - ClipboardMessage::WriteText(text, tx) => { - clipboard_manager.lock().unwrap().write_text(text); - tx.send(()).unwrap(); - } - ClipboardMessage::ReadText(tx) => tx - .send(clipboard_manager.lock().unwrap().read_text()) - .unwrap(), - }, + } + Message::UserEvent(_) => (), } let it = RunIteration { - window_count: windows.lock().expect("poisoned webview collection").len(), + window_count: windows.borrow().len(), }; it } -fn handle_event_loop( - event: Event<'_, Message>, - event_loop: &EventLoopWindowTarget, +fn handle_event_loop( + event: Event<'_, Message>, + event_loop: &EventLoopWindowTarget>, control_flow: &mut ControlFlow, - context: EventLoopIterationContext<'_>, + context: EventLoopIterationContext<'_, T>, web_context: &WebContextStore, ) -> RunIteration { let EventLoopIterationContext { callback, + webview_id_map, windows, - window_event_listeners, - global_shortcut_manager, - global_shortcut_manager_handle, - clipboard_manager, - menu_event_listeners, - #[cfg(feature = "system-tray")] - tray_context, + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager, } = context; - if *control_flow == ControlFlow::Exit { - return RunIteration { - window_count: windows.lock().expect("poisoned webview collection").len(), - }; + if *control_flow != ControlFlow::Exit { + *control_flow = ControlFlow::Wait; } - *control_flow = ControlFlow::Wait; - match event { Event::NewEvents(StartCause::Init) => { callback(RunEvent::Ready); @@ -2306,32 +2677,52 @@ fn handle_event_loop( callback(RunEvent::MainEventsCleared); } - Event::GlobalShortcutEvent(accelerator_id) => { - for (id, handler) in &*global_shortcut_manager_handle.listeners.lock().unwrap() { - if accelerator_id == *id { - handler(); - } - } + Event::LoopDestroyed => { + callback(RunEvent::Exit); } + Event::MenuEvent { window_id, menu_id, origin: MenuType::MenuBar, .. } => { - let window_id = window_id.unwrap(); // always Some on MenuBar event + #[allow(unused_mut)] + let mut window_id = window_id.unwrap(); // always Some on MenuBar event + + #[cfg(target_os = "macos")] + { + // safety: we're only checking to see if the window_id is 0 + // which is the value sent by macOS when the window is minimized (NSApplication::sharedApplication::mainWindow is null) + if window_id == unsafe { WindowId::dummy() } { + window_id = *webview_id_map.0.lock().unwrap().keys().next().unwrap(); + } + } + let event = MenuEvent { menu_item_id: menu_id.0, }; let window_menu_event_listeners = { - let listeners = menu_event_listeners.lock().unwrap(); - listeners.get(&window_id).cloned().unwrap_or_default() + // on macOS the window id might be the inspector window if it is detached + let window_id = if let Some(window_id) = webview_id_map.get(&window_id) { + window_id + } else { + *webview_id_map.0.lock().unwrap().values().next().unwrap() + }; + windows + .borrow() + .get(&window_id) + .unwrap() + .menu_event_listeners + .clone() }; - for handler in window_menu_event_listeners.lock().unwrap().values() { + let listeners = window_menu_event_listeners.lock().unwrap(); + let handlers = listeners.values(); + for handler in handlers { handler(&event); } } - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] Event::MenuEvent { window_id: _, menu_id, @@ -2339,12 +2730,39 @@ fn handle_event_loop( .. } => { let event = SystemTrayEvent::MenuItemClick(menu_id.0); - for handler in tray_context.listeners.lock().unwrap().values() { - handler(&event); + + let trays = system_tray_manager.trays.lock().unwrap(); + let trays_iter = trays.iter(); + + let (mut listeners, mut tray_id) = (None, 0); + for (id, tray_context) in trays_iter { + let has_menu = { + let items = tray_context.items.lock().unwrap(); + items.contains_key(&menu_id.0) + }; + if has_menu { + listeners.replace(tray_context.listeners.lock().unwrap().clone()); + tray_id = *id; + break; + } + } + drop(trays); + if let Some(listeners) = listeners { + let handlers = listeners.iter(); + for handler in handlers { + handler(&event); + } + + let global_listeners = system_tray_manager.global_listeners.lock().unwrap(); + let global_listeners_iter = global_listeners.iter(); + for global_listener in global_listeners_iter { + global_listener(tray_id, &event); + } } } - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] Event::TrayEvent { + id, bounds, event, position: _cursor_position, @@ -2360,217 +2778,178 @@ fn handle_event_loop( // default to left click _ => SystemTrayEvent::LeftClick { position, size }, }; - for handler in tray_context.listeners.lock().unwrap().values() { - handler(&event); + let trays = system_tray_manager.trays.lock().unwrap(); + if let Some(tray_context) = trays.get(&id.0) { + let listeners = tray_context.listeners.lock().unwrap(); + let iter = listeners.iter(); + for handler in iter { + handler(&event); + } + } + + let global_listeners = system_tray_manager.global_listeners.lock().unwrap(); + let global_listeners_iter = global_listeners.iter(); + for global_listener in global_listeners_iter { + global_listener(id.0, &event); + } + } + Event::UserEvent(Message::Webview(id, WebviewMessage::WebviewEvent(event))) => { + if let Some(event) = WindowEventWrapper::from(&event).0 { + let windows = windows.borrow(); + let window = windows.get(&id); + if let Some(window) = window { + callback(RunEvent::WindowEvent { + label: window.label.clone(), + event: event.clone(), + }); + + let listeners = window.window_event_listeners.lock().unwrap(); + let handlers = listeners.values(); + for handler in handlers { + handler(&event); + } + } } } Event::WindowEvent { event, window_id, .. } => { - // NOTE(amrbashir): we handle this event here instead of `match` statement below because - // we want to focus the webview as soon as possible, especially on windows. - if event == WryWindowEvent::Focused(true) { - if let Some(WindowHandle::Webview(webview)) = windows - .lock() - .expect("poisoned webview collection") - .get(&window_id) - .map(|w| &w.inner) + if let Some(window_id) = webview_id_map.get(&window_id) { { - webview.focus(); - } - } - - { - let windows_lock = windows.lock().expect("poisoned webview collection"); - if let Some(window_handle) = windows_lock.get(&window_id).map(|w| &w.inner) { - if let Some(event) = WindowEventWrapper::parse(window_handle, &event).0 { - drop(windows_lock); - for handler in window_event_listeners - .lock() - .unwrap() - .get(&window_id) - .unwrap() - .lock() - .unwrap() - .values() - { - handler(&event); + let windows_ref = windows.borrow(); + if let Some(window) = windows_ref.get(&window_id) { + if let Some(event) = WindowEventWrapper::parse(&window.inner, &event).0 { + let label = window.label.clone(); + let window_event_listeners = window.window_event_listeners.clone(); + + drop(windows_ref); + + callback(RunEvent::WindowEvent { + label, + event: event.clone(), + }); + let listeners = window_event_listeners.lock().unwrap(); + let handlers = listeners.values(); + for handler in handlers { + handler(&event); + } } } } - } - match event { - WryWindowEvent::CloseRequested => { - on_close_requested( - callback, - window_id, - windows.clone(), - control_flow, - window_event_listeners, - menu_event_listeners.clone(), - ); - } - WryWindowEvent::Resized(_) => { - if let Some(WindowHandle::Webview(webview)) = windows - .lock() - .expect("poisoned webview collection") - .get(&window_id) - .map(|w| &w.inner) - { - if let Err(e) = webview.resize() { - #[cfg(debug_assertions)] - eprintln!("{}", e); + match event { + #[cfg(windows)] + WryWindowEvent::ThemeChanged(theme) => { + if let Some(window) = windows.borrow().get(&window_id) { + if let Some(WindowHandle::Webview { inner, .. }) = &window.inner { + let theme = match theme { + WryTheme::Dark => wry::webview::Theme::Dark, + WryTheme::Light => wry::webview::Theme::Light, + _ => wry::webview::Theme::Light, + }; + inner.set_theme(theme); + } + } + } + WryWindowEvent::CloseRequested => { + on_close_requested(callback, window_id, windows.clone()); + } + WryWindowEvent::Destroyed => { + let removed = windows.borrow_mut().remove(&window_id).is_some(); + if removed { + let is_empty = windows.borrow().is_empty(); + if is_empty { + let (tx, rx) = channel(); + callback(RunEvent::ExitRequested { tx }); + + let recv = rx.try_recv(); + let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); + + if !should_prevent { + *control_flow = ControlFlow::Exit; + } + } } } + _ => {} } - _ => {} } } - Event::UserEvent(message) => { - if let Message::Window(id, WindowMessage::Close) = message { - on_window_close( - callback, - id, - windows.lock().expect("poisoned webview collection"), - control_flow, - #[cfg(target_os = "linux")] - window_event_listeners, - menu_event_listeners.clone(), - ); - } else { + Event::UserEvent(message) => match message { + Message::Window(id, WindowMessage::Close) => { + on_window_close(id, windows.clone()); + } + Message::UserEvent(t) => callback(RunEvent::UserEvent(t)), + message => { return handle_user_message( event_loop, message, UserMessageContext { - window_event_listeners, - global_shortcut_manager, - clipboard_manager, - menu_event_listeners, + webview_id_map, windows, - #[cfg(feature = "system-tray")] - tray_context, + #[cfg(all(desktop, feature = "system-tray"))] + system_tray_manager, }, web_context, ); } - } + }, _ => (), } let it = RunIteration { - window_count: windows.lock().expect("poisoned webview collection").len(), + window_count: windows.borrow().len(), }; it } -fn on_close_requested<'a>( - callback: &'a mut (dyn FnMut(RunEvent) + 'static), - window_id: WindowId, - windows: Arc>>, - control_flow: &mut ControlFlow, - window_event_listeners: &WindowEventListeners, - menu_event_listeners: MenuEventListeners, -) -> Option { +fn on_close_requested<'a, T: UserEvent>( + callback: &'a mut (dyn FnMut(RunEvent) + 'static), + window_id: WebviewId, + windows: Arc>>, +) { let (tx, rx) = channel(); - let windows_guard = windows.lock().expect("poisoned webview collection"); - if let Some(w) = windows_guard.get(&window_id) { + let windows_ref = windows.borrow(); + if let Some(w) = windows_ref.get(&window_id) { let label = w.label.clone(); - drop(windows_guard); - for handler in window_event_listeners - .lock() - .unwrap() - .get(&window_id) - .unwrap() - .lock() - .unwrap() - .values() - { + let window_event_listeners = w.window_event_listeners.clone(); + + drop(windows_ref); + + let listeners = window_event_listeners.lock().unwrap(); + let handlers = listeners.values(); + for handler in handlers { handler(&WindowEvent::CloseRequested { - label: label.clone(), signal_tx: tx.clone(), }); } - callback(RunEvent::CloseRequested { + callback(RunEvent::WindowEvent { label, - signal_tx: tx, + event: WindowEvent::CloseRequested { signal_tx: tx }, }); if let Ok(true) = rx.try_recv() { - None } else { - on_window_close( - callback, - window_id, - windows.lock().expect("poisoned webview collection"), - control_flow, - #[cfg(target_os = "linux")] - window_event_listeners, - menu_event_listeners, - ) + on_window_close(window_id, windows); } - } else { - None } } -fn on_window_close<'a>( - callback: &'a mut (dyn FnMut(RunEvent) + 'static), - window_id: WindowId, - mut windows: MutexGuard<'a, HashMap>, - control_flow: &mut ControlFlow, - #[cfg(target_os = "linux")] window_event_listeners: &WindowEventListeners, - menu_event_listeners: MenuEventListeners, -) -> Option { - #[allow(unused_mut)] - let w = if let Some(mut webview) = windows.remove(&window_id) { - let is_empty = windows.is_empty(); - drop(windows); - menu_event_listeners.lock().unwrap().remove(&window_id); - callback(RunEvent::WindowClose(webview.label.clone())); - - if is_empty { - let (tx, rx) = channel(); - callback(RunEvent::ExitRequested { - window_label: webview.label.clone(), - tx, - }); - - let recv = rx.try_recv(); - let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); - - if !should_prevent { - *control_flow = ControlFlow::Exit; - callback(RunEvent::Exit); - } - } - Some(webview) - } else { - None - }; - // TODO: tao does not fire the destroyed event properly - #[cfg(target_os = "linux")] - { - for handler in window_event_listeners - .lock() - .unwrap() - .get(&window_id) - .unwrap() - .lock() - .unwrap() - .values() - { - handler(&WindowEvent::Destroyed); - } +fn on_window_close(window_id: WebviewId, windows: Arc>>) { + if let Some(window_wrapper) = windows.borrow_mut().get_mut(&window_id) { + window_wrapper.inner = None; } - w } -fn center_window(window: &Window, window_size: WryPhysicalSize) -> Result<()> { +pub fn center_window(window: &Window, window_size: WryPhysicalSize) -> Result<()> { if let Some(monitor) = window.current_monitor() { let screen_size = monitor.size(); + let monitor_pos = monitor.position(); let x = (screen_size.width as i32 - window_size.width as i32) / 2; let y = (screen_size.height as i32 - window_size.height as i32) / 2; - window.set_outer_position(WryPhysicalPosition::new(x, y)); + window.set_outer_position(WryPhysicalPosition::new( + monitor_pos.x + x, + monitor_pos.y + y, + )); Ok(()) } else { Err(Error::FailedToGetMonitor) @@ -2610,11 +2989,12 @@ fn to_wry_menu( wry_menu } -fn create_webview( - event_loop: &EventLoopWindowTarget, - web_context: &WebContextStore, - context: Context, - pending: PendingWindow, +fn create_webview( + window_id: WebviewId, + event_loop: &EventLoopWindowTarget>, + web_context_store: &WebContextStore, + context: Context, + pending: PendingWindow>, ) -> Result { #[allow(unused_mut)] let PendingWindow { @@ -2622,13 +3002,38 @@ fn create_webview( uri_scheme_protocols, mut window_builder, ipc_handler, - file_drop_handler, label, url, menu_ids, - js_event_listeners, + #[cfg(target_os = "android")] + on_webview_created, .. } = pending; + let webview_id_map = context.webview_id_map.clone(); + #[cfg(windows)] + let proxy = context.proxy.clone(); + + let window_event_listeners = WindowEventListeners::default(); + + #[cfg(windows)] + { + window_builder.inner = window_builder + .inner + .with_drag_and_drop(webview_attributes.file_drop_handler_enabled); + } + + #[cfg(windows)] + let window_theme = window_builder.inner.window.preferred_theme; + + #[cfg(target_os = "macos")] + { + if window_builder.tabbing_identifier.is_none() + || window_builder.inner.window.transparent + || !window_builder.inner.window.decorations + { + window_builder.inner = window_builder.inner.with_automatic_window_tabbing(false); + } + } let is_window_transparent = window_builder.inner.window.transparent; let menu_items = if let Some(menu) = window_builder.menu { @@ -2641,17 +3046,7 @@ fn create_webview( }; let window = window_builder.inner.build(event_loop).unwrap(); - context - .window_event_listeners - .lock() - .unwrap() - .insert(window.id(), WindowEventListenersMap::default()); - - context - .menu_event_listeners - .lock() - .unwrap() - .insert(window.id(), WindowMenuEventListeners::default()); + webview_id_map.insert(window.id(), window_id); if window_builder.center { let _ = center_window(&window, window.inner_size()); @@ -2660,22 +3055,40 @@ fn create_webview( .map_err(|e| Error::CreateWebview(Box::new(e)))? .with_url(&url) .unwrap() // safe to unwrap because we validate the URL beforehand - .with_transparent(is_window_transparent); + .with_transparent(is_window_transparent) + .with_accept_first_mouse(webview_attributes.accept_first_mouse); + if webview_attributes.file_drop_handler_enabled { + webview_builder = webview_builder + .with_file_drop_handler(create_file_drop_handler(window_event_listeners.clone())); + } + if let Some(navigation_handler) = pending.navigation_handler { + webview_builder = webview_builder.with_navigation_handler(move |url| { + Url::parse(&url).map(&navigation_handler).unwrap_or(true) + }); + } + if let Some(user_agent) = webview_attributes.user_agent { + webview_builder = webview_builder.with_user_agent(&user_agent); + } + + #[cfg(windows)] + if let Some(additional_browser_args) = webview_attributes.additional_browser_args { + webview_builder = webview_builder.with_additional_browser_args(&additional_browser_args); + } + + #[cfg(windows)] + if let Some(theme) = window_theme { + webview_builder = webview_builder.with_theme(match theme { + WryTheme::Dark => wry::webview::Theme::Dark, + WryTheme::Light => wry::webview::Theme::Light, + _ => wry::webview::Theme::Light, + }); + } + if let Some(handler) = ipc_handler { webview_builder = webview_builder.with_ipc_handler(create_ipc_handler( - context.clone(), - label.clone(), - menu_ids.clone(), - js_event_listeners.clone(), - handler, - )); - } - if let Some(handler) = file_drop_handler { - webview_builder = webview_builder.with_file_drop_handler(create_file_drop_handler( context, label.clone(), menu_ids, - js_event_listeners, handler, )); } @@ -2691,19 +3104,19 @@ fn create_webview( webview_builder = webview_builder.with_initialization_script(&script); } - let mut web_context = web_context.lock().expect("poisoned WebContext store"); + let mut web_context = web_context_store.lock().expect("poisoned WebContext store"); let is_first_context = web_context.is_empty(); let automation_enabled = std::env::var("TAURI_AUTOMATION").as_deref() == Ok("true"); - let web_context = match web_context.entry( - // force a unique WebContext when automation is false; + let web_context_key = // force a unique WebContext when automation is false; // the context must be stored on the HashMap because it must outlive the WebView on macOS if automation_enabled { webview_attributes.data_directory.clone() } else { // random unique key - Some(Uuid::new_v4().to_hyphenated().to_string().into()) - }, - ) { + Some(Uuid::new_v4().as_hyphenated().to_string().into()) + }; + let entry = web_context.entry(web_context_key.clone()); + let web_context = match entry { Occupied(occupied) => occupied.into_mut(), Vacant(vacant) => { let mut web_context = WebContext::new(webview_attributes.data_directory); @@ -2720,9 +3133,26 @@ fn create_webview( webview_builder.webview.clipboard = true; } + if webview_attributes.incognito { + webview_builder.webview.incognito = true; + } + #[cfg(any(debug_assertions, feature = "devtools"))] { - webview_builder = webview_builder.with_dev_tool(true); + webview_builder = webview_builder.with_devtools(true); + } + + #[cfg(target_os = "android")] + { + if let Some(on_webview_created) = on_webview_created { + webview_builder = webview_builder.on_webview_created(move |ctx| { + on_webview_created(tauri_runtime::window::CreationContext { + env: ctx.env, + activity: ctx.activity, + webview: ctx.webview, + }) + }); + } } let webview = webview_builder @@ -2730,57 +3160,91 @@ fn create_webview( .build() .map_err(|e| Error::CreateWebview(Box::new(e)))?; + #[cfg(windows)] + { + let controller = webview.controller(); + let proxy_ = proxy.clone(); + let mut token = EventRegistrationToken::default(); + unsafe { + controller.add_GotFocus( + &FocusChangedEventHandler::create(Box::new(move |_, _| { + let _ = proxy_.send_event(Message::Webview( + window_id, + WebviewMessage::WebviewEvent(WebviewEvent::Focused(true)), + )); + Ok(()) + })), + &mut token, + ) + } + .unwrap(); + unsafe { + controller.add_LostFocus( + &FocusChangedEventHandler::create(Box::new(move |_, _| { + let _ = proxy.send_event(Message::Webview( + window_id, + WebviewMessage::WebviewEvent(WebviewEvent::Focused(false)), + )); + Ok(()) + })), + &mut token, + ) + } + .unwrap(); + } + Ok(WindowWrapper { label, - inner: WindowHandle::Webview(webview), + inner: Some(WindowHandle::Webview { + inner: Arc::new(webview), + context_store: web_context_store.clone(), + context_key: if automation_enabled { + None + } else { + web_context_key + }, + }), menu_items, + window_event_listeners, + menu_event_listeners: Default::default(), }) } /// Create a wry ipc handler from a tauri ipc handler. -fn create_ipc_handler( - context: Context, +fn create_ipc_handler( + context: Context, label: String, menu_ids: Arc>>, - js_event_listeners: Arc>>>, - handler: WebviewIpcHandler, -) -> Box { + handler: WebviewIpcHandler>, +) -> Box { Box::new(move |window, request| { + let window_id = context.webview_id_map.get(&window.id()).unwrap(); handler( DetachedWindow { dispatcher: WryDispatcher { - window_id: window.id(), + window_id, context: context.clone(), }, label: label.clone(), menu_ids: menu_ids.clone(), - js_event_listeners: js_event_listeners.clone(), }, request, ); }) } -/// Create a wry file drop handler from a tauri file drop handler. -fn create_file_drop_handler( - context: Context, - label: String, - menu_ids: Arc>>, - js_event_listeners: Arc>>>, - handler: FileDropHandler, -) -> Box bool + 'static> { - Box::new(move |window, event| { - handler( - FileDropEventWrapper(event).into(), - DetachedWindow { - dispatcher: WryDispatcher { - window_id: window.id(), - context: context.clone(), - }, - label: label.clone(), - menu_ids: menu_ids.clone(), - js_event_listeners: js_event_listeners.clone(), - }, - ) +/// Create a wry file drop handler. +fn create_file_drop_handler(window_event_listeners: WindowEventListeners) -> Box { + Box::new(move |_window, event| { + let event: FileDropEvent = FileDropEventWrapper(event).into(); + let window_event = WindowEvent::FileDrop(event); + let listeners_map = window_event_listeners.lock().unwrap(); + let has_listener = !listeners_map.is_empty(); + let handlers = listeners_map.values(); + for listener in handlers { + listener(&window_event); + } + // block the default OS action on file drop if we had a listener + has_listener }) } diff --git a/core/tauri-runtime-wry/src/system_tray.rs b/core/tauri-runtime-wry/src/system_tray.rs index 7ff96e557ef9..594e13cc4908 100644 --- a/core/tauri-runtime-wry/src/system_tray.rs +++ b/core/tauri-runtime-wry/src/system_tray.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -9,65 +9,192 @@ pub use tauri_runtime::{ }, Icon, SystemTrayEvent, }; +use wry::application::event_loop::EventLoopWindowTarget; pub use wry::application::{ event::TrayEvent, event_loop::EventLoopProxy, menu::{ ContextMenu as WryContextMenu, CustomMenuItem as WryCustomMenuItem, MenuItem as WryMenuItem, }, + system_tray::Icon as WryTrayIcon, + TrayId as WryTrayId, }; #[cfg(target_os = "macos")] -pub use wry::application::platform::macos::CustomMenuItemExtMacOS; +pub use wry::application::platform::macos::{ + CustomMenuItemExtMacOS, SystemTrayBuilderExtMacOS, SystemTrayExtMacOS, +}; -use crate::{Error, Message, Result, TrayMessage}; +use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder}; -use tauri_runtime::menu::MenuHash; +use crate::{send_user_message, Context, Error, Message, Result, TrayId, TrayMessage}; -use uuid::Uuid; +use tauri_runtime::{menu::MenuHash, SystemTray, UserEvent}; use std::{ collections::HashMap, + fmt, sync::{Arc, Mutex}, }; +pub type GlobalSystemTrayEventHandler = Box; +pub type GlobalSystemTrayEventListeners = Arc>>>; + pub type SystemTrayEventHandler = Box; -pub type SystemTrayEventListeners = Arc>>; +pub type SystemTrayEventListeners = Arc>>>; pub type SystemTrayItems = Arc>>; +#[derive(Clone, Default)] +pub struct TrayContext { + pub tray: Arc>>, + pub listeners: SystemTrayEventListeners, + pub items: SystemTrayItems, +} + +impl fmt::Debug for TrayContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TrayContext") + .field("items", &self.items) + .finish() + } +} + +#[derive(Clone, Default)] +pub struct SystemTrayManager { + pub trays: Arc>>, + pub global_listeners: GlobalSystemTrayEventListeners, +} + +impl fmt::Debug for SystemTrayManager { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SystemTrayManager") + .field("trays", &self.trays) + .finish() + } +} + +/// Wrapper around a [`wry::application::system_tray::Icon`] that can be created from an [`WindowIcon`]. +pub struct TrayIcon(pub(crate) WryTrayIcon); + +impl TryFrom for TrayIcon { + type Error = Error; + fn try_from(icon: Icon) -> std::result::Result { + WryTrayIcon::from_rgba(icon.rgba, icon.width, icon.height) + .map(Self) + .map_err(crate::icon_err) + } +} + +pub fn create_tray( + id: WryTrayId, + system_tray: SystemTray, + event_loop: &EventLoopWindowTarget, +) -> crate::Result<(WrySystemTray, HashMap)> { + let icon = TrayIcon::try_from(system_tray.icon.expect("tray icon not set"))?; + + let mut items = HashMap::new(); + + #[allow(unused_mut)] + let mut builder = SystemTrayBuilder::new( + icon.0, + system_tray + .menu + .map(|menu| to_wry_context_menu(&mut items, menu)), + ) + .with_id(id); + + #[cfg(target_os = "macos")] + { + builder = builder + .with_icon_as_template(system_tray.icon_as_template) + .with_menu_on_left_click(system_tray.menu_on_left_click); + + if let Some(title) = system_tray.title { + builder = builder.with_title(&title); + } + } + + if let Some(tooltip) = system_tray.tooltip { + builder = builder.with_tooltip(&tooltip); + } + + let tray = builder + .build(event_loop) + .map_err(|e| Error::SystemTray(Box::new(e)))?; + + Ok((tray, items)) +} + #[derive(Debug, Clone)] -pub struct SystemTrayHandle { - pub(crate) proxy: EventLoopProxy, +pub struct SystemTrayHandle { + pub(crate) context: Context, + pub(crate) id: TrayId, + pub(crate) proxy: EventLoopProxy>, } -impl TrayHandle for SystemTrayHandle { +impl TrayHandle for SystemTrayHandle { fn set_icon(&self, icon: Icon) -> Result<()> { self .proxy - .send_event(Message::Tray(TrayMessage::UpdateIcon(icon))) + .send_event(Message::Tray(self.id, TrayMessage::UpdateIcon(icon))) .map_err(|_| Error::FailedToSendMessage) } + fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> { self .proxy - .send_event(Message::Tray(TrayMessage::UpdateMenu(menu))) + .send_event(Message::Tray(self.id, TrayMessage::UpdateMenu(menu))) .map_err(|_| Error::FailedToSendMessage) } + fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> { self .proxy - .send_event(Message::Tray(TrayMessage::UpdateItem(id, update))) + .send_event(Message::Tray(self.id, TrayMessage::UpdateItem(id, update))) .map_err(|_| Error::FailedToSendMessage) } + #[cfg(target_os = "macos")] fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> { self .proxy - .send_event(Message::Tray(TrayMessage::UpdateIconAsTemplate( - is_template, - ))) + .send_event(Message::Tray( + self.id, + TrayMessage::UpdateIconAsTemplate(is_template), + )) + .map_err(|_| Error::FailedToSendMessage) + } + + #[cfg(target_os = "macos")] + fn set_title(&self, title: &str) -> tauri_runtime::Result<()> { + self + .proxy + .send_event(Message::Tray( + self.id, + TrayMessage::UpdateTitle(title.to_owned()), + )) .map_err(|_| Error::FailedToSendMessage) } + + fn set_tooltip(&self, tooltip: &str) -> Result<()> { + self + .proxy + .send_event(Message::Tray( + self.id, + TrayMessage::UpdateTooltip(tooltip.to_owned()), + )) + .map_err(|_| Error::FailedToSendMessage) + } + + fn destroy(&self) -> Result<()> { + let (tx, rx) = std::sync::mpsc::channel(); + send_user_message( + &self.context, + Message::Tray(self.id, TrayMessage::Destroy(tx)), + )?; + rx.recv().unwrap()?; + Ok(()) + } } impl From for crate::MenuItemWrapper { diff --git a/core/tauri-runtime-wry/src/webview.rs b/core/tauri-runtime-wry/src/webview.rs new file mode 100644 index 000000000000..283c32fb5c3b --- /dev/null +++ b/core/tauri-runtime-wry/src/webview.rs @@ -0,0 +1,54 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +mod imp { + use std::rc::Rc; + + pub type Webview = Rc; +} + +#[cfg(target_os = "macos")] +mod imp { + use cocoa::base::id; + + pub struct Webview { + pub webview: id, + pub manager: id, + pub ns_window: id, + } +} + +#[cfg(target_os = "ios")] +mod imp { + use cocoa::base::id; + + pub struct Webview { + pub webview: id, + pub manager: id, + pub view_controller: id, + } +} + +#[cfg(windows)] +mod imp { + use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller; + pub struct Webview { + pub controller: ICoreWebView2Controller, + } +} + +#[cfg(target_os = "android")] +mod imp { + use wry::webview::JniHandle; + pub type Webview = JniHandle; +} + +pub use imp::*; diff --git a/core/tauri-runtime/.license_template b/core/tauri-runtime/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/core/tauri-runtime/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/core/tauri-runtime/CHANGELOG.md b/core/tauri-runtime/CHANGELOG.md index 92bf88315810..589748c30c05 100644 --- a/core/tauri-runtime/CHANGELOG.md +++ b/core/tauri-runtime/CHANGELOG.md @@ -1,8 +1,291 @@ # Changelog +## \[0.13.0-alpha.5] + +- [`39f1b04f`](https://www.github.com/tauri-apps/tauri/commit/39f1b04f7be4966488484829cd54c8ce72a04200)([#6943](https://www.github.com/tauri-apps/tauri/pull/6943)) Moved the `event` JS APIs to a plugin. +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`cebd7526`](https://www.github.com/tauri-apps/tauri/commit/cebd75261ac71b98976314a450cb292eeeec1515)([#6728](https://www.github.com/tauri-apps/tauri/pull/6728)) Moved the `clipboard` feature to its own plugin in the plugins-workspace repository. +- [`3f17ee82`](https://www.github.com/tauri-apps/tauri/commit/3f17ee82f6ff21108806edb7b00500b8512b8dc7)([#6737](https://www.github.com/tauri-apps/tauri/pull/6737)) Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository. + +## \[0.13.0-alpha.4] + +- Added `android` configuration object under `tauri > bundle`. + - Bumped due to a bump in tauri-utils. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 + +## \[0.13.0-alpha.3] + +- Pull changes from Tauri 1.3 release. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[0.13.0-alpha.2] + +- Add `find_class`, `run_on_android_context` on `RuntimeHandle`. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 +- Added the `shadow` option when creating a window and `Window::set_shadow`. + - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08 +- Implemented `with_webview` on Android and iOS. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 + +## \[0.13.0-alpha.1] + +- Update gtk to 0.16. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 + +## \[0.13.0-alpha.0] + +- Parse `android` and `ios` Tauri configuration files. + - Bumped due to a bump in tauri-utils. + - [b3a3afc7](https://www.github.com/tauri-apps/tauri/commit/b3a3afc7de8de4021d73559288f5192732a706cf) feat(core): detect android and ios platform configuration files ([#4997](https://www.github.com/tauri-apps/tauri/pull/4997)) on 2022-08-22 +- First mobile alpha release! + - Bumped due to a bump in tauri-utils. + - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08 + +## \[0.14.0] + +### New Features + +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` methods to `WindowBuilder`. +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods to the `Dispatch` trait. +- [`000104bc`](https://www.github.com/tauri-apps/tauri/commit/000104bc3bc0c9ff3d20558ab9cf2080f126e9e0)([#6472](https://www.github.com/tauri-apps/tauri/pull/6472)) Add `Window::is_focused` getter. + +### Enhancements + +- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85. + +### Bug Fixes + +- [`2b487c94`](https://www.github.com/tauri-apps/tauri/commit/2b487c946737352187d7e042dd6142873e62a4ca)([#7012](https://www.github.com/tauri-apps/tauri/pull/7012)) Fixes typo in `CursorIcon` deserialization of the `ZoomIn` variant. + +### What's Changed + +- [`076e1a81`](https://www.github.com/tauri-apps/tauri/commit/076e1a81a50468e3dfb34ae9ca7e77c5e1758daa)([#7119](https://www.github.com/tauri-apps/tauri/pull/7119)) Use `u32` instead of `u64` for js event listener ids +- [`ff5e4dbb`](https://www.github.com/tauri-apps/tauri/commit/ff5e4dbbb01bf3fc9c5143df732c75eef6fd98cb)([#6794](https://www.github.com/tauri-apps/tauri/pull/6794)) impl `From<&WindowConfig>` for `WebviewAttributes`. + +## \[0.13.0] + +- Added the `additional_browser_args` option when creating a window. + - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14 +- Added the `content_protected` option when creating a window and `Window::set_content_protected` to change it at runtime. + - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13 +- Added `Builder::device_event_filter` and `App::set_device_event_filter` methods. + - [73fd60ee](https://www.github.com/tauri-apps/tauri/commit/73fd60eef2b60f5dc84525ef9c315f4d80c4414f) expose set_device_event_filter in tauri ([#5562](https://www.github.com/tauri-apps/tauri/pull/5562)) on 2022-12-13 +- Add `is_minimized()` window method. + - [62144ef3](https://www.github.com/tauri-apps/tauri/commit/62144ef3be63b237869e511826edfb938e2c7174) feat: add is_minimized (fix [#3878](https://www.github.com/tauri-apps/tauri/pull/3878)) ([#5618](https://www.github.com/tauri-apps/tauri/pull/5618)) on 2022-12-13 +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 +- Pin raw-window-handle to 0.5.0 to keep MSRV. + - [c46c09f3](https://www.github.com/tauri-apps/tauri/commit/c46c09f31d9f5169ca8a7e62406a9ea170e3a5c5) fix(deps): pin raw-window-handle to 0.5.0 ([#6480](https://www.github.com/tauri-apps/tauri/pull/6480)) on 2023-03-17 +- Added `navigation_handler` field on `PendingWindow`. + - [3f35b452](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42) Expose wry navigation_handler via WindowBuilder closes [#4080](https://www.github.com/tauri-apps/tauri/pull/4080) ([#5686](https://www.github.com/tauri-apps/tauri/pull/5686)) on 2022-12-27 +- Add `title` getter on window. + - [233e43b0](https://www.github.com/tauri-apps/tauri/commit/233e43b0c34fada1ca025378533a0b76931a6540) feat: add `title` getter on window, closes [#5023](https://www.github.com/tauri-apps/tauri/pull/5023) ([#5515](https://www.github.com/tauri-apps/tauri/pull/5515)) on 2022-12-13 +- Added `TrayHandle::set_tooltip` and `SystemTray::with_tooltip`. + - [2265e097](https://www.github.com/tauri-apps/tauri/commit/2265e09718f6ebfeb1d200f11e1e1e069075af6e) feat(windows): implement `with_tooltip` ([#5938](https://www.github.com/tauri-apps/tauri/pull/5938)) on 2023-01-01 +- Added window's `url()` getter. + - [d17027e1](https://www.github.com/tauri-apps/tauri/commit/d17027e1a0db3e8c5ae81fc4f472c5918fbce611) feat: expose url method ([#5914](https://www.github.com/tauri-apps/tauri/pull/5914)) on 2022-12-26 +- On Windows, change webview theme based on Window theme for more accurate `prefers-color-scheme` support. + - [7a8d570d](https://www.github.com/tauri-apps/tauri/commit/7a8d570db72667367eb24b75ddc5dd07a968f7c0) fix: sync webview theme with window theme on Windows, closes [#5802](https://www.github.com/tauri-apps/tauri/pull/5802) ([#5874](https://www.github.com/tauri-apps/tauri/pull/5874)) on 2022-12-27 + +## \[0.12.2] + +- Block remote URLs from accessing the IPC. + - [9c0593c33](https://www.github.com/tauri-apps/tauri/commit/9c0593c33af52cd9e00ec784d15f63efebdf039c) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.12.1] + +- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`. + - Bumped due to a bump in tauri-utils. + - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18 + +## \[0.12.0] + +- Readd the option to create an unfocused window via the `focused` method. The `focus` function has been deprecated. + - [4036e15f](https://www.github.com/tauri-apps/tauri/commit/4036e15f5af933bdc0d0913508b5103958afc143) feat(core): reimplement window initial focus flag, closes [#5120](https://www.github.com/tauri-apps/tauri/pull/5120) ([#5338](https://www.github.com/tauri-apps/tauri/pull/5338)) on 2022-10-08 +- Added `Runtime::show()`, `RuntimeHandle::show()`, `Runtime::hide()`, `RuntimeHandle::hide()` for hiding/showing the entire application on macOS. + - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03 +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 +- Added `tabbing_identifier` to the window builder on macOS. + - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19 +- Added methods to set the system tray title on macOS. + - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30 +- Added the `user_agent` option when creating a window. + - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02 + +## \[0.11.2] + +- Block remote URLs from accessing the IPC. + - [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.11.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - Bumped due to a bump in tauri-utils. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[0.11.0] + +- Added APIs to create a system tray at runtime. + - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09 +- Update windows to 0.39.0 and webview2-com to 0.19.1. + - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03 + +## \[0.10.3] + +- Block remote URLs from accessing the IPC. + - [fa90214b0](https://www.github.com/tauri-apps/tauri/commit/fa90214b052b1a5d38d54fbf1ca422b4c37cfd1f) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.10.2] + +- Added option to disable tray menu on left click on macOS. + - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05 + +## \[0.10.1] + +- Expose `platform::windows_version` function. + - Bumped due to a bump in tauri-utils. + - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30 + +## \[0.10.0] + +- Added `fn new` constructors for `PhysicalSize`, `LogicalSize`, `PhysicalPosition` and `LogicalPosition` and missing conversion methods. + - [c7d13a1c](https://www.github.com/tauri-apps/tauri/commit/c7d13a1c60cdbe0c42834ea059321d7a3a7f01a0) feat(core): add missing methods to the dpi module ([#4393](https://www.github.com/tauri-apps/tauri/pull/4393)) on 2022-06-19 +- Implement `raw_window_handle::HasRawWindowHandle` on Linux. + - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26 +- Removed the `hwnd` and `ns_window` functions from `Dispatch` in favor of `raw_window_handle`. + - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26 +- The theme API is now implemented on macOS 10.14+. + - [6d94ce42](https://www.github.com/tauri-apps/tauri/commit/6d94ce42353204a02fe9c82ed397d349439f75ef) feat(core): theme is now implemented on macOS ([#4380](https://www.github.com/tauri-apps/tauri/pull/4380)) on 2022-06-17 + +## \[0.9.0] + +- Upgrade to `stable`! + - Bumped due to a bump in tauri-utils. + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[0.8.1] + +- Add `Menu::os_default` which will create a menu filled with default menu items and submenus. + - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15 + +## \[0.8.0] + +- Removed `TrayIcon` and renamed `WindowIcon` to `Icon`, a shared type for both icons. + - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14 + +## \[0.7.0] + +- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`. + - Bumped due to a bump in tauri-utils. + - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10 + +## \[0.6.0] + +- Update `windows-rs` to `0.37.0`, which requires Rust 1.61.0+. + - [2326be39](https://www.github.com/tauri-apps/tauri/commit/2326be39821890cdd4de76e7029a531424dcb26f) feat(core): update windows-rs to 0.37.0 ([#4199](https://www.github.com/tauri-apps/tauri/pull/4199)) on 2022-05-24 + +## \[0.5.1] + +- Fix `.mjs` not being recognised as a file extension for JavaScript files (`text/javascript`). + - [45c45253](https://www.github.com/tauri-apps/tauri/commit/45c45253866ce0de317a6a547af3ea0434d4bcac) fix: add mjs mime type (fix: [#4098](https://www.github.com/tauri-apps/tauri/pull/4098)) ([#4108](https://www.github.com/tauri-apps/tauri/pull/4108)) on 2022-05-13 + +## \[0.5.0] + +- Expose methods to access the underlying native handles of the webview. + - [c82b4761](https://www.github.com/tauri-apps/tauri/commit/c82b4761e1660592472dc55308ad69d9efc5855b) feat(core): expose `with_webview` API to access the platform webview ([#4058](https://www.github.com/tauri-apps/tauri/pull/4058)) on 2022-05-04 + +## \[0.4.0] + +- The `AboutMetadata` string setters now take `impl Into`. + - [b14aa896](https://www.github.com/tauri-apps/tauri/commit/b14aa89673c3563522e5c04baf9630fa1c4739b0) feat(core): improve `AboutMetadata` setters on 2022-03-29 +- \**Breaking change::* Added the `clipboard` Cargo feature. + - [24e4ff20](https://www.github.com/tauri-apps/tauri/commit/24e4ff208ee0fe1a4cc5b10667ea0922ac63dfb5) refactor(core): add clipboard Cargo feature, enhancing binary size ([#3957](https://www.github.com/tauri-apps/tauri/pull/3957)) on 2022-04-24 +- Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`. + - [c54ddfe9](https://www.github.com/tauri-apps/tauri/commit/c54ddfe9338e7eb90b4d5b02dfde687d432d5bc1) feat: expose window cursor APIs, closes [#3888](https://www.github.com/tauri-apps/tauri/pull/3888) [#3890](https://www.github.com/tauri-apps/tauri/pull/3890) ([#3935](https://www.github.com/tauri-apps/tauri/pull/3935)) on 2022-04-21 +- \**Breaking change::* Added the `global-shortcut` Cargo feature. + - [e11878bc](https://www.github.com/tauri-apps/tauri/commit/e11878bcf7174b261a1fa146fc7d564d12e6312a) refactor(core): add global-shortcut Cargo feature, enhancing binary size ([#3956](https://www.github.com/tauri-apps/tauri/pull/3956)) on 2022-04-24 +- Added `WindowEvent::ThemeChanged(theme)`. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Added `theme` getter on `Window`. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Added `theme` setter to the WindowBuilder. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 + +## \[0.3.4] + +- Added `close_devtools` and `is_devtools_open` APIs to the `Dispatch` trait. + - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28 +- **Breaking change:** The `MenuItem::About` variant is now associated with a tuple value `(String, AboutMetadata)`. + - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28 +- Support window parenting on macOS + - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23 +- The file drop event is now part of the `WindowEvent` enum instead of a having a dedicated handler. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 +- **Breaking change:** Use the dedicated `WindowEvent` enum on `RunEvent`. + - [edad9f4f](https://www.github.com/tauri-apps/tauri/commit/edad9f4f55dcc69a06cd9d6d5a5068c94ecb77dd) refactor(core): add `RunEvent::WindowEvent` ([#3793](https://www.github.com/tauri-apps/tauri/pull/3793)) on 2022-03-28 +- Added `create_proxy` to the `Runtime` and `RuntimeHandle` traits. + - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15 +- Force `Error` boxed errors to be `Sync`. + - [da1e8793](https://www.github.com/tauri-apps/tauri/commit/da1e879358895f7b190b1c1b20d23da23666a74b) feat(core): improve and cleanup the `Error` enum ([#3748](https://www.github.com/tauri-apps/tauri/pull/3748)) on 2022-03-22 +- **Breaking change:** Move the `FileDropEvent` struct to the `window` module. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 +- Allow specifying a user event type for the event loop message. + - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15 +- Added the `WindowEvent::FileDrop` variant. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 + +## \[0.3.3] + +- **Breaking change:** Move `ico` and `png` parsing behind `icon-ico` and `icon-png` Cargo features. + - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05 +- The `PendingWindow::new` and `PendingWindow::with_config` functions now return `Result` validating the window label. + - [64e00542](https://www.github.com/tauri-apps/tauri/commit/64e0054299c95f10ef5a1a9d3f914bbaeff3d73f) refactor(core): do not panic on invalid window labels,[#3544](https://www.github.com/tauri-apps/tauri/pull/3544) ([#3596](https://www.github.com/tauri-apps/tauri/pull/3596)) on 2022-03-03 + +## \[0.3.2] + +- Fix requirements for `RuntimeHandle`, `ClipboardManager`, `GlobalShortcutHandle` and `TrayHandle`. + - [84895a9c](https://www.github.com/tauri-apps/tauri/commit/84895a9cd270fc743e236d0f4d4cd6210b24a30f) fix(runtime): trait requirements ([#3489](https://www.github.com/tauri-apps/tauri/pull/3489)) on 2022-02-17 + +## \[0.3.1] + +- Change default value for the `freezePrototype` configuration to `false`. + - Bumped due to a bump in tauri-utils. + - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12 + +## \[0.3.0] + +- Replace `WindowBuilder`'s `has_menu` with `get_menu`. + - [ac37b56e](https://www.github.com/tauri-apps/tauri/commit/ac37b56ef43c9e97039967a5fd99f0d2dccb5b5a) fix(core): menu id map not reflecting the current window menu ([#2726](https://www.github.com/tauri-apps/tauri/pull/2726)) on 2021-10-08 +- The `run_return` API is now available on Linux. + - [8483fde9](https://www.github.com/tauri-apps/tauri/commit/8483fde975aac8833d2ce426e42fb40aeaeecba9) feat(core): expose `run_return` on Linux ([#3352](https://www.github.com/tauri-apps/tauri/pull/3352)) on 2022-02-07 +- Add `Menu::with_items` constructor, taking an iterator of `MenuEntry`. + - [7cc95e10](https://www.github.com/tauri-apps/tauri/commit/7cc95e10ec66d8b155e9bb7f89cf73df56d1f107) feat(core): add `Menu::with_items`, closes [#2807](https://www.github.com/tauri-apps/tauri/pull/2807) ([#2966](https://www.github.com/tauri-apps/tauri/pull/2966)) on 2021-12-27 +- Change event loop callbacks definition to allow callers to move in mutable values. + - [bdbf905e](https://www.github.com/tauri-apps/tauri/commit/bdbf905e5d802b58693d2bd27582ce4269faf79c) Transformed event-loop callback to FnMut to allow mutable values ([#2667](https://www.github.com/tauri-apps/tauri/pull/2667)) on 2021-09-27 +- Added `any_thread` constructor on the `Runtime` trait (only possible on Linux and Windows). + - [af44bf81](https://www.github.com/tauri-apps/tauri/commit/af44bf8168310cf77fbe102a53e7c433f11641a3) feat(core): allow app run on any thread on Linux & Windows, closes [#3172](https://www.github.com/tauri-apps/tauri/pull/3172) ([#3353](https://www.github.com/tauri-apps/tauri/pull/3353)) on 2022-02-07 +- Added `run_on_main_thread` API on `RuntimeHandle`. + - [53fdfe52](https://www.github.com/tauri-apps/tauri/commit/53fdfe52bb30d52653c72ca9f42506c3863dcf4a) feat(core): expose `run_on_main_thread` API ([#2711](https://www.github.com/tauri-apps/tauri/pull/2711)) on 2021-10-04 +- **Breaking change:** Renamed the `RPC` interface to `IPC`. + - [3420aa50](https://www.github.com/tauri-apps/tauri/commit/3420aa5031b3274a95c6c5fa0f8683ca13213396) refactor: IPC handler \[TRI-019] ([#9](https://www.github.com/tauri-apps/tauri/pull/9)) on 2022-01-09 +- Added `open_devtools` to the `Dispatcher` trait. + - [55aa22de](https://www.github.com/tauri-apps/tauri/commit/55aa22de80c3de873e29bcffcb5b2fe236a637a6) feat(core): add `Window#open_devtools` API, closes [#1213](https://www.github.com/tauri-apps/tauri/pull/1213) ([#3350](https://www.github.com/tauri-apps/tauri/pull/3350)) on 2022-02-07 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 +- The window label is now validated and must be alphanumeric, resulting in a panic if it isn't. + - [680554de](https://www.github.com/tauri-apps/tauri/commit/680554de3ef6b7fccf87c441ad355cfef7aab6fe) feat: validate window label \[TRI-021] ([#13](https://www.github.com/tauri-apps/tauri/pull/13)) on 2021-10-23 +- Added `clipboard` field on the `WebviewAttributes` struct, which must be set to `true` to enable clipboard access on the webview. + - [d42ccfb3](https://www.github.com/tauri-apps/tauri/commit/d42ccfb34f71851dfeb22fe74c83a8bdbddb5550) feat: add `clipboard` flag to `WebviewAttributes` \[TRI-032] ([#12](https://www.github.com/tauri-apps/tauri/pull/12)) on 2021-10-23 +- Replace all of the `winapi` crate references with the `windows` crate, and replace `webview2` and `webview2-sys` with `webview2-com` and `webview2-com-sys` built with the `windows` crate. This goes along with updates to the TAO and WRY `next` branches. + - [bb00d5bd](https://www.github.com/tauri-apps/tauri/commit/bb00d5bd6c9dfcb6bdd0d308dadb70e6c6aafe5c) Replace winapi with windows crate and use webview2-com instead of webview2 ([#2615](https://www.github.com/tauri-apps/tauri/pull/2615)) on 2021-09-24 +- Update the `windows` crate to 0.25.0, which comes with pre-built libraries. WRY and Tao can both reference the same types directly from the `windows` crate instead of sharing bindings in `webview2-com-sys`. + - [34be6cf3](https://www.github.com/tauri-apps/tauri/commit/34be6cf37a98ee7cbd66623ebddae08e5a6520fd) Update webview2-com and windows crates ([#2875](https://www.github.com/tauri-apps/tauri/pull/2875)) on 2021-11-11 + ## \[0.2.1] -- **Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttibutes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure. +- **Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttributes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure. - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23 - Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing. - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23 diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 77759a45f106..c9b16271d2ad 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "tauri-runtime" -version = "0.2.1" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.studio" -repository = "https://github.com/tauri-apps/tauri" +version = "0.13.0-alpha.5" description = "Runtime for Tauri applications" -edition = "2021" -rust-version = "1.57" -exclude = [ ".license_template", "CHANGELOG.md", "/target" ] +exclude = [ "CHANGELOG.md", "/target" ] readme = "README.md" +# workspace defined package items +authors = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } + [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "doc_cfg" ] @@ -26,23 +28,23 @@ targets = [ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" thiserror = "1.0" -tauri-utils = { version = "1.0.0-beta.3", path = "../tauri-utils" } -uuid = { version = "0.8.2", features = [ "v4" ] } +tauri-utils = { version = "2.0.0-alpha.5", path = "../tauri-utils" } +uuid = { version = "1", features = [ "v4" ] } http = "0.2.4" http-range = "0.1.4" -infer = "0.4" - -[target."cfg(windows)".dependencies] -webview2-com = "0.11.0" +raw-window-handle = "0.5" +rand = "0.8" +url = { version = "2" } [target."cfg(windows)".dependencies.windows] -version = "0.30.0" -features = [ - "Win32_Foundation", -] +version = "0.44" +features = [ "Win32_Foundation" ] [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] -gtk = { version = "0.15", features = [ "v3_20" ] } +gtk = { version = "0.16", features = [ "v3_24" ] } + +[target."cfg(target_os = \"android\")".dependencies] +jni = "0.20" [features] devtools = [ ] diff --git a/core/tauri-runtime/README.md b/core/tauri-runtime/README.md index 0c415da3afef..4bb9b97f55a2 100644 --- a/core/tauri-runtime/README.md +++ b/core/tauri-runtime/README.md @@ -6,33 +6,38 @@ [![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) [![devto](https://img.shields.io/badge/blog-dev.to-black.svg)](https://dev.to/tauri) -![](https://img.shields.io/github/workflow/status/tauri-apps/tauri/test%20library?label=test%20library -) -[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.studio) +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) +[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.app) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) [![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri) -| Component | Version | -| --------- | ------------------------------------------- | -| tauri-runtime | [![](https://img.shields.io/crates/v/tauri-runtime?style=flat-square)](https://crates.io/crates/tauri-runtime) | +| Component | Version | +| ------------- | -------------------------------------------------------------------------------------------------------------- | +| tauri-runtime | [![](https://img.shields.io/crates/v/tauri-runtime?style=flat-square)](https://crates.io/crates/tauri-runtime) | ## About Tauri + Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily. Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task. ## This module + This is the glue layer between tauri itself and lower level webview libraries. To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. ## Semver + **tauri** is following [Semantic Versioning 2.0](https://semver.org/). + ## Licenses + Code: (c) 2021 - The Tauri Programme within The Commons Conservancy. MIT or MIT/Apache 2.0 where applicable. Logo: CC-BY-NC-ND + - Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) diff --git a/core/tauri-runtime/build.rs b/core/tauri-runtime/build.rs new file mode 100644 index 000000000000..2ccb32962447 --- /dev/null +++ b/core/tauri-runtime/build.rs @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// creates a cfg alias if `has_feature` is true. +// `alias` must be a snake case string. +fn alias(alias: &str, has_feature: bool) { + if has_feature { + println!("cargo:rustc-cfg={alias}"); + } +} + +fn main() { + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "ios" || target_os == "android"; + alias("desktop", !mobile); + alias("mobile", mobile); +} diff --git a/core/tauri-runtime/src/http/mime_type.rs b/core/tauri-runtime/src/http/mime_type.rs deleted file mode 100644 index d22a8d3fa6e7..000000000000 --- a/core/tauri-runtime/src/http/mime_type.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::fmt; - -const MIMETYPE_PLAIN: &str = "text/plain"; - -/// [Web Compatible MimeTypes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#important_mime_types_for_web_developers) -pub enum MimeType { - Css, - Csv, - Html, - Ico, - Js, - Json, - Jsonld, - OctetStream, - Rtf, - Svg, - Mp4, -} - -impl std::fmt::Display for MimeType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mime = match self { - MimeType::Css => "text/css", - MimeType::Csv => "text/csv", - MimeType::Html => "text/html", - MimeType::Ico => "image/vnd.microsoft.icon", - MimeType::Js => "text/javascript", - MimeType::Json => "application/json", - MimeType::Jsonld => "application/ld+json", - MimeType::OctetStream => "application/octet-stream", - MimeType::Rtf => "application/rtf", - MimeType::Svg => "image/svg+xml", - MimeType::Mp4 => "video/mp4", - }; - write!(f, "{}", mime) - } -} - -impl MimeType { - /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType. - pub fn parse_from_uri(uri: &str) -> MimeType { - let suffix = uri.split('.').last(); - match suffix { - Some("bin") => Self::OctetStream, - Some("css") => Self::Css, - Some("csv") => Self::Csv, - Some("html") => Self::Html, - Some("ico") => Self::Ico, - Some("js") => Self::Js, - Some("json") => Self::Json, - Some("jsonld") => Self::Jsonld, - Some("rtf") => Self::Rtf, - Some("svg") => Self::Svg, - Some("mp4") => Self::Mp4, - // Assume HTML when a TLD is found for eg. `wry:://tauri.studio` | `wry://hello.com` - Some(_) => Self::Html, - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types - // using octet stream according to this: - None => Self::OctetStream, - } - } - - /// infer mimetype from content (or) URI if needed. - pub fn parse(content: &[u8], uri: &str) -> String { - let mime = if uri.ends_with(".svg") { - // when reading svg, we can't use `infer` - None - } else { - infer::get(content).map(|info| info.mime_type()) - }; - - match mime { - Some(mime) if mime == MIMETYPE_PLAIN => Self::parse_from_uri(uri).to_string(), - None => Self::parse_from_uri(uri).to_string(), - Some(mime) => mime.to_string(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn should_parse_mimetype_from_uri() { - let css = MimeType::parse_from_uri( - "https://unpkg.com/browse/bootstrap@4.1.0/dist/css/bootstrap-grid.css", - ) - .to_string(); - assert_eq!(css, "text/css".to_string()); - - let csv: String = MimeType::parse_from_uri("https://example.com/random.csv").to_string(); - assert_eq!(csv, "text/csv".to_string()); - - let ico: String = - MimeType::parse_from_uri("https://icons.duckduckgo.com/ip3/microsoft.com.ico").to_string(); - assert_eq!(ico, String::from("image/vnd.microsoft.icon")); - - let html: String = MimeType::parse_from_uri("https://tauri.studio/index.html").to_string(); - assert_eq!(html, String::from("text/html")); - - let js: String = - MimeType::parse_from_uri("https://unpkg.com/react@17.0.1/umd/react.production.min.js") - .to_string(); - assert_eq!(js, "text/javascript".to_string()); - - let json: String = - MimeType::parse_from_uri("https://unpkg.com/browse/react@17.0.1/build-info.json").to_string(); - assert_eq!(json, String::from("application/json")); - - let jsonld: String = MimeType::parse_from_uri("https:/example.com/hello.jsonld").to_string(); - assert_eq!(jsonld, String::from("application/ld+json")); - - let rtf: String = MimeType::parse_from_uri("https://example.com/document.rtf").to_string(); - assert_eq!(rtf, String::from("application/rtf")); - - let svg: String = MimeType::parse_from_uri("https://example.com/picture.svg").to_string(); - assert_eq!(svg, String::from("image/svg+xml")); - - let mp4: String = MimeType::parse_from_uri("https://example.com/video.mp4").to_string(); - assert_eq!(mp4, String::from("video/mp4")); - - let custom_scheme = MimeType::parse_from_uri("wry://tauri.studio").to_string(); - assert_eq!(custom_scheme, String::from("text/html")); - } -} diff --git a/core/tauri-runtime/src/http/mod.rs b/core/tauri-runtime/src/http/mod.rs index 35a694d0281e..7ce36f4fadee 100644 --- a/core/tauri-runtime/src/http/mod.rs +++ b/core/tauri-runtime/src/http/mod.rs @@ -1,18 +1,18 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT // custom wry types -mod mime_type; mod request; mod response; pub use self::{ - mime_type::MimeType, request::{Request, RequestParts}, response::{Builder as ResponseBuilder, Response, ResponseParts}, }; +pub use tauri_utils::mime_type::MimeType; + // re-expose default http types pub use http::{header, method, status, uri::InvalidUri, version, Uri}; diff --git a/core/tauri-runtime/src/http/request.rs b/core/tauri-runtime/src/http/request.rs index 40ccd5629106..42729de06baf 100644 --- a/core/tauri-runtime/src/http/request.rs +++ b/core/tauri-runtime/src/http/request.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -17,8 +17,8 @@ use super::{ /// /// - **Linux:** Headers are not exposed. pub struct Request { - pub head: RequestParts, - pub body: Vec, + head: RequestParts, + body: Vec, } /// Component parts of an HTTP `Request` @@ -47,6 +47,17 @@ impl Request { } } + /// Creates a new `Request` with the given head and body. + /// + /// # Stability + /// + /// This API is used internally. It may have breaking changes in the future. + #[inline] + #[doc(hidden)] + pub fn new_internal(head: RequestParts, body: Vec) -> Request { + Request { head, body } + } + /// Returns a reference to the associated HTTP method. #[inline] pub fn method(&self) -> &Method { @@ -72,6 +83,10 @@ impl Request { } /// Consumes the request returning the head and body RequestParts. + /// + /// # Stability + /// + /// This API is used internally. It may have breaking changes in the future. #[inline] pub fn into_parts(self) -> (RequestParts, Vec) { (self.head, self.body) diff --git a/core/tauri-runtime/src/http/response.rs b/core/tauri-runtime/src/http/response.rs index 7e25498347a6..a004d4dc0d5f 100644 --- a/core/tauri-runtime/src/http/response.rs +++ b/core/tauri-runtime/src/http/response.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -7,7 +7,7 @@ use super::{ status::StatusCode, version::Version, }; -use std::fmt; +use std::{borrow::Cow, fmt}; type Result = core::result::Result>; @@ -32,8 +32,8 @@ type Result = core::result::Result>; /// ``` /// pub struct Response { - pub head: ResponseParts, - pub body: Vec, + head: ResponseParts, + body: Cow<'static, [u8]>, } /// Component parts of an HTTP `Response` @@ -42,16 +42,16 @@ pub struct Response { /// header fields. #[derive(Clone)] pub struct ResponseParts { - /// The response's status + /// The response's status. pub status: StatusCode, - /// The response's version + /// The response's version. pub version: Version, - /// The response's headers + /// The response's headers. pub headers: HeaderMap, - /// The response's mimetype type + /// The response's mimetype type. pub mimetype: Option, } @@ -67,23 +67,46 @@ pub struct Builder { impl Response { /// Creates a new blank `Response` with the body #[inline] - pub fn new(body: Vec) -> Response { + pub fn new(body: Cow<'static, [u8]>) -> Response { Response { head: ResponseParts::new(), body, } } - /// Returns the `StatusCode`. + /// Consumes the response returning the head and body ResponseParts. + /// + /// # Stability + /// + /// This API is used internally. It may have breaking changes in the future. + #[inline] + #[doc(hidden)] + pub fn into_parts(self) -> (ResponseParts, Cow<'static, [u8]>) { + (self.head, self.body) + } + + /// Sets the status code. + #[inline] + pub fn set_status(&mut self, status: StatusCode) { + self.head.status = status; + } + + /// Returns the [`StatusCode`]. #[inline] pub fn status(&self) -> StatusCode { self.head.status } + /// Sets the mimetype. + #[inline] + pub fn set_mimetype(&mut self, mimetype: Option) { + self.head.mimetype = mimetype; + } + /// Returns a reference to the mime type. #[inline] - pub fn mimetype(&self) -> Option { - self.head.mimetype.clone() + pub fn mimetype(&self) -> Option<&String> { + self.head.mimetype.as_ref() } /// Returns a reference to the associated version. @@ -92,15 +115,27 @@ impl Response { self.head.version } + /// Returns a mutable reference to the associated header field map. + #[inline] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head.headers + } + /// Returns a reference to the associated header field map. #[inline] pub fn headers(&self) -> &HeaderMap { &self.head.headers } + /// Returns a mutable reference to the associated HTTP body. + #[inline] + pub fn body_mut(&mut self) -> &mut Cow<'static, [u8]> { + &mut self.body + } + /// Returns a reference to the associated HTTP body. #[inline] - pub fn body(&self) -> &Vec { + pub fn body(&self) -> &Cow<'static, [u8]> { &self.body } } @@ -108,7 +143,7 @@ impl Response { impl Default for Response { #[inline] fn default() -> Response { - Response::new(Vec::new()) + Response::new(Default::default()) } } @@ -245,8 +280,11 @@ impl Builder { /// .body(Vec::new()) /// .unwrap(); /// ``` - pub fn body(self, body: Vec) -> Result { - self.inner.map(move |head| Response { head, body }) + pub fn body(self, body: impl Into>) -> Result { + self.inner.map(move |head| Response { + head, + body: body.into(), + }) } // private diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index 41338cc74149..5cd05cac3047 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -6,13 +6,13 @@ #![cfg_attr(doc_cfg, feature(doc_cfg))] +use raw_window_handle::RawDisplayHandle; use serde::Deserialize; -use std::{fmt::Debug, path::PathBuf, sync::mpsc::Sender}; +use std::{fmt::Debug, sync::mpsc::Sender}; +use tauri_utils::Theme; +use url::Url; use uuid::Uuid; -#[cfg(windows)] -use windows::Win32::Foundation::HWND; - pub mod http; /// Create window and system tray menus. pub mod menu; @@ -25,7 +25,7 @@ use monitor::Monitor; use webview::WindowBuilder; use window::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - DetachedWindow, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, PendingWindow, WindowEvent, }; use crate::http::{ @@ -35,17 +35,84 @@ use crate::http::{ InvalidUri, }; -#[cfg(feature = "system-tray")] +#[cfg(all(desktop, feature = "system-tray"))] +use std::fmt; + +pub type TrayId = u16; +pub type TrayEventHandler = dyn Fn(&SystemTrayEvent) + Send + 'static; + +#[cfg(all(desktop, feature = "system-tray"))] #[non_exhaustive] -#[derive(Debug, Default)] pub struct SystemTray { + pub id: TrayId, pub icon: Option, pub menu: Option, #[cfg(target_os = "macos")] pub icon_as_template: bool, + #[cfg(target_os = "macos")] + pub menu_on_left_click: bool, + #[cfg(target_os = "macos")] + pub title: Option, + pub on_event: Option>, + pub tooltip: Option, +} + +#[cfg(all(desktop, feature = "system-tray"))] +impl fmt::Debug for SystemTray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("SystemTray"); + d.field("id", &self.id) + .field("icon", &self.icon) + .field("menu", &self.menu); + #[cfg(target_os = "macos")] + { + d.field("icon_as_template", &self.icon_as_template) + .field("menu_on_left_click", &self.menu_on_left_click) + .field("title", &self.title); + } + d.finish() + } +} + +#[cfg(all(desktop, feature = "system-tray"))] +impl Clone for SystemTray { + fn clone(&self) -> Self { + Self { + id: self.id, + icon: self.icon.clone(), + menu: self.menu.clone(), + on_event: None, + #[cfg(target_os = "macos")] + icon_as_template: self.icon_as_template, + #[cfg(target_os = "macos")] + menu_on_left_click: self.menu_on_left_click, + #[cfg(target_os = "macos")] + title: self.title.clone(), + tooltip: self.tooltip.clone(), + } + } +} + +#[cfg(all(desktop, feature = "system-tray"))] +impl Default for SystemTray { + fn default() -> Self { + Self { + id: rand::random(), + icon: None, + menu: None, + #[cfg(target_os = "macos")] + icon_as_template: false, + #[cfg(target_os = "macos")] + menu_on_left_click: false, + #[cfg(target_os = "macos")] + title: None, + on_event: None, + tooltip: None, + } + } } -#[cfg(feature = "system-tray")] +#[cfg(all(desktop, feature = "system-tray"))] impl SystemTray { /// Creates a new system tray that only renders an icon. pub fn new() -> Self { @@ -56,7 +123,14 @@ impl SystemTray { self.menu.as_ref() } - /// Sets the tray icon. Must be a [`Icon::File`] on Linux and a [`Icon::Raw`] on Windows and macOS. + /// Sets the tray id. + #[must_use] + pub fn with_id(mut self, id: TrayId) -> Self { + self.id = id; + self + } + + /// Sets the tray icon. #[must_use] pub fn with_icon(mut self, icon: Icon) -> Self { self.icon.replace(icon); @@ -71,16 +145,48 @@ impl SystemTray { self } + /// Sets whether the menu should appear when the tray receives a left click. Defaults to `true`. + #[cfg(target_os = "macos")] + #[must_use] + pub fn with_menu_on_left_click(mut self, menu_on_left_click: bool) -> Self { + self.menu_on_left_click = menu_on_left_click; + self + } + + #[cfg(target_os = "macos")] + #[must_use] + pub fn with_title(mut self, title: &str) -> Self { + self.title = Some(title.to_owned()); + self + } + + /// Sets the tray icon tooltip. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported + #[must_use] + pub fn with_tooltip(mut self, tooltip: &str) -> Self { + self.tooltip = Some(tooltip.to_owned()); + self + } + /// Sets the menu to show when the system tray is right clicked. #[must_use] pub fn with_menu(mut self, menu: menu::SystemTrayMenu) -> Self { self.menu.replace(menu); self } + + #[must_use] + pub fn on_event(mut self, f: F) -> Self { + self.on_event.replace(Box::new(f)); + self + } } /// Type of user attention requested on a window. -#[derive(Debug, Clone, Copy, PartialEq, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum UserAttentionType { /// ## Platform-specific @@ -93,15 +199,35 @@ pub enum UserAttentionType { Informational, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(tag = "type")] +pub enum DeviceEventFilter { + /// Always filter out device events. + Always, + /// Filter out device events while the window is not focused. + Unfocused, + /// Report all device events regardless of window focus. + Never, +} + +impl Default for DeviceEventFilter { + fn default() -> Self { + Self::Unfocused + } +} + #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { /// Failed to create webview. #[error("failed to create webview: {0}")] - CreateWebview(Box), + CreateWebview(Box), /// Failed to create window. #[error("failed to create window")] CreateWindow, + /// The given window label is invalid. + #[error("Window labels must only include alphanumeric characters, `-`, `/`, `:` and `_`.")] + InvalidWindowLabel, /// Failed to send message to webview. #[error("failed to send message to the webview")] FailedToSendMessage, @@ -112,19 +238,16 @@ pub enum Error { #[error("JSON error: {0}")] Json(#[from] serde_json::Error), /// Encountered an error creating the app system tray. - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] #[error("error encountered during tray setup: {0}")] - SystemTray(Box), + SystemTray(Box), /// Failed to load window icon. #[error("invalid icon: {0}")] - InvalidIcon(Box), + InvalidIcon(Box), /// Failed to get monitor on window operation. #[error("failed to get monitor")] FailedToGetMonitor, - /// Global shortcut error. - #[error(transparent)] - GlobalShortcut(Box), #[error("Invalid header name: {0}")] InvalidHeaderName(#[from] InvalidHeaderName), #[error("Invalid header value: {0}")] @@ -137,67 +260,45 @@ pub enum Error { InvalidMethod(#[from] InvalidMethod), #[error("Infallible error, something went really wrong: {0}")] Infallible(#[from] std::convert::Infallible), + #[error("the event loop has been closed")] + EventLoopClosed, } /// Result type. pub type Result = std::result::Result; -/// A icon definition. +/// Window icon. #[derive(Debug, Clone)] -#[non_exhaustive] -pub enum Icon { - /// Icon from file path. - File(PathBuf), - /// Icon from raw bytes. - Raw(Vec), +pub struct Icon { + /// RGBA bytes of the icon. + pub rgba: Vec, + /// Icon width. + pub width: u32, + /// Icon height. + pub height: u32, } -impl Icon { - /// Converts the icon to a the expected system tray format. - /// We expect the code that passes the Icon enum to have already checked the platform. - #[cfg(target_os = "linux")] - pub fn into_tray_icon(self) -> PathBuf { - match self { - Icon::File(path) => path, - Icon::Raw(_) => { - panic!("linux requires the system menu icon to be a file path, not bytes.") - } - } - } +/// A type that can be used as an user event. +pub trait UserEvent: Debug + Clone + Send + 'static {} - /// Converts the icon to a the expected system tray format. - /// We expect the code that passes the Icon enum to have already checked the platform. - #[cfg(not(target_os = "linux"))] - pub fn into_tray_icon(self) -> Vec { - match self { - Icon::Raw(bytes) => bytes, - Icon::File(_) => { - panic!("non-linux system menu icons must be bytes, not a file path.") - } - } - } -} +impl UserEvent for T {} /// Event triggered on the event loop run. #[non_exhaustive] -pub enum RunEvent { +pub enum RunEvent { /// Event loop is exiting. Exit, /// Event loop is about to exit ExitRequested { - /// Label of the last window managed by the runtime. - window_label: String, tx: Sender, }, - /// Window close was requested by the user. - CloseRequested { + /// An event associated with a window. + WindowEvent { /// The window label. label: String, - /// A signal sender. If a `true` value is emitted, the window won't be closed. - signal_tx: Sender, + /// The detailed event. + event: WindowEvent, }, - /// Window closed. - WindowClose(String), /// Application ready. Ready, /// Sent if the event loop is being resumed. @@ -206,6 +307,8 @@ pub enum RunEvent { /// /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the “main body” of your event loop. MainEventsCleared, + /// A custom event defined by the user. + UserEvent(T), } /// Action to take when the event loop is about to exit @@ -253,115 +356,150 @@ pub enum ActivationPolicy { } /// A [`Send`] handle to the runtime. -pub trait RuntimeHandle: Debug + Send + Sized + Clone + 'static { - type Runtime: Runtime; +pub trait RuntimeHandle: Debug + Clone + Send + Sync + Sized + 'static { + type Runtime: Runtime; + + /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. + fn create_proxy(&self) -> >::EventLoopProxy; + /// Create a new webview window. fn create_window( &self, - pending: PendingWindow, - ) -> crate::Result>; + pending: PendingWindow, + ) -> Result>; /// Run a task on the main thread. - fn run_on_main_thread(&self, f: F) -> crate::Result<()>; + fn run_on_main_thread(&self, f: F) -> Result<()>; - #[cfg(all(windows, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))] - fn remove_system_tray(&self) -> crate::Result<()>; -} - -/// A global shortcut manager. -pub trait GlobalShortcutManager: Debug { - /// Whether the application has registered the given `accelerator`. - fn is_registered(&self, accelerator: &str) -> crate::Result; + /// Adds an icon to the system tray with the specified menu items. + #[cfg(all(desktop, feature = "system-tray"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "system-tray"))))] + fn system_tray( + &self, + system_tray: SystemTray, + ) -> Result<>::TrayHandler>; - /// Register a global shortcut of `accelerator`. - fn register( - &mut self, - accelerator: &str, - handler: F, - ) -> crate::Result<()>; + fn raw_display_handle(&self) -> RawDisplayHandle; - /// Unregister all accelerators registered by the manager instance. - fn unregister_all(&mut self) -> crate::Result<()>; + /// Shows the application, but does not automatically focus it. + #[cfg(target_os = "macos")] + #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + fn show(&self) -> Result<()>; - /// Unregister the provided `accelerator`. - fn unregister(&mut self, accelerator: &str) -> crate::Result<()>; + /// Hides the application. + #[cfg(target_os = "macos")] + #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + fn hide(&self) -> Result<()>; + + /// Finds an Android class in the project scope. + #[cfg(target_os = "android")] + fn find_class<'a>( + &'a self, + env: jni::JNIEnv<'a>, + activity: jni::objects::JObject<'a>, + name: impl Into, + ) -> std::result::Result, jni::errors::Error>; + + /// Dispatch a closure to run on the Android context. + /// + /// The closure takes the JNI env, the Android activity instance and the possibly null webview. + #[cfg(target_os = "android")] + fn run_on_android_context(&self, f: F) + where + F: FnOnce(jni::JNIEnv<'_>, jni::objects::JObject<'_>, jni::objects::JObject<'_>) + + Send + + 'static; } -/// Clipboard manager. -pub trait ClipboardManager: Debug { - /// Writes the text into the clipboard as plain text. - fn write_text>(&mut self, text: T) -> Result<()>; - /// Read the content in the clipboard as plain text. - fn read_text(&self) -> Result>; +pub trait EventLoopProxy: Debug + Clone + Send + Sync { + fn send_event(&self, event: T) -> Result<()>; } /// The webview runtime interface. -pub trait Runtime: Sized + 'static { +pub trait Runtime: Debug + Sized + 'static { /// The message dispatcher. - type Dispatcher: Dispatch; + type Dispatcher: Dispatch; /// The runtime handle type. - type Handle: RuntimeHandle; - /// The global shortcut manager type. - type GlobalShortcutManager: GlobalShortcutManager + Clone + Send; - /// The clipboard manager type. - type ClipboardManager: ClipboardManager + Clone + Send; + type Handle: RuntimeHandle; /// The tray handler type. - #[cfg(feature = "system-tray")] - type TrayHandler: menu::TrayHandle + Clone + Send; + #[cfg(all(desktop, feature = "system-tray"))] + type TrayHandler: menu::TrayHandle; + /// The proxy type. + type EventLoopProxy: EventLoopProxy; /// Creates a new webview runtime. Must be used on the main thread. - fn new() -> crate::Result; + fn new() -> Result; /// Creates a new webview runtime on any thread. #[cfg(any(windows, target_os = "linux"))] #[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))] - fn new_any_thread() -> crate::Result; + fn new_any_thread() -> Result; + + /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. + fn create_proxy(&self) -> Self::EventLoopProxy; /// Gets a runtime handle. fn handle(&self) -> Self::Handle; - /// Gets the global shortcut manager. - fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager; - - /// Gets the clipboard manager. - fn clipboard_manager(&self) -> Self::ClipboardManager; - /// Create a new webview window. - fn create_window(&self, pending: PendingWindow) -> crate::Result>; + fn create_window(&self, pending: PendingWindow) -> Result>; /// Adds the icon to the system tray with the specified menu items. - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - fn system_tray(&self, system_tray: SystemTray) -> crate::Result; + fn system_tray(&self, system_tray: SystemTray) -> Result; /// Registers a system tray event handler. - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - fn on_system_tray_event(&mut self, f: F) -> Uuid; + fn on_system_tray_event(&mut self, f: F); /// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default. #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] fn set_activation_policy(&mut self, activation_policy: ActivationPolicy); + /// Shows the application, but does not automatically focus it. + #[cfg(target_os = "macos")] + #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + fn show(&self); + + /// Hides the application. + #[cfg(target_os = "macos")] + #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + fn hide(&self); + + /// Change the device event filter mode. + /// + /// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`] + /// will ignore them by default for unfocused windows on Windows. This method allows changing + /// the filter to explicitly capture them again. + /// + /// ## Platform-specific + /// + /// - ** Linux / macOS / iOS / Android**: Unsupported. + /// + /// [`tao`]: https://crates.io/crates/tao + fn set_device_event_filter(&mut self, filter: DeviceEventFilter); + /// Runs the one step of the webview runtime event loop and returns control flow to the caller. - fn run_iteration(&mut self, callback: F) -> RunIteration; + #[cfg(desktop)] + fn run_iteration) + 'static>(&mut self, callback: F) -> RunIteration; /// Run the webview runtime. - fn run(self, callback: F); + fn run) + 'static>(self, callback: F); } /// Webview dispatcher. A thread-safe handle to the webview API. -pub trait Dispatch: Debug + Clone + Send + Sized + 'static { +pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static { /// The runtime this [`Dispatch`] runs under. - type Runtime: Runtime; + type Runtime: Runtime; - /// The winoow builder type. - type WindowBuilder: WindowBuilder + Clone; + /// The window builder type. + type WindowBuilder: WindowBuilder; /// Run a task on the main thread. - fn run_on_main_thread(&self, f: F) -> crate::Result<()>; + fn run_on_main_thread(&self, f: F) -> Result<()>; /// Registers a window event handler. fn on_window_event(&self, f: F) -> Uuid; @@ -369,70 +507,106 @@ pub trait Dispatch: Debug + Clone + Send + Sized + 'static { /// Registers a window event handler. fn on_menu_event(&self, f: F) -> Uuid; + /// Runs a closure with the platform webview object as argument. + fn with_webview) + Send + 'static>(&self, f: F) -> Result<()>; + + /// Open the web inspector which is usually called devtools. #[cfg(any(debug_assertions, feature = "devtools"))] fn open_devtools(&self); + /// Close the web inspector which is usually called devtools. + #[cfg(any(debug_assertions, feature = "devtools"))] + fn close_devtools(&self); + + /// Gets the devtools window's current open state. + #[cfg(any(debug_assertions, feature = "devtools"))] + fn is_devtools_open(&self) -> Result; + // GETTERS + /// Returns the webview's current URL. + fn url(&self) -> Result; + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. - fn scale_factor(&self) -> crate::Result; + fn scale_factor(&self) -> Result; /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop. - fn inner_position(&self) -> crate::Result>; + fn inner_position(&self) -> Result>; /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. - fn outer_position(&self) -> crate::Result>; + fn outer_position(&self) -> Result>; /// Returns the physical size of the window's client area. /// /// The client area is the content of the window, excluding the title bar and borders. - fn inner_size(&self) -> crate::Result>; + fn inner_size(&self) -> Result>; /// Returns the physical size of the entire window. /// /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead. - fn outer_size(&self) -> crate::Result>; + fn outer_size(&self) -> Result>; /// Gets the window's current fullscreen state. - fn is_fullscreen(&self) -> crate::Result; + fn is_fullscreen(&self) -> Result; + + /// Gets the window's current minimized state. + fn is_minimized(&self) -> Result; /// Gets the window's current maximized state. - fn is_maximized(&self) -> crate::Result; + fn is_maximized(&self) -> Result; + + /// Gets the window's current focus state. + fn is_focused(&self) -> Result; /// Gets the window’s current decoration state. - fn is_decorated(&self) -> crate::Result; + fn is_decorated(&self) -> Result; /// Gets the window’s current resizable state. - fn is_resizable(&self) -> crate::Result; + fn is_resizable(&self) -> Result; + + /// Gets the window's native maximize button state. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + fn is_maximizable(&self) -> Result; + + /// Gets the window's native minize button state. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + fn is_minimizable(&self) -> Result; + + /// Gets the window's native close button state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + fn is_closable(&self) -> Result; - /// Gets the window's current vibility state. - fn is_visible(&self) -> crate::Result; + /// Gets the window's current visibility state. + fn is_visible(&self) -> Result; + /// Gets the window's current title. + fn title(&self) -> Result; /// Gets the window menu current visibility state. - fn is_menu_visible(&self) -> crate::Result; + fn is_menu_visible(&self) -> Result; /// Returns the monitor on which the window currently resides. /// /// Returns None if current monitor can't be detected. - fn current_monitor(&self) -> crate::Result>; + fn current_monitor(&self) -> Result>; /// Returns the primary monitor of the system. /// /// Returns None if it can't identify any monitor as a primary one. - fn primary_monitor(&self) -> crate::Result>; + fn primary_monitor(&self) -> Result>; /// Returns the list of all the monitors available on the system. - fn available_monitors(&self) -> crate::Result>; - - /// Returns the native handle that is used by this window. - #[cfg(windows)] - fn hwnd(&self) -> crate::Result; - - /// Returns the native handle that is used by this window. - #[cfg(target_os = "macos")] - fn ns_window(&self) -> crate::Result<*mut std::ffi::c_void>; + fn available_monitors(&self) -> Result>; - /// Returns the `ApplicatonWindow` from gtk crate that is used by this window. + /// Returns the `ApplicationWindow` from gtk crate that is used by this window. #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -440,96 +614,151 @@ pub trait Dispatch: Debug + Clone + Send + Sized + 'static { target_os = "netbsd", target_os = "openbsd" ))] - fn gtk_window(&self) -> crate::Result; + fn gtk_window(&self) -> Result; + + fn raw_window_handle(&self) -> Result; + + /// Returns the current window theme. + fn theme(&self) -> Result; // SETTERS /// Centers the window. - fn center(&self) -> crate::Result<()>; + fn center(&self) -> Result<()>; /// Opens the dialog to prints the contents of the webview. - fn print(&self) -> crate::Result<()>; + fn print(&self) -> Result<()>; /// Requests user attention to the window. /// /// Providing `None` will unset the request for user attention. - fn request_user_attention(&self, request_type: Option) -> crate::Result<()>; + fn request_user_attention(&self, request_type: Option) -> Result<()>; /// Create a new webview window. fn create_window( &mut self, - pending: PendingWindow, - ) -> crate::Result>; + pending: PendingWindow, + ) -> Result>; /// Updates the window resizable flag. - fn set_resizable(&self, resizable: bool) -> crate::Result<()>; + fn set_resizable(&self, resizable: bool) -> Result<()>; + + /// Updates the window's native maximize button state. + /// + /// ## Platform-specific + /// + /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. + /// - **Linux / iOS / Android:** Unsupported. + fn set_maximizable(&self, maximizable: bool) -> Result<()>; + + /// Updates the window's native minimize button state. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + fn set_minimizable(&self, minimizable: bool) -> Result<()>; + + /// Updates the window's native close button state. + /// + /// ## Platform-specific + /// + /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. + /// Depending on the system, this function may not have any effect when called on a window that is already visible" + /// - **iOS / Android:** Unsupported. + fn set_closable(&self, closable: bool) -> Result<()>; /// Updates the window title. - fn set_title>(&self, title: S) -> crate::Result<()>; + fn set_title>(&self, title: S) -> Result<()>; /// Maximizes the window. - fn maximize(&self) -> crate::Result<()>; + fn maximize(&self) -> Result<()>; /// Unmaximizes the window. - fn unmaximize(&self) -> crate::Result<()>; + fn unmaximize(&self) -> Result<()>; /// Minimizes the window. - fn minimize(&self) -> crate::Result<()>; + fn minimize(&self) -> Result<()>; /// Unminimizes the window. - fn unminimize(&self) -> crate::Result<()>; + fn unminimize(&self) -> Result<()>; /// Shows the window menu. - fn show_menu(&self) -> crate::Result<()>; + fn show_menu(&self) -> Result<()>; /// Hides the window menu. - fn hide_menu(&self) -> crate::Result<()>; + fn hide_menu(&self) -> Result<()>; /// Shows the window. - fn show(&self) -> crate::Result<()>; + fn show(&self) -> Result<()>; /// Hides the window. - fn hide(&self) -> crate::Result<()>; + fn hide(&self) -> Result<()>; /// Closes the window. - fn close(&self) -> crate::Result<()>; + fn close(&self) -> Result<()>; - /// Updates the hasDecorations flag. - fn set_decorations(&self, decorations: bool) -> crate::Result<()>; + /// Updates the decorations flag. + fn set_decorations(&self, decorations: bool) -> Result<()>; + + /// Updates the shadow flag. + fn set_shadow(&self, enable: bool) -> Result<()>; /// Updates the window alwaysOnTop flag. - fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()>; + fn set_always_on_top(&self, always_on_top: bool) -> Result<()>; + + /// Prevents the window contents from being captured by other apps. + fn set_content_protected(&self, protected: bool) -> Result<()>; /// Resizes the window. - fn set_size(&self, size: Size) -> crate::Result<()>; + fn set_size(&self, size: Size) -> Result<()>; /// Updates the window min size. - fn set_min_size(&self, size: Option) -> crate::Result<()>; + fn set_min_size(&self, size: Option) -> Result<()>; /// Updates the window max size. - fn set_max_size(&self, size: Option) -> crate::Result<()>; + fn set_max_size(&self, size: Option) -> Result<()>; /// Updates the window position. - fn set_position(&self, position: Position) -> crate::Result<()>; + fn set_position(&self, position: Position) -> Result<()>; /// Updates the window fullscreen state. - fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()>; + fn set_fullscreen(&self, fullscreen: bool) -> Result<()>; /// Bring the window to front and focus. - fn set_focus(&self) -> crate::Result<()>; + fn set_focus(&self) -> Result<()>; /// Updates the window icon. - fn set_icon(&self, icon: Icon) -> crate::Result<()>; + fn set_icon(&self, icon: Icon) -> Result<()>; + + /// Whether to hide the window icon from the taskbar or not. + fn set_skip_taskbar(&self, skip: bool) -> Result<()>; + + /// Grabs the cursor, preventing it from leaving the window. + /// + /// There's no guarantee that the cursor will be hidden. You should + /// hide it by yourself if you want so. + fn set_cursor_grab(&self, grab: bool) -> Result<()>; + + /// Modifies the cursor's visibility. + /// + /// If `false`, this will hide the cursor. If `true`, this will show the cursor. + fn set_cursor_visible(&self, visible: bool) -> Result<()>; + + // Modifies the cursor icon of the window. + fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()>; + + /// Changes the position of the cursor in window coordinates. + fn set_cursor_position>(&self, position: Pos) -> Result<()>; - /// Whether to show the window icon in the task bar or not. - fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()>; + /// Ignores the window cursor events. + fn set_ignore_cursor_events(&self, ignore: bool) -> Result<()>; /// Starts dragging the window. - fn start_dragging(&self) -> crate::Result<()>; + fn start_dragging(&self) -> Result<()>; /// Executes javascript on the window this [`Dispatch`] represents. - fn eval_script>(&self, script: S) -> crate::Result<()>; + fn eval_script>(&self, script: S) -> Result<()>; /// Applies the specified `update` to the menu item associated with the given `id`. - fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> crate::Result<()>; + fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> Result<()>; } diff --git a/core/tauri-runtime/src/menu.rs b/core/tauri-runtime/src/menu.rs index 5e116f39d917..bc65aa890b06 100644 --- a/core/tauri-runtime/src/menu.rs +++ b/core/tauri-runtime/src/menu.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -146,12 +146,16 @@ pub enum MenuUpdate { SetNativeImage(NativeImage), } -pub trait TrayHandle: fmt::Debug { +pub trait TrayHandle: fmt::Debug + Clone + Send + Sync { fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>; fn set_menu(&self, menu: crate::menu::SystemTrayMenu) -> crate::Result<()>; fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>; #[cfg(target_os = "macos")] fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()>; + #[cfg(target_os = "macos")] + fn set_title(&self, title: &str) -> crate::Result<()>; + fn set_tooltip(&self, tooltip: &str) -> crate::Result<()>; + fn destroy(&self) -> crate::Result<()>; } /// A window menu. @@ -186,9 +190,134 @@ impl Menu { Default::default() } + /// Creates a menu filled with default menu items and submenus. + /// + /// ## Platform-specific: + /// + /// - **Windows**: + /// - File + /// - CloseWindow + /// - Quit + /// - Edit + /// - Cut + /// - Copy + /// - Paste + /// - Window + /// - Minimize + /// - CloseWindow + /// + /// - **Linux**: + /// - File + /// - CloseWindow + /// - Quit + /// - Window + /// - Minimize + /// - CloseWindow + /// + /// - **macOS**: + /// - App + /// - About + /// - Separator + /// - Services + /// - Separator + /// - Hide + /// - HideOthers + /// - ShowAll + /// - Separator + /// - Quit + /// - File + /// - CloseWindow + /// - Edit + /// - Undo + /// - Redo + /// - Separator + /// - Cut + /// - Copy + /// - Paste + /// - SelectAll + /// - View + /// - EnterFullScreen + /// - Window + /// - Minimize + /// - Zoom + /// - Separator + /// - CloseWindow + pub fn os_default(#[allow(unused)] app_name: &str) -> Self { + let mut menu = Menu::new(); + #[cfg(target_os = "macos")] + { + menu = menu.add_submenu(Submenu::new( + app_name, + Menu::new() + .add_native_item(MenuItem::About( + app_name.to_string(), + AboutMetadata::default(), + )) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Services) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Hide) + .add_native_item(MenuItem::HideOthers) + .add_native_item(MenuItem::ShowAll) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Quit), + )); + } + + let mut file_menu = Menu::new(); + file_menu = file_menu.add_native_item(MenuItem::CloseWindow); + #[cfg(not(target_os = "macos"))] + { + file_menu = file_menu.add_native_item(MenuItem::Quit); + } + menu = menu.add_submenu(Submenu::new("File", file_menu)); + + #[cfg(not(target_os = "linux"))] + let mut edit_menu = Menu::new(); + #[cfg(target_os = "macos")] + { + edit_menu = edit_menu.add_native_item(MenuItem::Undo); + edit_menu = edit_menu.add_native_item(MenuItem::Redo); + edit_menu = edit_menu.add_native_item(MenuItem::Separator); + } + #[cfg(not(target_os = "linux"))] + { + edit_menu = edit_menu.add_native_item(MenuItem::Cut); + edit_menu = edit_menu.add_native_item(MenuItem::Copy); + edit_menu = edit_menu.add_native_item(MenuItem::Paste); + } + #[cfg(target_os = "macos")] + { + edit_menu = edit_menu.add_native_item(MenuItem::SelectAll); + } + #[cfg(not(target_os = "linux"))] + { + menu = menu.add_submenu(Submenu::new("Edit", edit_menu)); + } + #[cfg(target_os = "macos")] + { + menu = menu.add_submenu(Submenu::new( + "View", + Menu::new().add_native_item(MenuItem::EnterFullScreen), + )); + } + + let mut window_menu = Menu::new(); + window_menu = window_menu.add_native_item(MenuItem::Minimize); + #[cfg(target_os = "macos")] + { + window_menu = window_menu.add_native_item(MenuItem::Zoom); + window_menu = window_menu.add_native_item(MenuItem::Separator); + } + window_menu = window_menu.add_native_item(MenuItem::CloseWindow); + menu = menu.add_submenu(Submenu::new("Window", window_menu)); + + menu + } + /// Creates a new window menu with the given items. /// - /// # Example + /// # Examples /// ``` /// # use tauri_runtime::menu::{Menu, MenuItem, CustomMenuItem, Submenu}; /// Menu::with_items([ @@ -396,25 +525,101 @@ impl From for MenuEntry { } } +/// Application metadata for the [`MenuItem::About`] action. +/// +/// ## Platform-specific +/// +/// - **Windows / macOS / Android / iOS:** The metadata is ignored on these platforms. +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub struct AboutMetadata { + /// The application name. + pub version: Option, + /// The authors of the application. + pub authors: Option>, + /// Application comments. + pub comments: Option, + /// The copyright of the application. + pub copyright: Option, + /// The license of the application. + pub license: Option, + /// The application website. + pub website: Option, + /// The website label. + pub website_label: Option, +} + +impl AboutMetadata { + /// Creates the default metadata for the [`MenuItem::About`] action, which is just empty. + pub fn new() -> Self { + Default::default() + } + + /// Defines the application version. + pub fn version(mut self, version: impl Into) -> Self { + self.version.replace(version.into()); + self + } + + /// Defines the application authors. + pub fn authors(mut self, authors: Vec) -> Self { + self.authors.replace(authors); + self + } + + /// Defines the application comments. + pub fn comments(mut self, comments: impl Into) -> Self { + self.comments.replace(comments.into()); + self + } + + /// Defines the application copyright. + pub fn copyright(mut self, copyright: impl Into) -> Self { + self.copyright.replace(copyright.into()); + self + } + + /// Defines the application license. + pub fn license(mut self, license: impl Into) -> Self { + self.license.replace(license.into()); + self + } + + /// Defines the application's website link. + pub fn website(mut self, website: impl Into) -> Self { + self.website.replace(website.into()); + self + } + + /// Defines the application's website link name. + pub fn website_label(mut self, website_label: impl Into) -> Self { + self.website_label.replace(website_label.into()); + self + } +} + /// A menu item, bound to a pre-defined action or `Custom` emit an event. Note that status bar only /// supports `Custom` menu item variants. And on the menu bar, some platforms might not support some /// of the variants. Unsupported variant will be no-op on such platform. #[derive(Debug, Clone)] #[non_exhaustive] pub enum MenuItem { - /// Shows a standard "About" item + /// Shows a standard "About" item. + /// + /// The first value is the application name, and the second is its metadata. /// /// ## Platform-specific /// /// - **Windows / Android / iOS:** Unsupported + /// - **Linux:** The metadata is only applied on Linux /// - About(String), + About(String, AboutMetadata), /// A standard "hide the app" menu item. /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Android / iOS:** Unsupported /// Hide, @@ -446,7 +651,7 @@ pub enum MenuItem { /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Android / iOS:** Unsupported /// CloseWindow, @@ -454,7 +659,7 @@ pub enum MenuItem { /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Android / iOS:** Unsupported /// Quit, @@ -462,7 +667,7 @@ pub enum MenuItem { /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Android / iOS / Linux:** Unsupported /// Copy, @@ -470,7 +675,7 @@ pub enum MenuItem { /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Android / iOS / Linux:** Unsupported /// Cut, @@ -496,7 +701,7 @@ pub enum MenuItem { /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Windows / Android / iOS / Linux:** Unsupported /// SelectAll, @@ -504,7 +709,7 @@ pub enum MenuItem { /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Android / iOS / Linux:** Unsupported /// Paste, @@ -520,7 +725,7 @@ pub enum MenuItem { /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Android / iOS:** Unsupported /// Minimize, @@ -536,7 +741,7 @@ pub enum MenuItem { /// /// ## Platform-specific /// - /// - **Windows / Android / iOS:** Unsupported + /// - **Android / iOS:** Unsupported /// Separator, } diff --git a/core/tauri-runtime/src/monitor.rs b/core/tauri-runtime/src/monitor.rs index d14125a845a4..ce0139148723 100644 --- a/core/tauri-runtime/src/monitor.rs +++ b/core/tauri-runtime/src/monitor.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT diff --git a/core/tauri-runtime/src/webview.rs b/core/tauri-runtime/src/webview.rs index efe70d1833f2..af2277f8a467 100644 --- a/core/tauri-runtime/src/webview.rs +++ b/core/tauri-runtime/src/webview.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -6,7 +6,12 @@ use crate::{menu::Menu, window::DetachedWindow, Icon}; -use tauri_utils::config::{WindowConfig, WindowUrl}; +#[cfg(target_os = "macos")] +use tauri_utils::TitleBarStyle; +use tauri_utils::{ + config::{WindowConfig, WindowEffectsConfig, WindowUrl}, + Theme, +}; #[cfg(windows)] use windows::Win32::Foundation::HWND; @@ -14,27 +19,64 @@ use windows::Win32::Foundation::HWND; use std::{fmt, path::PathBuf}; /// The attributes used to create an webview. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct WebviewAttributes { pub url: WindowUrl, + pub user_agent: Option, pub initialization_scripts: Vec, pub data_directory: Option, pub file_drop_handler_enabled: bool, pub clipboard: bool, + pub accept_first_mouse: bool, + pub additional_browser_args: Option, + pub window_effects: Option, + pub incognito: bool, } +impl From<&WindowConfig> for WebviewAttributes { + fn from(config: &WindowConfig) -> Self { + let mut builder = Self::new(config.url.clone()); + builder = builder.incognito(config.incognito); + builder = builder.accept_first_mouse(config.accept_first_mouse); + if !config.file_drop_enabled { + builder = builder.disable_file_drop_handler(); + } + if let Some(user_agent) = &config.user_agent { + builder = builder.user_agent(user_agent); + } + if let Some(additional_browser_args) = &config.additional_browser_args { + builder = builder.additional_browser_args(additional_browser_args); + } + if let Some(effects) = &config.window_effects { + builder = builder.window_effects(effects.clone()); + } + builder + } +} impl WebviewAttributes { /// Initializes the default attributes for a webview. pub fn new(url: WindowUrl) -> Self { Self { url, + user_agent: None, initialization_scripts: Vec::new(), data_directory: None, file_drop_handler_enabled: true, clipboard: false, + accept_first_mouse: false, + additional_browser_args: None, + window_effects: None, + incognito: false, } } + /// Sets the user agent + #[must_use] + pub fn user_agent(mut self, user_agent: &str) -> Self { + self.user_agent = Some(user_agent.to_string()); + self + } + /// Sets the init script. #[must_use] pub fn initialization_script(mut self, script: &str) -> Self { @@ -65,12 +107,40 @@ impl WebviewAttributes { self.clipboard = true; self } + + /// Sets whether clicking an inactive window also clicks through to the webview. + #[must_use] + pub fn accept_first_mouse(mut self, accept: bool) -> Self { + self.accept_first_mouse = accept; + self + } + + /// Sets additional browser arguments. **Windows Only** + #[must_use] + pub fn additional_browser_args(mut self, additional_args: &str) -> Self { + self.additional_browser_args = Some(additional_args.to_string()); + self + } + + /// Sets window effects + #[must_use] + pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self { + self.window_effects = Some(effects); + self + } + + /// Enable or disable incognito mode for the WebView. + #[must_use] + pub fn incognito(mut self, incognito: bool) -> Self { + self.incognito = incognito; + self + } } /// Do **NOT** implement this trait except for use in a custom [`Runtime`](crate::Runtime). /// /// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation. -pub trait WindowBuilderBase: fmt::Debug + Sized {} +pub trait WindowBuilderBase: fmt::Debug + Clone + Sized {} /// A builder for all attributes related to a single webview. /// @@ -97,7 +167,7 @@ pub trait WindowBuilder: WindowBuilderBase { /// Window size. #[must_use] - fn inner_size(self, min_width: f64, min_height: f64) -> Self; + fn inner_size(self, width: f64, height: f64) -> Self; /// Window min inner size. #[must_use] @@ -108,9 +178,38 @@ pub trait WindowBuilder: WindowBuilderBase { fn max_inner_size(self, max_width: f64, max_height: f64) -> Self; /// Whether the window is resizable or not. + /// When resizable is set to false, native window's maximize button is automatically disabled. #[must_use] fn resizable(self, resizable: bool) -> Self; + /// Whether the window's native maximize button is enabled or not. + /// If resizable is set to false, this setting is ignored. + /// + /// ## Platform-specific + /// + /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. + /// - **Linux / iOS / Android:** Unsupported. + #[must_use] + fn maximizable(self, maximizable: bool) -> Self; + + /// Whether the window's native minimize button is enabled or not. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + #[must_use] + fn minimizable(self, minimizable: bool) -> Self; + + /// Whether the window's native close button is enabled or not. + /// + /// ## Platform-specific + /// + /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. + /// Depending on the system, this function may not have any effect when called on a window that is already visible" + /// - **iOS / Android:** Unsupported. + #[must_use] + fn closable(self, closable: bool) -> Self; + /// The title of the window in the title bar. #[must_use] fn title>(self, title: S) -> Self; @@ -119,9 +218,9 @@ pub trait WindowBuilder: WindowBuilderBase { #[must_use] fn fullscreen(self, fullscreen: bool) -> Self; - /// Whether the window will be initially hidden or focused. + /// Whether the window will be initially focused or not. #[must_use] - fn focus(self) -> Self; + fn focused(self, focused: bool) -> Self; /// Whether the window should be maximized upon creation. #[must_use] @@ -131,7 +230,7 @@ pub trait WindowBuilder: WindowBuilderBase { #[must_use] fn visible(self, visible: bool) -> Self; - /// Whether the the window should be transparent. If this is true, writing colors + /// Whether the window should be transparent. If this is true, writing colors /// with alpha values different than `1.0` will produce a transparent window. #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] #[cfg_attr( @@ -149,6 +248,10 @@ pub trait WindowBuilder: WindowBuilderBase { #[must_use] fn always_on_top(self, always_on_top: bool) -> Self; + /// Prevents the window contents from being captured by other apps. + #[must_use] + fn content_protected(self, protected: bool) -> Self; + /// Sets the window icon. fn icon(self, icon: Icon) -> crate::Result; @@ -156,6 +259,18 @@ pub trait WindowBuilder: WindowBuilderBase { #[must_use] fn skip_taskbar(self, skip: bool) -> Self; + /// Sets whether or not the window has shadow. + /// + /// ## Platform-specific + /// + /// - **Windows:** + /// - `false` has no effect on decorated window, shadows are always ON. + /// - `true` will make ndecorated window have a 1px white border, + /// and on Windows 11, it will have a rounded corners. + /// - **Linux:** Unsupported. + #[must_use] + fn shadow(self, enable: bool) -> Self; + /// Sets a parent to the window to be created. /// /// A child window has the WS_CHILD style and is confined to the client area of its parent window. @@ -165,6 +280,15 @@ pub trait WindowBuilder: WindowBuilderBase { #[must_use] fn parent_window(self, parent: HWND) -> Self; + /// Sets a parent to the window to be created. + /// + /// A child window has the WS_CHILD style and is confined to the client area of its parent window. + /// + /// For more information, see + #[cfg(target_os = "macos")] + #[must_use] + fn parent_window(self, parent: *mut std::ffi::c_void) -> Self; + /// Set an owner to the window to be created. /// /// From MSDN: @@ -177,6 +301,29 @@ pub trait WindowBuilder: WindowBuilderBase { #[must_use] fn owner_window(self, owner: HWND) -> Self; + /// Hide the titlebar. Titlebar buttons will still be visible. + #[cfg(target_os = "macos")] + #[must_use] + fn title_bar_style(self, style: TitleBarStyle) -> Self; + + /// Hide the window title. + #[cfg(target_os = "macos")] + #[must_use] + fn hidden_title(self, hidden: bool) -> Self; + + /// Defines the window [tabbing identifier] for macOS. + /// + /// Windows with matching tabbing identifiers will be grouped together. + /// If the tabbing identifier is not set, automatic tabbing will be disabled. + /// + /// [tabbing identifier]: + #[cfg(target_os = "macos")] + #[must_use] + fn tabbing_identifier(self, identifier: &str) -> Self; + + /// Forces a theme or uses the system settings if None was provided. + fn theme(self, theme: Option) -> Self; + /// Whether the icon was set or not. fn has_icon(&self) -> bool; @@ -184,21 +331,5 @@ pub trait WindowBuilder: WindowBuilderBase { fn get_menu(&self) -> Option<&Menu>; } -/// The file drop event payload. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum FileDropEvent { - /// The file(s) have been dragged onto the window, but have not been dropped yet. - Hovered(Vec), - /// The file(s) have been dropped onto the window. - Dropped(Vec), - /// The file drop was aborted. - Cancelled, -} - /// IPC handler. -pub type WebviewIpcHandler = Box, String) + Send>; - -/// File drop handler callback -/// Return `true` in the callback to block the OS' default behavior of handling a file drop. -pub type FileDropHandler = Box) -> bool + Send>; +pub type WebviewIpcHandler = Box, String) + Send>; diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 77a2a4d9dec1..fbb7899f9b92 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -7,27 +7,30 @@ use crate::{ http::{Request as HttpRequest, Response as HttpResponse}, menu::{Menu, MenuEntry, MenuHash, MenuId}, - webview::{FileDropHandler, WebviewAttributes, WebviewIpcHandler}, - Dispatch, Runtime, WindowBuilder, + webview::{WebviewAttributes, WebviewIpcHandler}, + Dispatch, Runtime, UserEvent, WindowBuilder, }; -use serde::Serialize; -use tauri_utils::config::WindowConfig; +use serde::{Deserialize, Deserializer, Serialize}; +use tauri_utils::{config::WindowConfig, Theme}; +use url::Url; use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, hash::{Hash, Hasher}, + path::PathBuf, sync::{mpsc::Sender, Arc, Mutex}, }; type UriSchemeProtocol = dyn Fn(&HttpRequest) -> Result> + Send + Sync + 'static; +type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; + /// UI scaling utilities. pub mod dpi; /// An event from a window. #[derive(Debug, Clone)] -#[non_exhaustive] pub enum WindowEvent { /// The size of the window has changed. Contains the client area's new dimensions. Resized(dpi::PhysicalSize), @@ -35,8 +38,6 @@ pub enum WindowEvent { Moved(dpi::PhysicalPosition), /// The window has been requested to close. CloseRequested { - /// The window label. - label: String, /// A signal sender. If a `true` value is emitted, the window won't be closed. signal_tx: Sender, }, @@ -59,6 +60,24 @@ pub enum WindowEvent { /// The window inner size. new_inner_size: dpi::PhysicalSize, }, + /// An event associated with the file drop action. + FileDrop(FileDropEvent), + /// The system window theme has changed. + /// + /// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme. + ThemeChanged(Theme), +} + +/// The file drop event payload. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum FileDropEvent { + /// The file(s) have been dragged onto the window, but have not been dropped yet. + Hovered(Vec), + /// The file(s) have been dropped onto the window. + Dropped(Vec), + /// The file drop was aborted. + Cancelled, } /// A menu event. @@ -80,13 +99,127 @@ fn get_menu_ids(map: &mut HashMap, menu: &Menu) { } } +/// Describes the appearance of the mouse cursor. +#[non_exhaustive] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] +pub enum CursorIcon { + /// The platform-dependent default cursor. + #[default] + Default, + /// A simple crosshair. + Crosshair, + /// A hand (often used to indicate links in web browsers). + Hand, + /// Self explanatory. + Arrow, + /// Indicates something is to be moved. + Move, + /// Indicates text that may be selected or edited. + Text, + /// Program busy indicator. + Wait, + /// Help indicator (often rendered as a "?") + Help, + /// Progress indicator. Shows that processing is being done. But in contrast + /// with "Wait" the user may still interact with the program. Often rendered + /// as a spinning beach ball, or an arrow with a watch or hourglass. + Progress, + + /// Cursor showing that something cannot be done. + NotAllowed, + ContextMenu, + Cell, + VerticalText, + Alias, + Copy, + NoDrop, + /// Indicates something can be grabbed. + Grab, + /// Indicates something is grabbed. + Grabbing, + AllScroll, + ZoomIn, + ZoomOut, + + /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor + /// is used when the movement starts from the south-east corner of the box. + EResize, + NResize, + NeResize, + NwResize, + SResize, + SeResize, + SwResize, + WResize, + EwResize, + NsResize, + NeswResize, + NwseResize, + ColResize, + RowResize, +} + +impl<'de> Deserialize<'de> for CursorIcon { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_str() { + "default" => CursorIcon::Default, + "crosshair" => CursorIcon::Crosshair, + "hand" => CursorIcon::Hand, + "arrow" => CursorIcon::Arrow, + "move" => CursorIcon::Move, + "text" => CursorIcon::Text, + "wait" => CursorIcon::Wait, + "help" => CursorIcon::Help, + "progress" => CursorIcon::Progress, + "notallowed" => CursorIcon::NotAllowed, + "contextmenu" => CursorIcon::ContextMenu, + "cell" => CursorIcon::Cell, + "verticaltext" => CursorIcon::VerticalText, + "alias" => CursorIcon::Alias, + "copy" => CursorIcon::Copy, + "nodrop" => CursorIcon::NoDrop, + "grab" => CursorIcon::Grab, + "grabbing" => CursorIcon::Grabbing, + "allscroll" => CursorIcon::AllScroll, + "zoomin" => CursorIcon::ZoomIn, + "zoomout" => CursorIcon::ZoomOut, + "eresize" => CursorIcon::EResize, + "nresize" => CursorIcon::NResize, + "neresize" => CursorIcon::NeResize, + "nwresize" => CursorIcon::NwResize, + "sresize" => CursorIcon::SResize, + "seresize" => CursorIcon::SeResize, + "swresize" => CursorIcon::SwResize, + "wresize" => CursorIcon::WResize, + "ewresize" => CursorIcon::EwResize, + "nsresize" => CursorIcon::NsResize, + "neswresize" => CursorIcon::NeswResize, + "nwseresize" => CursorIcon::NwseResize, + "colresize" => CursorIcon::ColResize, + "rowresize" => CursorIcon::RowResize, + _ => CursorIcon::Default, + }) + } +} + +#[cfg(target_os = "android")] +pub struct CreationContext<'a> { + pub env: jni::JNIEnv<'a>, + pub activity: jni::objects::JObject<'a>, + pub webview: jni::objects::JObject<'a>, +} + /// A webview window that has yet to be built. -pub struct PendingWindow { +pub struct PendingWindow> { /// The label that the window will be named. pub label: String, /// The [`WindowBuilder`] that the window will be created with. - pub window_builder: ::WindowBuilder, + pub window_builder: >::WindowBuilder, /// The [`WebviewAttributes`] that the webview will be created with. pub webview_attributes: WebviewAttributes, @@ -94,19 +227,23 @@ pub struct PendingWindow { pub uri_scheme_protocols: HashMap>, /// How to handle IPC calls on the webview window. - pub ipc_handler: Option>, + pub ipc_handler: Option>, + + /// Maps runtime id to a string menu id. + pub menu_ids: Arc>>, - /// How to handle a file dropping onto the webview window. - pub file_drop_handler: Option>, + /// A handler to decide if incoming url is allowed to navigate. + pub navigation_handler: Option bool + Send>>, /// The resolved URL to load on the webview. pub url: String, - /// Maps runtime id to a string menu id. - pub menu_ids: Arc>>, + #[cfg(target_os = "android")] + #[allow(clippy::type_complexity)] + pub on_webview_created: + Option) -> Result<(), jni::errors::Error> + Send>>, - /// A HashMap mapping JS event names with associated listener ids. - pub js_event_listeners: Arc>>>, + pub web_resource_request_handler: Option>, } pub fn is_label_valid(label: &str) -> bool { @@ -122,29 +259,34 @@ pub fn assert_label_is_valid(label: &str) { ); } -impl PendingWindow { +impl> PendingWindow { /// Create a new [`PendingWindow`] with a label and starting url. pub fn new( - window_builder: ::WindowBuilder, + window_builder: >::WindowBuilder, webview_attributes: WebviewAttributes, label: impl Into, - ) -> Self { + ) -> crate::Result { let mut menu_ids = HashMap::new(); if let Some(menu) = window_builder.get_menu() { get_menu_ids(&mut menu_ids, menu); } let label = label.into(); - assert_label_is_valid(&label); - Self { - window_builder, - webview_attributes, - uri_scheme_protocols: Default::default(), - label, - ipc_handler: None, - file_drop_handler: None, - url: "tauri://localhost".to_string(), - menu_ids: Arc::new(Mutex::new(menu_ids)), - js_event_listeners: Default::default(), + if !is_label_valid(&label) { + Err(crate::Error::InvalidWindowLabel) + } else { + Ok(Self { + window_builder, + webview_attributes, + uri_scheme_protocols: Default::default(), + label, + ipc_handler: None, + menu_ids: Arc::new(Mutex::new(menu_ids)), + navigation_handler: Default::default(), + url: "tauri://localhost".to_string(), + #[cfg(target_os = "android")] + on_webview_created: None, + web_resource_request_handler: Default::default(), + }) } } @@ -153,24 +295,30 @@ impl PendingWindow { window_config: WindowConfig, webview_attributes: WebviewAttributes, label: impl Into, - ) -> Self { - let window_builder = <::WindowBuilder>::with_config(window_config); + ) -> crate::Result { + let window_builder = + <>::WindowBuilder>::with_config(window_config); let mut menu_ids = HashMap::new(); if let Some(menu) = window_builder.get_menu() { get_menu_ids(&mut menu_ids, menu); } let label = label.into(); - assert_label_is_valid(&label); - Self { - window_builder, - webview_attributes, - uri_scheme_protocols: Default::default(), - label, - ipc_handler: None, - file_drop_handler: None, - url: "tauri://localhost".to_string(), - menu_ids: Arc::new(Mutex::new(menu_ids)), - js_event_listeners: Default::default(), + if !is_label_valid(&label) { + Err(crate::Error::InvalidWindowLabel) + } else { + Ok(Self { + window_builder, + webview_attributes, + uri_scheme_protocols: Default::default(), + label, + ipc_handler: None, + menu_ids: Arc::new(Mutex::new(menu_ids)), + navigation_handler: Default::default(), + url: "tauri://localhost".to_string(), + #[cfg(target_os = "android")] + on_webview_created: None, + web_resource_request_handler: Default::default(), + }) } } @@ -196,20 +344,22 @@ impl PendingWindow { .uri_scheme_protocols .insert(uri_scheme, Box::new(move |data| (protocol)(data))); } -} -/// Key for a JS event listener. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct JsEventListenerKey { - /// The associated window label. - pub window_label: Option, - /// The event name. - pub event: String, + #[cfg(target_os = "android")] + pub fn on_webview_created< + F: Fn(CreationContext<'_>) -> Result<(), jni::errors::Error> + Send + 'static, + >( + mut self, + f: F, + ) -> Self { + self.on_webview_created.replace(Box::new(f)); + self + } } /// A webview window that is not yet managed by Tauri. #[derive(Debug)] -pub struct DetachedWindow { +pub struct DetachedWindow> { /// Name of the window pub label: String, @@ -218,31 +368,27 @@ pub struct DetachedWindow { /// Maps runtime id to a string menu id. pub menu_ids: Arc>>, - - /// A HashMap mapping JS event names with associated listener ids. - pub js_event_listeners: Arc>>>, } -impl Clone for DetachedWindow { +impl> Clone for DetachedWindow { fn clone(&self) -> Self { Self { label: self.label.clone(), dispatcher: self.dispatcher.clone(), menu_ids: self.menu_ids.clone(), - js_event_listeners: self.js_event_listeners.clone(), } } } -impl Hash for DetachedWindow { +impl> Hash for DetachedWindow { /// Only use the [`DetachedWindow`]'s label to represent its hash. fn hash(&self, state: &mut H) { self.label.hash(state) } } -impl Eq for DetachedWindow {} -impl PartialEq for DetachedWindow { +impl> Eq for DetachedWindow {} +impl> PartialEq for DetachedWindow { /// Only use the [`DetachedWindow`]'s label to compare equality. fn eq(&self, other: &Self) -> bool { self.label.eq(&other.label) diff --git a/core/tauri-runtime/src/window/dpi.rs b/core/tauri-runtime/src/window/dpi.rs index 5a91205108b2..0710db98dbc6 100644 --- a/core/tauri-runtime/src/window/dpi.rs +++ b/core/tauri-runtime/src/window/dpi.rs @@ -1,18 +1,11 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use serde::{Deserialize, Serialize}; -fn validate_scale_factor(scale_factor: f64) -> bool { - scale_factor.is_sign_positive() && scale_factor.is_normal() -} - -/// A pixel definition. Must be created from a `f64` value. pub trait Pixel: Copy + Into { - /// Creates the pixel from the `f64` value. fn from_f64(f: f64) -> Self; - /// Casts a pixel. fn cast(self) -> P { P::from_f64(self.into()) } @@ -23,81 +16,87 @@ impl Pixel for u8 { f.round() as u8 } } - impl Pixel for u16 { fn from_f64(f: f64) -> Self { f.round() as u16 } } - impl Pixel for u32 { fn from_f64(f: f64) -> Self { f.round() as u32 } } - impl Pixel for i8 { fn from_f64(f: f64) -> Self { f.round() as i8 } } - impl Pixel for i16 { fn from_f64(f: f64) -> Self { f.round() as i16 } } - impl Pixel for i32 { fn from_f64(f: f64) -> Self { f.round() as i32 } } - impl Pixel for f32 { fn from_f64(f: f64) -> Self { f as f32 } } - impl Pixel for f64 { - #[allow(clippy::wrong_self_convention)] fn from_f64(f: f64) -> Self { f } } -/// A position represented in physical pixels. +/// Checks that the scale factor is a normal positive `f64`. +/// +/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from +/// anywhere other than tao, it's recommended to validate them using this function before passing them to tao; +/// otherwise, you risk panics. +#[inline] +pub fn validate_scale_factor(scale_factor: f64) -> bool { + scale_factor.is_sign_positive() && scale_factor.is_normal() +} + +/// A position represented in logical pixels. +/// +/// The position is stored as floats, so please be careful. Casting floats to integers truncates the +/// fractional part, which can cause noticeable issues. To help with that, an `Into<(i32, i32)>` +/// implementation is provided which does the rounding for you. #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, Serialize, Deserialize)] -pub struct PhysicalPosition

{ - /// Vertical axis value. +pub struct LogicalPosition

{ pub x: P, - /// Horizontal axis value. pub y: P, } -impl PhysicalPosition

{ - /// Converts the physical position to a logical one, using the scale factor. +impl

LogicalPosition

{ #[inline] - pub fn to_logical(self, scale_factor: f64) -> LogicalPosition { - assert!(validate_scale_factor(scale_factor)); - let x = self.x.into() / scale_factor; - let y = self.y.into() / scale_factor; - LogicalPosition { x, y }.cast() + pub const fn new(x: P, y: P) -> Self { + LogicalPosition { x, y } } } -/// A position represented in logical pixels. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, Serialize, Deserialize)] -pub struct LogicalPosition

{ - /// Vertical axis value. - pub x: P, - /// Horizontal axis value. - pub y: P, -} +impl LogicalPosition

{ + #[inline] + pub fn from_physical>, X: Pixel>( + physical: T, + scale_factor: f64, + ) -> Self { + physical.into().to_logical(scale_factor) + } + + #[inline] + pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition { + assert!(validate_scale_factor(scale_factor)); + let x = self.x.into() * scale_factor; + let y = self.y.into() * scale_factor; + PhysicalPosition::new(x, y).cast() + } -impl LogicalPosition { - /// Casts the logical size to another pixel type. #[inline] pub fn cast(&self) -> LogicalPosition { LogicalPosition { @@ -107,47 +106,122 @@ impl LogicalPosition { } } -/// A position that's either physical or logical. -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type", content = "data")] -pub enum Position { - /// Physical position. - Physical(PhysicalPosition), - /// Logical position. - Logical(LogicalPosition), +impl From<(X, X)> for LogicalPosition

{ + fn from((x, y): (X, X)) -> LogicalPosition

{ + LogicalPosition::new(x.cast(), y.cast()) + } } -/// A size represented in physical pixels. +impl From> for (X, X) { + fn from(pos: LogicalPosition

) -> Self { + (pos.x.cast(), pos.y.cast()) + } +} + +impl From<[X; 2]> for LogicalPosition

{ + fn from([x, y]: [X; 2]) -> LogicalPosition

{ + LogicalPosition::new(x.cast(), y.cast()) + } +} + +impl From> for [X; 2] { + fn from(pos: LogicalPosition

) -> Self { + [pos.x.cast(), pos.y.cast()] + } +} + +/// A position represented in physical pixels. #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, Serialize, Deserialize)] -pub struct PhysicalSize { - /// Width. - pub width: T, - /// Height. - pub height: T, +pub struct PhysicalPosition

{ + pub x: P, + pub y: P, +} + +impl

PhysicalPosition

{ + #[inline] + pub const fn new(x: P, y: P) -> Self { + PhysicalPosition { x, y } + } } -impl PhysicalSize { - /// Converts the physical size to a logical one, applying the scale factor. +impl PhysicalPosition

{ #[inline] - pub fn to_logical(self, scale_factor: f64) -> LogicalSize { + pub fn from_logical>, X: Pixel>( + logical: T, + scale_factor: f64, + ) -> Self { + logical.into().to_physical(scale_factor) + } + + #[inline] + pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition { assert!(validate_scale_factor(scale_factor)); - let width = self.width.into() / scale_factor; - let height = self.height.into() / scale_factor; - LogicalSize { width, height }.cast() + let x = self.x.into() / scale_factor; + let y = self.y.into() / scale_factor; + LogicalPosition::new(x, y).cast() + } + + #[inline] + pub fn cast(&self) -> PhysicalPosition { + PhysicalPosition { + x: self.x.cast(), + y: self.y.cast(), + } + } +} + +impl From<(X, X)> for PhysicalPosition

{ + fn from((x, y): (X, X)) -> PhysicalPosition

{ + PhysicalPosition::new(x.cast(), y.cast()) + } +} + +impl From> for (X, X) { + fn from(pos: PhysicalPosition

) -> Self { + (pos.x.cast(), pos.y.cast()) + } +} + +impl From<[X; 2]> for PhysicalPosition

{ + fn from([x, y]: [X; 2]) -> PhysicalPosition

{ + PhysicalPosition::new(x.cast(), y.cast()) + } +} + +impl From> for [X; 2] { + fn from(pos: PhysicalPosition

) -> Self { + [pos.x.cast(), pos.y.cast()] } } /// A size represented in logical pixels. #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, Serialize, Deserialize)] -pub struct LogicalSize { - /// Width. - pub width: T, - /// Height. - pub height: T, +pub struct LogicalSize

{ + pub width: P, + pub height: P, +} + +impl

LogicalSize

{ + #[inline] + pub const fn new(width: P, height: P) -> Self { + LogicalSize { width, height } + } } -impl LogicalSize { - /// Casts the logical size to another pixel type. +impl LogicalSize

{ + #[inline] + pub fn from_physical>, X: Pixel>(physical: T, scale_factor: f64) -> Self { + physical.into().to_logical(scale_factor) + } + + #[inline] + pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize { + assert!(validate_scale_factor(scale_factor)); + let width = self.width.into() * scale_factor; + let height = self.height.into() * scale_factor; + PhysicalSize::new(width, height).cast() + } + #[inline] pub fn cast(&self) -> LogicalSize { LogicalSize { @@ -157,12 +231,171 @@ impl LogicalSize { } } +impl From<(X, X)> for LogicalSize

{ + fn from((x, y): (X, X)) -> LogicalSize

{ + LogicalSize::new(x.cast(), y.cast()) + } +} + +impl From> for (X, X) { + fn from(size: LogicalSize

) -> Self { + (size.width.cast(), size.height.cast()) + } +} + +impl From<[X; 2]> for LogicalSize

{ + fn from([x, y]: [X; 2]) -> LogicalSize

{ + LogicalSize::new(x.cast(), y.cast()) + } +} + +impl From> for [X; 2] { + fn from(size: LogicalSize

) -> Self { + [size.width.cast(), size.height.cast()] + } +} + +/// A size represented in physical pixels. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, Serialize, Deserialize)] +pub struct PhysicalSize

{ + pub width: P, + pub height: P, +} + +impl

PhysicalSize

{ + #[inline] + pub const fn new(width: P, height: P) -> Self { + PhysicalSize { width, height } + } +} + +impl PhysicalSize

{ + #[inline] + pub fn from_logical>, X: Pixel>(logical: T, scale_factor: f64) -> Self { + logical.into().to_physical(scale_factor) + } + + #[inline] + pub fn to_logical(&self, scale_factor: f64) -> LogicalSize { + assert!(validate_scale_factor(scale_factor)); + let width = self.width.into() / scale_factor; + let height = self.height.into() / scale_factor; + LogicalSize::new(width, height).cast() + } + + #[inline] + pub fn cast(&self) -> PhysicalSize { + PhysicalSize { + width: self.width.cast(), + height: self.height.cast(), + } + } +} + +impl From<(X, X)> for PhysicalSize

{ + fn from((x, y): (X, X)) -> PhysicalSize

{ + PhysicalSize::new(x.cast(), y.cast()) + } +} + +impl From> for (X, X) { + fn from(size: PhysicalSize

) -> Self { + (size.width.cast(), size.height.cast()) + } +} + +impl From<[X; 2]> for PhysicalSize

{ + fn from([x, y]: [X; 2]) -> PhysicalSize

{ + PhysicalSize::new(x.cast(), y.cast()) + } +} + +impl From> for [X; 2] { + fn from(size: PhysicalSize

) -> Self { + [size.width.cast(), size.height.cast()] + } +} + /// A size that's either physical or logical. #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", content = "data")] pub enum Size { - /// Physical size. Physical(PhysicalSize), - /// Logical size. Logical(LogicalSize), } + +impl Size { + pub fn new>(size: S) -> Size { + size.into() + } + + pub fn to_logical(&self, scale_factor: f64) -> LogicalSize

{ + match *self { + Size::Physical(size) => size.to_logical(scale_factor), + Size::Logical(size) => size.cast(), + } + } + + pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize

{ + match *self { + Size::Physical(size) => size.cast(), + Size::Logical(size) => size.to_physical(scale_factor), + } + } +} + +impl From> for Size { + #[inline] + fn from(size: PhysicalSize

) -> Size { + Size::Physical(size.cast()) + } +} + +impl From> for Size { + #[inline] + fn from(size: LogicalSize

) -> Size { + Size::Logical(size.cast()) + } +} + +/// A position that's either physical or logical. +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type", content = "data")] +pub enum Position { + Physical(PhysicalPosition), + Logical(LogicalPosition), +} + +impl Position { + pub fn new>(position: S) -> Position { + position.into() + } + + pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition

{ + match *self { + Position::Physical(position) => position.to_logical(scale_factor), + Position::Logical(position) => position.cast(), + } + } + + pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition

{ + match *self { + Position::Physical(position) => position.cast(), + Position::Logical(position) => position.to_physical(scale_factor), + } + } +} + +impl From> for Position { + #[inline] + fn from(position: PhysicalPosition

) -> Position { + Position::Physical(position.cast()) + } +} + +impl From> for Position { + #[inline] + fn from(position: LogicalPosition

) -> Position { + Position::Logical(position.cast()) + } +} diff --git a/core/tauri-utils/.license_template b/core/tauri-utils/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/core/tauri-utils/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/core/tauri-utils/CHANGELOG.md b/core/tauri-utils/CHANGELOG.md index 8ad9274cdee3..ad110dde0937 100644 --- a/core/tauri-utils/CHANGELOG.md +++ b/core/tauri-utils/CHANGELOG.md @@ -1,5 +1,275 @@ # Changelog +## \[2.0.0-alpha.5] + +- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Remove `enable_tauri_api` from the IPC scope. +- [`09376af5`](https://www.github.com/tauri-apps/tauri/commit/09376af59424cc27803fa2820d2ac0d4cdc90a6d)([#6704](https://www.github.com/tauri-apps/tauri/pull/6704)) Moved the `cli` feature to its own plugin in the plugins-workspace repository. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the `protocol` scope configuration to the `asset_protocol` field in `SecurityConfig`. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the updater configuration to the `BundleConfig`. +- [`b072daa3`](https://www.github.com/tauri-apps/tauri/commit/b072daa3bd3e38b808466666619ddb885052c5b2)([#6919](https://www.github.com/tauri-apps/tauri/pull/6919)) Moved the `updater` feature to its own plugin in the plugins-workspace repository. +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed the allowlist configuration. +- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Remove the updater's dialog option. + +## \[2.0.0-alpha.4] + +- Added `android` configuration object under `tauri > bundle`. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 + +## \[2.0.0-alpha.3] + +- Pull changes from Tauri 1.3 release. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[2.0.0-alpha.2] + +- Added the `shadow` option to the window configuration and `set_shadow` option to the `window` allow list. + - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08 + +## \[2.0.0-alpha.1] + +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Added `crate_name` field on `PackageInfo`. + - [630a7f4b](https://www.github.com/tauri-apps/tauri/commit/630a7f4b18cef169bfd48673609306fec434e397) refactor: remove mobile log initialization, ref [#6049](https://www.github.com/tauri-apps/tauri/pull/6049) ([#6081](https://www.github.com/tauri-apps/tauri/pull/6081)) on 2023-01-17 + +## \[2.0.0-alpha.0] + +- Parse `android` and `ios` Tauri configuration files. + - [b3a3afc7](https://www.github.com/tauri-apps/tauri/commit/b3a3afc7de8de4021d73559288f5192732a706cf) feat(core): detect android and ios platform configuration files ([#4997](https://www.github.com/tauri-apps/tauri/pull/4997)) on 2022-08-22 +- First mobile alpha release! + - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08 + +## \[1.4.0] + +### New Features + +- [`acc36fe1`](https://www.github.com/tauri-apps/tauri/commit/acc36fe1176cc8aa9063bde932abeb94796c5c72)([#6158](https://www.github.com/tauri-apps/tauri/pull/6158)) Add option to configure `require_literal_leading_dot` on `fs` and `asset` protcol scopes. +- [`35cd751a`](https://www.github.com/tauri-apps/tauri/commit/35cd751adc6fef1f792696fa0cfb471b0bf99374)([#5176](https://www.github.com/tauri-apps/tauri/pull/5176)) Added the `desktop_template` option on `tauri.conf.json > tauri > bundle > deb`. +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` options to the window configuration. +- [`3cb7a3e6`](https://www.github.com/tauri-apps/tauri/commit/3cb7a3e642bb10ee90dc1d24daa48b8c8c15c9ce)([#6997](https://www.github.com/tauri-apps/tauri/pull/6997)) Add `MimeType::parse_with_fallback` and `MimeType::parse_from_uri_with_fallback` +- [`29488205`](https://www.github.com/tauri-apps/tauri/commit/2948820579d20dfaa0861c2f0a58bd7737a7ffd1)([#6867](https://www.github.com/tauri-apps/tauri/pull/6867)) Allow specifying custom language files of Tauri's custom messages for the NSIS installer +- [`e092f799`](https://www.github.com/tauri-apps/tauri/commit/e092f799469ff32c7d1595d0f07d06fd2dab5c29)([#6887](https://www.github.com/tauri-apps/tauri/pull/6887)) Add `nsis > template` option to specify custom NSIS installer template. +- [`cd3846c8`](https://www.github.com/tauri-apps/tauri/commit/cd3846c8ce61ab2879b3911e831525e6242aaab2)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Add `WindowsUpdateInstallMode::nsis_args` +- [`85e77fb7`](https://www.github.com/tauri-apps/tauri/commit/85e77fb797ec17882f55d0944578d929fc6c9c1f)([#6762](https://www.github.com/tauri-apps/tauri/pull/6762)) Correctly determine MIME type of `.txt` files. + +## \[1.3.0] + +- Added the `additional_browser_args` option to the window configuration. + - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14 +- Added the `content_protected` option to the window configuration. + - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13 +- Correctly determine mime type of `.less`, `.sass` and `.styl` files. + - [5fdf8dcb](https://www.github.com/tauri-apps/tauri/commit/5fdf8dcb8ed171d06121dceb32078a7e4f86cc64) fix(core): mime type of .less, .sass and .styl files ([#6316](https://www.github.com/tauri-apps/tauri/pull/6316)) on 2023-02-19 +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 +- Add initial support for building `nsis` bundles on non-Windows platforms. + - [60e6f6c3](https://www.github.com/tauri-apps/tauri/commit/60e6f6c3f1605f3064b5bb177992530ff788ccf0) feat(bundler): Add support for creating NSIS bundles on unix hosts ([#5788](https://www.github.com/tauri-apps/tauri/pull/5788)) on 2023-01-19 +- Add `nsis` bundle target + - [c94e1326](https://www.github.com/tauri-apps/tauri/commit/c94e1326a7c0767a13128a8b1d327a00156ece12) feat(bundler): add `nsis`, closes [#4450](https://www.github.com/tauri-apps/tauri/pull/4450), closes [#2319](https://www.github.com/tauri-apps/tauri/pull/2319) ([#4674](https://www.github.com/tauri-apps/tauri/pull/4674)) on 2023-01-03 +- Added configuration to specify remote URLs allowed to access the IPC. + - [ee71c31f](https://www.github.com/tauri-apps/tauri/commit/ee71c31fd09cc5427da6d29d37c003a914547696) feat(core): allow configuring remote domains with IPC access, closes [#5088](https://www.github.com/tauri-apps/tauri/pull/5088) ([#5918](https://www.github.com/tauri-apps/tauri/pull/5918)) on 2023-04-11 + +## \[1.2.1] + +- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`. + - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18 + +## \[1.2.0] + +- Validate `package > productName` in the tauri config and produce errors if it contains one of the following characters `/\:*?\"<>|` + - [b9316a64](https://www.github.com/tauri-apps/tauri/commit/b9316a64eaa9348c79efafb8b94960d9b4d5b27a) fix(cli): validate `productName` in config, closes [#5233](https://www.github.com/tauri-apps/tauri/pull/5233) ([#5262](https://www.github.com/tauri-apps/tauri/pull/5262)) on 2022-09-28 +- Properly serialize HTML template tags. + - [aec5537d](https://www.github.com/tauri-apps/tauri/commit/aec5537de0205f62b2ae5c89da04d21930a6fc2e) fix(codegen): serialize template tags, closes [#4410](https://www.github.com/tauri-apps/tauri/pull/4410) ([#5247](https://www.github.com/tauri-apps/tauri/pull/5247)) on 2022-09-28 +- `PatternKind::Isolation` is now defined even without the `isolation` feature. + - [a178f95d](https://www.github.com/tauri-apps/tauri/commit/a178f95d68b773779b40235a3a22115a5e36aa6a) feat: config schema generator ([#5193](https://www.github.com/tauri-apps/tauri/pull/5193)) on 2022-10-28 +- Added the `app` allowlist module. + - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03 +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 +- Add `tauri.conf.json > bundle > publisher` field to specify the app publisher. + - [628285c1](https://www.github.com/tauri-apps/tauri/commit/628285c1cf43f03ed62378f3b6cc0c991317526f) feat(bundler): add `publisher` field, closes [#5273](https://www.github.com/tauri-apps/tauri/pull/5273) ([#5283](https://www.github.com/tauri-apps/tauri/pull/5283)) on 2022-09-28 +- Canonicalize the return value of `platform::resource_dir`. + - [a06dc699](https://www.github.com/tauri-apps/tauri/commit/a06dc6993148f10ff7623c9dcc81f313dd960ad0) fix(core): canonicalize resource dir to fix scope check, closes [#5196](https://www.github.com/tauri-apps/tauri/pull/5196) ([#5218](https://www.github.com/tauri-apps/tauri/pull/5218)) on 2022-09-29 +- Added `title` option on the system tray configuration (macOS only). + - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30 +- Added the `user_agent` option to the window configuration. + - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02 +- Add `mime_type` module. + - [54c337e0](https://www.github.com/tauri-apps/tauri/commit/54c337e06f3bc624c4780cf002bc54790f446c90) feat(cli): hotreload support for frontend static files, closes [#2173](https://www.github.com/tauri-apps/tauri/pull/2173) ([#5256](https://www.github.com/tauri-apps/tauri/pull/5256)) on 2022-09-28 + +## \[1.1.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[1.1.0] + +- Allow adding `build > beforeBundleCommand` in tauri.conf.json to run a shell command before the bundling phase. + - [57ab9847](https://www.github.com/tauri-apps/tauri/commit/57ab9847eb2d8c9a5da584b873b7c072e9ee26bf) feat(cli): add `beforeBundleCommand`, closes [#4879](https://www.github.com/tauri-apps/tauri/pull/4879) ([#4893](https://www.github.com/tauri-apps/tauri/pull/4893)) on 2022-08-09 +- Change `before_dev_command` and `before_build_command` config value to allow configuring the current working directory. + - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02 +- Allow configuring the `before_dev_command` to force the CLI to wait for the command to finish before proceeding. + - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02 +- Added support to configuration files in TOML format (Tauri.toml file). + - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02 +- Refactored the `config::parse` module. + - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02 +- Update windows to 0.39.0 and webview2-com to 0.19.1. + - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03 + +## \[1.0.3] + +- Added `menu_on_left_click: bool` to the `SystemTrayConfig`. + - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05 +- Added `config::parse::read_platform` and `config::parse::get_platform_config_filename`. + - [8e3e7fc6](https://www.github.com/tauri-apps/tauri/commit/8e3e7fc64641afc7a6833bc93205e6f525562545) feat(cli): improve bundle identifier validation, closes [#4589](https://www.github.com/tauri-apps/tauri/pull/4589) ([#4596](https://www.github.com/tauri-apps/tauri/pull/4596)) on 2022-07-05 + +## \[1.0.2] + +- Expose `platform::windows_version` function. + - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30 + +## \[1.0.1] + +- Changed the `BundleConfig::targets` to a `BundleTarget` enum to enhance generated documentation. + - [31c15cd2](https://www.github.com/tauri-apps/tauri/commit/31c15cd2bd94dbe39fb94982a15cbe02ac5d8925) docs(config): enhance documentation for bundle targets, closes [#3251](https://www.github.com/tauri-apps/tauri/pull/3251) ([#4418](https://www.github.com/tauri-apps/tauri/pull/4418)) on 2022-06-21 +- Added `platform::is_windows_7`. + - [57039fb2](https://www.github.com/tauri-apps/tauri/commit/57039fb2166571de85271b014a8711a29f06be1a) fix(core): add windows 7 notification support ([#4491](https://www.github.com/tauri-apps/tauri/pull/4491)) on 2022-06-28 +- Suppress unused variable warning in release builds. + - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22 +- Added webview install mode options. + - [2ca762d2](https://www.github.com/tauri-apps/tauri/commit/2ca762d207030a892a6d128b519e150e2d733468) feat(bundler): extend webview2 installation options, closes [#2882](https://www.github.com/tauri-apps/tauri/pull/2882) [#2452](https://www.github.com/tauri-apps/tauri/pull/2452) ([#4466](https://www.github.com/tauri-apps/tauri/pull/4466)) on 2022-06-26 + +## \[1.0.0] + +- Upgrade to `stable`! + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[1.0.0-rc.9] + +- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`. + - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10 + +## \[1.0.0-rc.8] + +- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`. + - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25 + - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27 + +## \[1.0.0-rc.7] + +- Allow configuring the display options for the MSI execution allowing quieter updates. + - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15 + +## \[1.0.0-rc.6] + +- Added `$schema` support to `tauri.conf.json`. + - [715cbde3](https://www.github.com/tauri-apps/tauri/commit/715cbde3842a916c4ebeab2cab348e1774b5c192) feat(config): add `$schema` to `tauri.conf.json`, closes [#3464](https://www.github.com/tauri-apps/tauri/pull/3464) ([#4031](https://www.github.com/tauri-apps/tauri/pull/4031)) on 2022-05-03 +- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable. + - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02 + +## \[1.0.0-rc.5] + +- Added the `io` module with the `read_line` method. + - [b5622882](https://www.github.com/tauri-apps/tauri/commit/b5622882cf3748e1e5a90915f415c0cd922aaaf8) fix(cli): exit on non-compilation Cargo errors, closes [#3930](https://www.github.com/tauri-apps/tauri/pull/3930) ([#3942](https://www.github.com/tauri-apps/tauri/pull/3942)) on 2022-04-22 +- **Breaking change:** Removed the `useBootstrapper` option. Use https://github.com/tauri-apps/fix-path-env-rs instead. + - [6a5ff08c](https://www.github.com/tauri-apps/tauri/commit/6a5ff08ce9052b656aa40accedfd4315825164a3) refactor: remove bootstrapper, closes [#3786](https://www.github.com/tauri-apps/tauri/pull/3786) ([#3832](https://www.github.com/tauri-apps/tauri/pull/3832)) on 2022-03-31 + +## \[1.0.0-rc.4] + +- Added an option to disable the CSP injection of distributable assets nonces and hashes. + - [f6e32ee1](https://www.github.com/tauri-apps/tauri/commit/f6e32ee1880eb364ed76beb937c9d12e14d54910) feat(core): add dangerous option to disable compile time CSP injection ([#3775](https://www.github.com/tauri-apps/tauri/pull/3775)) on 2022-03-28 + +- Use the default value for `MacConfig.minimumSystemVersion` if the value is set to an empty string. + - [c81534eb](https://www.github.com/tauri-apps/tauri/commit/c81534ebd873c358e0346c7949aeb171803149a5) feat(cli): use default macOS minimum system version when it is empty ([#3658](https://www.github.com/tauri-apps/tauri/pull/3658)) on 2022-03-13 + +- Replace multiple dependencies who's C code compiled concurrently and caused + the other ones to bloat compile time significantly. + +- `zstd` -> `brotli` + +- `blake3` -> a vendored version of the blake3 reference + +- `ring` -> `getrandom` + +See https://github.com/tauri-apps/tauri/pull/3773 for more information about +these specific choices. + +- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27 + +## \[1.0.0-rc.3] + +- Use `is_symlink` API compatible with Rust v1.57 instead of std/fs/struct.Metadata.html#method.is_symlink. + - [73388119](https://www.github.com/tauri-apps/tauri/commit/73388119e653e7902b19beef2ab6d7c5f8a7b83a) use older symlink check function ([#3579](https://www.github.com/tauri-apps/tauri/pull/3579)) on 2022-03-01 + +## \[1.0.0-rc.2] + +- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`. + - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17 + +## \[1.0.0-rc.1] + +- Change default value for the `freezePrototype` configuration to `false`. + - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12 + +## \[1.0.0-rc.0] + +- The `allowlist` configuration now includes a `clipboard` object, controlling the exposure of the `writeText` and `readText` APIs. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- The dialog allowlist now includes flags for the `message`, `ask` and `confirm` APIs. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- The `allowlist` configuration now includes a `process` object, controlling the exposure of the `relaunch` and `exit` APIs. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- The `window` allowlist now includes options to enable all window modification APIs: `center`, `close`, `create`, `hide`, `maximize`, `minimize`, `print`, `requestUserAttention`, `setAlwaysOnTop`, `setDecorations`, `setFocus`, `setFullscreen`, `setIcon`, `setMaxSize`, `setMinSize`, `setPosition`, `setResizable`, `setSize`, `setSkipTaskbar`, `setTitle`, `show`, `startDragging`, `unmaximize` and `unminimize`. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- Added `asset` allowlist configuration, which enables the `asset` protocol and defines it access scope. + - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \[TRI-026] \[TRI-010] \[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09 +- Change `CliArg` numeric types from `u64` to `usize`. + - [1f988535](https://www.github.com/tauri-apps/tauri/commit/1f98853573a837dd0cfc2161b206a5033ec2da5e) chore(deps) Update Tauri Core ([#2480](https://www.github.com/tauri-apps/tauri/pull/2480)) on 2021-08-24 +- Apply `nonce` to `script` and `style` tags and set them on the `CSP` (`script-src` and `style-src` fetch directives). + - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09 +- The path returned from `tauri::api::process::current_binary` is now cached when loading the binary. + - [7c3db7a3](https://www.github.com/tauri-apps/tauri/commit/7c3db7a3811fd4de3e71c78cfd00894fa51ab786) cache current binary path much sooner ([#45](https://www.github.com/tauri-apps/tauri/pull/45)) on 2022-02-01 +- Added `dev_csp` to the `security` configuration object. + - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09 +- Fixes resource directory resolution on Linux. + - [1a28904b](https://www.github.com/tauri-apps/tauri/commit/1a28904b8ebea92e143d5dc21ebd209e9edec531) fix(core): resource path resolution on Linux, closes [#2493](https://www.github.com/tauri-apps/tauri/pull/2493) on 2021-08-22 +- Allow using a fixed version for the Webview2 runtime via the `tauri > bundle > windows > webviewFixedRuntimePath` config option. + - [85df94f2](https://www.github.com/tauri-apps/tauri/commit/85df94f2b0d40255812b42c5e32a70c4b45392df) feat(core): config for fixed webview2 runtime version path ([#27](https://www.github.com/tauri-apps/tauri/pull/27)) on 2021-11-02 +- The updater `pubkey` is now a required field for security reasons. Sign your updates with the `tauri signer` command. + - [d95cc831](https://www.github.com/tauri-apps/tauri/commit/d95cc83105dda52df7514e30e54f3676cdb374ee) feat: enforce updater public key \[TRI-015] ([#42](https://www.github.com/tauri-apps/tauri/pull/42)) on 2022-01-09 +- Added the `isolation` pattern. + - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) Co-authored-by: Lucas Fernandes Nogueira on 2022-01-17 +- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension. + +Here is the logic flow that determines if JSON or JSON5 will be used to parse the config: + +1. Check if `tauri.conf.json` exists + a. Parse it with `serde_json` + b. Parse it with `json5` if `serde_json` fails + c. Return original `serde_json` error if all above steps failed +2. Check if `tauri.conf.json5` exists + a. Parse it with `json5` + b. Return error if all above steps failed +3. Return error if all above steps failed + +- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03 +- Move the copying of resources and sidecars from `cli.rs` to `tauri-build` so using the Cargo CLI directly processes the files for the application execution in development. + - [5eb72c24](https://www.github.com/tauri-apps/tauri/commit/5eb72c24deddf5a01093bea96b90c0d8806afc3f) refactor: copy resources and sidecars on the Cargo build script ([#3357](https://www.github.com/tauri-apps/tauri/pull/3357)) on 2022-02-08 +- **Breaking change**\* Remove default webview window when `tauri.conf.json > tauri > windows` is not set. + - [c119060e](https://www.github.com/tauri-apps/tauri/commit/c119060e3d9a5a824639fb6b3c45a87e7a62e4e2) refactor(core): empty default value for config > tauri > windows ([#3380](https://www.github.com/tauri-apps/tauri/pull/3380)) on 2022-02-10 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 +- Adds `scope` glob array config under `tauri > allowlist > fs`. + Adds `assetScope` glob array config under `tauri > allowlist > protocol`. + Adds `scope` URL array config under `tauri > allowlist > http`. + - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \[TRI-026] \[TRI-010] \[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09 + - [0ad1c651](https://www.github.com/tauri-apps/tauri/commit/0ad1c6515f696fadefddbf133a9561836b3d5934) feat(core): add `http` allowlist scope \[TRI-008] ([#24](https://www.github.com/tauri-apps/tauri/pull/24)) on 2021-10-29 +- The `shell` allowlist now includes a `sidecar` flag, which enables the use of the `shell` API to execute sidecars. + - [eed01728](https://www.github.com/tauri-apps/tauri/commit/eed017287fed2ade689af4268e8b63b9c9f2e585) feat(core): add `shell > sidecar` allowlist and `process` feature flag \[TRI-037] ([#18](https://www.github.com/tauri-apps/tauri/pull/18)) on 2021-10-24 +- Force updater endpoint URL to use `https` on release builds. + - [c077f449](https://www.github.com/tauri-apps/tauri/commit/c077f449270cffbf7956b1af81e1fb237ebf564a) feat: force endpoint URL to use https on release \[TRI-015] ([#41](https://www.github.com/tauri-apps/tauri/pull/41)) on 2022-01-09 + ## \[1.0.0-beta.3] - Fixes minimum window height being used as maximum height. diff --git a/core/tauri-utils/Cargo.toml b/core/tauri-utils/Cargo.toml index 6bc445e6bb71..271628a05a8e 100644 --- a/core/tauri-utils/Cargo.toml +++ b/core/tauri-utils/Cargo.toml @@ -1,47 +1,65 @@ [package] name = "tauri-utils" -version = "1.0.0-beta.3" -authors = [ "Tauri Programme within The Commons Conservancy" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.studio" -repository = "https://github.com/tauri-apps/tauri" +version = "2.0.0-alpha.5" description = "Utilities for Tauri" -edition = "2021" -rust-version = "1.57" -exclude = [ ".license_template", "CHANGELOG.md", "/target" ] +exclude = [ "CHANGELOG.md", "/target" ] readme = "README.md" +# workspace defined package items +authors = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } + [dependencies] -serde = { version = "1.0", features = [ "derive" ] } -serde_json = "1.0" -thiserror = "1.0.30" +serde = { version = "1", features = [ "derive" ] } +serde_json = "1" +thiserror = "1" phf = { version = "0.10", features = [ "macros" ] } -zstd = { version = "0.10", optional = true } -url = { version = "2.2", features = [ "serde" ] } +brotli = { version = "3", optional = true, default-features = false, features = [ "std" ] } +url = { version = "2", features = [ "serde" ] } kuchiki = "0.8" html5ever = "0.25" -proc-macro2 = { version = "1.0", optional = true } -quote = { version = "1.0", optional = true } -schemars = { version = "0.8", features = ["url"], optional = true } -serde_with = "1.12" -aes-gcm = { version = "0.9", optional = true } -ring = { version = "0.16", optional = true, features = ["std"] } -once_cell = { version = "1.9", optional = true } -serialize-to-javascript = "=0.1.1" +proc-macro2 = { version = "1", optional = true } +quote = { version = "1", optional = true } +schemars = { version = "0.8", features = [ "url" ], optional = true } +serde_with = "3" +aes-gcm = { version = "0.10", optional = true } +getrandom = { version = "0.2", optional = true, features = [ "std" ] } +serialize-to-javascript = { version = "=0.1.1", optional = true } ctor = "0.1" json5 = { version = "0.4", optional = true } -json-patch = "0.2" -glob = { version = "0.3.0", optional = true } +toml = { version = "0.5", optional = true } +json-patch = "1.0" +glob = { version = "0.3", optional = true } walkdir = { version = "2", optional = true } +memchr = "2" +semver = "1" +infer = "0.12" +dunce = "1" [target."cfg(target_os = \"linux\")".dependencies] heck = "0.4" +[target."cfg(windows)".dependencies.windows] +version = "0.44.0" +features = [ + "implement", + "Win32_Foundation", + "Win32_System_Com", + "Win32_System_LibraryLoader", + "Win32_System_SystemInformation" +] + [features] build = [ "proc-macro2", "quote" ] -compression = [ "zstd" ] -schema = ["schemars"] -isolation = [ "aes-gcm", "ring", "once_cell" ] -process-relaunch-dangerous-allow-symlink-macos = [] +compression = [ "brotli" ] +schema = [ "schemars" ] +isolation = [ "aes-gcm", "getrandom", "serialize-to-javascript" ] +process-relaunch-dangerous-allow-symlink-macos = [ ] config-json5 = [ "json5" ] +config-toml = [ "toml" ] resources = [ "glob", "walkdir" ] diff --git a/core/tauri-utils/README.md b/core/tauri-utils/README.md index 36044cae8458..fcc3d5956c3f 100644 --- a/core/tauri-utils/README.md +++ b/core/tauri-utils/README.md @@ -2,37 +2,41 @@ -[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri) -[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) -[![devto](https://img.shields.io/badge/blog-dev.to-black.svg)](https://dev.to/tauri) - -![](https://img.shields.io/github/workflow/status/tauri-apps/tauri/test%20library?label=test%20library -) -[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.studio) - +[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev) +[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri) +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield) +[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) +[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) -[![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri) +[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) -| Component | Version | -| --------- | ------------------------------------------- | +| Component | Version | +| ----------- | ---------------------------------------------------------------------------------------------------- | | tauri-utils | [![](https://img.shields.io/crates/v/tauri-utils?style=flat-square)](https://crates.io/crates/utils) | ## About Tauri + Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily. Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task. ## This module + This is common code that is reused in many places and offers useful utilities like parsing configuration files, detecting platform triples, injecting the CSP, and managing assets. To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. ## Semver + **tauri** is following [Semantic Versioning 2.0](https://semver.org/). + ## Licenses + Code: (c) 2021 - The Tauri Programme within The Commons Conservancy. MIT or MIT/Apache 2.0 where applicable. Logo: CC-BY-NC-ND + - Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) diff --git a/core/tauri-utils/src/assets.rs b/core/tauri-utils/src/assets.rs index cd43489544d7..5b035a38690e 100644 --- a/core/tauri-utils/src/assets.rs +++ b/core/tauri-utils/src/assets.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -144,8 +144,12 @@ impl Assets for EmbeddedAssets { self .assets .get(key.as_ref()) - .copied() - .map(zstd::decode_all) + .map(|&(mut asdf)| { + // with the exception of extremely small files, output should usually be + // at least as large as the compressed version. + let mut buf = Vec::with_capacity(asdf.len()); + brotli::BrotliDecompress(&mut asdf, &mut buf).map(|()| buf) + }) .and_then(Result::ok) .map(Cow::Owned) } @@ -170,8 +174,7 @@ impl Assets for EmbeddedAssets { .get(html_path.as_ref()) .copied() .into_iter() - .flatten() - .into_iter(), + .flatten(), ) .copied(), ) diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 2d97256eaf6d..b8a1792934d4 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -14,37 +14,52 @@ use heck::ToKebabCase; #[cfg(feature = "schema")] use schemars::JsonSchema; +use semver::Version; use serde::{ de::{Deserializer, Error as DeError, Visitor}, - Deserialize, Serialize, + Deserialize, Serialize, Serializer, }; use serde_json::Value as JsonValue; use serde_with::skip_serializing_none; use url::Url; -use std::{collections::HashMap, fmt, fs::read_to_string, path::PathBuf}; +use std::{ + collections::HashMap, + fmt::{self, Display}, + fs::read_to_string, + path::PathBuf, + str::FromStr, +}; /// Items to help with parsing content into a [`Config`]. pub mod parse; +use crate::{TitleBarStyle, WindowEffect, WindowEffectState}; + pub use self::parse::parse; +fn default_true() -> bool { + true +} + /// An URL to open on a Tauri webview window. -#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)] +#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(untagged)] #[non_exhaustive] pub enum WindowUrl { /// An external URL. External(Url), - /// An app URL. + /// The path portion of an app URL. + /// For instance, to load `tauri://localhost/users/john`, + /// you can simply provide `users/john` in this configuration. App(PathBuf), } impl fmt::Display for WindowUrl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::External(url) => write!(f, "{}", url), + Self::External(url) => write!(f, "{url}"), Self::App(path) => write!(f, "{}", path.display()), } } @@ -56,47 +71,233 @@ impl Default for WindowUrl { } } -/// Targets to bundle. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +/// A bundle referenced by tauri-bundler. +#[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(untagged)] +#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))] +pub enum BundleType { + /// The debian bundle (.deb). + Deb, + /// The AppImage bundle (.appimage). + AppImage, + /// The Microsoft Installer bundle (.msi). + Msi, + /// The NSIS bundle (.exe). + Nsis, + /// The macOS application bundle (.app). + App, + /// The Apple Disk Image bundle (.dmg). + Dmg, + /// The Tauri updater bundle. + Updater, +} + +impl Display for BundleType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Deb => "deb", + Self::AppImage => "appimage", + Self::Msi => "msi", + Self::Nsis => "nsis", + Self::App => "app", + Self::Dmg => "dmg", + Self::Updater => "updater", + } + ) + } +} + +impl Serialize for BundleType { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl<'de> Deserialize<'de> for BundleType { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.to_lowercase().as_str() { + "deb" => Ok(Self::Deb), + "appimage" => Ok(Self::AppImage), + "msi" => Ok(Self::Msi), + "nsis" => Ok(Self::Nsis), + "app" => Ok(Self::App), + "dmg" => Ok(Self::Dmg), + "updater" => Ok(Self::Updater), + _ => Err(DeError::custom(format!("unknown bundle target '{s}'"))), + } + } +} + +/// Targets to bundle. Each value is case insensitive. +#[derive(Debug, PartialEq, Eq, Clone)] pub enum BundleTarget { + /// Bundle all targets. + All, /// A list of bundle targets. - All(Vec), + List(Vec), /// A single bundle target. - One(String), + One(BundleType), +} + +#[cfg(feature = "schema")] +impl schemars::JsonSchema for BundleTarget { + fn schema_name() -> std::string::String { + "BundleTarget".to_owned() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + let any_of = vec![ + schemars::schema::SchemaObject { + enum_values: Some(vec!["all".into()]), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some("Bundle all targets.".to_owned()), + ..Default::default() + })), + ..Default::default() + } + .into(), + schemars::_private::apply_metadata( + gen.subschema_for::>(), + schemars::schema::Metadata { + description: Some("A list of bundle targets.".to_owned()), + ..Default::default() + }, + ), + schemars::_private::apply_metadata( + gen.subschema_for::(), + schemars::schema::Metadata { + description: Some("A single bundle target.".to_owned()), + ..Default::default() + }, + ), + ]; + + schemars::schema::SchemaObject { + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(any_of), + ..Default::default() + })), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some("Targets to bundle. Each value is case insensitive.".to_owned()), + ..Default::default() + })), + ..Default::default() + } + .into() + } +} + +impl Default for BundleTarget { + fn default() -> Self { + Self::All + } +} + +impl Serialize for BundleTarget { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + match self { + Self::All => serializer.serialize_str("all"), + Self::List(l) => l.serialize(serializer), + Self::One(t) => serializer.serialize_str(t.to_string().as_ref()), + } + } +} + +impl<'de> Deserialize<'de> for BundleTarget { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize, Serialize)] + #[serde(untagged)] + pub enum BundleTargetInner { + List(Vec), + One(BundleType), + All(String), + } + + match BundleTargetInner::deserialize(deserializer)? { + BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All), + BundleTargetInner::All(t) => Err(DeError::custom(format!("invalid bundle type {t}"))), + BundleTargetInner::List(l) => Ok(Self::List(l)), + BundleTargetInner::One(t) => Ok(Self::One(t)), + } + } } impl BundleTarget { - /// Gets the bundle targets as a [`Vec`]. + /// Gets the bundle targets as a [`Vec`]. The vector is empty when set to [`BundleTarget::All`]. #[allow(dead_code)] - pub fn to_vec(&self) -> Vec { + pub fn to_vec(&self) -> Vec { match self { - Self::All(list) => list.clone(), + Self::All => vec![], + Self::List(list) => list.clone(), Self::One(i) => vec![i.clone()], } } } +/// Configuration for AppImage bundles. +/// +/// See more: https://tauri.app/v1/api/config#appimageconfig +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct AppImageConfig { + /// Include additional gstreamer dependencies needed for audio and video playback. + /// This increases the bundle size by ~15-35MB depending on your build system. + #[serde(default, alias = "bundle-media-framework")] + pub bundle_media_framework: bool, +} + /// Configuration for Debian (.deb) bundles. +/// +/// See more: https://tauri.app/v1/api/config#debconfig #[skip_serializing_none] -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct DebConfig { /// The list of deb dependencies your application relies on. pub depends: Option>, - /// Enable the boostrapper script. - #[serde(default)] - pub use_bootstrapper: bool, /// The files to include on the package. #[serde(default)] pub files: HashMap, + /// Path to a custom desktop file Handlebars template. + /// + /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`. + pub desktop_template: Option, +} + +fn de_minimum_system_version<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let version = Option::::deserialize(deserializer)?; + match version { + Some(v) if v.is_empty() => Ok(minimum_system_version()), + e => Ok(e), + } } /// Configuration for the macOS bundles. +/// +/// See more: https://tauri.app/v1/api/config#macconfig #[skip_serializing_none] -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct MacConfig { @@ -104,35 +305,66 @@ pub struct MacConfig { /// /// If a name is used, ".framework" must be omitted and it will look for standard install locations. You may also use a path to a specific framework. pub frameworks: Option>, - /// A version string indicating the minimum macOS X version that the bundled application supports. + /// A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`. + /// + /// Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist` + /// and the `MACOSX_DEPLOYMENT_TARGET` environment variable. + /// + /// An empty string is considered an invalid value so the default value is used. + #[serde( + deserialize_with = "de_minimum_system_version", + default = "minimum_system_version", + alias = "minimum-system-version" + )] pub minimum_system_version: Option, /// Allows your application to communicate with the outside world. /// It should be a lowercase, without port and protocol domain name. + #[serde(alias = "exception-domain")] pub exception_domain: Option, /// The path to the license file to add to the DMG bundle. pub license: Option, - /// Enable the boostrapper script. - #[serde(default)] - pub use_bootstrapper: bool, /// Identity to use for code signing. + #[serde(alias = "signing-identity")] pub signing_identity: Option, /// Provider short name for notarization. + #[serde(alias = "provider-short-name")] pub provider_short_name: Option, /// Path to the entitlements file. pub entitlements: Option, } +impl Default for MacConfig { + fn default() -> Self { + Self { + frameworks: None, + minimum_system_version: minimum_system_version(), + exception_domain: None, + license: None, + signing_identity: None, + provider_short_name: None, + entitlements: None, + } + } +} + +fn minimum_system_version() -> Option { + Some("10.13".into()) +} + /// Configuration for a target language for the WiX build. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +/// +/// See more: https://tauri.app/v1/api/config#wixlanguageconfig +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct WixLanguageConfig { /// The path to a locale (`.wxl`) file. See . + #[serde(alias = "locale-path")] pub locale_path: Option, } /// The languages to build using WiX. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(untagged)] pub enum WixLanguage { @@ -151,7 +383,9 @@ impl Default for WixLanguage { } /// Configuration for the MSI bundle using WiX. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +/// +/// See more: https://tauri.app/v1/api/config#wixconfig +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct WixConfig { @@ -161,79 +395,317 @@ pub struct WixConfig { /// A custom .wxs template to use. pub template: Option, /// A list of paths to .wxs files with WiX fragments to use. - #[serde(default)] + #[serde(default, alias = "fragment-paths")] pub fragment_paths: Vec, /// The ComponentGroup element ids you want to reference from the fragments. - #[serde(default)] + #[serde(default, alias = "component-group-refs")] pub component_group_refs: Vec, /// The Component element ids you want to reference from the fragments. - #[serde(default)] + #[serde(default, alias = "component-refs")] pub component_refs: Vec, /// The FeatureGroup element ids you want to reference from the fragments. - #[serde(default)] + #[serde(default, alias = "feature-group-refs")] pub feature_group_refs: Vec, /// The Feature element ids you want to reference from the fragments. - #[serde(default)] + #[serde(default, alias = "feature-refs")] pub feature_refs: Vec, /// The Merge element ids you want to reference from the fragments. - #[serde(default)] + #[serde(default, alias = "merge-refs")] pub merge_refs: Vec, /// Disables the Webview2 runtime installation after app install. - #[serde(default)] + /// + /// Will be removed in v2, prefer the [`WindowsConfig::webview_install_mode`] option. + #[serde(default, alias = "skip-webview-install")] pub skip_webview_install: bool, /// The path to the license file to render on the installer. /// /// Must be an RTF file, so if a different extension is provided, we convert it to the RTF format. pub license: Option, /// Create an elevated update task within Windows Task Scheduler. - #[serde(default)] + #[serde(default, alias = "enable-elevated-update-task")] pub enable_elevated_update_task: bool, /// Path to a bitmap file to use as the installation user interface banner. /// This bitmap will appear at the top of all but the first page of the installer. /// /// The required dimensions are 493px × 58px. + #[serde(alias = "banner-path")] pub banner_path: Option, /// Path to a bitmap file to use on the installation user interface dialogs. /// It is used on the welcome and completion dialogs. /// The required dimensions are 493px × 312px. + #[serde(alias = "dialog-image-path")] pub dialog_image_path: Option, } +/// Configuration for the Installer bundle using NSIS. +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct NsisConfig { + /// A custom .nsi template to use. + pub template: Option, + /// The path to the license file to render on the installer. + pub license: Option, + /// The path to a bitmap file to display on the header of installers pages. + /// + /// The recommended dimensions are 150px x 57px. + #[serde(alias = "header-image")] + pub header_image: Option, + /// The path to a bitmap file for the Welcome page and the Finish page. + /// + /// The recommended dimensions are 164px x 314px. + #[serde(alias = "sidebar-image")] + pub sidebar_image: Option, + /// The path to an icon file used as the installer icon. + #[serde(alias = "install-icon")] + pub installer_icon: Option, + /// Whether the installation will be for all users or just the current user. + #[serde(default, alias = "install-mode")] + pub install_mode: NSISInstallerMode, + /// A list of installer languages. + /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used. + /// To allow the user to select the language, set `display_language_selector` to `true`. + /// + /// See for the complete list of languages. + pub languages: Option>, + /// A key-value pair where the key is the language and the + /// value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages. + /// + /// See for an example `.nsh` file. + /// + /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array, + pub custom_language_files: Option>, + /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. + /// By default the OS language is selected, with a fallback to the first language in the `languages` array. + #[serde(default, alias = "display-language-selector")] + pub display_language_selector: bool, +} + +/// Install Modes for the NSIS installer. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +pub enum NSISInstallerMode { + /// Default mode for the installer. + /// + /// Install the app by default in a directory that doesn't require Administrator access. + /// + /// Installer metadata will be saved under the `HKCU` registry path. + CurrentUser, + /// Install the app by default in the `Program Files` folder directory requires Administrator + /// access for the installation. + /// + /// Installer metadata will be saved under the `HKLM` registry path. + PerMachine, + /// Combines both modes and allows the user to choose at install time + /// whether to install for the current user or per machine. Note that this mode + /// will require Administrator access even if the user wants to install it for the current user only. + /// + /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice. + Both, +} + +impl Default for NSISInstallerMode { + fn default() -> Self { + Self::CurrentUser + } +} + +/// Install modes for the Webview2 runtime. +/// Note that for the updater bundle [`Self::DownloadBootstrapper`] is used. +/// +/// For more information see . +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +pub enum WebviewInstallMode { + /// Do not install the Webview2 as part of the Windows Installer. + Skip, + /// Download the bootstrapper and run it. + /// Requires an internet connection. + /// Results in a smaller installer size, but is not recommended on Windows 7. + DownloadBootstrapper { + /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`. + #[serde(default = "default_true")] + silent: bool, + }, + /// Embed the bootstrapper and run it. + /// Requires an internet connection. + /// Increases the installer size by around 1.8MB, but offers better support on Windows 7. + EmbedBootstrapper { + /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`. + #[serde(default = "default_true")] + silent: bool, + }, + /// Embed the offline installer and run it. + /// Does not require an internet connection. + /// Increases the installer size by around 127MB. + OfflineInstaller { + /// Instructs the installer to run the installer in silent mode. Defaults to `true`. + #[serde(default = "default_true")] + silent: bool, + }, + /// Embed a fixed webview2 version and use it at runtime. + /// Increases the installer size by around 180MB. + FixedRuntime { + /// The path to the fixed runtime to use. + /// + /// The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section). + /// The `.cab` file must be extracted to a folder and this folder path must be defined on this field. + path: PathBuf, + }, +} + +impl Default for WebviewInstallMode { + fn default() -> Self { + Self::DownloadBootstrapper { silent: true } + } +} + /// Windows bundler configuration. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +/// +/// See more: https://tauri.app/v1/api/config#windowsconfig +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct WindowsConfig { /// Specifies the file digest algorithm to use for creating file signatures. /// Required for code signing. SHA-256 is recommended. + #[serde(alias = "digest-algorithm")] pub digest_algorithm: Option, /// Specifies the SHA1 hash of the signing certificate. + #[serde(alias = "certificate-thumbprint")] pub certificate_thumbprint: Option, /// Server to use during timestamping. + #[serde(alias = "timestamp-url")] pub timestamp_url: Option, - /// Path to the webview fixed runtime to use. + /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may + /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true. + #[serde(default)] + pub tsp: bool, + /// The installation mode for the Webview2 runtime. + #[serde(default, alias = "webview-install-mode")] + pub webview_install_mode: WebviewInstallMode, + /// Path to the webview fixed runtime to use. Overwrites [`Self::webview_install_mode`] if set. + /// + /// Will be removed in v2, prefer the [`Self::webview_install_mode`] option. /// /// The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section). /// The `.cab` file must be extracted to a folder and this folder path must be defined on this field. + #[serde(alias = "webview-fixed-runtime-path")] pub webview_fixed_runtime_path: Option, + /// Validates a second app installation, blocking the user from installing an older version if set to `false`. + /// + /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`. + /// + /// The default value of this flag is `true`. + #[serde(default = "default_true", alias = "allow-downgrades")] + pub allow_downgrades: bool, /// Configuration for the MSI generated with WiX. pub wix: Option, + /// Configuration for the installer generated with NSIS. + pub nsis: Option, +} + +impl Default for WindowsConfig { + fn default() -> Self { + Self { + digest_algorithm: None, + certificate_thumbprint: None, + timestamp_url: None, + tsp: false, + webview_install_mode: Default::default(), + webview_fixed_runtime_path: None, + allow_downgrades: true, + wix: None, + nsis: None, + } + } +} + +/// The Updater configuration object. +/// +/// See more: https://tauri.app/v1/api/config#updaterconfig +#[skip_serializing_none] +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct UpdaterConfig { + /// Whether the updater is active or not. + #[serde(default)] + pub active: bool, + /// Signature public key. + #[serde(default)] // use default just so the schema doesn't flag it as required + pub pubkey: String, + /// The Windows configuration for the updater. + #[serde(default)] + pub windows: UpdaterWindowsConfig, +} + +impl<'de> Deserialize<'de> for UpdaterConfig { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct InnerUpdaterConfig { + #[serde(default)] + active: bool, + pubkey: Option, + #[serde(default)] + windows: UpdaterWindowsConfig, + } + + let config = InnerUpdaterConfig::deserialize(deserializer)?; + + if config.active && config.pubkey.is_none() { + return Err(DeError::custom( + "The updater `pubkey` configuration is required.", + )); + } + + Ok(UpdaterConfig { + active: config.active, + pubkey: config.pubkey.unwrap_or_default(), + windows: config.windows, + }) + } +} + +impl Default for UpdaterConfig { + fn default() -> Self { + Self { + active: false, + pubkey: "".into(), + windows: Default::default(), + } + } } /// Configuration for tauri-bundler. +/// +/// See more: https://tauri.app/v1/api/config#bundleconfig #[skip_serializing_none] -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct BundleConfig { - /// Whether we should build your app with tauri-bundler or plain `cargo build` + /// Whether Tauri should bundle your application or just output the executable. #[serde(default)] pub active: bool, - /// The bundle targets, currently supports ["deb", "app", "msi", "appimage", "dmg"] or "all" - pub targets: Option, - /// The app's identifier + /// The bundle targets, currently supports ["deb", "appimage", "nsis", "msi", "app", "dmg", "updater"] or "all". + #[serde(default)] + pub targets: BundleTarget, + /// The application identifier in reverse domain name notation (e.g. `com.tauri.example`). + /// This string must be unique across applications since it is used in system configurations like + /// the bundle ID and path to the webview data directory. + /// This string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-), + /// and periods (.). pub identifier: String, + /// The application's publisher. Defaults to the second element in the identifier string. + /// Currently maps to the Manufacturer property of the Windows Installer. + pub publisher: Option, /// The app's icons #[serde(default)] pub icon: Vec, @@ -244,11 +716,19 @@ pub struct BundleConfig { /// A copyright string associated with your application. pub copyright: Option, /// The application kind. + /// + /// Should be one of the following: + /// Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather. pub category: Option, /// A short description of your application. + #[serde(alias = "short-description")] pub short_description: Option, /// A longer, multi-line description of the application. + #[serde(alias = "long-description")] pub long_description: Option, + /// Configuration for the AppImage bundle. + #[serde(default)] + pub appimage: AppImageConfig, /// Configuration for the Debian bundle. #[serde(default)] pub deb: DebConfig, @@ -266,174 +746,49 @@ pub struct BundleConfig { /// - "my-binary-x86_64-unknown-linux-gnu" for Linux /// /// so don't forget to provide binaries for all targeted platforms. + #[serde(alias = "external-bin")] pub external_bin: Option>, /// Configuration for the Windows bundle. #[serde(default)] pub windows: WindowsConfig, + /// iOS configuration. + #[serde(rename = "iOS", default)] + pub ios: IosConfig, + /// Android configuration. + #[serde(default)] + pub android: AndroidConfig, + /// The updater configuration. + #[serde(default)] + pub updater: UpdaterConfig, } -/// A CLI argument definition. -#[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +/// a tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct CliArg { - /// The short version of the argument, without the preceding -. - /// - /// NOTE: Any leading - characters will be stripped, and only the first non - character will be used as the short version. - pub short: Option, - /// The unique argument name - pub name: String, - /// The argument description which will be shown on the help information. - /// Typically, this is a short (one line) description of the arg. - pub description: Option, - /// The argument long description which will be shown on the help information. - /// Typically this a more detailed (multi-line) message that describes the argument. - pub long_description: Option, - /// Specifies that the argument takes a value at run time. - /// - /// NOTE: values for arguments may be specified in any of the following methods - /// - Using a space such as -o value or --option value - /// - Using an equals and no space such as -o=value or --option=value - /// - Use a short and no space such as -ovalue - pub takes_value: Option, - /// Specifies that the argument may have an unknown number of multiple values. Without any other settings, this argument may appear only once. - /// - /// For example, --opt val1 val2 is allowed, but --opt val1 val2 --opt val3 is not. - /// - /// NOTE: Setting this requires `takes_value` to be set to true. - pub multiple: Option, - /// Specifies that the argument may appear more than once. - /// For flags, this results in the number of occurrences of the flag being recorded. For example -ddd or -d -d -d would count as three occurrences. - /// For options or arguments that take a value, this does not affect how many values they can accept. (i.e. only one at a time is allowed) - /// - /// For example, --opt val1 --opt val2 is allowed, but --opt val1 val2 is not. - pub multiple_occurrences: Option, - /// Specifies how many values are required to satisfy this argument. For example, if you had a - /// `-f ` argument where you wanted exactly 3 'files' you would set - /// `number_of_values = 3`, and this argument wouldn't be satisfied unless the user provided - /// 3 and only 3 values. - /// - /// **NOTE:** Does *not* require `multiple_occurrences = true` to be set. Setting - /// `multiple_occurrences = true` would allow `-f -f ` where - /// as *not* setting it would only allow one occurrence of this argument. - /// - /// **NOTE:** implicitly sets `takes_value = true` and `multiple_values = true`. - pub number_of_values: Option, - /// Specifies a list of possible values for this argument. - /// At runtime, the CLI verifies that only one of the specified values was used, or fails with an error message. - pub possible_values: Option>, - /// Specifies the minimum number of values for this argument. - /// For example, if you had a -f argument where you wanted at least 2 'files', - /// you would set `minValues: 2`, and this argument would be satisfied if the user provided, 2 or more values. - pub min_values: Option, - /// Specifies the maximum number of values are for this argument. - /// For example, if you had a -f argument where you wanted up to 3 'files', - /// you would set .max_values(3), and this argument would be satisfied if the user provided, 1, 2, or 3 values. - pub max_values: Option, - /// Sets whether or not the argument is required by default. - /// - /// - Required by default means it is required, when no other conflicting rules have been evaluated - /// - Conflicting rules take precedence over being required. - pub required: Option, - /// Sets an arg that override this arg's required setting - /// i.e. this arg will be required unless this other argument is present. - pub required_unless_present: Option, - /// Sets args that override this arg's required setting - /// i.e. this arg will be required unless all these other arguments are present. - pub required_unless_present_all: Option>, - /// Sets args that override this arg's required setting - /// i.e. this arg will be required unless at least one of these other arguments are present. - pub required_unless_present_any: Option>, - /// Sets a conflicting argument by name - /// i.e. when using this argument, the following argument can't be present and vice versa. - pub conflicts_with: Option, - /// The same as conflictsWith but allows specifying multiple two-way conflicts per argument. - pub conflicts_with_all: Option>, - /// Tets an argument by name that is required when this one is present - /// i.e. when using this argument, the following argument must be present. - pub requires: Option, - /// Sts multiple arguments by names that are required when this one is present - /// i.e. when using this argument, the following arguments must be present. - pub requires_all: Option>, - /// Allows a conditional requirement with the signature [arg, value] - /// the requirement will only become valid if `arg`'s value equals `${value}`. - pub requires_if: Option>, - /// Allows specifying that an argument is required conditionally with the signature [arg, value] - /// the requirement will only become valid if the `arg`'s value equals `${value}`. - pub required_if_eq: Option>, - /// Requires that options use the --option=val syntax - /// i.e. an equals between the option and associated value. - pub require_equals: Option, - /// The positional argument index, starting at 1. - /// - /// The index refers to position according to other positional argument. - /// It does not define position in the argument list as a whole. When utilized with multiple=true, - /// only the last positional argument may be defined as multiple (i.e. the one with the highest index). - pub index: Option, -} +pub struct Color(pub u8, pub u8, pub u8, pub u8); -/// describes a CLI configuration +/// The window effects configuration object #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct CliConfig { - /// Command description which will be shown on the help information. - pub description: Option, - /// Command long description which will be shown on the help information. - pub long_description: Option, - /// Adds additional help information to be displayed in addition to auto-generated help. - /// This information is displayed before the auto-generated help information. - /// This is often used for header information. - pub before_help: Option, - /// Adds additional help information to be displayed in addition to auto-generated help. - /// This information is displayed after the auto-generated help information. - /// This is often used to describe how to use the arguments, or caveats to be noted. - pub after_help: Option, - /// List of arguments for the command - pub args: Option>, - /// List of subcommands of this command - pub subcommands: Option>, -} - -impl CliConfig { - /// List of arguments for the command - pub fn args(&self) -> Option<&Vec> { - self.args.as_ref() - } - - /// List of subcommands of this command - pub fn subcommands(&self) -> Option<&HashMap> { - self.subcommands.as_ref() - } - - /// Command description which will be shown on the help information. - pub fn description(&self) -> Option<&String> { - self.description.as_ref() - } - - /// Command long description which will be shown on the help information. - pub fn long_description(&self) -> Option<&String> { - self.description.as_ref() - } - - /// Adds additional help information to be displayed in addition to auto-generated help. - /// This information is displayed before the auto-generated help information. - /// This is often used for header information. - pub fn before_help(&self) -> Option<&String> { - self.before_help.as_ref() - } - - /// Adds additional help information to be displayed in addition to auto-generated help. - /// This information is displayed after the auto-generated help information. - /// This is often used to describe how to use the arguments, or caveats to be noted. - pub fn after_help(&self) -> Option<&String> { - self.after_help.as_ref() - } +pub struct WindowEffectsConfig { + /// List of Window effects to apply to the Window. + /// Conflicting effects will apply the first one and ignore the rest. + pub effects: Vec, + /// Window effect state **macOS Only** + pub state: Option, + /// Window effect corner radius **macOS Only** + pub radius: Option, + /// Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only + /// on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11. + pub color: Option, } /// The window configuration object. +/// +/// See more: https://tauri.app/v1/api/config#windowconfig #[skip_serializing_none] #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] @@ -445,10 +800,13 @@ pub struct WindowConfig { /// The window webview URL. #[serde(default)] pub url: WindowUrl, + /// The user agent for the webview + #[serde(alias = "user-agent")] + pub user_agent: Option, /// Whether the file drop is enabled or not on the webview. By default it is enabled. /// /// Disabling it is required to use drag and drop on the frontend on Windows. - #[serde(default = "default_file_drop_enabled")] + #[serde(default = "default_true", alias = "file-drop-enabled")] pub file_drop_enabled: bool, /// Whether or not the window starts centered or not. #[serde(default)] @@ -464,46 +822,129 @@ pub struct WindowConfig { #[serde(default = "default_height")] pub height: f64, /// The min window width. + #[serde(alias = "min-width")] pub min_width: Option, /// The min window height. + #[serde(alias = "min-height")] pub min_height: Option, /// The max window width. + #[serde(alias = "max-width")] pub max_width: Option, /// The max window height. + #[serde(alias = "max-height")] pub max_height: Option, - /// Whether the window is resizable or not. - #[serde(default = "default_resizable")] + /// Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled. + #[serde(default = "default_true")] pub resizable: bool, + /// Whether the window's native maximize button is enabled or not. + /// If resizable is set to false, this setting is ignored. + /// + /// ## Platform-specific + /// + /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. + /// - **Linux / iOS / Android:** Unsupported. + #[serde(default = "default_true")] + pub maximizable: bool, + /// Whether the window's native minimize button is enabled or not. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + #[serde(default = "default_true")] + pub minimizable: bool, + /// Whether the window's native close button is enabled or not. + /// + /// ## Platform-specific + /// + /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. + /// Depending on the system, this function may not have any effect when called on a window that is already visible" + /// - **iOS / Android:** Unsupported. + #[serde(default = "default_true")] + pub closable: bool, /// The window title. #[serde(default = "default_title")] pub title: String, /// Whether the window starts as fullscreen or not. #[serde(default)] pub fullscreen: bool, - /// Whether the window will be initially hidden or focused. - #[serde(default = "default_focus")] + /// Whether the window will be initially focused or not. + #[serde(default = "default_true")] pub focus: bool, /// Whether the window is transparent or not. /// - /// Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > tauri > macosPrivateApi`. - /// WARNING: Using private APIs on `macOS` prevents your application from being accepted for the `App Store`. + /// Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`. + /// WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`. #[serde(default)] pub transparent: bool, /// Whether the window is maximized or not. #[serde(default)] pub maximized: bool, /// Whether the window is visible or not. - #[serde(default = "default_visible")] + #[serde(default = "default_true")] pub visible: bool, /// Whether the window should have borders and bars. - #[serde(default = "default_decorations")] + #[serde(default = "default_true")] pub decorations: bool, /// Whether the window should always be on top of other windows. - #[serde(default)] + #[serde(default, alias = "always-on-top")] pub always_on_top: bool, - /// Whether or not the window icon should be added to the taskbar. - #[serde(default)] + /// Prevents the window contents from being captured by other apps. + #[serde(default, alias = "content-protected")] + pub content_protected: bool, + /// If `true`, hides the window icon from the taskbar on Windows and Linux. + #[serde(default, alias = "skip-taskbar")] pub skip_taskbar: bool, + /// The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+. + pub theme: Option, + /// The style of the macOS title bar. + #[serde(default, alias = "title-bar-style")] + pub title_bar_style: TitleBarStyle, + /// If `true`, sets the window title to be hidden on macOS. + #[serde(default, alias = "hidden-title")] + pub hidden_title: bool, + /// Whether clicking an inactive window also clicks through to the webview on macOS. + #[serde(default, alias = "accept-first-mouse")] + pub accept_first_mouse: bool, + /// Defines the window [tabbing identifier] for macOS. + /// + /// Windows with matching tabbing identifiers will be grouped together. + /// If the tabbing identifier is not set, automatic tabbing will be disabled. + /// + /// [tabbing identifier]: + #[serde(default, alias = "tabbing-identifier")] + pub tabbing_identifier: Option, + /// Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection` + /// so if you use this method, you also need to disable these components by yourself if you want. + #[serde(default, alias = "additional-browser-args")] + pub additional_browser_args: Option, + /// Whether or not the window has shadow. + /// + /// ## Platform-specific + /// + /// - **Windows:** + /// - `false` has no effect on decorated window, shadow are always ON. + /// - `true` will make ndecorated window have a 1px white border, + /// and on Windows 11, it will have a rounded corners. + /// - **Linux:** Unsupported. + #[serde(default = "default_true")] + pub shadow: bool, + /// Window effects. + /// + /// Requires the window to be transparent. + /// + /// ## Platform-specific: + /// + /// - **Windows**: If using decorations or shadows, you may want to try this workaround https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891 + /// - **Linux**: Unsupported + #[serde(default, alias = "window-effects")] + pub window_effects: Option, + /// Whether or not the webview should be launched in incognito mode. + /// + /// ## Platform-specific: + /// + /// - **Android**: Unsupported. + #[serde(default)] + pub incognito: bool, } impl Default for WindowConfig { @@ -511,7 +952,8 @@ impl Default for WindowConfig { Self { label: default_window_label(), url: WindowUrl::default(), - file_drop_enabled: default_file_drop_enabled(), + user_agent: None, + file_drop_enabled: true, center: false, x: None, y: None, @@ -521,16 +963,29 @@ impl Default for WindowConfig { min_height: None, max_width: None, max_height: None, - resizable: default_resizable(), + resizable: true, + maximizable: true, + minimizable: true, + closable: true, title: default_title(), fullscreen: false, focus: false, transparent: false, maximized: false, - visible: default_visible(), - decorations: default_decorations(), + visible: true, + decorations: true, always_on_top: false, + content_protected: false, skip_taskbar: false, + theme: None, + title_bar_style: Default::default(), + hidden_title: false, + accept_first_mouse: false, + tabbing_identifier: None, + additional_browser_args: None, + shadow: true, + window_effects: None, + incognito: false, } } } @@ -547,927 +1002,310 @@ fn default_height() -> f64 { 600f64 } -fn default_resizable() -> bool { - true -} - fn default_title() -> String { "Tauri App".to_string() } -fn default_focus() -> bool { - true -} - -fn default_visible() -> bool { - true -} - -fn default_decorations() -> bool { - true -} - -fn default_file_drop_enabled() -> bool { - true -} - -/// Security configuration. -#[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +/// A Content-Security-Policy directive source list. +/// See . +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct SecurityConfig { - /// The Content Security Policy that will be injected on all HTML files on the built application. - /// If [`dev_csp`](SecurityConfig.dev_csp) is not specified, this value is also injected on dev. - /// - /// This is a really important part of the configuration since it helps you ensure your WebView is secured. - /// See . - pub csp: Option, - /// The Content Security Policy that will be injected on all HTML files on development. - /// - /// This is a really important part of the configuration since it helps you ensure your WebView is secured. - /// See . - pub dev_csp: Option, - /// Freeze the `Object.prototype` when using the custom protocol. - #[serde(default = "default_freeze_prototype")] - pub freeze_prototype: bool, +#[serde(rename_all = "camelCase", untagged)] +pub enum CspDirectiveSources { + /// An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator. + Inline(String), + /// A list of CSP sources. The collection will be concatenated with a space separator for the CSP string. + List(Vec), } -impl Default for SecurityConfig { +impl Default for CspDirectiveSources { fn default() -> Self { - Self { - csp: None, - dev_csp: None, - freeze_prototype: default_freeze_prototype(), - } + Self::List(Vec::new()) } } -fn default_freeze_prototype() -> bool { - true -} - -/// Defines an allowlist type. -pub trait Allowlist { - /// Returns all features associated with the allowlist struct. - fn all_features() -> Vec<&'static str>; - /// Returns the tauri features enabled on this allowlist. - fn to_features(&self) -> Vec<&'static str>; -} - -macro_rules! check_feature { - ($self:ident, $features:ident, $flag:ident, $feature_name: expr) => { - if $self.$flag { - $features.push($feature_name) - } - }; -} - -/// Filesystem scope definition. -/// It is a list of glob patterns that restrict the API access from the webview. -/// -/// Each 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`, `$CWD`. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -pub struct FsAllowlistScope(pub Vec); - -/// Allowlist for the file system APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct FsAllowlistConfig { - /// The access scope for the filesystem APIs. - #[serde(default)] - pub scope: FsAllowlistScope, - /// Use this flag to enable all file system API features. - #[serde(default)] - pub all: bool, - /// Read file from local filesystem. - #[serde(default)] - pub read_file: bool, - /// Write file to local filesystem. - #[serde(default)] - pub write_file: bool, - /// Read directory from local filesystem. - #[serde(default)] - pub read_dir: bool, - /// Copy file from local filesystem. - #[serde(default)] - pub copy_file: bool, - /// Create directory from local filesystem. - #[serde(default)] - pub create_dir: bool, - /// Remove directory from local filesystem. - #[serde(default)] - pub remove_dir: bool, - /// Remove file from local filesystem. - #[serde(default)] - pub remove_file: bool, - /// Rename file from local filesystem. - #[serde(default)] - pub rename_file: bool, -} - -impl Allowlist for FsAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - scope: Default::default(), - all: false, - read_file: true, - write_file: true, - read_dir: true, - copy_file: true, - create_dir: true, - remove_dir: true, - remove_file: true, - rename_file: true, - }; - let mut features = allowlist.to_features(); - features.push("fs-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["fs-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, read_file, "fs-read-file"); - check_feature!(self, features, write_file, "fs-write-file"); - check_feature!(self, features, read_dir, "fs-read-dir"); - check_feature!(self, features, copy_file, "fs-copy-file"); - check_feature!(self, features, create_dir, "fs-create-dir"); - check_feature!(self, features, remove_dir, "fs-remove-dir"); - check_feature!(self, features, remove_file, "fs-remove-file"); - check_feature!(self, features, rename_file, "fs-rename-file"); - features +impl From for Vec { + fn from(sources: CspDirectiveSources) -> Self { + match sources { + CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(), + CspDirectiveSources::List(l) => l, } } } -/// Allowlist for the window APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct WindowAllowlistConfig { - /// Use this flag to enable all window API features. - #[serde(default)] - pub all: bool, - /// Allows dynamic window creation. - #[serde(default)] - pub create: bool, - /// Allows centering the window. - #[serde(default)] - pub center: bool, - /// Allows requesting user attention on the window. - #[serde(default)] - pub request_user_attention: bool, - /// Allows setting the resizable flag of the window. - #[serde(default)] - pub set_resizable: bool, - /// Allows changing the window title. - #[serde(default)] - pub set_title: bool, - /// Allows maximizing the window. - #[serde(default)] - pub maximize: bool, - /// Allows unmaximizing the window. - #[serde(default)] - pub unmaximize: bool, - /// Allows minimizing the window. - #[serde(default)] - pub minimize: bool, - /// Allows unminimizing the window. - #[serde(default)] - pub unminimize: bool, - /// Allows showing the window. - #[serde(default)] - pub show: bool, - /// Allows hiding the window. - #[serde(default)] - pub hide: bool, - /// Allows closing the window. - #[serde(default)] - pub close: bool, - /// Allows setting the decorations flag of the window. - #[serde(default)] - pub set_decorations: bool, - /// Allows setting the always_on_top flag of the window. - #[serde(default)] - pub set_always_on_top: bool, - /// Allows setting the window size. - #[serde(default)] - pub set_size: bool, - /// Allows setting the window minimum size. - #[serde(default)] - pub set_min_size: bool, - /// Allows setting the window maximum size. - #[serde(default)] - pub set_max_size: bool, - /// Allows changing the position of the window. - #[serde(default)] - pub set_position: bool, - /// Allows setting the fullscreen flag of the window. - #[serde(default)] - pub set_fullscreen: bool, - /// Allows focusing the window. - #[serde(default)] - pub set_focus: bool, - /// Allows changing the window icon. - #[serde(default)] - pub set_icon: bool, - /// Allows setting the skip_taskbar flag of the window. - #[serde(default)] - pub set_skip_taskbar: bool, - /// Allows start dragging on the window. - #[serde(default)] - pub start_dragging: bool, - /// Allows opening the system dialog to print the window content. - #[serde(default)] - pub print: bool, -} - -impl Allowlist for WindowAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - all: false, - create: true, - center: true, - request_user_attention: true, - set_resizable: true, - set_title: true, - maximize: true, - unmaximize: true, - minimize: true, - unminimize: true, - show: true, - hide: true, - close: true, - set_decorations: true, - set_always_on_top: true, - set_size: true, - set_min_size: true, - set_max_size: true, - set_position: true, - set_fullscreen: true, - set_focus: true, - set_icon: true, - set_skip_taskbar: true, - start_dragging: true, - print: true, - }; - let mut features = allowlist.to_features(); - features.push("window-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["window-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, create, "window-create"); - check_feature!(self, features, center, "window-center"); - check_feature!( - self, - features, - request_user_attention, - "window-request-user-attention" - ); - check_feature!(self, features, set_resizable, "window-set-resizable"); - check_feature!(self, features, set_title, "window-set-title"); - check_feature!(self, features, maximize, "window-maximize"); - check_feature!(self, features, unmaximize, "window-unmaximize"); - check_feature!(self, features, minimize, "window-minimize"); - check_feature!(self, features, unminimize, "window-unminimize"); - check_feature!(self, features, show, "window-show"); - check_feature!(self, features, hide, "window-hide"); - check_feature!(self, features, close, "window-close"); - check_feature!(self, features, set_decorations, "window-set-decorations"); - check_feature!( - self, - features, - set_always_on_top, - "window-set-always-on-top" - ); - check_feature!(self, features, set_size, "window-set-size"); - check_feature!(self, features, set_min_size, "window-set-min-size"); - check_feature!(self, features, set_max_size, "window-set-max-size"); - check_feature!(self, features, set_position, "window-set-position"); - check_feature!(self, features, set_fullscreen, "window-set-fullscreen"); - check_feature!(self, features, set_focus, "window-set-focus"); - check_feature!(self, features, set_icon, "window-set-icon"); - check_feature!(self, features, set_skip_taskbar, "window-set-skip-taskbar"); - check_feature!(self, features, start_dragging, "window-start-dragging"); - check_feature!(self, features, print, "window-print"); - features +impl CspDirectiveSources { + /// Whether the given source is configured on this directive or not. + pub fn contains(&self, source: &str) -> bool { + match self { + Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")), + Self::List(l) => l.contains(&source.into()), } } -} - -/// A command allowed to be executed by the webview API. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -pub struct ShellAllowedCommand { - /// The name for this allowed shell command configuration. - /// - /// This name will be used inside of the webview API to call this command along with - /// any specified arguments. - pub name: String, - - /// The command name. - /// It 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`, `$CWD`. - #[serde(rename = "cmd")] - pub command: PathBuf, - - /// The allowed arguments for the command execution. - #[serde(default)] - pub args: ShellAllowedArgs, - - /// If this command is a sidecar command. - #[serde(default)] - pub sidecar: bool, -} - -/// A set of command arguments allowed to be executed by the webview API. -/// -/// A value of `true` will allow any arguments to be passed to the command. `false` will disable all -/// arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to -/// be passed to the attached command configuration. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(untagged, deny_unknown_fields)] -#[non_exhaustive] -pub enum ShellAllowedArgs { - /// Use a simple boolean to allow all or disable all arguments to this command configuration. - Flag(bool), - - /// A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration. - List(Vec), -} -impl Default for ShellAllowedArgs { - fn default() -> Self { - Self::Flag(false) + /// Appends the given source to this directive. + pub fn push>(&mut self, source: S) { + match self { + Self::Inline(s) => { + s.push(' '); + s.push_str(source.as_ref()); + } + Self::List(l) => { + l.push(source.as_ref().to_string()); + } + } } -} - -/// A command argument allowed to be executed by the webview API. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(untagged, deny_unknown_fields)] -#[non_exhaustive] -pub enum ShellAllowedArg { - /// A non-configurable argument that is passed to the command in the order it was specified. - Fixed(String), - - /// A variable that is set while calling the command from the webview API. - /// - Var { - /// [regex] validator to require passed values to conform to an expected input. - /// - /// This will require the argument value passed to this variable to match the `validator` regex - /// before it will be executed. - /// - /// [regex]: https://docs.rs/regex/latest/regex/#syntax - #[serde(default)] - validator: String, - }, -} - -/// Shell scope definition. -/// It is a list of command names and associated CLI arguments that restrict the API access from the webview. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -pub struct ShellAllowlistScope(pub Vec); - -/// Defines the `shell > open` api scope. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(untagged, deny_unknown_fields)] -#[non_exhaustive] -pub enum ShellAllowlistOpen { - /// If the shell open API should be enabled. - /// - /// If enabled, the default validation regex (`^https?://`) is used. - Flag(bool), - /// Enable the shell open API, with a custom regex that the opened path must match against. - /// - /// If using a custom regex to support a non-http(s) schema, care should be used to prevent values - /// that allow flag-like strings to pass validation. e.g. `--enable-debugging`, `-i`, `/R`. - Validate(String), -} - -impl Default for ShellAllowlistOpen { - fn default() -> Self { - Self::Flag(false) + /// Extends this CSP directive source list with the given array of sources. + pub fn extend(&mut self, sources: Vec) { + for s in sources { + self.push(s); + } } } -/// Allowlist for the shell APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +/// A Content-Security-Policy definition. +/// See . +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct ShellAllowlistConfig { - /// Access scope for the binary execution APIs. - /// Sidecars are automatically enabled. - #[serde(default)] - pub scope: ShellAllowlistScope, - /// Use this flag to enable all shell API features. - #[serde(default)] - pub all: bool, - /// Enable binary execution. - #[serde(default)] - pub execute: bool, - /// Enable sidecar execution, allowing the JavaScript layer to spawn a sidecar command, - /// an executable that is shipped with the application. - /// For more information see . - #[serde(default)] - pub sidecar: bool, - /// Open URL with the user's default application. - #[serde(default)] - pub open: ShellAllowlistOpen, -} - -impl Allowlist for ShellAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - scope: Default::default(), - all: false, - execute: true, - sidecar: true, - open: ShellAllowlistOpen::Flag(true), - }; - let mut features = allowlist.to_features(); - features.push("shell-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["shell-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, execute, "shell-execute"); - check_feature!(self, features, sidecar, "shell-sidecar"); - - if !matches!(self.open, ShellAllowlistOpen::Flag(false)) { - features.push("shell-open") +#[serde(rename_all = "camelCase", untagged)] +pub enum Csp { + /// The entire CSP policy in a single text string. + Policy(String), + /// An object mapping a directive with its sources values as a list of strings. + DirectiveMap(HashMap), +} + +impl From> for Csp { + fn from(map: HashMap) -> Self { + Self::DirectiveMap(map) + } +} + +impl From for HashMap { + fn from(csp: Csp) -> Self { + match csp { + Csp::Policy(policy) => { + let mut map = HashMap::new(); + for directive in policy.split(';') { + let mut tokens = directive.trim().split(' '); + if let Some(directive) = tokens.next() { + let sources = tokens.map(|s| s.to_string()).collect::>(); + map.insert(directive.to_string(), CspDirectiveSources::List(sources)); + } + } + map } - - features + Csp::DirectiveMap(m) => m, } } } -/// Allowlist for the dialog APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct DialogAllowlistConfig { - /// Use this flag to enable all dialog API features. - #[serde(default)] - pub all: bool, - /// Allows the API to open a dialog window to pick files. - #[serde(default)] - pub open: bool, - /// Allows the API to open a dialog window to pick where to save files. - #[serde(default)] - pub save: bool, - /// Allows the API to show a message dialog window. - #[serde(default)] - pub message: bool, - /// Allows the API to show a dialog window with Yes/No buttons. - #[serde(default)] - pub ask: bool, - /// Allows the API to show a dialog window with Ok/Cancel buttons. - #[serde(default)] - pub confirm: bool, -} - -impl Allowlist for DialogAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - all: false, - open: true, - save: true, - message: true, - ask: true, - confirm: true, - }; - let mut features = allowlist.to_features(); - features.push("dialog-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["dialog-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, open, "dialog-open"); - check_feature!(self, features, save, "dialog-save"); - check_feature!(self, features, message, "dialog-message"); - check_feature!(self, features, ask, "dialog-ask"); - check_feature!(self, features, confirm, "dialog-confirm"); - features +impl Display for Csp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Policy(s) => write!(f, "{s}"), + Self::DirectiveMap(m) => { + let len = m.len(); + let mut i = 0; + for (directive, sources) in m { + let sources: Vec = sources.clone().into(); + write!(f, "{} {}", directive, sources.join(" "))?; + i += 1; + if i != len { + write!(f, "; ")?; + } + } + Ok(()) + } } } } -/// HTTP API scope definition. -/// It is a list of URLs that can be accessed by the webview when using the HTTP APIs. -/// The URL path is matched against the request URL using a glob pattern. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -pub struct HttpAllowlistScope(pub Vec); - -/// Allowlist for the HTTP APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +/// The possible values for the `dangerous_disable_asset_csp_modification` config option. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[serde(untagged)] #[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct HttpAllowlistConfig { - /// The access scope for the HTTP APIs. - #[serde(default)] - pub scope: HttpAllowlistScope, - /// Use this flag to enable all HTTP API features. - #[serde(default)] - pub all: bool, - /// Allows making HTTP requests. - #[serde(default)] - pub request: bool, +pub enum DisabledCspModificationKind { + /// If `true`, disables all CSP modification. + /// `false` is the default value and it configures Tauri to control the CSP. + Flag(bool), + /// Disables the given list of CSP directives modifications. + List(Vec), } -impl Allowlist for HttpAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - scope: Default::default(), - all: false, - request: true, - }; - let mut features = allowlist.to_features(); - features.push("http-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["http-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, request, "http-request"); - features +impl DisabledCspModificationKind { + /// Determines whether the given CSP directive can be modified or not. + pub fn can_modify(&self, directive: &str) -> bool { + match self { + Self::Flag(f) => !f, + Self::List(l) => !l.contains(&directive.into()), } } } -/// Allowlist for the notification APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct NotificationAllowlistConfig { - /// Use this flag to enable all notification API features. - #[serde(default)] - pub all: bool, -} - -impl Allowlist for NotificationAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { all: false }; - let mut features = allowlist.to_features(); - features.push("notification-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["notification-all"] - } else { - vec![] - } +impl Default for DisabledCspModificationKind { + fn default() -> Self { + Self::Flag(false) } } -/// Allowlist for the global shortcut APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +/// External command access definition. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct GlobalShortcutAllowlistConfig { - /// Use this flag to enable all global shortcut API features. +pub struct RemoteDomainAccessScope { + /// The URL scheme to allow. By default, all schemas are allowed. + pub scheme: Option, + /// The domain to allow. + pub domain: String, + /// The list of window labels this scope applies to. + pub windows: Vec, + /// The list of plugins that are allowed in this scope. #[serde(default)] - pub all: bool, -} - -impl Allowlist for GlobalShortcutAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { all: false }; - let mut features = allowlist.to_features(); - features.push("global-shortcut-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["global-shortcut-all"] - } else { - vec![] - } - } + pub plugins: Vec, } -/// Allowlist for the OS APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +/// Protocol scope definition. +/// It is a list of glob patterns that restrict the API access from the webview. +/// +/// Each 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`. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[serde(untagged)] #[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct OsAllowlistConfig { - /// Use this flag to enable all OS API features. - #[serde(default)] - pub all: bool, +pub enum FsScope { + /// A list of paths that are allowed by this scope. + AllowedPaths(Vec), + /// A complete scope configuration. + #[serde(rename_all = "camelCase")] + Scope { + /// A list of paths that are allowed by this scope. + #[serde(default)] + allow: Vec, + /// A list of paths that are not allowed by this scope. + /// This gets precedence over the [`Self::Scope::allow`] list. + #[serde(default)] + deny: Vec, + /// Whether or not paths that contain components that start with a `.` + /// will require that `.` appears literally in the pattern; `*`, `?`, `**`, + /// or `[...]` will not match. This is useful because such files are + /// conventionally considered hidden on Unix systems and it might be + /// desirable to skip them when listing files. + /// + /// Defaults to `false` on Unix systems and `true` on Windows + // dotfiles are not supposed to be exposed by default on unix + #[serde(alias = "require-literal-leading-dot")] + require_literal_leading_dot: Option, + }, } -impl Allowlist for OsAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { all: false }; - let mut features = allowlist.to_features(); - features.push("os-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["os-all"] - } else { - vec![] - } +impl Default for FsScope { + fn default() -> Self { + Self::AllowedPaths(Vec::new()) } } -/// Allowlist for the path APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct PathAllowlistConfig { - /// Use this flag to enable all path API features. - #[serde(default)] - pub all: bool, -} - -impl Allowlist for PathAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { all: false }; - let mut features = allowlist.to_features(); - features.push("path-all"); - features +impl FsScope { + /// The list of allowed paths. + pub fn allowed_paths(&self) -> &Vec { + match self { + Self::AllowedPaths(p) => p, + Self::Scope { allow, .. } => allow, + } } - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["path-all"] - } else { - vec![] + /// The list of forbidden paths. + pub fn forbidden_paths(&self) -> Option<&Vec> { + match self { + Self::AllowedPaths(_) => None, + Self::Scope { deny, .. } => Some(deny), } } } -/// Allowlist for the custom protocols. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +/// Config for the asset custom protocol. +/// +/// See more: https://tauri.app/v1/api/config#assetprotocolconfig +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct ProtocolAllowlistConfig { +pub struct AssetProtocolConfig { /// The access scope for the asset protocol. #[serde(default)] - pub asset_scope: FsAllowlistScope, - /// Use this flag to enable all custom protocols. - #[serde(default)] - pub all: bool, + pub scope: FsScope, /// Enables the asset protocol. #[serde(default)] - pub asset: bool, + pub enable: bool, } -impl Allowlist for ProtocolAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - asset_scope: Default::default(), - all: false, - asset: true, - }; - let mut features = allowlist.to_features(); - features.push("protocol-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["protocol-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, asset, "protocol-asset"); - features - } - } -} - -/// Allowlist for the process APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +/// Security configuration. +/// +/// See more: https://tauri.app/v1/api/config#securityconfig +#[skip_serializing_none] +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct ProcessAllowlistConfig { - /// Use this flag to enable all process APIs. - #[serde(default)] - pub all: bool, - /// Enables the relaunch API. - #[serde(default)] - pub relaunch: bool, - /// Dangerous option that allows macOS to relaunch even if the binary contains a symlink. +pub struct SecurityConfig { + /// The Content Security Policy that will be injected on all HTML files on the built application. + /// If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev. /// - /// This is due to macOS having less symlink protection. Highly recommended to not set this flag - /// unless you have a very specific reason too, and understand the implications of it. - #[serde(default)] - pub relaunch_dangerous_allow_symlink_macos: bool, - /// Enables the exit API. - #[serde(default)] - pub exit: bool, -} - -impl Allowlist for ProcessAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - all: false, - relaunch: true, - relaunch_dangerous_allow_symlink_macos: false, - exit: true, - }; - let mut features = allowlist.to_features(); - features.push("process-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["process-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, relaunch, "process-relaunch"); - check_feature!( - self, - features, - relaunch_dangerous_allow_symlink_macos, - "process-relaunch-dangerous-allow-symlink-macos" - ); - check_feature!(self, features, exit, "process-exit"); - features - } - } -} - -/// Allowlist for the clipboard APIs. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct ClipboardAllowlistConfig { - /// Use this flag to enable all clipboard APIs. - #[serde(default)] - pub all: bool, - /// Enables the clipboard's `writeText` API. - #[serde(default)] - pub write_text: bool, - /// Enables the clipboard's `readText` API. - #[serde(default)] - pub read_text: bool, -} - -impl Allowlist for ClipboardAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - all: false, - write_text: true, - read_text: true, - }; - let mut features = allowlist.to_features(); - features.push("clipboard-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["clipboard-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, write_text, "clipboard-write-text"); - check_feature!(self, features, read_text, "clipboard-read-text"); - features - } - } -} - -/// Allowlist configuration. -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct AllowlistConfig { - /// Use this flag to enable all API features. - #[serde(default)] - pub all: bool, - /// File system API allowlist. - #[serde(default)] - pub fs: FsAllowlistConfig, - /// Window API allowlist. - #[serde(default)] - pub window: WindowAllowlistConfig, - /// Shell API allowlist. - #[serde(default)] - pub shell: ShellAllowlistConfig, - /// Dialog API allowlist. - #[serde(default)] - pub dialog: DialogAllowlistConfig, - /// HTTP API allowlist. - #[serde(default)] - pub http: HttpAllowlistConfig, - /// Notification API allowlist. - #[serde(default)] - pub notification: NotificationAllowlistConfig, - /// Global shortcut API allowlist. - #[serde(default)] - pub global_shortcut: GlobalShortcutAllowlistConfig, - /// OS allowlist. - #[serde(default)] - pub os: OsAllowlistConfig, - /// Path API allowlist. - #[serde(default)] - pub path: PathAllowlistConfig, - /// Custom protocol allowlist. - #[serde(default)] - pub protocol: ProtocolAllowlistConfig, - /// Process API allowlist. - #[serde(default)] - pub process: ProcessAllowlistConfig, - /// Clipboard APIs allowlist. - #[serde(default)] - pub clipboard: ClipboardAllowlistConfig, -} - -impl Allowlist for AllowlistConfig { - fn all_features() -> Vec<&'static str> { - let mut features = vec!["api-all"]; - features.extend(FsAllowlistConfig::all_features()); - features.extend(WindowAllowlistConfig::all_features()); - features.extend(ShellAllowlistConfig::all_features()); - features.extend(DialogAllowlistConfig::all_features()); - features.extend(HttpAllowlistConfig::all_features()); - features.extend(NotificationAllowlistConfig::all_features()); - features.extend(GlobalShortcutAllowlistConfig::all_features()); - features.extend(OsAllowlistConfig::all_features()); - features.extend(PathAllowlistConfig::all_features()); - features.extend(ProtocolAllowlistConfig::all_features()); - features.extend(ProcessAllowlistConfig::all_features()); - features.extend(ClipboardAllowlistConfig::all_features()); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["api-all"] - } else { - let mut features = Vec::new(); - features.extend(self.fs.to_features()); - features.extend(self.window.to_features()); - features.extend(self.shell.to_features()); - features.extend(self.dialog.to_features()); - features.extend(self.http.to_features()); - features.extend(self.notification.to_features()); - features.extend(self.global_shortcut.to_features()); - features.extend(self.os.to_features()); - features.extend(self.path.to_features()); - features.extend(self.protocol.to_features()); - features.extend(self.process.to_features()); - features.extend(self.clipboard.to_features()); - features - } - } -} - -fn default_window_config() -> Vec { - vec![Default::default()] + /// This is a really important part of the configuration since it helps you ensure your WebView is secured. + /// See . + pub csp: Option, + /// The Content Security Policy that will be injected on all HTML files on development. + /// + /// This is a really important part of the configuration since it helps you ensure your WebView is secured. + /// See . + #[serde(alias = "dev-csp")] + pub dev_csp: Option, + /// Freeze the `Object.prototype` when using the custom protocol. + #[serde(default, alias = "freeze-prototype")] + pub freeze_prototype: bool, + /// Disables the Tauri-injected CSP sources. + /// + /// At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy + /// to only allow loading of your own scripts and styles by injecting nonce and hash sources. + /// This stricts your CSP, which may introduce issues when using along with other flexing sources. + /// + /// This configuration option allows both a boolean and a list of strings as value. + /// A boolean instructs Tauri to disable the injection for all CSP injections, + /// and a list of strings indicates the CSP directives that Tauri cannot inject. + /// + /// **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP. + /// Your application might be vulnerable to XSS attacks without this Tauri protection. + #[serde(default, alias = "dangerous-disable-asset-csp-modification")] + pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind, + /// Allow external domains to send command to Tauri. + /// + /// By default, external domains do not have access to `window.__TAURI__`, which means they cannot + /// communicate with the commands defined in Rust. This prevents attacks where an externally + /// loaded malicious or compromised sites could start executing commands on the user's device. + /// + /// This configuration allows a set of external domains to have access to the Tauri commands. + /// When you configure a domain to be allowed to access the IPC, all subpaths are allowed. Subdomains are not allowed. + /// + /// **WARNING:** Only use this option if you either have internal checks against malicious + /// external sites or you can trust the allowed external sites. You application might be + /// vulnerable to dangerous Tauri command related attacks otherwise. + #[serde(default, alias = "dangerous-remote-domain-ipc-access")] + pub dangerous_remote_domain_ipc_access: Vec, + /// Custom protocol config. + #[serde(default, alias = "asset-protocol")] + pub asset_protocol: AssetProtocolConfig, } /// The application pattern. #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase", tag = "use", content = "options")] #[cfg_attr(feature = "schema", derive(JsonSchema))] pub enum PatternKind { /// Brownfield pattern. Brownfield, /// Isolation pattern. Recommended for security purposes. - #[cfg(feature = "isolation")] Isolation { /// The dir containing the index.html file that contains the secure isolation application. dir: PathBuf, @@ -1481,8 +1319,10 @@ impl Default for PatternKind { } /// The Tauri configuration object. +/// +/// See more: https://tauri.app/v1/api/config#tauriconfig #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct TauriConfig { @@ -1490,203 +1330,207 @@ pub struct TauriConfig { #[serde(default)] pub pattern: PatternKind, /// The windows configuration. - #[serde(default = "default_window_config")] + #[serde(default)] pub windows: Vec, - /// The CLI configuration. - pub cli: Option, /// The bundler configuration. #[serde(default)] pub bundle: BundleConfig, - /// The allowlist configuration. - #[serde(default)] - pub allowlist: AllowlistConfig, /// Security configuration. #[serde(default)] pub security: SecurityConfig, - /// The updater configuration. - #[serde(default)] - pub updater: UpdaterConfig, /// Configuration for app system tray. + #[serde(alias = "system-tray")] pub system_tray: Option, /// MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`. - #[serde(rename = "macOSPrivateApi", default)] + #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)] pub macos_private_api: bool, } -impl Default for TauriConfig { - fn default() -> Self { - Self { - pattern: Default::default(), - windows: default_window_config(), - cli: None, - bundle: BundleConfig::default(), - allowlist: AllowlistConfig::default(), - security: SecurityConfig::default(), - updater: UpdaterConfig::default(), - system_tray: None, - macos_private_api: false, - } - } -} - impl TauriConfig { /// Returns all Cargo features. - #[allow(dead_code)] pub fn all_features() -> Vec<&'static str> { - let mut features = AllowlistConfig::all_features(); - features.extend(vec![ - "cli", - "updater", + vec![ "system-tray", "macos-private-api", "isolation", - ]); - features + "protocol-asset", + ] } /// Returns the enabled Cargo features. - #[allow(dead_code)] pub fn features(&self) -> Vec<&str> { - let mut features = self.allowlist.to_features(); - if self.cli.is_some() { - features.push("cli"); - } - if self.updater.active { - features.push("updater"); - } + let mut features = Vec::new(); if self.system_tray.is_some() { features.push("system-tray"); } if self.macos_private_api { features.push("macos-private-api"); } - #[cfg(feature = "isolation")] if let PatternKind::Isolation { .. } = self.pattern { features.push("isolation"); } + if self.security.asset_protocol.enable { + features.push("protocol-asset"); + } features.sort_unstable(); features - } -} - -/// A URL to an updater server. -/// -/// The URL must use the `https` scheme on production. -#[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -pub struct UpdaterEndpoint(pub Url); - -impl std::fmt::Display for UpdaterEndpoint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl<'de> Deserialize<'de> for UpdaterEndpoint { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let url = Url::deserialize(deserializer)?; - #[cfg(all(not(debug_assertions), not(feature = "schema")))] - { - if url.scheme() != "https" { - return Err(serde::de::Error::custom( - "The configured updater endpoint must use the `https` protocol.", - )); - } - } - Ok(Self(url)) - } -} - -/// The Updater configuration object. -#[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct UpdaterConfig { - /// Whether the updater is active or not. - #[serde(default)] - pub active: bool, - /// Display built-in dialog or use event system if disabled. - #[serde(default = "default_dialog")] - pub dialog: bool, - /// The updater endpoints. TLS is enforced on production. - pub endpoints: Option>, - /// Signature public key. - #[serde(default)] // use default just so the schema doesn't flag it as required - pub pubkey: String, -} - -impl<'de> Deserialize<'de> for UpdaterConfig { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct InnerUpdaterConfig { - #[serde(default)] - active: bool, - #[serde(default = "default_dialog")] - dialog: bool, - endpoints: Option>, - pubkey: Option, - } + } +} - let config = InnerUpdaterConfig::deserialize(deserializer)?; +/// Install modes for the Windows update. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[cfg_attr(feature = "schema", schemars(rename_all = "camelCase"))] +pub enum WindowsUpdateInstallMode { + /// Specifies there's a basic UI during the installation process, including a final dialog box at the end. + BasicUi, + /// The quiet mode means there's no user interaction required. + /// Requires admin privileges if the installer does. + Quiet, + /// Specifies unattended mode, which means the installation only shows a progress bar. + Passive, + // to add more modes, we need to check if the updater relaunch makes sense + // i.e. for a full UI mode, the user can also mark the installer to start the app +} + +impl WindowsUpdateInstallMode { + /// Returns the associated `msiexec.exe` arguments. + pub fn msiexec_args(&self) -> &'static [&'static str] { + match self { + Self::BasicUi => &["/qb+"], + Self::Quiet => &["/quiet"], + Self::Passive => &["/passive"], + } + } - if config.active && config.pubkey.is_none() { - return Err(DeError::custom( - "The updater `pubkey` configuration is required.", - )); + /// Returns the associated nsis arguments. + pub fn nsis_args(&self) -> &'static [&'static str] { + match self { + Self::Passive => &["/P", "/R"], + Self::Quiet => &["/S", "/R"], + _ => &[], } + } +} - Ok(UpdaterConfig { - active: config.active, - dialog: config.dialog, - endpoints: config.endpoints, - pubkey: config.pubkey.unwrap_or_default(), - }) +impl Display for WindowsUpdateInstallMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::BasicUi => "basicUI", + Self::Quiet => "quiet", + Self::Passive => "passive", + } + ) } } -impl Default for UpdaterConfig { +impl Default for WindowsUpdateInstallMode { fn default() -> Self { - Self { - active: false, - dialog: default_dialog(), - endpoints: None, - pubkey: "".into(), + Self::Passive + } +} + +impl Serialize for WindowsUpdateInstallMode { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl<'de> Deserialize<'de> for WindowsUpdateInstallMode { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.to_lowercase().as_str() { + "basicui" => Ok(Self::BasicUi), + "quiet" => Ok(Self::Quiet), + "passive" => Ok(Self::Passive), + _ => Err(DeError::custom(format!( + "unknown update install mode '{s}'" + ))), } } } +/// The updater configuration for Windows. +/// +/// See more: https://tauri.app/v1/api/config#updaterwindowsconfig +#[skip_serializing_none] +#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct UpdaterWindowsConfig { + /// The installation mode for the update on Windows. Defaults to `passive`. + #[serde(default, alias = "install-mode")] + pub install_mode: WindowsUpdateInstallMode, +} + /// Configuration for application system tray icon. +/// +/// See more: https://tauri.app/v1/api/config#systemtrayconfig #[skip_serializing_none] -#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SystemTrayConfig { - /// Path to the icon to use on the system tray. - /// - /// It is forced to be a `.png` file on Linux and macOS, and a `.ico` file on Windows. + /// Path to the default icon to use on the system tray. + #[serde(alias = "icon-path")] pub icon_path: PathBuf, /// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS. - #[serde(default)] + #[serde(default, alias = "icon-as-template")] pub icon_as_template: bool, + /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click on macOS. + #[serde(default = "default_true", alias = "menu-on-left-click")] + pub menu_on_left_click: bool, + /// Title for MacOS tray + pub title: Option, } -// We enable the unnecessary_wraps because we need -// to use an Option for dialog otherwise the CLI schema will mark -// the dialog as a required field which is not as we default it to true. -fn default_dialog() -> bool { - true +/// General configuration for the iOS target. +#[skip_serializing_none] +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct IosConfig { + /// The development team. This value is required for iOS development because code signing is enforced. + /// The `TAURI_APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it. + #[serde(alias = "development-team")] + pub development_team: Option, } -/// The `dev_path` and `dist_dir` options. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +/// General configuration for the iOS target. +#[skip_serializing_none] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct AndroidConfig { + /// The minimum API level required for the application to run. + /// The Android system will prevent the user from installing the application if the system's API level is lower than the value specified. + #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")] + pub min_sdk_version: u32, +} + +impl Default for AndroidConfig { + fn default() -> Self { + Self { + min_sdk_version: default_min_sdk_version(), + } + } +} + +fn default_min_sdk_version() -> u32 { + 24 +} + +/// Defines the URL or assets to embed in the application. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(untagged, deny_unknown_fields)] #[non_exhaustive] @@ -1700,38 +1544,98 @@ pub enum AppUrl { impl std::fmt::Display for AppUrl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Url(url) => write!(f, "{}", url), + Self::Url(url) => write!(f, "{url}"), Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()), } } } +/// Describes the shell command to run before `tauri dev`. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", untagged)] +pub enum BeforeDevCommand { + /// Run the given script with the default options. + Script(String), + /// Run the given script with custom options. + ScriptWithOptions { + /// The script to execute. + script: String, + /// The current working directory. + cwd: Option, + /// Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`. + #[serde(default)] + wait: bool, + }, +} + +/// Describes a shell command to be executed when a CLI hook is triggered. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", untagged)] +pub enum HookCommand { + /// Run the given script with the default options. + Script(String), + /// Run the given script with custom options. + ScriptWithOptions { + /// The script to execute. + script: String, + /// The current working directory. + cwd: Option, + }, +} + /// The Build configuration object. +/// +/// See more: https://tauri.app/v1/api/config#buildconfig #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct BuildConfig { /// The binary used to build and run the application. pub runner: Option, - /// The path or URL to use on development. - #[serde(default = "default_dev_path")] + /// The path to the application assets or URL to load in development. + /// + /// This is usually an URL to a dev server, which serves your application assets + /// with live reloading. Most modern JavaScript bundlers provides a way to start a dev server by default. + /// + /// See [vite](https://vitejs.dev/guide/), [Webpack DevServer](https://webpack.js.org/configuration/dev-server/) and [sirv](https://github.com/lukeed/sirv) + /// for examples on how to set up a dev server. + #[serde(default = "default_dev_path", alias = "dev-path")] pub dev_path: AppUrl, - /// The path to the app's dist dir. This path must contain your index.html file. - #[serde(default = "default_dist_dir")] + /// The path to the application assets or URL to load in production. + /// + /// When a path relative to the configuration file is provided, + /// it is read recursively and all files are embedded in the application binary. + /// Tauri then looks for an `index.html` file unless you provide a custom window URL. + /// + /// You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary. + /// In this case, all files are added to the root and you must reference it that way in your HTML files. + /// + /// When an URL is provided, the application won't have bundled assets + /// and the application will load that URL by default. + #[serde(default = "default_dist_dir", alias = "dist-dir")] pub dist_dir: AppUrl, /// A shell command to run before `tauri dev` kicks in. /// /// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation. - pub before_dev_command: Option, + #[serde(alias = "before-dev-command")] + pub before_dev_command: Option, /// A shell command to run before `tauri build` kicks in. /// /// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation. - pub before_build_command: Option, + #[serde(alias = "before-build-command")] + pub before_build_command: Option, + /// A shell command to run before the bundling phase in `tauri build` kicks in. + /// + /// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation. + #[serde(alias = "before-bundle-command")] + pub before_bundle_command: Option, /// Features passed to `cargo` commands. pub features: Option>, /// Whether we should inject the Tauri API on `window.__TAURI__` or not. - #[serde(default)] + #[serde(default, alias = "with-global-tauri")] pub with_global_tauri: bool, } @@ -1743,6 +1647,7 @@ impl Default for BuildConfig { dist_dir: default_dist_dir(), before_dev_command: None, before_build_command: None, + before_bundle_command: None, features: None, with_global_tauri: false, } @@ -1759,7 +1664,7 @@ fn default_dist_dir() -> AppUrl { AppUrl::Url(WindowUrl::App("../dist".into())) } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] struct PackageVersion(String); impl<'d> serde::Deserialize<'d> for PackageVersion { @@ -1780,21 +1685,33 @@ impl<'d> serde::Deserialize<'d> for PackageVersion { let path = PathBuf::from(value); if path.exists() { let json_str = read_to_string(&path) - .map_err(|e| DeError::custom(format!("failed to read version JSON file: {}", e)))?; + .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?; let package_json: serde_json::Value = serde_json::from_str(&json_str) - .map_err(|e| DeError::custom(format!("failed to read version JSON file: {}", e)))?; + .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?; if let Some(obj) = package_json.as_object() { let version = obj .get("version") .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))? .as_str() - .ok_or_else(|| DeError::custom("`version` must be a string"))?; - Ok(PackageVersion(version.into())) + .ok_or_else(|| { + DeError::custom(format!("`{} > version` must be a string", path.display())) + })?; + Ok(PackageVersion( + Version::from_str(version) + .map_err(|_| DeError::custom("`package > version` must be a semver string"))? + .to_string(), + )) } else { - Err(DeError::custom("value is not a path to a JSON object")) + Err(DeError::custom( + "`package > version` value is not a path to a JSON object", + )) } } else { - Ok(PackageVersion(value.into())) + Ok(PackageVersion( + Version::from_str(value) + .map_err(|_| DeError::custom("`package > version` must be a semver string"))? + .to_string(), + )) } } } @@ -1804,13 +1721,17 @@ impl<'d> serde::Deserialize<'d> for PackageVersion { } /// The package configuration. -#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] +/// +/// See more: https://tauri.app/v1/api/config#packageconfig +#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct PackageConfig { /// App name. + #[serde(alias = "product-name")] + #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))] pub product_name: Option, - /// App version. It is a semver version number or a path to a `package.json` file contaning the `version` field. + /// App version. It is a semver version number or a path to a `package.json` file containing the `version` field. If removed the version number from `Cargo.toml` is used. #[serde(deserialize_with = "version_deserializer", default)] pub version: Option, } @@ -1837,12 +1758,78 @@ impl PackageConfig { } } -/// The config type mapped to `tauri.conf.json`. +/// The Tauri configuration object. +/// It is read from a file where you can define your frontend assets, +/// configure the bundler and define a system tray. +/// +/// The configuration file is generated by the +/// [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in +/// your Tauri application source directory (src-tauri). +/// +/// Once generated, you may modify it at will to customize your Tauri application. +/// +/// ## File Formats +/// +/// By default, the configuration is defined as a JSON file named `tauri.conf.json`. +/// +/// Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. +/// The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. +/// The TOML file name is `Tauri.toml`. +/// +/// ## Platform-Specific Configuration +/// +/// In addition to the default configuration file, Tauri can +/// read a platform-specific configuration from `tauri.linux.conf.json`, +/// `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` +/// (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), +/// which gets merged with the main configuration object. +/// +/// ## Configuration Structure +/// +/// The configuration is composed of the following objects: +/// +/// - [`package`](#packageconfig): Package settings +/// - [`tauri`](#tauriconfig): The Tauri config +/// - [`build`](#buildconfig): The build configuration +/// - [`plugins`](#pluginconfig): The plugins config +/// +/// ```json title="Example tauri.config.json file" +/// { +/// "build": { +/// "beforeBuildCommand": "", +/// "beforeDevCommand": "", +/// "devPath": "../dist", +/// "distDir": "../dist" +/// }, +/// "package": { +/// "productName": "tauri-app", +/// "version": "0.1.0" +/// }, +/// "tauri": { +/// "bundle": {}, +/// "security": { +/// "csp": null +/// }, +/// "windows": [ +/// { +/// "fullscreen": false, +/// "height": 600, +/// "resizable": true, +/// "title": "Tauri App", +/// "width": 800 +/// } +/// ] +/// } +/// } +/// ``` #[skip_serializing_none] #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Config { + /// The JSON schema for the Tauri config. + #[serde(rename = "$schema")] + pub schema: Option, /// Package settings. #[serde(default)] pub package: PackageConfig, @@ -1858,7 +1845,9 @@ pub struct Config { } /// The plugin configs holds a HashMap mapping a plugin name to its configuration object. -#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] +/// +/// See more: https://tauri.app/v1/api/config#pluginconfig +#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))] pub struct PluginConfig(pub HashMap); @@ -1869,6 +1858,7 @@ fn default_build() -> BuildConfig { dist_dir: default_dist_dir(), before_dev_command: None, before_build_command: None, + before_bundle_command: None, features: None, with_global_tauri: false, } @@ -2034,7 +2024,7 @@ mod build { ::tauri::utils::config::$struct { $($field: #$field),+ } - }); + }) }; } @@ -2044,7 +2034,7 @@ mod build { tokens.append_all(match self { Self::App(path) => { - let path = path_buf_lit(&path); + let path = path_buf_lit(path); quote! { #prefix::App(#path) } } Self::External(url) => { @@ -2055,10 +2045,96 @@ mod build { } } + impl ToTokens for crate::Theme { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = quote! { ::tauri::utils::Theme }; + + tokens.append_all(match self { + Self::Light => quote! { #prefix::Light }, + Self::Dark => quote! { #prefix::Dark }, + }) + } + } + + impl ToTokens for Color { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Color(r, g, b, a) = self; + tokens.append_all(quote! {::tauri::utils::Color(#r,#g,#b,#a)}); + } + } + impl ToTokens for WindowEffectsConfig { + fn to_tokens(&self, tokens: &mut TokenStream) { + let effects = vec_lit(self.effects.clone(), |d| d); + let state = opt_lit(self.state.as_ref()); + let radius = opt_lit(self.radius.as_ref()); + let color = opt_lit(self.color.as_ref()); + + literal_struct!(tokens, WindowEffectsConfig, effects, state, radius, color) + } + } + + impl ToTokens for crate::TitleBarStyle { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = quote! { ::tauri::utils::TitleBarStyle }; + + tokens.append_all(match self { + Self::Visible => quote! { #prefix::Visible }, + Self::Transparent => quote! { #prefix::Transparent }, + Self::Overlay => quote! { #prefix::Overlay }, + }) + } + } + + impl ToTokens for crate::WindowEffect { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = quote! { ::tauri::utils::WindowEffect }; + + #[allow(deprecated)] + tokens.append_all(match self { + WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased}, + WindowEffect::Light => quote! { #prefix::Light}, + WindowEffect::Dark => quote! { #prefix::Dark}, + WindowEffect::MediumLight => quote! { #prefix::MediumLight}, + WindowEffect::UltraDark => quote! { #prefix::UltraDark}, + WindowEffect::Titlebar => quote! { #prefix::Titlebar}, + WindowEffect::Selection => quote! { #prefix::Selection}, + WindowEffect::Menu => quote! { #prefix::Menu}, + WindowEffect::Popover => quote! { #prefix::Popover}, + WindowEffect::Sidebar => quote! { #prefix::Sidebar}, + WindowEffect::HeaderView => quote! { #prefix::HeaderView}, + WindowEffect::Sheet => quote! { #prefix::Sheet}, + WindowEffect::WindowBackground => quote! { #prefix::WindowBackground}, + WindowEffect::HudWindow => quote! { #prefix::HudWindow}, + WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI}, + WindowEffect::Tooltip => quote! { #prefix::Tooltip}, + WindowEffect::ContentBackground => quote! { #prefix::ContentBackground}, + WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground}, + WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground}, + WindowEffect::Mica => quote! { #prefix::Mica}, + WindowEffect::Blur => quote! { #prefix::Blur}, + WindowEffect::Acrylic => quote! { #prefix::Acrylic}, + }) + } + } + + impl ToTokens for crate::WindowEffectState { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = quote! { ::tauri::utils::WindowEffectState }; + + #[allow(deprecated)] + tokens.append_all(match self { + WindowEffectState::Active => quote! { #prefix::Active}, + WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState}, + WindowEffectState::Inactive => quote! { #prefix::Inactive}, + }) + } + } + impl ToTokens for WindowConfig { fn to_tokens(&self, tokens: &mut TokenStream) { let label = str_lit(&self.label); let url = &self.url; + let user_agent = opt_str_lit(self.user_agent.as_ref()); let file_drop_enabled = self.file_drop_enabled; let center = self.center; let x = opt_lit(self.x.as_ref()); @@ -2070,6 +2146,9 @@ mod build { let max_width = opt_lit(self.max_width.as_ref()); let max_height = opt_lit(self.max_height.as_ref()); let resizable = self.resizable; + let maximizable = self.maximizable; + let minimizable = self.minimizable; + let closable = self.closable; let title = str_lit(&self.title); let fullscreen = self.fullscreen; let focus = self.focus; @@ -2078,13 +2157,24 @@ mod build { let visible = self.visible; let decorations = self.decorations; let always_on_top = self.always_on_top; + let content_protected = self.content_protected; let skip_taskbar = self.skip_taskbar; + let theme = opt_lit(self.theme.as_ref()); + let title_bar_style = &self.title_bar_style; + let hidden_title = self.hidden_title; + let accept_first_mouse = self.accept_first_mouse; + let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref()); + let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref()); + let shadow = self.shadow; + let window_effects = opt_lit(self.window_effects.as_ref()); + let incognito = self.incognito; literal_struct!( tokens, WindowConfig, label, url, + user_agent, file_drop_enabled, center, x, @@ -2096,6 +2186,9 @@ mod build { max_width, max_height, resizable, + maximizable, + minimizable, + closable, title, fullscreen, focus, @@ -2104,104 +2197,17 @@ mod build { visible, decorations, always_on_top, - skip_taskbar - ); - } - } - - impl ToTokens for CliArg { - fn to_tokens(&self, tokens: &mut TokenStream) { - let short = opt_lit(self.short.as_ref()); - let name = str_lit(&self.name); - let description = opt_str_lit(self.description.as_ref()); - let long_description = opt_str_lit(self.long_description.as_ref()); - let takes_value = opt_lit(self.takes_value.as_ref()); - let multiple = opt_lit(self.multiple.as_ref()); - let multiple_occurrences = opt_lit(self.multiple_occurrences.as_ref()); - let number_of_values = opt_lit(self.number_of_values.as_ref()); - let possible_values = opt_vec_str_lit(self.possible_values.as_ref()); - let min_values = opt_lit(self.min_values.as_ref()); - let max_values = opt_lit(self.max_values.as_ref()); - let required = opt_lit(self.required.as_ref()); - let required_unless_present = opt_str_lit(self.required_unless_present.as_ref()); - let required_unless_present_all = opt_vec_str_lit(self.required_unless_present_all.as_ref()); - let required_unless_present_any = opt_vec_str_lit(self.required_unless_present_any.as_ref()); - let conflicts_with = opt_str_lit(self.conflicts_with.as_ref()); - let conflicts_with_all = opt_vec_str_lit(self.conflicts_with_all.as_ref()); - let requires = opt_str_lit(self.requires.as_ref()); - let requires_all = opt_vec_str_lit(self.requires_all.as_ref()); - let requires_if = opt_vec_str_lit(self.requires_if.as_ref()); - let required_if_eq = opt_vec_str_lit(self.required_if_eq.as_ref()); - let require_equals = opt_lit(self.require_equals.as_ref()); - let index = opt_lit(self.index.as_ref()); - - literal_struct!( - tokens, - CliArg, - short, - name, - description, - long_description, - takes_value, - multiple, - multiple_occurrences, - number_of_values, - possible_values, - min_values, - max_values, - required, - required_unless_present, - required_unless_present_all, - required_unless_present_any, - conflicts_with, - conflicts_with_all, - requires, - requires_all, - requires_if, - required_if_eq, - require_equals, - index - ); - } - } - - impl ToTokens for CliConfig { - fn to_tokens(&self, tokens: &mut TokenStream) { - let description = opt_str_lit(self.description.as_ref()); - let long_description = opt_str_lit(self.long_description.as_ref()); - let before_help = opt_str_lit(self.before_help.as_ref()); - let after_help = opt_str_lit(self.after_help.as_ref()); - let args = { - let args = self.args.as_ref().map(|args| { - let arg = args.iter().map(|a| quote! { #a }); - quote! { vec![#(#arg),*] } - }); - opt_lit(args.as_ref()) - }; - let subcommands = opt_lit( - self - .subcommands - .as_ref() - .map(|map| { - map_lit( - quote! { ::std::collections::HashMap }, - map, - str_lit, - identity, - ) - }) - .as_ref(), - ); - - literal_struct!( - tokens, - CliConfig, - description, - long_description, - before_help, - after_help, - args, - subcommands + content_protected, + skip_taskbar, + theme, + title_bar_style, + hidden_title, + accept_first_mouse, + tabbing_identifier, + additional_browser_args, + shadow, + window_effects, + incognito ); } } @@ -2212,6 +2218,8 @@ mod build { tokens.append_all(match self { Self::Brownfield => quote! { #prefix::Brownfield }, + #[cfg(not(feature = "isolation"))] + Self::Isolation { dir: _ } => quote! { #prefix::Brownfield }, #[cfg(feature = "isolation")] Self::Isolation { dir } => { let dir = path_buf_lit(dir); @@ -2221,43 +2229,83 @@ mod build { } } + impl ToTokens for WebviewInstallMode { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = quote! { ::tauri::utils::config::WebviewInstallMode }; + + tokens.append_all(match self { + Self::Skip => quote! { #prefix::Skip }, + Self::DownloadBootstrapper { silent } => { + quote! { #prefix::DownloadBootstrapper { silent: #silent } } + } + Self::EmbedBootstrapper { silent } => { + quote! { #prefix::EmbedBootstrapper { silent: #silent } } + } + Self::OfflineInstaller { silent } => { + quote! { #prefix::OfflineInstaller { silent: #silent } } + } + Self::FixedRuntime { path } => { + let path = path_buf_lit(path); + quote! { #prefix::FixedRuntime { path: #path } } + } + }) + } + } + impl ToTokens for WindowsConfig { fn to_tokens(&self, tokens: &mut TokenStream) { - let webview_fixed_runtime_path = opt_lit( - self - .webview_fixed_runtime_path - .as_ref() - .map(path_buf_lit) - .as_ref(), - ); + let webview_install_mode = if let Some(fixed_runtime_path) = &self.webview_fixed_runtime_path + { + WebviewInstallMode::FixedRuntime { + path: fixed_runtime_path.clone(), + } + } else { + self.webview_install_mode.clone() + }; tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig { - webview_fixed_runtime_path: #webview_fixed_runtime_path, + webview_install_mode: #webview_install_mode, ..Default::default() }}) } } + impl ToTokens for UpdaterConfig { + fn to_tokens(&self, tokens: &mut TokenStream) { + let active = self.active; + let pubkey = str_lit(&self.pubkey); + let windows = &self.windows; + + literal_struct!(tokens, UpdaterConfig, active, pubkey, windows); + } + } + impl ToTokens for BundleConfig { fn to_tokens(&self, tokens: &mut TokenStream) { let identifier = str_lit(&self.identifier); + let publisher = quote!(None); let icon = vec_lit(&self.icon, str_lit); let active = self.active; - let targets = quote!(None); + let targets = quote!(Default::default()); let resources = quote!(None); let copyright = quote!(None); let category = quote!(None); let short_description = quote!(None); let long_description = quote!(None); + let appimage = quote!(Default::default()); let deb = quote!(Default::default()); let macos = quote!(Default::default()); let external_bin = opt_vec_str_lit(self.external_bin.as_ref()); let windows = &self.windows; + let ios = quote!(Default::default()); + let android = quote!(Default::default()); + let updater = &self.updater; literal_struct!( tokens, BundleConfig, active, identifier, + publisher, icon, targets, resources, @@ -2265,10 +2313,14 @@ mod build { category, short_description, long_description, + appimage, deb, macos, external_bin, - windows + windows, + ios, + android, + updater ); } } @@ -2297,6 +2349,7 @@ mod build { let runner = quote!(None); let before_dev_command = quote!(None); let before_build_command = quote!(None); + let before_bundle_command = quote!(None); let features = quote!(None); literal_struct!( @@ -2308,162 +2361,167 @@ mod build { with_global_tauri, before_dev_command, before_build_command, + before_bundle_command, features ); } } - impl ToTokens for UpdaterConfig { + impl ToTokens for WindowsUpdateInstallMode { fn to_tokens(&self, tokens: &mut TokenStream) { - let active = self.active; - let dialog = self.dialog; - let pubkey = str_lit(&self.pubkey); - let endpoints = opt_lit( - self - .endpoints - .as_ref() - .map(|list| { - vec_lit(list, |url| { - let url = url.0.as_str(); - quote! { ::tauri::utils::config::UpdaterEndpoint(#url.parse().unwrap()) } - }) - }) - .as_ref(), - ); + let prefix = quote! { ::tauri::utils::config::WindowsUpdateInstallMode }; - literal_struct!(tokens, UpdaterConfig, active, dialog, pubkey, endpoints); + tokens.append_all(match self { + Self::BasicUi => quote! { #prefix::BasicUi }, + Self::Quiet => quote! { #prefix::Quiet }, + Self::Passive => quote! { #prefix::Passive }, + }) } } - impl ToTokens for SecurityConfig { + impl ToTokens for UpdaterWindowsConfig { fn to_tokens(&self, tokens: &mut TokenStream) { - let csp = opt_str_lit(self.csp.as_ref()); - let dev_csp = opt_str_lit(self.dev_csp.as_ref()); - let freeze_prototype = self.freeze_prototype; - - literal_struct!(tokens, SecurityConfig, csp, dev_csp, freeze_prototype); + let install_mode = &self.install_mode; + literal_struct!(tokens, UpdaterWindowsConfig, install_mode); } } - impl ToTokens for SystemTrayConfig { + impl ToTokens for CspDirectiveSources { fn to_tokens(&self, tokens: &mut TokenStream) { - let icon_as_template = self.icon_as_template; - let icon_path = path_buf_lit(&self.icon_path); - literal_struct!(tokens, SystemTrayConfig, icon_path, icon_as_template); - } - } + let prefix = quote! { ::tauri::utils::config::CspDirectiveSources }; - impl ToTokens for FsAllowlistScope { - fn to_tokens(&self, tokens: &mut TokenStream) { - let allowed_paths = vec_lit(&self.0, path_buf_lit); - tokens.append_all(quote! { ::tauri::utils::config::FsAllowlistScope(#allowed_paths) }) + tokens.append_all(match self { + Self::Inline(sources) => { + let sources = sources.as_str(); + quote!(#prefix::Inline(#sources.into())) + } + Self::List(list) => { + let list = vec_lit(list, str_lit); + quote!(#prefix::List(#list)) + } + }) } } - impl ToTokens for FsAllowlistConfig { + impl ToTokens for Csp { fn to_tokens(&self, tokens: &mut TokenStream) { - let scope = &self.scope; - tokens.append_all(quote! { ::tauri::utils::config::FsAllowlistConfig { scope: #scope, ..Default::default() } }) - } - } + let prefix = quote! { ::tauri::utils::config::Csp }; - impl ToTokens for ProtocolAllowlistConfig { - fn to_tokens(&self, tokens: &mut TokenStream) { - let asset_scope = &self.asset_scope; - tokens.append_all(quote! { ::tauri::utils::config::ProtocolAllowlistConfig { asset_scope: #asset_scope, ..Default::default() } }) + tokens.append_all(match self { + Self::Policy(policy) => { + let policy = policy.as_str(); + quote!(#prefix::Policy(#policy.into())) + } + Self::DirectiveMap(list) => { + let map = map_lit( + quote! { ::std::collections::HashMap }, + list, + str_lit, + identity, + ); + quote!(#prefix::DirectiveMap(#map)) + } + }) } } - impl ToTokens for HttpAllowlistScope { + impl ToTokens for DisabledCspModificationKind { fn to_tokens(&self, tokens: &mut TokenStream) { - let allowed_urls = vec_lit(&self.0, url_lit); - tokens.append_all(quote! { ::tauri::utils::config::HttpAllowlistScope(#allowed_urls) }) - } - } + let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind }; - impl ToTokens for HttpAllowlistConfig { - fn to_tokens(&self, tokens: &mut TokenStream) { - let scope = &self.scope; - tokens.append_all(quote! { ::tauri::utils::config::HttpAllowlistConfig { scope: #scope, ..Default::default() } }) + tokens.append_all(match self { + Self::Flag(flag) => { + quote! { #prefix::Flag(#flag) } + } + Self::List(directives) => { + let directives = vec_lit(directives, str_lit); + quote! { #prefix::List(#directives) } + } + }); } } - impl ToTokens for ShellAllowedCommand { + impl ToTokens for RemoteDomainAccessScope { fn to_tokens(&self, tokens: &mut TokenStream) { - let name = str_lit(&self.name); - let command = path_buf_lit(&self.command); - let args = &self.args; - let sidecar = &self.sidecar; + let scheme = opt_str_lit(self.scheme.as_ref()); + let domain = str_lit(&self.domain); + let windows = vec_lit(&self.windows, str_lit); + let plugins = vec_lit(&self.plugins, str_lit); - literal_struct!(tokens, ShellAllowedCommand, name, command, args, sidecar); + literal_struct!( + tokens, + RemoteDomainAccessScope, + scheme, + domain, + windows, + plugins + ); } } - impl ToTokens for ShellAllowedArgs { + impl ToTokens for SecurityConfig { fn to_tokens(&self, tokens: &mut TokenStream) { - let prefix = quote! { ::tauri::utils::config::ShellAllowedArgs }; + let csp = opt_lit(self.csp.as_ref()); + let dev_csp = opt_lit(self.dev_csp.as_ref()); + let freeze_prototype = self.freeze_prototype; + let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification; + let dangerous_remote_domain_ipc_access = + vec_lit(&self.dangerous_remote_domain_ipc_access, identity); + let asset_protocol = &self.asset_protocol; - tokens.append_all(match self { - Self::Flag(flag) => quote!(#prefix::Flag(#flag)), - Self::List(list) => { - let list = vec_lit(list, identity); - quote!(#prefix::List(#list)) - } - }) + literal_struct!( + tokens, + SecurityConfig, + csp, + dev_csp, + freeze_prototype, + dangerous_disable_asset_csp_modification, + dangerous_remote_domain_ipc_access, + asset_protocol + ); } } - impl ToTokens for ShellAllowedArg { + impl ToTokens for SystemTrayConfig { fn to_tokens(&self, tokens: &mut TokenStream) { - let prefix = quote! { ::tauri::utils::config::ShellAllowedArg }; - - tokens.append_all(match self { - Self::Fixed(fixed) => { - let fixed = str_lit(fixed); - quote!(#prefix::Fixed(#fixed)) - } - Self::Var { validator } => { - let validator = str_lit(validator); - quote!(#prefix::Var { validator: #validator }) - } - }) + let icon_as_template = self.icon_as_template; + let menu_on_left_click = self.menu_on_left_click; + let icon_path = path_buf_lit(&self.icon_path); + let title = opt_str_lit(self.title.as_ref()); + literal_struct!( + tokens, + SystemTrayConfig, + icon_path, + icon_as_template, + menu_on_left_click, + title + ); } } - impl ToTokens for ShellAllowlistOpen { + impl ToTokens for FsScope { fn to_tokens(&self, tokens: &mut TokenStream) { - let prefix = quote! { ::tauri::utils::config::ShellAllowlistOpen }; + let prefix = quote! { ::tauri::utils::config::FsScope }; tokens.append_all(match self { - Self::Flag(flag) => quote!(#prefix::Flag(#flag)), - Self::Validate(regex) => quote!(#prefix::Validate(#regex)), - }) - } - } - - impl ToTokens for ShellAllowlistScope { - fn to_tokens(&self, tokens: &mut TokenStream) { - let allowed_commands = vec_lit(&self.0, identity); - tokens.append_all(quote! { ::tauri::utils::config::ShellAllowlistScope(#allowed_commands) }) + Self::AllowedPaths(allow) => { + let allowed_paths = vec_lit(allow, path_buf_lit); + quote! { #prefix::AllowedPaths(#allowed_paths) } + } + Self::Scope { allow, deny , require_literal_leading_dot} => { + let allow = vec_lit(allow, path_buf_lit); + let deny = vec_lit(deny, path_buf_lit); + let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref()); + quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } } + } + }); } } - impl ToTokens for ShellAllowlistConfig { + impl ToTokens for AssetProtocolConfig { fn to_tokens(&self, tokens: &mut TokenStream) { let scope = &self.scope; - tokens.append_all(quote! { ::tauri::utils::config::ShellAllowlistConfig { scope: #scope, ..Default::default() } }) - } - } - - impl ToTokens for AllowlistConfig { - fn to_tokens(&self, tokens: &mut TokenStream) { - let fs = &self.fs; - let protocol = &self.protocol; - let http = &self.http; - let shell = &self.shell; - tokens.append_all( - quote! { ::tauri::utils::config::AllowlistConfig { fs: #fs, protocol: #protocol, http: #http, shell: #shell, ..Default::default() } }, - ) + tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } }) } } @@ -2471,12 +2529,9 @@ mod build { fn to_tokens(&self, tokens: &mut TokenStream) { let pattern = &self.pattern; let windows = vec_lit(&self.windows, identity); - let cli = opt_lit(self.cli.as_ref()); let bundle = &self.bundle; - let updater = &self.updater; let security = &self.security; let system_tray = opt_lit(self.system_tray.as_ref()); - let allowlist = &self.allowlist; let macos_private_api = self.macos_private_api; literal_struct!( @@ -2484,12 +2539,9 @@ mod build { TauriConfig, pattern, windows, - cli, bundle, - updater, security, system_tray, - allowlist, macos_private_api ); } @@ -2518,12 +2570,13 @@ mod build { impl ToTokens for Config { fn to_tokens(&self, tokens: &mut TokenStream) { + let schema = quote!(None); let package = &self.package; let tauri = &self.tauri; let build = &self.build; let plugins = &self.plugins; - literal_struct!(tokens, Config, package, tauri, build, plugins); + literal_struct!(tokens, Config, schema, package, tauri, build, plugins); } } } @@ -2544,69 +2597,42 @@ mod test { // get default dev path let d_path = default_dev_path(); // get default window - let d_windows = default_window_config(); - // get default title - let d_title = default_title(); + let d_windows: Vec = vec![]; // get default bundle let d_bundle = BundleConfig::default(); - // get default updater - let d_updater = UpdaterConfig::default(); // create a tauri config. let tauri = TauriConfig { pattern: Default::default(), - windows: vec![WindowConfig { - label: "main".to_string(), - url: WindowUrl::default(), - file_drop_enabled: true, - center: false, - x: None, - y: None, - width: 800f64, - height: 600f64, - min_width: None, - min_height: None, - max_width: None, - max_height: None, - resizable: true, - title: String::from("Tauri App"), - fullscreen: false, - focus: false, - transparent: false, - maximized: false, - visible: true, - decorations: true, - always_on_top: false, - skip_taskbar: false, - }], + windows: vec![], bundle: BundleConfig { active: false, - targets: None, + targets: Default::default(), identifier: String::from(""), + publisher: None, icon: Vec::new(), resources: None, copyright: None, category: None, short_description: None, long_description: None, + appimage: Default::default(), deb: Default::default(), macos: Default::default(), external_bin: None, windows: Default::default(), - }, - cli: None, - updater: UpdaterConfig { - active: false, - dialog: true, - pubkey: "".into(), - endpoints: None, + ios: Default::default(), + android: Default::default(), + updater: Default::default(), }, security: SecurityConfig { csp: None, dev_csp: None, - freeze_prototype: true, + freeze_prototype: false, + dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false), + dangerous_remote_domain_ipc_access: Vec::new(), + asset_protocol: AssetProtocolConfig::default(), }, - allowlist: AllowlistConfig::default(), system_tray: None, macos_private_api: false, }; @@ -2620,6 +2646,7 @@ mod test { dist_dir: AppUrl::Url(WindowUrl::App("../dist".into())), before_dev_command: None, before_build_command: None, + before_bundle_command: None, features: None, with_global_tauri: false, }; @@ -2628,14 +2655,12 @@ mod test { assert_eq!(t_config, tauri); assert_eq!(b_config, build); assert_eq!(d_bundle, tauri.bundle); - assert_eq!(d_updater, tauri.updater); assert_eq!( d_path, AppUrl::Url(WindowUrl::External( Url::parse("http://localhost:8080").unwrap() )) ); - assert_eq!(d_title, tauri.windows[0].title); assert_eq!(d_windows, tauri.windows); } } diff --git a/core/tauri-utils/src/config/parse.rs b/core/tauri-utils/src/config/parse.rs index 6fa49f7fabc2..6590620edd34 100644 --- a/core/tauri-utils/src/config/parse.rs +++ b/core/tauri-utils/src/config/parse.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -11,15 +11,87 @@ use std::path::{Path, PathBuf}; use thiserror::Error; /// All extensions that are possibly supported, but perhaps not enabled. -pub const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5"]; +pub const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5", "toml"]; -/// All extensions that are currently enabled. -pub const EXTENSIONS_ENABLED: &[&str] = &[ - "json", +/// All configuration formats that are possibly supported, but perhaps not enabled. +pub const SUPPORTED_FORMATS: &[ConfigFormat] = + &[ConfigFormat::Json, ConfigFormat::Json5, ConfigFormat::Toml]; + +/// All configuration formats that are currently enabled. +pub const ENABLED_FORMATS: &[ConfigFormat] = &[ + ConfigFormat::Json, #[cfg(feature = "config-json5")] - "json5", + ConfigFormat::Json5, + #[cfg(feature = "config-toml")] + ConfigFormat::Toml, ]; +/// The available configuration formats. +#[derive(Debug, Copy, Clone)] +pub enum ConfigFormat { + /// The default JSON (tauri.conf.json) format. + Json, + /// The JSON5 (tauri.conf.json5) format. + Json5, + /// The TOML (Tauri.toml file) format. + Toml, +} + +impl ConfigFormat { + /// Maps the config format to its file name. + pub fn into_file_name(self) -> &'static str { + match self { + Self::Json => "tauri.conf.json", + Self::Json5 => "tauri.conf.json5", + Self::Toml => "Tauri.toml", + } + } + + fn into_platform_file_name(self) -> &'static str { + match self { + Self::Json => { + if cfg!(target_os = "macos") { + "tauri.macos.conf.json" + } else if cfg!(windows) { + "tauri.windows.conf.json" + } else if cfg!(target_os = "android") { + "tauri.android.conf.json" + } else if cfg!(target_os = "ios") { + "tauri.ios.conf.json" + } else { + "tauri.linux.conf.json" + } + } + Self::Json5 => { + if cfg!(target_os = "macos") { + "tauri.macos.conf.json5" + } else if cfg!(windows) { + "tauri.windows.conf.json5" + } else if cfg!(target_os = "android") { + "tauri.android.conf.json" + } else if cfg!(target_os = "ios") { + "tauri.ios.conf.json" + } else { + "tauri.linux.conf.json5" + } + } + Self::Toml => { + if cfg!(target_os = "macos") { + "Tauri.macos.toml" + } else if cfg!(windows) { + "Tauri.windows.toml" + } else if cfg!(target_os = "android") { + "tauri.android.toml" + } else if cfg!(target_os = "ios") { + "tauri.ios.toml" + } else { + "Tauri.linux.toml" + } + } + } + } +} + /// Represents all the errors that can happen while reading the config. #[derive(Debug, Error)] #[non_exhaustive] @@ -45,7 +117,18 @@ pub enum ConfigError { error: ::json5::Error, }, - /// Unknown file extension encountered. + /// Failed to parse from TOML. + #[cfg(feature = "config-toml")] + #[error("unable to parse toml Tauri config file at {path} because {error}")] + FormatToml { + /// The path that failed to parse into TOML. + path: PathBuf, + + /// The parsing [`toml::Error`]. + error: ::toml::de::Error, + }, + + /// Unknown config file name encountered. #[error("unsupported format encountered {0}")] UnsupportedFormat(String), @@ -62,7 +145,7 @@ pub enum ConfigError { /// A generic IO error with context of what caused it. #[error("unable to read Tauri config file at {path} because {error}")] Io { - /// The path the IO error occured on. + /// The path the IO error occurred on. path: PathBuf, /// The [`std::io::Error`]. @@ -70,43 +153,85 @@ pub enum ConfigError { }, } +/// Determines if the given folder has a configuration file. +pub fn folder_has_configuration_file(folder: &Path) -> bool { + folder.join(ConfigFormat::Json.into_file_name()).exists() + || folder.join(ConfigFormat::Json5.into_file_name()).exists() + || folder.join(ConfigFormat::Toml.into_file_name()).exists() + // platform file names + || folder.join(ConfigFormat::Json.into_platform_file_name()).exists() + || folder.join(ConfigFormat::Json5.into_platform_file_name()).exists() + || folder.join(ConfigFormat::Toml.into_platform_file_name()).exists() +} + +/// Determines if the given file path represents a Tauri configuration file. +pub fn is_configuration_file(path: &Path) -> bool { + path + .file_name() + .map(|file_name| { + file_name == OsStr::new(ConfigFormat::Json.into_file_name()) + || file_name == OsStr::new(ConfigFormat::Json5.into_file_name()) + || file_name == OsStr::new(ConfigFormat::Toml.into_file_name()) + // platform file names + || file_name == OsStr::new(ConfigFormat::Json.into_platform_file_name()) + || file_name == OsStr::new(ConfigFormat::Json5.into_platform_file_name()) + || file_name == OsStr::new(ConfigFormat::Toml.into_platform_file_name()) + }) + .unwrap_or_default() +} + /// Reads the configuration from the given root directory. /// -/// It first looks for a `tauri.conf.json[5]` file on the given directory. The file must exist. +/// It first looks for a `tauri.conf.json[5]` or `Tauri.toml` file on the given directory. The file must exist. /// Then it looks for a platform-specific configuration file: -/// - `tauri.macos.conf.json[5]` on macOS -/// - `tauri.linux.conf.json[5]` on Linux -/// - `tauri.windows.conf.json[5]` on Windows +/// - `tauri.macos.conf.json[5]` or `Tauri.macos.toml` on macOS +/// - `tauri.linux.conf.json[5]` or `Tauri.linux.toml` on Linux +/// - `tauri.windows.conf.json[5]` or `Tauri.windows.toml` on Windows +/// - `tauri.android.conf.json[5]` or `Tauri.android.toml` on Android +/// - `tauri.ios.conf.json[5]` or `Tauri.ios.toml` on iOS /// Merging the configurations using [JSON Merge Patch (RFC 7396)]. /// /// [JSON Merge Patch (RFC 7396)]: https://datatracker.ietf.org/doc/html/rfc7396. pub fn read_from(root_dir: PathBuf) -> Result { - let mut config: Value = parse_value(root_dir.join("tauri.conf.json"))?; - - let platform_config_filename = if cfg!(target_os = "macos") { - "tauri.macos.conf.json" - } else if cfg!(windows) { - "tauri.windows.conf.json" - } else { - "tauri.linux.conf.json" - }; - let platform_config_path = root_dir.join(platform_config_filename); - if does_supported_extension_exist(&platform_config_path) { - let platform_config: Value = parse_value(platform_config_path)?; + let mut config: Value = parse_value(root_dir.join("tauri.conf.json"))?.0; + if let Some((platform_config, _)) = read_platform(root_dir)? { merge(&mut config, &platform_config); } Ok(config) } +/// Reads the platform-specific configuration file from the given root directory if it exists. +/// +/// Check [`read_from`] for more information. +pub fn read_platform(root_dir: PathBuf) -> Result, ConfigError> { + let platform_config_path = root_dir.join(ConfigFormat::Json.into_platform_file_name()); + if does_supported_file_name_exist(&platform_config_path) { + let (platform_config, path): (Value, PathBuf) = parse_value(platform_config_path)?; + Ok(Some((platform_config, path))) + } else { + Ok(None) + } +} + /// Check if a supported config file exists at path. /// /// The passed path is expected to be the path to the "default" configuration format, in this case /// JSON with `.json`. -pub fn does_supported_extension_exist(path: impl Into) -> bool { +pub fn does_supported_file_name_exist(path: impl Into) -> bool { let path = path.into(); - EXTENSIONS_ENABLED + let source_file_name = path.file_name().unwrap().to_str().unwrap(); + let lookup_platform_config = ENABLED_FORMATS .iter() - .any(|ext| path.with_extension(ext).exists()) + .any(|format| source_file_name == format.into_platform_file_name()); + ENABLED_FORMATS.iter().any(|format| { + path + .with_file_name(if lookup_platform_config { + format.into_platform_file_name() + } else { + format.into_file_name() + }) + .exists() + }) } /// Parse the config from path, including alternative formats. @@ -119,18 +244,39 @@ pub fn does_supported_extension_exist(path: impl Into) -> bool { /// 2. Check if `tauri.conf.json5` exists /// a. Parse it with `json5` /// b. Return error if all above steps failed -/// 3. Return error if all above steps failed -pub fn parse(path: impl Into) -> Result { +/// 3. Check if `Tauri.json` exists +/// a. Parse it with `toml` +/// b. Return error if all above steps failed +/// 4. Return error if all above steps failed +pub fn parse(path: impl Into) -> Result<(Config, PathBuf), ConfigError> { do_parse(path.into()) } /// See [`parse`] for specifics, returns a JSON [`Value`] instead of [`Config`]. -pub fn parse_value(path: impl Into) -> Result { +pub fn parse_value(path: impl Into) -> Result<(Value, PathBuf), ConfigError> { do_parse(path.into()) } -fn do_parse(path: PathBuf) -> Result { - let json5 = path.with_extension("json5"); +fn do_parse(path: PathBuf) -> Result<(D, PathBuf), ConfigError> { + let file_name = path + .file_name() + .map(OsStr::to_string_lossy) + .unwrap_or_default(); + let lookup_platform_config = ENABLED_FORMATS + .iter() + .any(|format| file_name == format.into_platform_file_name()); + + let json5 = path.with_file_name(if lookup_platform_config { + ConfigFormat::Json5.into_platform_file_name() + } else { + ConfigFormat::Json5.into_file_name() + }); + let toml = path.with_file_name(if lookup_platform_config { + ConfigFormat::Toml.into_platform_file_name() + } else { + ConfigFormat::Toml.into_file_name() + }); + let path_ext = path .extension() .map(OsStr::to_string_lossy) @@ -157,12 +303,12 @@ fn do_parse(path: PathBuf) -> Result { } }; - json + json.map(|j| (j, path)) } else if json5.exists() { #[cfg(feature = "config-json5")] { let raw = read_to_string(&json5)?; - do_parse_json5(&raw, &path) + do_parse_json5(&raw, &path).map(|config| (config, json5)) } #[cfg(not(feature = "config-json5"))] @@ -170,6 +316,18 @@ fn do_parse(path: PathBuf) -> Result { extension: ".json5".into(), feature: "config-json5".into(), }) + } else if toml.exists() { + #[cfg(feature = "config-toml")] + { + let raw = read_to_string(&toml)?; + do_parse_toml(&raw, &path).map(|config| (config, toml)) + } + + #[cfg(not(feature = "config-toml"))] + Err(ConfigError::DisabledFormat { + extension: ".toml".into(), + feature: "config-toml".into(), + }) } else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) { Err(ConfigError::UnsupportedFormat(path_ext.to_string())) } else { @@ -227,6 +385,14 @@ fn do_parse_json5(raw: &str, path: &Path) -> Result(raw: &str, path: &Path) -> Result { + ::toml::from_str(raw).map_err(|error| ConfigError::FormatToml { + path: path.into(), + error, + }) +} + /// Helper function to wrap IO errors from [`std::fs::read_to_string`] into a [`ConfigError`]. fn read_to_string(path: &Path) -> Result { std::fs::read_to_string(path).map_err(|error| ConfigError::Io { diff --git a/core/tauri-utils/src/html.rs b/core/tauri-utils/src/html.rs index a6849dd73c8e..13600817dde5 100644 --- a/core/tauri-utils/src/html.rs +++ b/core/tauri-utils/src/html.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -6,14 +6,20 @@ use std::path::{Path, PathBuf}; -use html5ever::{interface::QualName, namespace_url, ns, tendril::TendrilSink, LocalName}; +use html5ever::{ + interface::QualName, + namespace_url, ns, + serialize::{HtmlSerializer, SerializeOpts, Serializer, TraversalScope}, + tendril::TendrilSink, + LocalName, +}; pub use kuchiki::NodeRef; -use kuchiki::{Attribute, ExpandedName}; +use kuchiki::{Attribute, ExpandedName, NodeData}; use serde::Serialize; #[cfg(feature = "isolation")] use serialize_to_javascript::DefaultTemplate; -use crate::config::PatternKind; +use crate::config::{DisabledCspModificationKind, PatternKind}; #[cfg(feature = "isolation")] use crate::pattern::isolation::IsolationJavascriptCodegen; @@ -24,6 +30,90 @@ pub const SCRIPT_NONCE_TOKEN: &str = "__TAURI_SCRIPT_NONCE__"; /// The token used for style nonces. pub const STYLE_NONCE_TOKEN: &str = "__TAURI_STYLE_NONCE__"; +// taken from https://github.com/kuchiki-rs/kuchiki/blob/57ee6920d835315a498e748ba4b07a851ae5e498/src/serializer.rs#L12 +fn serialize_node_ref_internal( + node: &NodeRef, + serializer: &mut S, + traversal_scope: TraversalScope, +) -> crate::Result<()> { + match (traversal_scope, node.data()) { + (ref scope, NodeData::Element(element)) => { + if *scope == TraversalScope::IncludeNode { + let attrs = element.attributes.borrow(); + + // Unfortunately we need to allocate something to hold these &'a QualName + let attrs = attrs + .map + .iter() + .map(|(name, attr)| { + ( + QualName::new(attr.prefix.clone(), name.ns.clone(), name.local.clone()), + &attr.value, + ) + }) + .collect::>(); + + serializer.start_elem( + element.name.clone(), + attrs.iter().map(|&(ref name, value)| (name, &**value)), + )? + } + + let children = match element.template_contents.as_ref() { + Some(template_root) => template_root.children(), + None => node.children(), + }; + for child in children { + serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)? + } + + if *scope == TraversalScope::IncludeNode { + serializer.end_elem(element.name.clone())? + } + Ok(()) + } + + (_, &NodeData::DocumentFragment) | (_, &NodeData::Document(_)) => { + for child in node.children() { + serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)? + } + Ok(()) + } + + (TraversalScope::ChildrenOnly(_), _) => Ok(()), + + (TraversalScope::IncludeNode, NodeData::Doctype(doctype)) => { + serializer.write_doctype(&doctype.name).map_err(Into::into) + } + (TraversalScope::IncludeNode, NodeData::Text(text)) => { + serializer.write_text(&text.borrow()).map_err(Into::into) + } + (TraversalScope::IncludeNode, NodeData::Comment(text)) => { + serializer.write_comment(&text.borrow()).map_err(Into::into) + } + (TraversalScope::IncludeNode, NodeData::ProcessingInstruction(contents)) => { + let contents = contents.borrow(); + serializer + .write_processing_instruction(&contents.0, &contents.1) + .map_err(Into::into) + } + } +} + +/// Serializes the node to HTML. +pub fn serialize_node(node: &NodeRef) -> Vec { + let mut u8_vec = Vec::new(); + let mut ser = HtmlSerializer::new( + &mut u8_vec, + SerializeOpts { + traversal_scope: TraversalScope::IncludeNode, + ..Default::default() + }, + ); + serialize_node_ref_internal(node, &mut ser, TraversalScope::IncludeNode).unwrap(); + u8_vec +} + /// Parses the given HTML string. pub fn parse(html: String) -> NodeRef { kuchiki::parse_html().one(html) @@ -59,9 +149,16 @@ fn inject_nonce(document: &mut NodeRef, selector: &str, token: &str) { } /// Inject nonce tokens to all scripts and styles. -pub fn inject_nonce_token(document: &mut NodeRef) { - inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN); - inject_nonce(document, "style", STYLE_NONCE_TOKEN); +pub fn inject_nonce_token( + document: &mut NodeRef, + dangerous_disable_asset_csp_modification: &DisabledCspModificationKind, +) { + if dangerous_disable_asset_csp_modification.can_modify("script-src") { + inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN); + } + if dangerous_disable_asset_csp_modification.can_modify("style-src") { + inject_nonce(document, "style", STYLE_NONCE_TOKEN); + } } /// Injects a content security policy to the HTML. @@ -115,7 +212,6 @@ impl From<&PatternKind> for PatternObject { fn from(pattern_kind: &PatternKind) -> Self { match pattern_kind { PatternKind::Brownfield => Self::Brownfield, - #[cfg(feature = "isolation")] PatternKind::Isolation { .. } => Self::Isolation { side: IsolationSide::default(), }, diff --git a/core/tauri-utils/src/io.rs b/core/tauri-utils/src/io.rs new file mode 100644 index 000000000000..85d1e6da8402 --- /dev/null +++ b/core/tauri-utils/src/io.rs @@ -0,0 +1,49 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! IO helpers. + +use std::io::BufRead; + +/// Read a line breaking in both \n and \r. +/// +/// Adapted from . +pub fn read_line(r: &mut R, buf: &mut Vec) -> std::io::Result { + let mut read = 0; + loop { + let (done, used) = { + let available = match r.fill_buf() { + Ok(n) => n, + Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + match memchr::memchr(b'\n', available) { + Some(i) => { + let end = i + 1; + buf.extend_from_slice(&available[..end]); + (true, end) + } + None => match memchr::memchr(b'\r', available) { + Some(i) => { + let end = i + 1; + buf.extend_from_slice(&available[..end]); + (true, end) + } + None => { + buf.extend_from_slice(available); + (false, available.len()) + } + }, + } + }; + r.consume(used); + read += used; + if done || used == 0 { + if buf.ends_with(&[b'\n']) { + buf.pop(); + } + return Ok(read); + } + } +} diff --git a/core/tauri-utils/src/lib.rs b/core/tauri-utils/src/lib.rs index 8ff4e534ccfd..8d50acf243f1 100644 --- a/core/tauri-utils/src/lib.rs +++ b/core/tauri-utils/src/lib.rs @@ -1,13 +1,24 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! Tauri utility helpers #![warn(missing_docs, rust_2018_idioms)] +#![allow(clippy::deprecated_semver)] + +use std::{ + fmt::Display, + path::{Path, PathBuf}, +}; + +use semver::Version; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub mod assets; pub mod config; pub mod html; +pub mod io; +pub mod mime_type; pub mod platform; /// Prepare application resources and sidecars. #[cfg(feature = "resources")] @@ -22,11 +33,13 @@ pub struct PackageInfo { /// App name pub name: String, /// App version - pub version: String, + pub version: Version, /// The crate authors. pub authors: &'static str, /// The crate description. pub description: &'static str, + /// The crate name. + pub crate_name: &'static str, } impl PackageInfo { @@ -43,6 +56,203 @@ impl PackageInfo { } } +#[allow(deprecated)] +mod window_effects { + use super::*; + + #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)] + #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] + #[serde(rename_all = "camelCase")] + /// Platform-specific window effects + pub enum WindowEffect { + /// A default material appropriate for the view's effectiveAppearance. **macOS 10.14-** + #[deprecated( + since = "macOS 10.14", + note = "You should instead choose an appropriate semantic material." + )] + AppearanceBased, + /// **macOS 10.14-** + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + Light, + /// **macOS 10.14-** + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + Dark, + /// **macOS 10.14-** + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + MediumLight, + /// **macOS 10.14-** + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + UltraDark, + /// **macOS 10.10+** + Titlebar, + /// **macOS 10.10+** + Selection, + /// **macOS 10.11+** + Menu, + /// **macOS 10.11+** + Popover, + /// **macOS 10.11+** + Sidebar, + /// **macOS 10.14+** + HeaderView, + /// **macOS 10.14+** + Sheet, + /// **macOS 10.14+** + WindowBackground, + /// **macOS 10.14+** + HudWindow, + /// **macOS 10.14+** + FullScreenUI, + /// **macOS 10.14+** + Tooltip, + /// **macOS 10.14+** + ContentBackground, + /// **macOS 10.14+** + UnderWindowBackground, + /// **macOS 10.14+** + UnderPageBackground, + /// **Windows 11 Only** + Mica, + /// **Windows 7/10/11(22H1) Only** + /// + /// ## Notes + /// + /// This effect has bad performance when resizing/dragging the window on Windows 11 build 22621. + Blur, + /// **Windows 10/11 Only** + /// + /// ## Notes + /// + /// This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000. + Acrylic, + } + + /// Window effect state **macOS only** + /// + /// + #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)] + #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] + #[serde(rename_all = "camelCase")] + pub enum WindowEffectState { + /// Make window effect state follow the window's active state + FollowsWindowActiveState, + /// Make window effect state always active + Active, + /// Make window effect state always inactive + Inactive, + } +} + +pub use window_effects::{WindowEffect, WindowEffectState}; + +/// How the window title bar should be displayed on macOS. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +pub enum TitleBarStyle { + /// A normal title bar. + Visible, + /// Makes the title bar transparent, so the window background color is shown instead. + /// + /// Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color. + Transparent, + /// Shows the title bar as a transparent overlay over the window's content. + /// + /// Keep in mind: + /// - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect. + /// - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus . + /// - The color of the window title depends on the system theme. + Overlay, +} + +impl Default for TitleBarStyle { + fn default() -> Self { + Self::Visible + } +} + +impl Serialize for TitleBarStyle { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl<'de> Deserialize<'de> for TitleBarStyle { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_str() { + "transparent" => Self::Transparent, + "overlay" => Self::Overlay, + _ => Self::Visible, + }) + } +} + +impl Display for TitleBarStyle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Visible => "Visible", + Self::Transparent => "Transparent", + Self::Overlay => "Overlay", + } + ) + } +} + +/// System theme. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[non_exhaustive] +pub enum Theme { + /// Light theme. + Light, + /// Dark theme. + Dark, +} + +impl Serialize for Theme { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl<'de> Deserialize<'de> for Theme { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_str() { + "dark" => Self::Dark, + _ => Self::Light, + }) + } +} + +impl Display for Theme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Light => "light", + Self::Dark => "dark", + } + ) + } +} + /// Information about environment variables. #[derive(Debug, Clone)] #[non_exhaustive] @@ -53,11 +263,14 @@ pub struct Env { /// The APPDIR environment variable. #[cfg(target_os = "linux")] pub appdir: Option, + /// The command line arguments of the current process. + pub args: Vec, } #[allow(clippy::derivable_impls)] impl Default for Env { fn default() -> Self { + let args = std::env::args().skip(1).collect(); #[cfg(target_os = "linux")] { let env = Self { @@ -65,6 +278,7 @@ impl Default for Env { appimage: std::env::var_os("APPIMAGE"), #[cfg(target_os = "linux")] appdir: std::env::var_os("APPDIR"), + args, }; if env.appimage.is_some() || env.appdir.is_some() { // validate that we're actually running on an AppImage @@ -87,7 +301,7 @@ impl Default for Env { } #[cfg(not(target_os = "linux"))] { - Self {} + Self { args } } } } @@ -147,3 +361,38 @@ pub enum Error { #[error("could not walk directory `{0}`, try changing `allow_walk` to true on the `ResourcePaths` constructor.")] NotAllowedToWalkDir(std::path::PathBuf), } + +/// Suppresses the unused-variable warnings of the given inputs. +/// +/// This does not move any values. Instead, it just suppresses the warning by taking a +/// reference to the value. +#[macro_export] +macro_rules! consume_unused_variable { + ($($arg:expr),*) => { + $( + let _ = &$arg; + )* + () + }; +} + +/// Prints to the standard error, with a newline. +/// +/// Equivalent to the [`eprintln!`] macro, except that it's only effective for debug builds. +#[macro_export] +macro_rules! debug_eprintln { + () => ($crate::debug_eprintln!("")); + ($($arg:tt)*) => { + #[cfg(debug_assertions)] + eprintln!($($arg)*); + #[cfg(not(debug_assertions))] + $crate::consume_unused_variable!($($arg)*); + }; +} + +/// Reconstructs a path from its components using the platform separator then converts it to String and removes UNC prefixes on Windows if it exists. +pub fn display_path>(p: P) -> String { + dunce::simplified(&p.as_ref().components().collect::()) + .display() + .to_string() +} diff --git a/core/tauri-utils/src/mime_type.rs b/core/tauri-utils/src/mime_type.rs new file mode 100644 index 000000000000..6b51335e6746 --- /dev/null +++ b/core/tauri-utils/src/mime_type.rs @@ -0,0 +1,154 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Determine a mime type from a URI or file contents. + +use std::fmt; + +const MIMETYPE_PLAIN: &str = "text/plain"; + +/// [Web Compatible MimeTypes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#important_mime_types_for_web_developers) +#[allow(missing_docs)] +pub enum MimeType { + Css, + Csv, + Html, + Ico, + Js, + Json, + Jsonld, + Mp4, + OctetStream, + Rtf, + Svg, + Txt, +} + +impl std::fmt::Display for MimeType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mime = match self { + MimeType::Css => "text/css", + MimeType::Csv => "text/csv", + MimeType::Html => "text/html", + MimeType::Ico => "image/vnd.microsoft.icon", + MimeType::Js => "text/javascript", + MimeType::Json => "application/json", + MimeType::Jsonld => "application/ld+json", + MimeType::Mp4 => "video/mp4", + MimeType::OctetStream => "application/octet-stream", + MimeType::Rtf => "application/rtf", + MimeType::Svg => "image/svg+xml", + MimeType::Txt => MIMETYPE_PLAIN, + }; + write!(f, "{mime}") + } +} + +impl MimeType { + /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType. + pub fn parse_from_uri(uri: &str) -> MimeType { + Self::parse_from_uri_with_fallback(uri, Self::Html) + } + + /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType with specified fallback for unknown file extensions. + pub fn parse_from_uri_with_fallback(uri: &str, fallback: MimeType) -> MimeType { + let suffix = uri.split('.').last(); + match suffix { + Some("bin") => Self::OctetStream, + Some("css" | "less" | "sass" | "styl") => Self::Css, + Some("csv") => Self::Csv, + Some("html") => Self::Html, + Some("ico") => Self::Ico, + Some("js") => Self::Js, + Some("json") => Self::Json, + Some("jsonld") => Self::Jsonld, + Some("mjs") => Self::Js, + Some("mp4") => Self::Mp4, + Some("rtf") => Self::Rtf, + Some("svg") => Self::Svg, + Some("txt") => Self::Txt, + // Assume HTML when a TLD is found for eg. `wry:://tauri.app` | `wry://hello.com` + Some(_) => fallback, + // using octet stream according to this: + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + None => Self::OctetStream, + } + } + + /// infer mimetype from content (or) URI if needed. + pub fn parse(content: &[u8], uri: &str) -> String { + Self::parse_with_fallback(content, uri, Self::Html) + } + /// infer mimetype from content (or) URI if needed with specified fallback for unknown file extensions. + pub fn parse_with_fallback(content: &[u8], uri: &str, fallback: MimeType) -> String { + let mime = if uri.ends_with(".svg") { + // when reading svg, we can't use `infer` + None + } else { + infer::get(content).map(|info| info.mime_type()) + }; + + match mime { + Some(mime) if mime == MIMETYPE_PLAIN => { + Self::parse_from_uri_with_fallback(uri, fallback).to_string() + } + None => Self::parse_from_uri_with_fallback(uri, fallback).to_string(), + Some(mime) => mime.to_string(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_parse_mimetype_from_uri() { + let css = MimeType::parse_from_uri( + "https://unpkg.com/browse/bootstrap@4.1.0/dist/css/bootstrap-grid.css", + ) + .to_string(); + assert_eq!(css, "text/css".to_string()); + + let csv: String = MimeType::parse_from_uri("https://example.com/random.csv").to_string(); + assert_eq!(csv, "text/csv".to_string()); + + let ico: String = + MimeType::parse_from_uri("https://icons.duckduckgo.com/ip3/microsoft.com.ico").to_string(); + assert_eq!(ico, String::from("image/vnd.microsoft.icon")); + + let html: String = MimeType::parse_from_uri("https://tauri.app/index.html").to_string(); + assert_eq!(html, String::from("text/html")); + + let js: String = + MimeType::parse_from_uri("https://unpkg.com/react@17.0.1/umd/react.production.min.js") + .to_string(); + assert_eq!(js, "text/javascript".to_string()); + + let json: String = + MimeType::parse_from_uri("https://unpkg.com/browse/react@17.0.1/build-info.json").to_string(); + assert_eq!(json, String::from("application/json")); + + let jsonld: String = MimeType::parse_from_uri("https:/example.com/hello.jsonld").to_string(); + assert_eq!(jsonld, String::from("application/ld+json")); + + let mjs: String = MimeType::parse_from_uri("https://example.com/bundled.mjs").to_string(); + assert_eq!(mjs, String::from("text/javascript")); + + let mp4: String = MimeType::parse_from_uri("https://example.com/video.mp4").to_string(); + assert_eq!(mp4, String::from("video/mp4")); + + let rtf: String = MimeType::parse_from_uri("https://example.com/document.rtf").to_string(); + assert_eq!(rtf, String::from("application/rtf")); + + let svg: String = MimeType::parse_from_uri("https://example.com/picture.svg").to_string(); + assert_eq!(svg, String::from("image/svg+xml")); + + let txt: String = MimeType::parse_from_uri("https://example.com/file.txt").to_string(); + assert_eq!(txt, String::from("text/plain")); + + let custom_scheme = MimeType::parse_from_uri("wry://tauri.app").to_string(); + assert_eq!(custom_scheme, String::from("text/html")); + } +} diff --git a/core/tauri-utils/src/pattern/isolation.js b/core/tauri-utils/src/pattern/isolation.js index 42e2604ce5d8..ce284d59a1da 100644 --- a/core/tauri-utils/src/pattern/isolation.js +++ b/core/tauri-utils/src/pattern/isolation.js @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -43,7 +43,7 @@ algorithm.iv = window.crypto.getRandomValues(new Uint8Array(12)) let encoder = new TextEncoder() - let payloadRaw = encoder.encode(JSON.stringify(data)) + let payloadRaw = encoder.encode(__RAW_stringify_ipc_message_fn__(data)) return window.crypto.subtle .encrypt(algorithm, aesGcmKey, payloadRaw) diff --git a/core/tauri-utils/src/pattern/isolation.rs b/core/tauri-utils/src/pattern/isolation.rs index 8e3c8d581c39..ae5382450320 100644 --- a/core/tauri-utils/src/pattern/isolation.rs +++ b/core/tauri-utils/src/pattern/isolation.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -8,15 +8,10 @@ use std::fmt::{Debug, Formatter}; use std::string::FromUtf8Error; use aes_gcm::aead::Aead; -use aes_gcm::{aead::NewAead, Aes256Gcm, Nonce}; -use once_cell::sync::OnceCell; -use ring::error::Unspecified; -use ring::rand::SystemRandom; +use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; +use getrandom::{getrandom, Error as CsprngError}; use serialize_to_javascript::{default_template, Template}; -/// Cryptographically secure pseudo-random number generator. -static RNG: OnceCell = OnceCell::new(); - /// The style for the isolation iframe. pub const IFRAME_STYLE: &str = "#__tauri_isolation__ { display: none !important }"; @@ -25,10 +20,10 @@ pub const IFRAME_STYLE: &str = "#__tauri_isolation__ { display: none !important #[non_exhaustive] pub enum Error { /// Something went wrong with the CSPRNG. - #[error("Unspecified CSPRNG error")] - Csprng, + #[error("CSPRNG error")] + Csprng(#[from] CsprngError), - /// Something went wrong with decryping an AES-GCM payload + /// Something went wrong with decrypting an AES-GCM payload #[error("AES-GCM")] Aes, @@ -45,12 +40,6 @@ pub enum Error { Json(#[from] serde_json::Error), } -impl From for Error { - fn from(_: Unspecified) -> Self { - Self::Csprng - } -} - /// A formatted AES-GCM cipher instance along with the key used to initialize it. #[derive(Clone)] pub struct AesGcmPair { @@ -66,9 +55,9 @@ impl Debug for AesGcmPair { impl AesGcmPair { fn new() -> Result { - let rng = RNG.get_or_init(SystemRandom::new); - let raw: [u8; 32] = ring::rand::generate(rng)?.expose(); - let key = aes_gcm::Key::from_slice(&raw); + let mut raw = [0u8; 32]; + getrandom(&mut raw)?; + let key = aes_gcm::Key::::from_slice(&raw); Ok(Self { raw, key: Aes256Gcm::new(key), @@ -152,6 +141,9 @@ pub struct IsolationJavascriptCodegen { pub struct IsolationJavascriptRuntime<'a> { /// The key used on the Rust backend and the Isolation Javascript pub runtime_aes_gcm_key: &'a [u8; 32], + /// The function that stringifies a IPC message. + #[raw] + pub stringify_ipc_message_fn: &'a str, } #[cfg(test)] diff --git a/core/tauri-utils/src/pattern/mod.rs b/core/tauri-utils/src/pattern/mod.rs index d0a1019b0337..8861e1b43b3a 100644 --- a/core/tauri-utils/src/pattern/mod.rs +++ b/core/tauri-utils/src/pattern/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT diff --git a/core/tauri-utils/src/platform.rs b/core/tauri-utils/src/platform.rs index cabe9a99b55b..0ca4fd617966 100644 --- a/core/tauri-utils/src/platform.rs +++ b/core/tauri-utils/src/platform.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -133,10 +133,10 @@ pub fn target_triple() -> crate::Result { return Err(crate::Error::Environment); }; - format!("{}-{}", os, env) + format!("{os}-{env}") }; - Ok(format!("{}-{}", arch, os)) + Ok(format!("{arch}-{os}")) } /// Computes the resource directory of the current environment. @@ -157,8 +157,8 @@ pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result crate::Result crate::Result bool { + if let Some(v) = windows_version() { + // windows 7 is 6.1 + if v.0 == 6 && v.1 == 1 { + return true; + } + } + false + } + + fn encode_wide(string: impl AsRef) -> Vec { + string.as_ref().encode_wide().chain(once(0)).collect() + } + + /// Helper function to dynamically load function pointer. + /// `library` and `function` must be null-terminated. + pub fn get_function_impl(library: &str, function: &str) -> Option { + let library = encode_wide(library); + assert_eq!(function.chars().last(), Some('\0')); + let function = PCSTR::from_raw(function.as_ptr()); + + // Library names we will use are ASCII so we can use the A version to avoid string conversion. + let module = unsafe { LoadLibraryW(PCWSTR::from_raw(library.as_ptr())) }.unwrap_or_default(); + if module.is_invalid() { + None + } else { + Some(unsafe { GetProcAddress(module, function) }) + } + } + + macro_rules! get_function { + ($lib:expr, $func:ident) => { + get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')) + .map(|f| unsafe { std::mem::transmute::(f) }) + }; + } + + /// Returns a tuple of (major, minor, buildnumber) for the Windows version. + pub fn windows_version() -> Option<(u32, u32, u32)> { + type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> i32; + let handle = get_function!("ntdll.dll", RtlGetVersion); + if let Some(rtl_get_version) = handle { + unsafe { + let mut vi = OSVERSIONINFOW { + dwOSVersionInfoSize: 0, + dwMajorVersion: 0, + dwMinorVersion: 0, + dwBuildNumber: 0, + dwPlatformId: 0, + szCSDVersion: [0; 128], + }; + + let status = (rtl_get_version)(&mut vi as _); + + if status >= 0 { + Some((vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber)) + } else { + None + } + } + } else { + None + } + } +} diff --git a/core/tauri-utils/src/platform/starting_binary.rs b/core/tauri-utils/src/platform/starting_binary.rs index 3b06968dc87f..213e76d108e6 100644 --- a/core/tauri-utils/src/platform/starting_binary.rs +++ b/core/tauri-utils/src/platform/starting_binary.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -68,7 +68,9 @@ impl StartingBinary { ancestor .symlink_metadata() .as_ref() - .map(std::fs::Metadata::is_symlink), + .map(std::fs::Metadata::file_type) + .as_ref() + .map(std::fs::FileType::is_symlink), Ok(true) ) }) diff --git a/core/tauri-utils/src/resources.rs b/core/tauri-utils/src/resources.rs index 4ae0219ae6ce..cad7ae0dbb8b 100644 --- a/core/tauri-utils/src/resources.rs +++ b/core/tauri-utils/src/resources.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -29,7 +29,11 @@ pub fn external_binaries(external_binaries: &[String], target_triple: &str) -> V "{}-{}{}", curr_path, target_triple, - if cfg!(windows) { ".exe" } else { "" } + if target_triple.contains("windows") { + ".exe" + } else { + "" + } )); } paths diff --git a/core/tauri/.license_template b/core/tauri/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/core/tauri/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/core/tauri/.scripts/loop_qc.sh b/core/tauri/.scripts/loop_qc.sh index 2531364f1a53..2f17b91e6050 100644 --- a/core/tauri/.scripts/loop_qc.sh +++ b/core/tauri/.scripts/loop_qc.sh @@ -1,10 +1,10 @@ #!/bin/bash -# Copyright 2019-2021 Tauri Programme within The Commons Conservancy +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT -# Loop all quickcheck tests for tauri. +# Loop all quickcheck tests for tauri. while true do cargo test qc_ diff --git a/core/tauri/CHANGELOG.md b/core/tauri/CHANGELOG.md index 65c2fe3b46df..b024eb05e056 100644 --- a/core/tauri/CHANGELOG.md +++ b/core/tauri/CHANGELOG.md @@ -1,11 +1,1066 @@ # Changelog +## \[2.0.0-alpha.9] + +- [`256c30c7`](https://www.github.com/tauri-apps/tauri/commit/256c30c72b737e49ced0d6a6483910dc779fc185)([#6863](https://www.github.com/tauri-apps/tauri/pull/6863)) Enhance parsing of annotated Android plugin methods to support private functions. +- [`73c803a5`](https://www.github.com/tauri-apps/tauri/commit/73c803a561181137f20366f5d52511392a619f2b)([#6837](https://www.github.com/tauri-apps/tauri/pull/6837)) Added static function `loadConfig` on the Android `PluginManager` class. +- [`edb16d13`](https://www.github.com/tauri-apps/tauri/commit/edb16d13a503da4b264ce459319fec25374c5c4f)([#6831](https://www.github.com/tauri-apps/tauri/pull/6831)) Adjust Android plugin exception error. +- [`0ab5f40d`](https://www.github.com/tauri-apps/tauri/commit/0ab5f40d3a4207f20e4440587b41c4e78f91d233)([#6813](https://www.github.com/tauri-apps/tauri/pull/6813)) Add channel API for sending data across the IPC. +- [`31444ac1`](https://www.github.com/tauri-apps/tauri/commit/31444ac196add770f2ad18012d7c18bce7538f22)([#6725](https://www.github.com/tauri-apps/tauri/pull/6725)) On Android, update proguard rules. +- [`8ce32e74`](https://www.github.com/tauri-apps/tauri/commit/8ce32e74b5573931c3bc81e8e893a6d3b9686b0e)([#6986](https://www.github.com/tauri-apps/tauri/pull/6986)) Add `default_window_icon` getter on `App` and `AppHandle`. +- [`2a5175a8`](https://www.github.com/tauri-apps/tauri/commit/2a5175a8f8f318aac9a6434271f2cc065e5989ae)([#6779](https://www.github.com/tauri-apps/tauri/pull/6779)) Enhance Android's `JSObject` return types. +- [`bb2a8ccf`](https://www.github.com/tauri-apps/tauri/commit/bb2a8ccf1356e59b98947d827d61e4e99533f2bc)([#6830](https://www.github.com/tauri-apps/tauri/pull/6830)) Use actual iOS plugin instance to run command with `throws`. +- [`94224906`](https://www.github.com/tauri-apps/tauri/commit/942249060ed12a5d21a2b21c30e0638c1d2b9df0)([#6783](https://www.github.com/tauri-apps/tauri/pull/6783)) Generate `TauriActivity` Kotlin class on the build script. +- [`7a4b1fb9`](https://www.github.com/tauri-apps/tauri/commit/7a4b1fb96da475053c61960f362bbecf18cd00d4)([#6839](https://www.github.com/tauri-apps/tauri/pull/6839)) Added support to attibutes for each command path in the `generate_handler` macro. +- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Remove `enable_tauri_api` from the IPC scope. +- [`dfa407ff`](https://www.github.com/tauri-apps/tauri/commit/dfa407ffcbc8a853d61139b68b55747ae49fb231)([#6763](https://www.github.com/tauri-apps/tauri/pull/6763)) Expose plugin configuration on the Android and iOS plugin classes. +- [`3245d14b`](https://www.github.com/tauri-apps/tauri/commit/3245d14b9eb256a5c5675c7030bac7082855df47)([#6895](https://www.github.com/tauri-apps/tauri/pull/6895)) Moved the `app` feature to its own plugin in the plugins-workspace repository. +- [`09376af5`](https://www.github.com/tauri-apps/tauri/commit/09376af59424cc27803fa2820d2ac0d4cdc90a6d)([#6704](https://www.github.com/tauri-apps/tauri/pull/6704)) Moved the `cli` feature to its own plugin in the plugins-workspace repository. +- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Moved the dialog APIs to its own plugin in the plugins-workspace repository. +- [`39f1b04f`](https://www.github.com/tauri-apps/tauri/commit/39f1b04f7be4966488484829cd54c8ce72a04200)([#6943](https://www.github.com/tauri-apps/tauri/pull/6943)) Moved the `event` JS APIs to a plugin. +- [`fc4d687e`](https://www.github.com/tauri-apps/tauri/commit/fc4d687ef0ef2ea069ed73c40916da733b5dcb8f)([#6716](https://www.github.com/tauri-apps/tauri/pull/6716)) Moved the file system APIs to its own plugin in the plugins-workspace repository. +- [`f78a3783`](https://www.github.com/tauri-apps/tauri/commit/f78a378344bbec48533641661d865920a8f46f8f)([#6742](https://www.github.com/tauri-apps/tauri/pull/6742)) Moved the `http` feature to its own plugin in the plugins-workspace repository. +- [`29ce9ce2`](https://www.github.com/tauri-apps/tauri/commit/29ce9ce2ce7dfb260d556d5cffd075e8fe06660c)([#6902](https://www.github.com/tauri-apps/tauri/pull/6902)) Moved the `os` feature to its own plugin in the plugins-workspace repository. +- [`60cf9ed2`](https://www.github.com/tauri-apps/tauri/commit/60cf9ed2fcd7be4df41e86cf18735efe9b6cb254)([#6905](https://www.github.com/tauri-apps/tauri/pull/6905)) Moved the `process` feature to its own plugin in the plugins-workspace repository. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the `protocol` scope configuration to the `asset_protocol` field in `SecurityConfig`. +- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `shell` functionality to its own plugin in the plugins-workspace repository. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the updater configuration to the `BundleConfig`. +- [`b072daa3`](https://www.github.com/tauri-apps/tauri/commit/b072daa3bd3e38b808466666619ddb885052c5b2)([#6919](https://www.github.com/tauri-apps/tauri/pull/6919)) Moved the `updater` feature to its own plugin in the plugins-workspace repository. +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`d693e526`](https://www.github.com/tauri-apps/tauri/commit/d693e526e8607129d7f7b62a10db715f3b87d2b9)([#6780](https://www.github.com/tauri-apps/tauri/pull/6780)) Added the `onNewIntent` Plugin hook on Android. +- [`34b8f339`](https://www.github.com/tauri-apps/tauri/commit/34b8f339a4276ebff20b9d52caa103e8e3a7af66)([#6705](https://www.github.com/tauri-apps/tauri/pull/6705)) Add `app` method for the `PluginApi` struct. +- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `tauri::api::process` module to `tauri::process`. +- [`cdad6e08`](https://www.github.com/tauri-apps/tauri/commit/cdad6e083728ea61bd6fc734ef93f6306056ea2e)([#6774](https://www.github.com/tauri-apps/tauri/pull/6774)) Changed how the `tauri-android` dependency is injected. This requires the `gen/android` project to be recreated. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed the allowlist configuration. +- [`cebd7526`](https://www.github.com/tauri-apps/tauri/commit/cebd75261ac71b98976314a450cb292eeeec1515)([#6728](https://www.github.com/tauri-apps/tauri/pull/6728)) Moved the `clipboard` feature to its own plugin in the plugins-workspace repository. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed extract and move APIs from `tauri::api::file`. +- [`3f17ee82`](https://www.github.com/tauri-apps/tauri/commit/3f17ee82f6ff21108806edb7b00500b8512b8dc7)([#6737](https://www.github.com/tauri-apps/tauri/pull/6737)) Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository. +- [`ae102980`](https://www.github.com/tauri-apps/tauri/commit/ae102980fcdde3f55effdc0623ea425b48d07dd1)([#6719](https://www.github.com/tauri-apps/tauri/pull/6719)) Refactor the `Context` conditional fields and only parse the tray icon on desktop. +- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Remove the updater's dialog option. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed `UpdaterEvent`. See `tauri-plugin-updater` for new usage. +- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Moved the `window` JS APIs to its own plugin in the plugins-workspace repository. +- [`22a76338`](https://www.github.com/tauri-apps/tauri/commit/22a763381622407d58ae72aa24c0afff00b40e04)([#6713](https://www.github.com/tauri-apps/tauri/pull/6713)) Expose `SafePathBuf` type in `tauri::path`. +- [`c4171152`](https://www.github.com/tauri-apps/tauri/commit/c4171152c1846f425a937e82f8af1759bcc8c9ac)([#6909](https://www.github.com/tauri-apps/tauri/pull/6909)) Enable shadows by default. +- [`dfa407ff`](https://www.github.com/tauri-apps/tauri/commit/dfa407ffcbc8a853d61139b68b55747ae49fb231)([#6763](https://www.github.com/tauri-apps/tauri/pull/6763)) Change iOS plugin init function signature to `func init_plugin() -> Plugin`. + +## \[2.0.0-alpha.8] + +- Fixes boolean plugin parameters freezing the application. + - [9de89791](https://www.github.com/tauri-apps/tauri/commit/9de897919aa7236913ba6ca7c34a68099f4ff600) fix(core): iOS plugin freezing when receiving a bool parameter ([#6700](https://www.github.com/tauri-apps/tauri/pull/6700)) on 2023-04-13 + +## \[2.0.0-alpha.7] + +- Change minimum Android SDK version to 21 for the plugin library. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 +- Improve the `run_mobile_plugin` function error handling. + - [f0570d9f](https://www.github.com/tauri-apps/tauri/commit/f0570d9feee05792cc720d26ef32da5eaed7f797) feat(core): improve `run_mobile_plugin` error handling ([#6655](https://www.github.com/tauri-apps/tauri/pull/6655)) on 2023-04-07 +- Implement `Clone` for `plugin::PluginHandle`. + - [052c5822](https://www.github.com/tauri-apps/tauri/commit/052c5822b53d55e118674d13914f58113a0d1121) feat(core): implement Clone for PluginHandle ([#6644](https://www.github.com/tauri-apps/tauri/pull/6644)) on 2023-04-05 + +## \[2.0.0-alpha.6] + +- Fix compilation issues without the shell API features. + - [a8137927](https://www.github.com/tauri-apps/tauri/commit/a813792786b55c51173e557834f515d4b2f7ce00) fix(core): compilation issues without execute or sidecar features ([#6621](https://www.github.com/tauri-apps/tauri/pull/6621)) on 2023-04-03 + +## \[2.0.0-alpha.5] + +- Fixes ProGuard rules. + - [adf4627b](https://www.github.com/tauri-apps/tauri/commit/adf4627b73bd7098772b7f3020b4aca7228bf239) fix(core): adjust ProGuard rules ([#6588](https://www.github.com/tauri-apps/tauri/pull/6588)) on 2023-03-31 +- Added `raw` encoding option to read stdout and stderr raw bytes. + - [f992e7f5](https://www.github.com/tauri-apps/tauri/commit/f992e7f58bf975c654a3daf36780b31a32bac064) chore(changes): readd change file on 2023-04-03 +- Renamed the `default-tls` feature to `native-tls` and added `rustls-tls` feature. + - [cfdee00f](https://www.github.com/tauri-apps/tauri/commit/cfdee00f2b1455a9719bc44823fdaeabbe4c1cb2) refactor(core): fix tls features, use rustls on mobile ([#6591](https://www.github.com/tauri-apps/tauri/pull/6591)) on 2023-03-30 + +## \[2.0.0-alpha.4] + +- Allow a wry plugin to be registered at runtime. + - [ae296f3d](https://www.github.com/tauri-apps/tauri/commit/ae296f3de16fb6a8badbad5555075a5861681fe5) refactor(tauri-runtime-wry): register runtime plugin after run() ([#6478](https://www.github.com/tauri-apps/tauri/pull/6478)) on 2023-03-17 +- Inject `proguard-tauri.pro` file in the Android project. + - [bef4ef51](https://www.github.com/tauri-apps/tauri/commit/bef4ef51bc2c633b88db121c2087a38dddb7d6bf) feat(android): enable minify on release, add proguard rules ([#6257](https://www.github.com/tauri-apps/tauri/pull/6257)) on 2023-02-13 +- Return `bool` in the invoke handler. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 +- Use correct lib name in xcode project. + - [d1752fb1](https://www.github.com/tauri-apps/tauri/commit/d1752fb1f6223fa47d224cb6c62df9b74944a507) fix(cli): use correct lib name in xcode project ([#6387](https://www.github.com/tauri-apps/tauri/pull/6387)) on 2023-03-08 +- Run Android and iOS native plugins on the invoke handler if a Rust plugin command is not found. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 +- Added `initialize_android_plugin` and `initialize_ios_plugin` APIs on `AppHandle`. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 +- Changed the plugin setup hook to take a second argument of type `PluginApi`. + - [6aaba834](https://www.github.com/tauri-apps/tauri/commit/6aaba83476339fa413fe34d28877a932cb485117) refactor(plugin): add PluginApi and PluginHandle, expose on setup hook ([#6291](https://www.github.com/tauri-apps/tauri/pull/6291)) on 2023-02-16 +- Refactored the implementation of the `mobile_entry_point` macro. + - [9feab904](https://www.github.com/tauri-apps/tauri/commit/9feab904bf08b5c168d4779c21d0419409a68d30) feat(core): add API to call Android plugin ([#6239](https://www.github.com/tauri-apps/tauri/pull/6239)) on 2023-02-10 +- Removed the attohttpc client. The `reqwest-*` Cargo features were also removed. + - [dddaa943](https://www.github.com/tauri-apps/tauri/commit/dddaa943e7e0bf13935d567ef2f3f73e1c913300) refactor(core): remove attohttpc client, closes [#6415](https://www.github.com/tauri-apps/tauri/pull/6415) ([#6468](https://www.github.com/tauri-apps/tauri/pull/6468)) on 2023-03-17 +- Added `App::run_mobile_plugin` and `AppHandle::run_mobile_plugin`. + - [bfb2ab24](https://www.github.com/tauri-apps/tauri/commit/bfb2ab24e0b1d0860ea6e37688b5209541f0eda1) feat: add API to call iOS plugin ([#6242](https://www.github.com/tauri-apps/tauri/pull/6242)) on 2023-02-11 +- Added the `shadow` option when creating a window and `Window::set_shadow`. + - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08 +- Implemented `with_webview` on Android and iOS. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 + +## \[2.0.0-alpha.3] + +- Update gtk to 0.16. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Show all application logs on iOS. + - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27 +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Only proxy the dev server on mobile to simplify desktop usage. + - [78eaadae](https://www.github.com/tauri-apps/tauri/commit/78eaadae2e75ab165d1970e592bb1455bb8636e3) refactor(core): only proxy on mobile ([#6126](https://www.github.com/tauri-apps/tauri/pull/6126)) on 2023-01-23 +- Removed mobile logging initialization, which will be handled by `tauri-plugin-log`. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined +- Update rfd to 0.11. + - [f0a1d9cd](https://www.github.com/tauri-apps/tauri/commit/f0a1d9cdbcfb645ce1c5f1cdd597f764991772cd) chore: update rfd and wry versions ([#6174](https://www.github.com/tauri-apps/tauri/pull/6174)) on 2023-02-03 + +## \[2.0.0-alpha.2] + +- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`. + - [9ad0a9a0](https://www.github.com/tauri-apps/tauri/commit/9ad0a9a0aa88a67c3d81ef84df4aad23556affde) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22 + +## \[2.0.0-alpha.1] + +- Implement response cache on the dev server proxy, used when the server responds with status 304. + - [3ad5e72f](https://www.github.com/tauri-apps/tauri/commit/3ad5e72ff147b76267c010c778a3b94bba209bb0) feat(core): cache dev server proxy responses for 304 status code ([#5818](https://www.github.com/tauri-apps/tauri/pull/5818)) on 2022-12-12 +- Properly proxy dev server requests with query strings and fragments. + - [a9b4cf20](https://www.github.com/tauri-apps/tauri/commit/a9b4cf20a3e9a5cc984727a56111591504e084c0) fix(core): use entire request URL on dev server proxy ([#5819](https://www.github.com/tauri-apps/tauri/pull/5819)) on 2022-12-12 + +## \[2.0.0-alpha.0] + +- Added the `default-tls` and `reqwest-default-tls` Cargo features for enabling TLS suppport to connect over HTTPS. + - [f6f9192a](https://www.github.com/tauri-apps/tauri/commit/f6f9192aa51bd842df8aa1d1aa538b12aa6c2d29) fix(core): Android compilation on Windows ([#5658](https://www.github.com/tauri-apps/tauri/pull/5658)) on 2022-11-20 +- **Breaking change:** Use the custom protocol as a proxy to the development server on all platforms except Linux. + - [6f061504](https://www.github.com/tauri-apps/tauri/commit/6f0615044d09ec58393a7ebca5e45bb175e20db3) feat(cli): add `android dev` and `ios dev` commands ([#4982](https://www.github.com/tauri-apps/tauri/pull/4982)) on 2022-08-20 +- Support `with_webview` for Android platform alowing execution of JNI code in context. + - [8ea87e9c](https://www.github.com/tauri-apps/tauri/commit/8ea87e9c9ca8ba4c7017c8281f78aacd08f45785) feat(android): with_webview access for jni execution ([#5148](https://www.github.com/tauri-apps/tauri/pull/5148)) on 2022-09-08 +- First mobile alpha release! + - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08 +- **Breaking change:** The window creation and setup hook are now called when the event loop is ready. + - [b4622ea4](https://www.github.com/tauri-apps/tauri/commit/b4622ea4d32720bc3bb2a8c740bb70cfe32fed93) refactor(app): run setup and window creation when event loop is ready ([#4914](https://www.github.com/tauri-apps/tauri/pull/4914)) on 2022-08-11 +- Export types required by the `mobile_entry_point` macro. + - [98904863](https://www.github.com/tauri-apps/tauri/commit/9890486321c9c79ccfb7c547fafee85b5c3ffa71) feat(core): add `mobile_entry_point` macro ([#4983](https://www.github.com/tauri-apps/tauri/pull/4983)) on 2022-08-21 + +## \[1.4.0] + +### New Features + +- [`7c237209`](https://www.github.com/tauri-apps/tauri/commit/7c237209207cd2938df660b6fd87d3b7d728bd03)([#6546](https://www.github.com/tauri-apps/tauri/pull/6546)) Added `tauri::VERSION` const to get Tauri's version from Rust. +- [`4c39e46a`](https://www.github.com/tauri-apps/tauri/commit/4c39e46a3b438d007f139166ab2a0ba34291a10a)([#7026](https://www.github.com/tauri-apps/tauri/pull/7026)) Added `tauri::webview_version` , to get webview version. +- [`359058ce`](https://www.github.com/tauri-apps/tauri/commit/359058cecca44a9c30b65140c44a8bb3a6dd3be8)([#5939](https://www.github.com/tauri-apps/tauri/pull/5939)) Add `tauri::api::os::locale` function to get the system locale. +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` options to the window builder. +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods on `Window`. +- [`1d99f8a3`](https://www.github.com/tauri-apps/tauri/commit/1d99f8a3c2f989d1a5ba4d805e3a40b07a3ca8a5)([#4752](https://www.github.com/tauri-apps/tauri/pull/4752)) Expose the `test` module behind the `test` Cargo feature. +- [`000104bc`](https://www.github.com/tauri-apps/tauri/commit/000104bc3bc0c9ff3d20558ab9cf2080f126e9e0)([#6472](https://www.github.com/tauri-apps/tauri/pull/6472)) Add `Window::is_focused` and `Manager::get_focused_window` getters. +- [`441f9646`](https://www.github.com/tauri-apps/tauri/commit/441f96465488a4f8a5731cc51b8ac97b685898c7)([#5491](https://www.github.com/tauri-apps/tauri/pull/5491)) Add `MenuHandle::try_get_item` and `SystemTrayHandle::try_get_item` which returns a `Option` instead of panicking. + +### Enhancements + +- [`45330e38`](https://www.github.com/tauri-apps/tauri/commit/45330e38193d0b2a01aa926aec433acc6b8f6597)([#6375](https://www.github.com/tauri-apps/tauri/pull/6375)) Enhance the `asset` protocol to support streaming of large files. +- [`df89ccc1`](https://www.github.com/tauri-apps/tauri/commit/df89ccc1912db6b81d43d56c9e6d66980ece2e8d)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Support `passive` mode for NSIS updater. +- [`cd3846c8`](https://www.github.com/tauri-apps/tauri/commit/cd3846c8ce61ab2879b3911e831525e6242aaab2)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Restart the app after the NSIS updater is finished. +- [`db7c5fbf`](https://www.github.com/tauri-apps/tauri/commit/db7c5fbf2e86f3694720f65834eb2c258b7c1291)([#7143](https://www.github.com/tauri-apps/tauri/pull/7143)) Remove `attohttpc` in favor of `reqwest`. +- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85. +- [`5d85d099`](https://www.github.com/tauri-apps/tauri/commit/5d85d0990cd13a1446953a58633edc24eda55afe)([#7128](https://www.github.com/tauri-apps/tauri/pull/7128)) Send updater status events even if default dialog is enabled. + +### Bug Fixes + +- [`82169e69`](https://www.github.com/tauri-apps/tauri/commit/82169e69fc904d2c7980534c4479bb6f38259fb4)([#5208](https://www.github.com/tauri-apps/tauri/pull/5208)) Fix parsing `allowlist > http > scope` urls that added a trailing slash which broke matching the incoming requests url. +- [`b41b57eb`](https://www.github.com/tauri-apps/tauri/commit/b41b57ebb27befd366db5befaafb6043c18fdfef)([#7105](https://www.github.com/tauri-apps/tauri/pull/7105)) Fix panics when registering an invalid global shortcuts or checking it is registered and return proper errors instead. +- [`aecf1469`](https://www.github.com/tauri-apps/tauri/commit/aecf14690947d109745b4ad823a3e8f4338de47a)([#6889](https://www.github.com/tauri-apps/tauri/pull/6889)) Fix IPC failing after a failed navigation to an external URL. +- [`076e1a81`](https://www.github.com/tauri-apps/tauri/commit/076e1a81a50468e3dfb34ae9ca7e77c5e1758daa)([#7119](https://www.github.com/tauri-apps/tauri/pull/7119)) Fix unlistening to window events failing sometimes. +- [`3f35b452`](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42)([#4080](https://www.github.com/tauri-apps/tauri/pull/4080)) Fix `WindowBuilder::on_navigation` handler not registered properly. +- [`0503eb69`](https://www.github.com/tauri-apps/tauri/commit/0503eb69ce7df6b4ed8f5249fdb519b86cd57d8d)([#7078](https://www.github.com/tauri-apps/tauri/pull/7078)) On macOS and Linux, fix app crashing when creating a window with `data:` uri. +- [`3700793a`](https://www.github.com/tauri-apps/tauri/commit/3700793a2f1ea3686b1889c345d73007bb622a29)([#6934](https://www.github.com/tauri-apps/tauri/pull/6934)) Emit `UPTODATE` update status to javascript when the updater server returns status code `204` +- [`ff5e4dbb`](https://www.github.com/tauri-apps/tauri/commit/ff5e4dbbb01bf3fc9c5143df732c75eef6fd98cb)([#6794](https://www.github.com/tauri-apps/tauri/pull/6794)) Fix some configurations not applied when creating the window through Javascript. +- [`65fd674f`](https://www.github.com/tauri-apps/tauri/commit/65fd674f50ba0395d5cea23daf9d2f49f95647fe)([#6652](https://www.github.com/tauri-apps/tauri/pull/6652)) Play a sound when showing a notification on Windows. +- [`696d77c3`](https://www.github.com/tauri-apps/tauri/commit/696d77c3ce480f4a3b4c1c57dae64ff2bb7842ce)([#4493](https://www.github.com/tauri-apps/tauri/pull/4493)) Fixes global events not being received on window-specific event listeners. + +## \[1.3.0] + +- Added the `additional_browser_args` option when creating a window. + - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14 +- Fix passing `--profile` to cargo in `tauri build` causing conflict with `--release` passed by the CLI. + - [bfa69691](https://www.github.com/tauri-apps/tauri/commit/bfa69691a5171af97cc6a6d880cb3090338ed9e9) fix(cli): detect `--profile`. closes [#6255](https://www.github.com/tauri-apps/tauri/pull/6255) ([#6268](https://www.github.com/tauri-apps/tauri/pull/6268)) on 2023-02-18 +- Added the `content_protected` option when creating a window and `Window::set_content_protected` to change it at runtime. + - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13 +- Fix serialization of js `Map` when used in `invoke`. + - [d4d6a98d](https://www.github.com/tauri-apps/tauri/commit/d4d6a98d98fd09ba4e9ee4857ef3604d9e454337) fix(core): convert js `Map` to object before serialization, closes [#6078](https://www.github.com/tauri-apps/tauri/pull/6078) ([#6099](https://www.github.com/tauri-apps/tauri/pull/6099)) on 2023-01-19 +- Added `Window::on_navigation`. + - [3f35b452](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42) Expose wry navigation_handler via WindowBuilder closes [#4080](https://www.github.com/tauri-apps/tauri/pull/4080) ([#5686](https://www.github.com/tauri-apps/tauri/pull/5686)) on 2022-12-27 +- Sync `__TAURI_METADATA__.__windows` across all windows. + - [146a794c](https://www.github.com/tauri-apps/tauri/commit/146a794cb696816854648d33e2124e82154c7b2f) fix(core): sync windows metadata across all windows, closes [#5571](https://www.github.com/tauri-apps/tauri/pull/5571) ([#5615](https://www.github.com/tauri-apps/tauri/pull/5615)) on 2022-12-27 +- Fix `UpdaterBuilder::check` returning a parsing error when `204` is sent from server where it should instead return a `UpToDate` error. + - [eb1ec041](https://www.github.com/tauri-apps/tauri/commit/eb1ec0416c52c75830dee77e4d714d882d1145b3) fix(core/updater): read and parse response after checking status code, closes [#6192](https://www.github.com/tauri-apps/tauri/pull/6192) ([#6575](https://www.github.com/tauri-apps/tauri/pull/6575)) on 2023-03-31 +- Added `OkWithLabel` and `OkCancelWithLabels` variants to the `api::dialog::MessageDialogButtons` enum to set the text of the dialog buttons. + - [00e1efaa](https://www.github.com/tauri-apps/tauri/commit/00e1efaa9b33876d41dd360624b69971e70d3856) feat: customize button texts of message dialog ([#4383](https://www.github.com/tauri-apps/tauri/pull/4383)) on 2022-12-28 +- Added `Builder::device_event_filter` and `App::set_device_event_filter` methods. + - [73fd60ee](https://www.github.com/tauri-apps/tauri/commit/73fd60eef2b60f5dc84525ef9c315f4d80c4414f) expose set_device_event_filter in tauri ([#5562](https://www.github.com/tauri-apps/tauri/pull/5562)) on 2022-12-13 +- Fix resize glitch when double clicking a custom titlebar in the top resize area. + - [4892637f](https://www.github.com/tauri-apps/tauri/commit/4892637f83b0f64822a5d304e8786a4c92a8957d) fix: Resizing glitch on custom titlebar click (closes [#2549](https://www.github.com/tauri-apps/tauri/pull/2549)) ([#5966](https://www.github.com/tauri-apps/tauri/pull/5966)) on 2023-01-04 +- Fixes tray events not being delivered. + - [138cb8d7](https://www.github.com/tauri-apps/tauri/commit/138cb8d739b15bccdb388e555c20f17ffe16318c) fix(tauri-runtime-wry): tray event listener not registered ([#6270](https://www.github.com/tauri-apps/tauri/pull/6270)) on 2023-02-14 +- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`. + - [72389b00](https://www.github.com/tauri-apps/tauri/commit/72389b00d7b495ffd7750eb1e75a3b8537d07cf3) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22 +- Add `is_minimized()` window method. + - [62144ef3](https://www.github.com/tauri-apps/tauri/commit/62144ef3be63b237869e511826edfb938e2c7174) feat: add is_minimized (fix [#3878](https://www.github.com/tauri-apps/tauri/pull/3878)) ([#5618](https://www.github.com/tauri-apps/tauri/pull/5618)) on 2022-12-13 +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 +- Update the `open` crate to v3.2 to fix an URL encoding bug on Windows. + - [708efbd9](https://www.github.com/tauri-apps/tauri/commit/708efbd9b72508a5a5aa55092a48d3218e008ce3) fix(core/tauri): upgrade `open` to 3.2 to fix a bug on Windows ([#6441](https://www.github.com/tauri-apps/tauri/pull/6441)) on 2023-04-06 +- Added support to `mailto:` and `tel:` links on the shell API. + - [d0d873e3](https://www.github.com/tauri-apps/tauri/commit/d0d873e39a3cd5e51e9cf0145a024ffdb0c2a941) feat(core): add support to mailto: and tel: links, closes [#5521](https://www.github.com/tauri-apps/tauri/pull/5521) ([#5544](https://www.github.com/tauri-apps/tauri/pull/5544)) on 2022-12-12 +- Pin `os_info` to `=3.5`. + - [a8d640b3](https://www.github.com/tauri-apps/tauri/commit/a8d640b3c659c7cfea23fe60cc5d9ef377841c5e) fix(core): pin unarray and os_info ([#6212](https://www.github.com/tauri-apps/tauri/pull/6212)) on 2023-02-07 +- Pin raw-window-handle to 0.5.0 to keep MSRV. + - [c46c09f3](https://www.github.com/tauri-apps/tauri/commit/c46c09f31d9f5169ca8a7e62406a9ea170e3a5c5) fix(deps): pin raw-window-handle to 0.5.0 ([#6480](https://www.github.com/tauri-apps/tauri/pull/6480)) on 2023-03-17 +- Pin `time` to `0.3.15`. + - [3d16461b](https://www.github.com/tauri-apps/tauri/commit/3d16461b68583ba7db037fbc217786e79b46ddf2) fix(core): pin time to 0.3.15 ([#6312](https://www.github.com/tauri-apps/tauri/pull/6312)) on 2023-02-19 +- Added configuration to specify remote URLs allowed to access the IPC. + - [ee71c31f](https://www.github.com/tauri-apps/tauri/commit/ee71c31fd09cc5427da6d29d37c003a914547696) feat(core): allow configuring remote domains with IPC access, closes [#5088](https://www.github.com/tauri-apps/tauri/pull/5088) ([#5918](https://www.github.com/tauri-apps/tauri/pull/5918)) on 2023-04-11 +- Add `title` getter on window. + - [233e43b0](https://www.github.com/tauri-apps/tauri/commit/233e43b0c34fada1ca025378533a0b76931a6540) feat: add `title` getter on window, closes [#5023](https://www.github.com/tauri-apps/tauri/pull/5023) ([#5515](https://www.github.com/tauri-apps/tauri/pull/5515)) on 2022-12-13 +- Implement `SystemTray::with_tooltip` and `SystemTrayHandle::set_tooltip` for Windows and macOS. + - [2265e097](https://www.github.com/tauri-apps/tauri/commit/2265e09718f6ebfeb1d200f11e1e1e069075af6e) feat(windows): implement `with_tooltip` ([#5938](https://www.github.com/tauri-apps/tauri/pull/5938)) on 2023-01-01 +- Added window's `url()` getter. + - [d17027e1](https://www.github.com/tauri-apps/tauri/commit/d17027e1a0db3e8c5ae81fc4f472c5918fbce611) feat: expose url method ([#5914](https://www.github.com/tauri-apps/tauri/pull/5914)) on 2022-12-26 +- On Windows, change webview theme based on Window theme for more accurate `prefers-color-scheme` support. + - [7a8d570d](https://www.github.com/tauri-apps/tauri/commit/7a8d570db72667367eb24b75ddc5dd07a968f7c0) fix: sync webview theme with window theme on Windows, closes [#5802](https://www.github.com/tauri-apps/tauri/pull/5802) ([#5874](https://www.github.com/tauri-apps/tauri/pull/5874)) on 2022-12-27 +- Add a method to the `WindowBuilder` struct to recreate windows from tauri.conf.json configurations. + - [49dff27e](https://www.github.com/tauri-apps/tauri/commit/49dff27ef1b36c48dbfd49f44b9b3ac07b372bdf) feat(core): create WindowBuilder from WindowConfig ([#6073](https://www.github.com/tauri-apps/tauri/pull/6073)) on 2023-01-17 +- On Windows, Fix missing `WindowEvent::Focused` in `App::run` callback. + - [ff4ea1ea](https://www.github.com/tauri-apps/tauri/commit/ff4ea1eabbf2874b113c6b4698002929bbac737a) fix: dispatch focus event to app.run on Windows, closes [#6460](https://www.github.com/tauri-apps/tauri/pull/6460) ([#6504](https://www.github.com/tauri-apps/tauri/pull/6504)) on 2023-03-31 +- Pin `winnow` crate to 0.4.1 to keep the 1.60 MSRV. + +## \[1.2.5] + +- Block remote URLs from accessing the IPC. + - [9c0593c33](https://www.github.com/tauri-apps/tauri/commit/9c0593c33af52cd9e00ec784d15f63efebdf039c) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[1.2.4] + +- Pin `ignore` to `=0.4.18`. + - [adcb082b](https://www.github.com/tauri-apps/tauri/commit/adcb082b1651ecb2a6208b093e12f4185aa3fc98) chore(deps): pin `ignore` to =0.4.18 on 2023-01-17 + +## \[1.2.3] + +- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`. + - [f1b0ad6e](https://www.github.com/tauri-apps/tauri/commit/f1b0ad6e8b721cf1420a9a4b9be5b05c39941d16) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22 + +## \[1.2.2] + +- Invoke event listener in windows safely to avoid causing uncaught errors in windows that have loaded external urls + - [c14b1df3](https://www.github.com/tauri-apps/tauri/commit/c14b1df37284020b3edb32400eb4b9e32c945472) fix(core): Invoke event listener in windows safely to avoid causing uncaught errors in windows that have loaded external urls ([#5563](https://www.github.com/tauri-apps/tauri/pull/5563)) on 2022-12-08 +- Cleanup sidecar and tray icons when calling `app.exit()` from JS. + - [0f269608](https://www.github.com/tauri-apps/tauri/commit/0f26960891228c5909e06d9f850c44ffaebf536c) fix(core/api): cleanup before exit ([#5765](https://www.github.com/tauri-apps/tauri/pull/5765)) on 2022-12-07 +- Fix compatibility with older Linux distributions. + - [b490308c](https://www.github.com/tauri-apps/tauri/commit/b490308c8897b893292951754607c2253abbc6e1) fix(core): compilation error on older Linux versions, fixes [#5684](https://www.github.com/tauri-apps/tauri/pull/5684) ([#5697](https://www.github.com/tauri-apps/tauri/pull/5697)) on 2022-11-28 +- Add `tauri::Builder::enable_macos_default_menu` to enable or disable the default menu creation on macOS. + - [8866ecac](https://www.github.com/tauri-apps/tauri/commit/8866ecac3cd1af8bf02e29569d605be5a1afe22c) feat(core): add `tauri::Builder::enable_macos_default_menu` ([#5756](https://www.github.com/tauri-apps/tauri/pull/5756)) on 2022-12-07 + - [b293da35](https://www.github.com/tauri-apps/tauri/commit/b293da35dd5ae8c1569a3f3c994b4c1a4c227f4a) fix(changes): change `enable_macos_default_menu` bump to patch on 2022-12-08 + +## \[1.2.1] + +- Fixes a double serialization on the IPC. + - [677838cc](https://www.github.com/tauri-apps/tauri/commit/677838ccfadfdf37039be53bfad666bbe1dab8c3) fix double serialize on invoke ([#5639](https://www.github.com/tauri-apps/tauri/pull/5639)) on 2022-11-20 +- Moved the custom protocol headers support on Linux behind the `linux-protocol-headers` Cargo feature to enhance compatibility with older Linux distributions. + - [d7109460](https://www.github.com/tauri-apps/tauri/commit/d710946064c47fa488eca01a62403e70b2b5ff87) refactor: move Linux custom protocol headers support behind feature flag ([#5683](https://www.github.com/tauri-apps/tauri/pull/5683)) on 2022-11-24 +- Fixes definition of `impl HasRawDisplayHandle` for `AppHandle` and `App`. + - [ed43ff32](https://www.github.com/tauri-apps/tauri/commit/ed43ff324330d1bd9c042a53a6636dfc7d97b410) fix(tauri): add missing generics on AppHandle and App ([#5642](https://www.github.com/tauri-apps/tauri/pull/5642)) on 2022-11-17 + +## \[1.2.0] + +- Add `accept_first_mouse` option for macOS windows. + - [95f467ad](https://www.github.com/tauri-apps/tauri/commit/95f467add51448319983c54e2f382c7c09fb72d6) feat(core): add window `accept_first_mouse` option, closes [#5347](https://www.github.com/tauri-apps/tauri/pull/5347) ([#5374](https://www.github.com/tauri-apps/tauri/pull/5374)) on 2022-10-17 +- Add new app-specific `BaseDirectory` enum variants `AppConfig`, `AppData`, `AppLocalData`, `AppCache` and `AppLog` along with equivalent functions in `path` module and deprecated ambiguous variants `Log` and `App` along with their equivalent functions in `path` module. + - [5d89905e](https://www.github.com/tauri-apps/tauri/commit/5d89905e39ce0e6eaaec50a693679335449edb32) feat(api): add app-specific directory APIs, closes [#5263](https://www.github.com/tauri-apps/tauri/pull/5263) ([#5272](https://www.github.com/tauri-apps/tauri/pull/5272)) on 2022-09-28 +- Set the correct mimetype when streaming files through `asset:` protocol + - [39443b43](https://www.github.com/tauri-apps/tauri/commit/39443b4350bd208c4d6eec5e1095f215199f8aa3) fix(core): set correct mimetype for asset protocol streams, closes [#5203](https://www.github.com/tauri-apps/tauri/pull/5203) ([#5210](https://www.github.com/tauri-apps/tauri/pull/5210)) on 2022-09-30 + - [2d9c2b47](https://www.github.com/tauri-apps/tauri/commit/2d9c2b472416339829f9113f976f193bf8e0665f) Revert "fix(core): set correct mimetype for asset protocol streams, closes [#5203](https://www.github.com/tauri-apps/tauri/pull/5203) ([#5210](https://www.github.com/tauri-apps/tauri/pull/5210))" on 2022-09-30 + - [9b1a6a1c](https://www.github.com/tauri-apps/tauri/commit/9b1a6a1c02b8d62dd47d9ce42aa05723d7c1b892) fix(core): set correct mimetype for asset protocol streams, [#5203](https://www.github.com/tauri-apps/tauri/pull/5203) ([#5536](https://www.github.com/tauri-apps/tauri/pull/5536)) on 2022-11-04 +- Disable automatic window tabbing on macOS when the `tabbing_identifier` option is not defined, the window is transparent or does not have decorations. + - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19 +- The custom protocol now validates the request URI. This has implications when using the `asset` protocol without the `convertFileSrc` helper, the URL must now use the `asset://localhost/$filePath` format. + - [357480f4](https://www.github.com/tauri-apps/tauri/commit/357480f4ae43aa8da99f7ba61ae2ee51b4552c60) feat(core): custom protocol headers on Linux, closes [#4496](https://www.github.com/tauri-apps/tauri/pull/4496) ([#5421](https://www.github.com/tauri-apps/tauri/pull/5421)) on 2022-10-17 +- Escape glob special characters in files/directories when dropping files or using the open/save dialogs. + - [4cbdf0fb](https://www.github.com/tauri-apps/tauri/commit/4cbdf0fb1c0de5004eab51c36d5843a9816f18af) fix(core): escape glob characters in drop/dialogs , closes [#5234](https://www.github.com/tauri-apps/tauri/pull/5234) ([#5237](https://www.github.com/tauri-apps/tauri/pull/5237)) on 2022-10-05 +- Properly emit events with object payload. + - [79dd6e16](https://www.github.com/tauri-apps/tauri/commit/79dd6e16a7306351e2acf21166506b2876b58a7e) fix(core): properly emit events with object payload, closes [#5482](https://www.github.com/tauri-apps/tauri/pull/5482) ([#5492](https://www.github.com/tauri-apps/tauri/pull/5492)) on 2022-10-27 +- Fixes access to the `WebviewWindow.getByLabel` function in a `tauri://window-created` event listener. + - [e00b1e5f](https://www.github.com/tauri-apps/tauri/commit/e00b1e5f94b3f841bf107cc17ee74be9203ea080) fix(core): update metadata before window-created listeners, closes [#5191](https://www.github.com/tauri-apps/tauri/pull/5191) ([#5458](https://www.github.com/tauri-apps/tauri/pull/5458)) on 2022-10-22 +- Fixes resource reading being always rejected by the scope. + - [a06dc699](https://www.github.com/tauri-apps/tauri/commit/a06dc6993148f10ff7623c9dcc81f313dd960ad0) fix(core): canonicalize resource dir to fix scope check, closes [#5196](https://www.github.com/tauri-apps/tauri/pull/5196) ([#5218](https://www.github.com/tauri-apps/tauri/pull/5218)) on 2022-09-29 +- Fixes `__TAURI_PATTERN__` object freeze. + - [49f06ca4](https://www.github.com/tauri-apps/tauri/commit/49f06ca4b9f1d02933e46bbc50330b84ac81be87) fix: deepfreeze check by prop ([#5407](https://www.github.com/tauri-apps/tauri/pull/5407)) on 2022-10-17 +- Readd the option to create an unfocused window via the `focused` method. The `focus` function has been deprecated. + - [4036e15f](https://www.github.com/tauri-apps/tauri/commit/4036e15f5af933bdc0d0913508b5103958afc143) feat(core): reimplement window initial focus flag, closes [#5120](https://www.github.com/tauri-apps/tauri/pull/5120) ([#5338](https://www.github.com/tauri-apps/tauri/pull/5338)) on 2022-10-08 +- Add `hidden_title` option for macOS windows. + - [321f3fed](https://www.github.com/tauri-apps/tauri/commit/321f3fed19df40c1223099bce953332b7f00f7a9) feat(macos): `title_bar_style` and `hidden_title` window options, closes [#2663](https://www.github.com/tauri-apps/tauri/pull/2663) ([#3965](https://www.github.com/tauri-apps/tauri/pull/3965)) on 2022-09-30 +- Custom protocol headers are now implemented on Linux when running on webkit2gtk 2.36 or above. + - [357480f4](https://www.github.com/tauri-apps/tauri/commit/357480f4ae43aa8da99f7ba61ae2ee51b4552c60) feat(core): custom protocol headers on Linux, closes [#4496](https://www.github.com/tauri-apps/tauri/pull/4496) ([#5421](https://www.github.com/tauri-apps/tauri/pull/5421)) on 2022-10-17 +- Add `App::show()`, `AppHandle::show()`, `App::hide()` and `AppHandle::hide()` for hiding/showing the entire application on macOS. + - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03 +- Fix a deadlock when modifying the menu in the `on_menu_event` closure. + - [ae65951b](https://www.github.com/tauri-apps/tauri/commit/ae65951bc477126b71816d77424f8167814bbe8d) fix(core): fix deadlock in `on_menu_event`, closes [#5254](https://www.github.com/tauri-apps/tauri/pull/5254) ([#5257](https://www.github.com/tauri-apps/tauri/pull/5257)) on 2022-09-28 +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 +- Resolve base system directory in shell scope. + - [99fe1c56](https://www.github.com/tauri-apps/tauri/commit/99fe1c562ffcea4089f785c73f4e6706d4ebc16b) fix(core): resolve base dir in shell scope, closes [#5480](https://www.github.com/tauri-apps/tauri/pull/5480) ([#5508](https://www.github.com/tauri-apps/tauri/pull/5508)) on 2022-11-04 +- Added `tabbing_identifier` to the window builder on macOS. + - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19 +- Add `title_bar_style` option for macOS windows. + - [321f3fed](https://www.github.com/tauri-apps/tauri/commit/321f3fed19df40c1223099bce953332b7f00f7a9) feat(macos): `title_bar_style` and `hidden_title` window options, closes [#2663](https://www.github.com/tauri-apps/tauri/pull/2663) ([#3965](https://www.github.com/tauri-apps/tauri/pull/3965)) on 2022-09-30 +- Added methods to set the system tray title on macOS. + - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30 +- Added the `user_agent` option when creating a window. + - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02 + +## \[1.1.4] + +- Block remote URLs from accessing the IPC. + - [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[1.1.3] + +- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`. + - [2654c0f4](https://www.github.com/tauri-apps/tauri/commit/2654c0f49da23434d36447d0908fa24e61ff5e4e) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22 + +## \[1.1.2] + +- Escape glob special characters in files/directories when dropping files or using the open/save dialogs. + - [e4dc5bed](https://www.github.com/tauri-apps/tauri/commit/e4dc5bedbb54fbe6e06ab833d7fb7e0cacebad10) fix(core): escape glob characters in drop/dialogs , closes [#5234](https://www.github.com/tauri-apps/tauri/pull/5234) ([#5237](https://www.github.com/tauri-apps/tauri/pull/5237)) on 2022-10-05 + +## \[1.1.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - Bumped due to a bump in tauri-utils. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[1.1.0] + +- Implement `api::http::ClientBuilder::max_redirections` for the default attohttpc client. + - [ba5560b2](https://www.github.com/tauri-apps/tauri/commit/ba5560b2a1a61666d8e0bda31424333815714b2f) feat(core): implement max_redirections for attohttpc, ref [#4795](https://www.github.com/tauri-apps/tauri/pull/4795) ([#4811](https://www.github.com/tauri-apps/tauri/pull/4811)) on 2022-07-31 +- Implement `From for std::process::Command`. + - [9f1d34c2](https://www.github.com/tauri-apps/tauri/commit/9f1d34c288cbe64f0453cf210bc9488bb42ed19a) feat: implement From for std::process::Command, closes [#4673](https://www.github.com/tauri-apps/tauri/pull/4673) ([#4836](https://www.github.com/tauri-apps/tauri/pull/4836)) on 2022-08-02 +- Added support to configuration files in TOML format (Tauri.toml file). + - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02 +- Enhance `SystemTray::with_icon` to accept `tauri::Icon`. + - [964926ff](https://www.github.com/tauri-apps/tauri/commit/964926ff850b82e104d29fec4c8a1d9a16798c06) feat(core): enhance `SystemTray::with_icon` ([#4849](https://www.github.com/tauri-apps/tauri/pull/4849)) on 2022-08-03 +- Fixes CLI parser ignoring inner subcommands. + - [dcd50667](https://www.github.com/tauri-apps/tauri/commit/dcd506676c0a15ac4af7705b62574cc2eea3bb43) fix(core): parse inner CLI subcommands, closes [#4688](https://www.github.com/tauri-apps/tauri/pull/4688) ([#4841](https://www.github.com/tauri-apps/tauri/pull/4841)) on 2022-08-02 +- Fix `fs.readDir` recursive option reading symlinked directories that are not allowed by the scope. + - [f4121c12](https://www.github.com/tauri-apps/tauri/commit/f4121c128e69b06c3eb5eea14dd2af4720afed49) fix(endpoints/fs/readDir): don't read symlinks that are not allowed b… ([#5123](https://www.github.com/tauri-apps/tauri/pull/5123)) on 2022-09-08 +- Fix typo in invalid state access panic message. + - [c7fec3e1](https://www.github.com/tauri-apps/tauri/commit/c7fec3e1ff73f7d857548cab78777a3aaf084c68) fix typo in state.rs ([#4699](https://www.github.com/tauri-apps/tauri/pull/4699)) on 2022-07-25 +- Fixes updater breaking the app icon in Finder. + - [58fc1f21](https://www.github.com/tauri-apps/tauri/commit/58fc1f2150b6ddd1b322deb03ca9083222fc3522) fix(updater): blank icon after update on macOS, closes [#4613](https://www.github.com/tauri-apps/tauri/pull/4613) ([#4861](https://www.github.com/tauri-apps/tauri/pull/4861)) on 2022-08-04 +- Implement theme APIs for Linux. + - [f21cbecd](https://www.github.com/tauri-apps/tauri/commit/f21cbecdeb3571ac4ad971b9a865ff62a131a176) feat(core): implement theme APIs for Linux ([#4808](https://www.github.com/tauri-apps/tauri/pull/4808)) on 2022-08-02 +- Implement `raw_window_handle::HasRawDisplayHandle` for `App` and `AppHandle` + - [0ad9531d](https://www.github.com/tauri-apps/tauri/commit/0ad9531d799d81e1f807000a9d74dfd7998206fe) chore(deps): update tao to 0.13, wry to 0.20, rfd to 0.10, raw-window-handle to 0.5 ([#4804](https://www.github.com/tauri-apps/tauri/pull/4804)) on 2022-07-31 +- Retain command line arguments in `api::process::restart`. + - [6218c31e](https://www.github.com/tauri-apps/tauri/commit/6218c31e175d43b59b87bd5b36ee773467566f67) fix(core): retain command line arguments on restart, closes [#4760](https://www.github.com/tauri-apps/tauri/pull/4760) ([#4763](https://www.github.com/tauri-apps/tauri/pull/4763)) on 2022-07-25 +- Added APIs to create a system tray at runtime. + - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09 +- Add `api::Command::encoding` method to set the stdout/stderr encoding. + - [d8cf9f9f](https://www.github.com/tauri-apps/tauri/commit/d8cf9f9fcd617ac24fa418952fd4a32c08804f5c) Command support for specified character encoding, closes [#4644](https://www.github.com/tauri-apps/tauri/pull/4644) ([#4772](https://www.github.com/tauri-apps/tauri/pull/4772)) on 2022-07-28 +- Do not follow redirects when `api::http::ClientBuilder::max_redirections` is `0`. + - [d576e8ae](https://www.github.com/tauri-apps/tauri/commit/d576e8ae72b025ca41f96ddf7a885b84f950a4b1) feat(core): do not follow redirects if `max_redirects` is 0 closes [#4795](https://www.github.com/tauri-apps/tauri/pull/4795) ([#4812](https://www.github.com/tauri-apps/tauri/pull/4812)) on 2022-07-31 +- Added the `SystemTrayHandle::destroy` method. + - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09 +- Added `native-tls-vendored` and `reqwest-native-tls-vendored` Cargo features to compile and statically link to a vendored copy of OpenSSL on Linux. + - [331f3460](https://www.github.com/tauri-apps/tauri/commit/331f3460027614738ddbbbbcd04bfc59a349f3de) feat(core): add option to use vendored openssl, closes [#4470](https://www.github.com/tauri-apps/tauri/pull/4470) ([#4809](https://www.github.com/tauri-apps/tauri/pull/4809)) on 2022-08-02 +- Update windows to 0.39.0 and webview2-com to 0.19.1. + - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03 +- Add `exists` function to the fs module. + - [3c62dbc9](https://www.github.com/tauri-apps/tauri/commit/3c62dbc902c904d35a7472ce72a969084c95fbbe) feat(api): Add `exists` function to the fs module. ([#5060](https://www.github.com/tauri-apps/tauri/pull/5060)) on 2022-09-15 + +## \[1.0.9] + +- Block remote URLs from accessing the IPC. + - [fa90214b0](https://www.github.com/tauri-apps/tauri/commit/fa90214b052b1a5d38d54fbf1ca422b4c37cfd1f) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[1.0.8] + +- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`. + - [f0602e7c](https://www.github.com/tauri-apps/tauri/commit/f0602e7c294245ab6ef6fbf2a976ef398340ef58) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22 + +## \[1.0.7] + +- Escape glob special characters in files/directories when dropping files or using the open/save dialogs. + - [bcd9dc7f](https://www.github.com/tauri-apps/tauri/commit/bcd9dc7f859fa7e65fea5de835fa938ca1368eaf) fix(core): escape glob characters in drop/dialogs , closes [#5234](https://www.github.com/tauri-apps/tauri/pull/5234) ([#5237](https://www.github.com/tauri-apps/tauri/pull/5237)) on 2022-11-08 + +## \[1.0.6] + +- Fix `fs.readDir` recursive option reading symlinked directories that are not allowed by the scope. + - [bb178829](https://www.github.com/tauri-apps/tauri/commit/bb178829086e80916f9be190f02d83bc25802799) fix(endpoints/fs/readDir): don't read symlinks that are not allowed b… ([#5123](https://www.github.com/tauri-apps/tauri/pull/5123)) on 2022-09-08 + +## \[1.0.5] + +- Escape the MSI file path when running msiexec via powershell. + - [9af43134](https://www.github.com/tauri-apps/tauri/commit/9af43134e1e58369907281024d31bdb4d16ee6f6) fix(updater): escape MSI path ([#4737](https://www.github.com/tauri-apps/tauri/pull/4737)) on 2022-07-22 + +## \[1.0.4] + +- Reduce the amount of allocations when converting cases. + - [bc370e32](https://www.github.com/tauri-apps/tauri/commit/bc370e326810446e15b1f50fb962b980114ba16b) feat: reduce the amount of `heck`-related allocations ([#4634](https://www.github.com/tauri-apps/tauri/pull/4634)) on 2022-07-11 + +## \[1.0.3] + +- `tauri::Builder` will now include a default menu for macOS without explicitly using `Menu::os_default`, you can still override it through `tauri::Builder::menu` or remove it using `tauri::Builder::enable_macos_default_menu(false)`. + - [91055883](https://www.github.com/tauri-apps/tauri/commit/9105588373cc8401bd9ad79bdef26f509b2d76b7) feat: add implicit default menu for macOS only, closes [#4551](https://www.github.com/tauri-apps/tauri/pull/4551) ([#4570](https://www.github.com/tauri-apps/tauri/pull/4570)) on 2022-07-04 +- Use `toString()` on message/confirm/ask dialogs title and message values. + - [b8cd2a79](https://www.github.com/tauri-apps/tauri/commit/b8cd2a7993cd2aa5b71b30c545b3307245d254bf) feat(api): call `toString()` on dialog title and message, closes [#4583](https://www.github.com/tauri-apps/tauri/pull/4583) ([#4588](https://www.github.com/tauri-apps/tauri/pull/4588)) on 2022-07-04 +- Fix stack overflow on Windows on commands by changing the implementation of the `async_runtime::spawn` method. + - [7e3ac847](https://www.github.com/tauri-apps/tauri/commit/7e3ac8475cfa146f80e13cd4e3cdf82502018d9a) fix(core): command stack overflow on Windows, closes [#4548](https://www.github.com/tauri-apps/tauri/pull/4548) ([#4562](https://www.github.com/tauri-apps/tauri/pull/4562)) on 2022-07-03 +- Emits RunEvent::Exit prior to killing child processes managed by Tauri, allowing graceful shutdown of sidecar binaries. + - [34879f73](https://www.github.com/tauri-apps/tauri/commit/34879f73446e218fd08d0d079c086fa556a08b4e) fix: allow graceful shutdown of sidecar commands on exit ([#4561](https://www.github.com/tauri-apps/tauri/pull/4561)) on 2022-07-03 +- Added option to disable tray menu on left click on macOS. + - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05 +- Only run the updater default dialog mode in supported platforms or development mode. + - [e29fff25](https://www.github.com/tauri-apps/tauri/commit/e29fff2566bf130e9f9cae432edba2f842607516) fix(updater): do not run in dialog mode on .deb, closes [#4573](https://www.github.com/tauri-apps/tauri/pull/4573) ([#4577](https://www.github.com/tauri-apps/tauri/pull/4577)) on 2022-07-05 +- Configure the updater to relaunch after installing the update on Windows. + - [0fa74534](https://www.github.com/tauri-apps/tauri/commit/0fa745344e0667434ebf3011a8b9fd776dfc5f59) feat(updater): relaunch on Windows, closes [#4220](https://www.github.com/tauri-apps/tauri/pull/4220) ([#4568](https://www.github.com/tauri-apps/tauri/pull/4568)) on 2022-07-03 + +## \[1.0.2] + +- Fixes check for local URL when an external URL is provided to the window and it is based on the configured devPath. + - [2e74d20e](https://www.github.com/tauri-apps/tauri/commit/2e74d20e834c0cb8c19fed25f8e7968f758ca8b1) fix(core): check whether external url is local, ref [#4449](https://www.github.com/tauri-apps/tauri/pull/4449) ([#4536](https://www.github.com/tauri-apps/tauri/pull/4536)) on 2022-06-30 + +## \[1.0.1] + +- Added `fn new` constructors for `PhysicalSize`, `LogicalSize`, `PhysicalPosition` and `LogicalPosition` and missing conversion methods. + - [c7d13a1c](https://www.github.com/tauri-apps/tauri/commit/c7d13a1c60cdbe0c42834ea059321d7a3a7f01a0) feat(core): add missing methods to the dpi module ([#4393](https://www.github.com/tauri-apps/tauri/pull/4393)) on 2022-06-19 +- Set the bundle name and app metadata in the Info.plist file in development mode. + - [38f5db6e](https://www.github.com/tauri-apps/tauri/commit/38f5db6e6a8b496b50d486db6fd8204266de3a69) feat(codegen): fill app metadata in development Info.plist on 2022-06-21 +- Set the application icon in development mode on macOS. + - [307c2ebf](https://www.github.com/tauri-apps/tauri/commit/307c2ebfb68238dacab6088f9c6ba310c727c68c) feat(core): set macOS app icon in development ([#4385](https://www.github.com/tauri-apps/tauri/pull/4385)) on 2022-06-19 +- Fixes the error message when using the `window.unminimize` API without enabling it in the allowlist. + - [cbceb7d6](https://www.github.com/tauri-apps/tauri/commit/cbceb7d6cf7c9ee8c093e81d8569285ef3ca5fe3) fix: some typos ([#4403](https://www.github.com/tauri-apps/tauri/pull/4403)) on 2022-06-19 +- Initialize Tauri script when `devPath` is an external URL with path. + - [079b1cc0](https://www.github.com/tauri-apps/tauri/commit/079b1cc06e1e5437686bac9049d5ac569c3f42df) fix(core): properly get external URL origin, closes [#4414](https://www.github.com/tauri-apps/tauri/pull/4414) ([#4417](https://www.github.com/tauri-apps/tauri/pull/4417)) on 2022-06-21 +- Fixes deadlock when a plugin window ready event needs to block the thread waiting on the event loop. + - [9d33d093](https://www.github.com/tauri-apps/tauri/commit/9d33d09341fd995740149f92387b88120e33dcad) fix(core): deadlock on plugin webview ready hook ([#4462](https://www.github.com/tauri-apps/tauri/pull/4462)) on 2022-06-24 +- Adjust the updater to fallback to `$HOME/.cache` or the current working directory as temp directory if the system default is in a different mount point. + - [fd125f76](https://www.github.com/tauri-apps/tauri/commit/fd125f76d768099dc3d4b2d4114349ffc31ffac9) fix(updater): fallback if tmp is on different mount point, closes [#4500](https://www.github.com/tauri-apps/tauri/pull/4500) ([#4504](https://www.github.com/tauri-apps/tauri/pull/4504)) on 2022-06-28 +- Properly fill the origin window when using `emit_to` and `emit_all` from `Window`. + - [643ae846](https://www.github.com/tauri-apps/tauri/commit/643ae846d86108b00cec748cd02a307b5badba18) fix: fire window-specific event on Window emit_to/emit_all, closes [#4493](https://www.github.com/tauri-apps/tauri/pull/4493) ([#4498](https://www.github.com/tauri-apps/tauri/pull/4498)) on 2022-06-28 +- Implement `raw_window_handle::HasRawWindowHandle` on Linux. + - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26 +- Added `on_drop` hook to the `plugin::Builder`. + - [be4bb391](https://www.github.com/tauri-apps/tauri/commit/be4bb391a9bcd76dd949c001b1ace11684a8c6dc) feat: add `AppHandle::remove_plugin` and plugin `on_drop`, closes [#4361](https://www.github.com/tauri-apps/tauri/pull/4361) ([#4443](https://www.github.com/tauri-apps/tauri/pull/4443)) on 2022-06-23 +- Refactored the `tauri-runtime-wry` plugin interface. + - [e39e2999](https://www.github.com/tauri-apps/tauri/commit/e39e2999e0ab1843a8195ba83aea3d6de705c3d8) refactor(tauri-runtime-wry): enhance plugin interface ([#4476](https://www.github.com/tauri-apps/tauri/pull/4476)) on 2022-06-27 +- Added `AppHandle::remove_plugin`. + - [be4bb391](https://www.github.com/tauri-apps/tauri/commit/be4bb391a9bcd76dd949c001b1ace11684a8c6dc) feat: add `AppHandle::remove_plugin` and plugin `on_drop`, closes [#4361](https://www.github.com/tauri-apps/tauri/pull/4361) ([#4443](https://www.github.com/tauri-apps/tauri/pull/4443)) on 2022-06-23 +- The theme API is now implemented on macOS 10.14+. + - [6d94ce42](https://www.github.com/tauri-apps/tauri/commit/6d94ce42353204a02fe9c82ed397d349439f75ef) feat(core): theme is now implemented on macOS ([#4380](https://www.github.com/tauri-apps/tauri/pull/4380)) on 2022-06-17 +- Suppress unused variable warning in release builds. + - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22 +- Update tao to 0.12 and wry to 0.19. + - [f6edc6df](https://www.github.com/tauri-apps/tauri/commit/f6edc6df29b1c45b483fa87c481a3b95730b131b) chore(deps): update tao to 0.12, wry to 0.19, closes [#3220](https://www.github.com/tauri-apps/tauri/pull/3220) ([#4502](https://www.github.com/tauri-apps/tauri/pull/4502)) on 2022-06-28 +- Added `Notification::notify` API behind the `windows7-compat` Cargo feature, which includes Windows 7 support. + - [57039fb2](https://www.github.com/tauri-apps/tauri/commit/57039fb2166571de85271b014a8711a29f06be1a) fix(core): add windows 7 notification support ([#4491](https://www.github.com/tauri-apps/tauri/pull/4491)) on 2022-06-28 + +## \[1.0.0] + +- Allow choosing multiple folders in `dialog.open`. + - [4e51dce6](https://www.github.com/tauri-apps/tauri/commit/4e51dce6ca21c7664de779bc78a04be1051371f7) fix: dialog open supports multiple dirs, fixes [#4091](https://www.github.com/tauri-apps/tauri/pull/4091) ([#4354](https://www.github.com/tauri-apps/tauri/pull/4354)) on 2022-06-15 +- Upgrade to `stable`! + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[1.0.0-rc.17] + +- Add `Menu::os_default` which will create a menu filled with default menu items and submenus. + - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15 + +## \[1.0.0-rc.16] + +- **Breaking change:** The `TrayIcon` enum has been removed and now `Icon` is used instead. + This allows you to use more image formats and use embedded icons on Linux. + - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14 + +## \[1.0.0-rc.15] + +- Fixes filesystem scope check when using the HTTP API to upload files. + - [8ce5b762](https://www.github.com/tauri-apps/tauri/commit/8ce5b7626478db5c4767e894d005fafb79aa5a7f) fix: scope check when using the HTTP API to upload files closes [#4312](https://www.github.com/tauri-apps/tauri/pull/4312) on 2022-06-10 +- Fixes a memory leak in the command system. + - [f72cace3](https://www.github.com/tauri-apps/tauri/commit/f72cace36821dc675a6d25268ae85a21bdbd6296) fix: never remove ipc callback & mem never be released ([#4274](https://www.github.com/tauri-apps/tauri/pull/4274)) on 2022-06-05 +- Fixes the `Content-Type` header value when sending multipart requests using the `reqwest-client` feature. + - [f6205afc](https://www.github.com/tauri-apps/tauri/commit/f6205afc0d0e419ecb56b9b6b04bd0a0cc9f62ca) fix(core): wrong Content-Type when using reqwest's multipart, ref [#4312](https://www.github.com/tauri-apps/tauri/pull/4312) on 2022-06-10 +- Kill sidecar processes on app exit even when only the `shell-sidecar` feature is enabled. + - [6ba91272](https://www.github.com/tauri-apps/tauri/commit/6ba9127298632531b64d2831bccec6d22ef6c874) Fix: sidecar cleanup when only `shell-sidecar` is enabled ([#4254](https://www.github.com/tauri-apps/tauri/pull/4254)) on 2022-06-04 +- Fixes a crash when a request is made to `https://tauri.$URL` on Windows where `$URL` is not `localhost/**` e.g. `https://tauri.app`. + - [74457222](https://www.github.com/tauri-apps/tauri/commit/74457222b47221f08388f528a7d52133b6734af6) fix(core): handle requests to `https://tauri.*` on Windows ([#4270](https://www.github.com/tauri-apps/tauri/pull/4270)) on 2022-06-05 +- Set notification icon to app icon on Linux. + - [235e448d](https://www.github.com/tauri-apps/tauri/commit/235e448defd8271739804d9b005ffee9c149dd8e) fix: add a default icon to notifications on linux ([#4283](https://www.github.com/tauri-apps/tauri/pull/4283)) on 2022-06-09 +- **Breaking change:** Revert the window creation to be blocking in the main thread. This ensures the window is created before using other methods, but has an issue on Windows where the program deadlocks when creating a window in a Tauri command if it is not `async`. The documentation now states that commands must be `async` in other to prevent it until the issue is fixed in Webview2. + - [69ae6f14](https://www.github.com/tauri-apps/tauri/commit/69ae6f14943f94285f4279c3a92f9003f74aacff) refactor(window): block main thread when creating a new window ([#4298](https://www.github.com/tauri-apps/tauri/pull/4298)) on 2022-06-08 +- No longer ask for permission to send notifications and always allow it. + - [f482b094](https://www.github.com/tauri-apps/tauri/commit/f482b0942276e9402ab3725957535039bacb4fef) fix: remove notification permission prompt ([#4302](https://www.github.com/tauri-apps/tauri/pull/4302)) on 2022-06-09 +- **Breaking change:** Removed the `settings` module. + - [f482b094](https://www.github.com/tauri-apps/tauri/commit/f482b0942276e9402ab3725957535039bacb4fef) fix: remove notification permission prompt ([#4302](https://www.github.com/tauri-apps/tauri/pull/4302)) on 2022-06-09 +- **Breaking change**: Removed the `gtk-tray` and `ayatana-tray` Cargo features. + - [6216eb49](https://www.github.com/tauri-apps/tauri/commit/6216eb49e72863bfb6d4c9edb8827b21406ac393) refactor(core): drop `ayatana-tray` and `gtk-tray` Cargo features ([#4247](https://www.github.com/tauri-apps/tauri/pull/4247)) on 2022-06-02 +- Call `preventDefault()` in the mousedown event handler for `[data-tauri-drag-region]` elements. + - [a0e20621](https://www.github.com/tauri-apps/tauri/commit/a0e20621f5369682700daa234ec1f4df0c3c4d28) fix: preventDefault mousedown on data-tauri-drag-region, closes [#4059](https://www.github.com/tauri-apps/tauri/pull/4059) on 2022-06-13 +- Set permission to `0o700` for the tmp folder used to move the current AppImage on the updater process. + - [b77877fd](https://www.github.com/tauri-apps/tauri/commit/b77877fd2c643c810659fd059cb1bca5b2c68238) fix(updater): set tmp folder permissions ([#4311](https://www.github.com/tauri-apps/tauri/pull/4311)) on 2022-06-12 + +## \[1.0.0-rc.14] + +- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`. + - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25 + - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27 +- **Breaking change**: `UpdateBuilder::should_update` now takes the current version as a `semver::Version` and a `RemoteRelease` struct, allowing you to check other release fields. + - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25 +- **Breaking change:** The `tauri::UpdaterEvent::UpdateEvent` date field is now an `Option`. + - [ac7656ab](https://www.github.com/tauri-apps/tauri/commit/ac7656ab19ebeda1955698ff7a16199d7055d640) refactor(updater): strong type for the `pub_date` field, ref [#4162](https://www.github.com/tauri-apps/tauri/pull/4162) ([#4218](https://www.github.com/tauri-apps/tauri/pull/4218)) on 2022-05-25 +- **Breaking change:** The updater response `pub_date` now must be a valid RFC 3339 string. + - [ac7656ab](https://www.github.com/tauri-apps/tauri/commit/ac7656ab19ebeda1955698ff7a16199d7055d640) refactor(updater): strong type for the `pub_date` field, ref [#4162](https://www.github.com/tauri-apps/tauri/pull/4162) ([#4218](https://www.github.com/tauri-apps/tauri/pull/4218)) on 2022-05-25 + +## \[1.0.0-rc.13] + +- Fix `` cannot find macro `message_dialog` `` error. + - [80458a03](https://www.github.com/tauri-apps/tauri/commit/80458a031b15c83019f11a6c222a2993e16d5dc3) Fix `message_dialog` flag ([#4204](https://www.github.com/tauri-apps/tauri/pull/4204)) on 2022-05-25 + +## \[1.0.0-rc.12] + +- Expose option to set the dialog type. + - [f46175d5](https://www.github.com/tauri-apps/tauri/commit/f46175d5d46fa3eae66ad2415a0eb1efb7d31da2) feat(core): expose option to set dialog type, closes [#4183](https://www.github.com/tauri-apps/tauri/pull/4183) ([#4187](https://www.github.com/tauri-apps/tauri/pull/4187)) on 2022-05-21 +- Expose `title` option in the message dialog API. + - [ae99f991](https://www.github.com/tauri-apps/tauri/commit/ae99f991674d77c322a2240d10ed4b78ed2f4d4b) feat(core): expose message dialog's title option, ref [#4183](https://www.github.com/tauri-apps/tauri/pull/4183) ([#4186](https://www.github.com/tauri-apps/tauri/pull/4186)) on 2022-05-21 +- Immediately create windows when using `tauri::App` as manager. + - [52d17754](https://www.github.com/tauri-apps/tauri/commit/52d177543ab5d0b316628ef30c2a32f303bc451d) fix(core): immediately create window when using tauri::App, closes [#4170](https://www.github.com/tauri-apps/tauri/pull/4170) ([#4172](https://www.github.com/tauri-apps/tauri/pull/4172)) on 2022-05-21 +- Account the monitor position when centering a window. + - [a7a9fde1](https://www.github.com/tauri-apps/tauri/commit/a7a9fde16fb7c35d48d4f97e83ff95b8baf9e090) fix(core): account for monitor position when centering window ([#4166](https://www.github.com/tauri-apps/tauri/pull/4166)) on 2022-05-21 +- Allow the canonical, absolute form of a path for the filesystem scope on Windows if `std::fs::canonicalize` returns a path, fallback to `\\?\$PATH`. + - [78f2565e](https://www.github.com/tauri-apps/tauri/commit/78f2565e14a5a8292045200967a36d6a40899721) fix: allow return value of fs::canonicalize on fs scope, closes [#4130](https://www.github.com/tauri-apps/tauri/pull/4130) ([#4188](https://www.github.com/tauri-apps/tauri/pull/4188)) on 2022-05-21 +- Fixes updater documentation not showing on docs.rs. + - [55892c35](https://www.github.com/tauri-apps/tauri/commit/55892c35f5c60836a8b7244e1660a83a01a4bc37) fix(core): updater documentation not showing on docs.rs ([#4190](https://www.github.com/tauri-apps/tauri/pull/4190)) on 2022-05-22 +- Fixes HTTP timeout not working on Windows when using the `attohttpc` client. + - [d99c5d58](https://www.github.com/tauri-apps/tauri/commit/d99c5d583bdb1429966283cc5cd4a977cf0770b2) fix(core): HTTP timeout not working on Windows, closes [#4050](https://www.github.com/tauri-apps/tauri/pull/4050) ([#4185](https://www.github.com/tauri-apps/tauri/pull/4185)) on 2022-05-21 +- Update `windows-rs` to `0.37.0`, which requires Rust 1.61.0+. + - [2326be39](https://www.github.com/tauri-apps/tauri/commit/2326be39821890cdd4de76e7029a531424dcb26f) feat(core): update windows-rs to 0.37.0 ([#4199](https://www.github.com/tauri-apps/tauri/pull/4199)) on 2022-05-24 +- **Breaking change:** The `WindowBuilder` struct now has a lifetime annotation `WindowBuilder`. + - [52d17754](https://www.github.com/tauri-apps/tauri/commit/52d177543ab5d0b316628ef30c2a32f303bc451d) fix(core): immediately create window when using tauri::App, closes [#4170](https://www.github.com/tauri-apps/tauri/pull/4170) ([#4172](https://www.github.com/tauri-apps/tauri/pull/4172)) on 2022-05-21 + +## \[1.0.0-rc.11] + +- Added the `App::get_cli_matches` helper function. + - [617f1144](https://www.github.com/tauri-apps/tauri/commit/617f1144f3f5c4a88e229ff410a511aa72795eef) feat(core): add `App::get_cli_matches` helper ref [#4145](https://www.github.com/tauri-apps/tauri/pull/4145) on 2022-05-17 +- Fixes `fileDropEnabled` option not working. + - [706fcbd3](https://www.github.com/tauri-apps/tauri/commit/706fcbd39c9c08f58da12f874199bd0c06016fb2) fix(core): fileDropEnabled option is not working when creating a new WebviewWindow ([#4146](https://www.github.com/tauri-apps/tauri/pull/4146)) on 2022-05-18 +- Prepare window icon and menu even when loading remote URLs. Previously it was behind a `is local URL?` condition. + - [25aa4347](https://www.github.com/tauri-apps/tauri/commit/25aa4347b3a85c66d445e09f864b7fe6c140a0f9) fix(core): prepare window icon and menu on remote URLs, closes [#4131](https://www.github.com/tauri-apps/tauri/pull/4131) ([#4140](https://www.github.com/tauri-apps/tauri/pull/4140)) on 2022-05-16 +- Fix `.mjs` not being recognised as a file extension for JavaScript files (`text/javascript`). + - [45c45253](https://www.github.com/tauri-apps/tauri/commit/45c45253866ce0de317a6a547af3ea0434d4bcac) fix: add mjs mime type (fix: [#4098](https://www.github.com/tauri-apps/tauri/pull/4098)) ([#4108](https://www.github.com/tauri-apps/tauri/pull/4108)) on 2022-05-13 +- Added `PathResolver::resolve_resource` API. + - [e35aaebc](https://www.github.com/tauri-apps/tauri/commit/e35aaebc309f5796cf6f380d1ac7d5a8094b9033) feat(core): add `PathResolver::resolve_resource` API ([#4116](https://www.github.com/tauri-apps/tauri/pull/4116)) on 2022-05-13 +- Allow configuring the display options for the MSI execution allowing quieter updates. + - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15 + +## \[1.0.0-rc.10] + +- Update wry to 0.16.2 and webkit2gtk to 0.18.0. + - [71a553b7](https://www.github.com/tauri-apps/tauri/commit/71a553b715312e2bcceb963c83e42cffca7a63bc) chore(deps): update wry to 0.16.2, webkit2gtk to 0.18.0 ([#4099](https://www.github.com/tauri-apps/tauri/pull/4099)) on 2022-05-10 +- Adds the `App#wry_plugin` API to inject a plugin for the wry integration. + - [c8e0e5b9](https://www.github.com/tauri-apps/tauri/commit/c8e0e5b97d542e549b37be08b545515c862af0e5) feat(tauri-runtime-wry): add plugin API ([#4094](https://www.github.com/tauri-apps/tauri/pull/4094)) on 2022-05-10 + +## \[1.0.0-rc.9] + +- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable. + - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02 +- The file drop event payloads are now percent-decoded. + - [a0ecd81a](https://www.github.com/tauri-apps/tauri/commit/a0ecd81a934e1aa8935151a74cad686786054204) fix(core): percent decode file drop payloads, closes [#4034](https://www.github.com/tauri-apps/tauri/pull/4034) ([#4035](https://www.github.com/tauri-apps/tauri/pull/4035)) on 2022-05-03 +- Fix dialog crash on macOS when the `default_path` value is just the file name. + - [d31167c5](https://www.github.com/tauri-apps/tauri/commit/d31167c520e4e5ea5c75518c180574f5fffc1a40) fix(core): dialog crashing on macOS when the parent is empty ([#4028](https://www.github.com/tauri-apps/tauri/pull/4028)) on 2022-05-02 +- Fixes the `title` option being ignored in the dialog API endpoints. + - [220e7460](https://www.github.com/tauri-apps/tauri/commit/220e7460148df476171579878c3cfffcdb1423d8) fix(core): set dialog title via API, closes [#4029](https://www.github.com/tauri-apps/tauri/pull/4029) ([#4030](https://www.github.com/tauri-apps/tauri/pull/4030)) on 2022-05-02 +- Fixes nested isolation iframe injection. + - [022eed46](https://www.github.com/tauri-apps/tauri/commit/022eed46675976e8dfe5f352a875754b4bd78131) fix(core): nested isolation iframes, closes [#4015](https://www.github.com/tauri-apps/tauri/pull/4015) ([#4020](https://www.github.com/tauri-apps/tauri/pull/4020)) on 2022-05-01 +- Deserialize numeric values (seconds) in the http API `ClientBuilder.connect_timeout` and `HttpRequestBuilder.timeout` fields. + - [f3c5ca89](https://www.github.com/tauri-apps/tauri/commit/f3c5ca89e79d429183c4e15a9e7cebada2b493a0) fix(core): http api `connect_timeout` deserialization, closes [#4004](https://www.github.com/tauri-apps/tauri/pull/4004) ([#4006](https://www.github.com/tauri-apps/tauri/pull/4006)) on 2022-04-29 +- Fix updater dialog removing single and double quotes from the release notes + - [0180dcc8](https://www.github.com/tauri-apps/tauri/commit/0180dcc812bacb78822bc0f97a3202633821dbce) fix(updater): remove single\&double quotes escaping in updater dialog … ([#4047](https://www.github.com/tauri-apps/tauri/pull/4047)) on 2022-05-04 +- Expose methods to access the underlying native handles of the webview. + - [c82b4761](https://www.github.com/tauri-apps/tauri/commit/c82b4761e1660592472dc55308ad69d9efc5855b) feat(core): expose `with_webview` API to access the platform webview ([#4058](https://www.github.com/tauri-apps/tauri/pull/4058)) on 2022-05-04 + +## \[1.0.0-rc.8] + +- **Breaking change:** Removed the `ayatana-tray` from the default features. You must select one of `ayatana-tray` and `gtk-tray` to use system tray on Linux. + - [62cdb2b3](https://www.github.com/tauri-apps/tauri/commit/62cdb2b307534cf1cb69fdb8688f7c0f3176f41b) refactor(tauri): remove ayatana-tray from the default features ([#3976](https://www.github.com/tauri-apps/tauri/pull/3976)) on 2022-04-26 +- Re-export the `GlobalShortcutManager` when the `global-shortcut` feature is enabled. + - [62cdb2b3](https://www.github.com/tauri-apps/tauri/commit/62cdb2b307534cf1cb69fdb8688f7c0f3176f41b) refactor(tauri): remove ayatana-tray from the default features ([#3976](https://www.github.com/tauri-apps/tauri/pull/3976)) on 2022-04-26 +- Fixes `docs.rs` documentation build. + - [dd94917b](https://www.github.com/tauri-apps/tauri/commit/dd94917b619f22849a7487805315c5306e4847b8) fix(tauri): docs.rs build error ([#3974](https://www.github.com/tauri-apps/tauri/pull/3974)) on 2022-04-26 + +## \[1.0.0-rc.7] + +- **Breaking change:** Removed `tauri::api::file::ArchiveFormat::Plain`. + - [f7d3d93b](https://www.github.com/tauri-apps/tauri/commit/f7d3d93b625ffc8bccf8068793f44af2f9a4f997) refactor(core): improve performance of the `extract` API ([#3963](https://www.github.com/tauri-apps/tauri/pull/3963)) on 2022-04-25 +- Fallback to `{path}.html` when `{path}` is not found in the Tauri custom protocol handler. + - [7864d41d](https://www.github.com/tauri-apps/tauri/commit/7864d41de8650f20fa73cefbddd78381b9d8aa11) feat(core): fallback to `{path}.html` in Tauri protocol loader ref [#3887](https://www.github.com/tauri-apps/tauri/pull/3887) ([#3939](https://www.github.com/tauri-apps/tauri/pull/3939)) on 2022-04-21 +- **Breaking change:** Use ayatana-appindicator for Linux system tray by default. Use the `gtk-tray` Cargo feature to use `libappindicator` instead. + - [f2a30d8b](https://www.github.com/tauri-apps/tauri/commit/f2a30d8bc54fc3ba49e16f69a413eca5f61a9b1f) refactor(core): use ayatana appindicator by default, keep option to use gtk ([#3916](https://www.github.com/tauri-apps/tauri/pull/3916)) on 2022-04-19 +- Reduce the amount of generated code for the API endpoints. + - [c23f139b](https://www.github.com/tauri-apps/tauri/commit/c23f139ba86628fe0217a966bc8676afe7202a05) perf(core): improve binary size with api enum serde refactor ([#3952](https://www.github.com/tauri-apps/tauri/pull/3952)) on 2022-04-24 +- \**Breaking change::* Added the `clipboard` Cargo feature. + - [24e4ff20](https://www.github.com/tauri-apps/tauri/commit/24e4ff208ee0fe1a4cc5b10667ea0922ac63dfb5) refactor(core): add clipboard Cargo feature, enhancing binary size ([#3957](https://www.github.com/tauri-apps/tauri/pull/3957)) on 2022-04-24 +- **Breaking change:** The process Command API stdio lines now includes the trailing `\r`. + - [b5622882](https://www.github.com/tauri-apps/tauri/commit/b5622882cf3748e1e5a90915f415c0cd922aaaf8) fix(cli): exit on non-compilation Cargo errors, closes [#3930](https://www.github.com/tauri-apps/tauri/pull/3930) ([#3942](https://www.github.com/tauri-apps/tauri/pull/3942)) on 2022-04-22 +- Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`. + - [c54ddfe9](https://www.github.com/tauri-apps/tauri/commit/c54ddfe9338e7eb90b4d5b02dfde687d432d5bc1) feat: expose window cursor APIs, closes [#3888](https://www.github.com/tauri-apps/tauri/pull/3888) [#3890](https://www.github.com/tauri-apps/tauri/pull/3890) ([#3935](https://www.github.com/tauri-apps/tauri/pull/3935)) on 2022-04-21 +- **Breaking change:** The `tauri::api::file::Extract#extract_file` function has been moved to `tauri::api::file::Entry#extract`. + - [f7d3d93b](https://www.github.com/tauri-apps/tauri/commit/f7d3d93b625ffc8bccf8068793f44af2f9a4f997) refactor(core): improve performance of the `extract` API ([#3963](https://www.github.com/tauri-apps/tauri/pull/3963)) on 2022-04-25 +- **Breaking change:** The `tauri::api::file::Extract#files` function has been renamed to `with_files` for performance reasons. + - [f7d3d93b](https://www.github.com/tauri-apps/tauri/commit/f7d3d93b625ffc8bccf8068793f44af2f9a4f997) refactor(core): improve performance of the `extract` API ([#3963](https://www.github.com/tauri-apps/tauri/pull/3963)) on 2022-04-25 +- Improved the performance of the `tauri::api::fs::Extract` API. + - [f7d3d93b](https://www.github.com/tauri-apps/tauri/commit/f7d3d93b625ffc8bccf8068793f44af2f9a4f997) refactor(core): improve performance of the `extract` API ([#3963](https://www.github.com/tauri-apps/tauri/pull/3963)) on 2022-04-25 +- Fixes a panic when using the `create_tao_window` API. + - [320329a9](https://www.github.com/tauri-apps/tauri/commit/320329a9a7d8a249c0fc9dee6db5669057ca8b39) fix(core): insert to webview_id_map on tao window creation, closes [#3883](https://www.github.com/tauri-apps/tauri/pull/3883) ([#3932](https://www.github.com/tauri-apps/tauri/pull/3932)) on 2022-04-21 +- Fixes the HTTP API form text fields. + - [cc356084](https://www.github.com/tauri-apps/tauri/commit/cc35608430b47101255b93ae2f58dfd15e1297d3) fix(core): do not serialize strings in http api form, closes [#3910](https://www.github.com/tauri-apps/tauri/pull/3910) ([#3928](https://www.github.com/tauri-apps/tauri/pull/3928)) on 2022-04-20 +- Set the application bundle identifier for the notifications on macOS. + - [f67ae6bd](https://www.github.com/tauri-apps/tauri/commit/f67ae6bd6052d5bc30305ecaa3b83d071da3cfca) fix(core): set bundle id for notifications on macOS ([#3839](https://www.github.com/tauri-apps/tauri/pull/3839)) on 2022-04-01 +- Fixes a panic when a menu event is triggered when all windows are minimized on macOS. + - [70ff55c1](https://www.github.com/tauri-apps/tauri/commit/70ff55c1aa69ed59cd2a78d865e1cb398ef2a4ba) fix(core): panic on menu event with minimized windows, closes [#3902](https://www.github.com/tauri-apps/tauri/pull/3902) ([#3918](https://www.github.com/tauri-apps/tauri/pull/3918)) on 2022-04-20 +- Fixes a rendering issue when resizing the window with the devtools open. + - [80b714af](https://www.github.com/tauri-apps/tauri/commit/80b714af6b31365b9026bc92f8631b1721950447) fix: rendering issue when resizing with devtools open closes [#3914](https://www.github.com/tauri-apps/tauri/pull/3914) [#3814](https://www.github.com/tauri-apps/tauri/pull/3814) ([#3915](https://www.github.com/tauri-apps/tauri/pull/3915)) on 2022-04-19 +- Fixes the `WindowBuilder` export. + - [985d2508](https://www.github.com/tauri-apps/tauri/commit/985d250898f07ebc975b579ea25555337947ab76) fix(tauri): export `WindowBuilder` struct instead of trait, closes [#3827](https://www.github.com/tauri-apps/tauri/pull/3827) ([#3833](https://www.github.com/tauri-apps/tauri/pull/3833)) on 2022-03-31 +- The HTTP API now supports `multipart/form-data` requests. You need to set the `Content-Type` header and enable the `http-multipart` Cargo feature. + - [1397d912](https://www.github.com/tauri-apps/tauri/commit/1397d9121aae8188bceceacae5565fbbeb67ddd9) feat(core): add support to multipart/form-data requests, closes [#2118](https://www.github.com/tauri-apps/tauri/pull/2118) ([#3929](https://www.github.com/tauri-apps/tauri/pull/3929)) on 2022-04-22 +- \**Breaking change::* Added the `global-shortcut` Cargo feature. + - [e11878bc](https://www.github.com/tauri-apps/tauri/commit/e11878bcf7174b261a1fa146fc7d564d12e6312a) refactor(core): add global-shortcut Cargo feature, enhancing binary size ([#3956](https://www.github.com/tauri-apps/tauri/pull/3956)) on 2022-04-24 +- Added `tauri::api::http::HttpRequestBuilder#header` method. + - [81705bb3](https://www.github.com/tauri-apps/tauri/commit/81705bb332623a53433c28d941ff00da1a6d50fa) feat(updater): add method to set request headers closes [#3896](https://www.github.com/tauri-apps/tauri/pull/3896) ([#3931](https://www.github.com/tauri-apps/tauri/pull/3931)) on 2022-04-22 +- **Breaking change:** The `tauri::api::http::HttpRequestBuilder#headers` method now takes `header::HeaderMap` instead of a `HashMap`. + - [81705bb3](https://www.github.com/tauri-apps/tauri/commit/81705bb332623a53433c28d941ff00da1a6d50fa) feat(updater): add method to set request headers closes [#3896](https://www.github.com/tauri-apps/tauri/pull/3896) ([#3931](https://www.github.com/tauri-apps/tauri/pull/3931)) on 2022-04-22 +- **Breaking change:** The `tauri::api::http::Response#headers` method now returns `&header::HeaderMap` instead of `&HashMap`. + - [81705bb3](https://www.github.com/tauri-apps/tauri/commit/81705bb332623a53433c28d941ff00da1a6d50fa) feat(updater): add method to set request headers closes [#3896](https://www.github.com/tauri-apps/tauri/pull/3896) ([#3931](https://www.github.com/tauri-apps/tauri/pull/3931)) on 2022-04-22 +- **Breaking change:** The `api::http` timeouts are now represented as `std::time::Duration` instead of a `u64`. + - [0ecfad59](https://www.github.com/tauri-apps/tauri/commit/0ecfad5924d7e19719812fd2e460e5242d8f352f) refactor(updater): unset request timeout, add builder setter ([#3847](https://www.github.com/tauri-apps/tauri/pull/3847)) on 2022-04-02 +- **Breaking change:** The `tauri::api::http::FormPart::Bytes` enum variant has been renamed to `File` with a value object `{ file, mime, file_name }`. + - [1397d912](https://www.github.com/tauri-apps/tauri/commit/1397d9121aae8188bceceacae5565fbbeb67ddd9) feat(core): add support to multipart/form-data requests, closes [#2118](https://www.github.com/tauri-apps/tauri/pull/2118) ([#3929](https://www.github.com/tauri-apps/tauri/pull/3929)) on 2022-04-22 +- **Breaking change:** Removed `App::create_window`, `AppHandle::create_window`, `Builder::create_window` and `Window::create_window`. + - [7c7d854a](https://www.github.com/tauri-apps/tauri/commit/7c7d854ab9183d3f78bafd9027f0304d84abe159) refactor(core): remove deprecated APIs ([#3834](https://www.github.com/tauri-apps/tauri/pull/3834)) on 2022-04-01 +- **Breaking change:** Removed `tauri::api::http::FormPart::File`. + - [cc356084](https://www.github.com/tauri-apps/tauri/commit/cc35608430b47101255b93ae2f58dfd15e1297d3) fix(core): do not serialize strings in http api form, closes [#3910](https://www.github.com/tauri-apps/tauri/pull/3910) ([#3928](https://www.github.com/tauri-apps/tauri/pull/3928)) on 2022-04-20 +- Added `WindowEvent::ThemeChanged(theme)`. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Added `theme` getter on `Window`. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Added `UpdateResponse::body` and `UpdateResponse::date`. + - [c7696f34](https://www.github.com/tauri-apps/tauri/commit/c7696f34ecb7baa0da583736e727f74a01f5996a) feat(updater): add `body` and `date` getters ([#3802](https://www.github.com/tauri-apps/tauri/pull/3802)) on 2022-03-29 +- **Breaking change**: Removed the `tauri::updater::Error::UnsupportedPlatform` variant and added `UnsupportedLinuxPackage`, `UnsupportedOs` and `UnsupportedArch` for better error information. + - [ed716793](https://www.github.com/tauri-apps/tauri/commit/ed71679368845f603680465fdffcc90fe842bb8c) refactor(updater): improve unsupported error variants, closes [#3817](https://www.github.com/tauri-apps/tauri/pull/3817) ([#3849](https://www.github.com/tauri-apps/tauri/pull/3849)) on 2022-04-05 +- Add updater `Downloaded` status event. + - [9712ed1a](https://www.github.com/tauri-apps/tauri/commit/9712ed1a6893438270e99cfecbc7968b68716cba) feat(updater): add `Downloaded` status event ([#3804](https://www.github.com/tauri-apps/tauri/pull/3804)) on 2022-04-01 +- Allow setting app updater request headers via `AppHandle::updater().header()`. + - [81705bb3](https://www.github.com/tauri-apps/tauri/commit/81705bb332623a53433c28d941ff00da1a6d50fa) feat(updater): add method to set request headers closes [#3896](https://www.github.com/tauri-apps/tauri/pull/3896) ([#3931](https://www.github.com/tauri-apps/tauri/pull/3931)) on 2022-04-22 +- The updater default timeout is now unset, and the `UpdateBuilder` has a `timeout` setter. + - [0ecfad59](https://www.github.com/tauri-apps/tauri/commit/0ecfad5924d7e19719812fd2e460e5242d8f352f) refactor(updater): unset request timeout, add builder setter ([#3847](https://www.github.com/tauri-apps/tauri/pull/3847)) on 2022-04-02 +- Added `theme` setter to the WindowBuilder. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 + +## \[1.0.0-rc.6] + +- Fixes the `WindowEvent` type used on `Window::on_window_event`. + - [06aa87b6](https://www.github.com/tauri-apps/tauri/commit/06aa87b65f4608ded0dc312d526e83065618a1c8) fix(core): `WindowEvent` type used on `Window::on_window_event` ([#3796](https://www.github.com/tauri-apps/tauri/pull/3796)) on 2022-03-28 +- Fixes `WindowEvent::Destroyed` not firing. + - [169b5035](https://www.github.com/tauri-apps/tauri/commit/169b5035a93e3f33a420d4b2b0f8943e6404e07f) fix(core): actually fire `WindowEvent::Destroyed` ([#3797](https://www.github.com/tauri-apps/tauri/pull/3797)) on 2022-03-28 + +## \[1.0.0-rc.5] + +- Added `updater_target` method to the `Builder` struct. + - [579312f8](https://www.github.com/tauri-apps/tauri/commit/579312f834f08dca15e51e4f43c1d0bb65b54a51) feat(updater): separate intel and apple silicon targets, closes [#3359](https://www.github.com/tauri-apps/tauri/pull/3359) ([#3739](https://www.github.com/tauri-apps/tauri/pull/3739)) on 2022-03-23 + +- Added an option to disable the CSP injection of distributable assets nonces and hashes. + - [f6e32ee1](https://www.github.com/tauri-apps/tauri/commit/f6e32ee1880eb364ed76beb937c9d12e14d54910) feat(core): add dangerous option to disable compile time CSP injection ([#3775](https://www.github.com/tauri-apps/tauri/pull/3775)) on 2022-03-28 + +- Toggle devtools when `Ctrl + Shift + I` or `Command + Option + I` is pressed. + - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28 + +- Use asynchronous file dialog on macOS and Windows to properly set the parent window. + - [bf89a05f](https://www.github.com/tauri-apps/tauri/commit/bf89a05fcfef976886a833b24346e010fd1bd06c) fix(core): dialog parent window on macOS, closes [#3312](https://www.github.com/tauri-apps/tauri/pull/3312) ([#3753](https://www.github.com/tauri-apps/tauri/pull/3753)) on 2022-03-23 + +- The `Error` enum is now `Send + Sync`. + - [da1e8793](https://www.github.com/tauri-apps/tauri/commit/da1e879358895f7b190b1c1b20d23da23666a74b) feat(core): improve and cleanup the `Error` enum ([#3748](https://www.github.com/tauri-apps/tauri/pull/3748)) on 2022-03-22 + +- Do not allow path traversal on the asset protocol. + - [34a402f9](https://www.github.com/tauri-apps/tauri/commit/34a402f9b559af377b276d73b800e5e8b7dacbb1) fix(core): do not allow path traversal on the asset protocol ([#3774](https://www.github.com/tauri-apps/tauri/pull/3774)) on 2022-03-27 + +- Properly apply the CSP when loading a route that fallbacks to index.html. + - [bcd43168](https://www.github.com/tauri-apps/tauri/commit/bcd43168a528dc4c54e28788430a93654c8fb452) fix(core): properly add CSP header to fallback routes ([#3641](https://www.github.com/tauri-apps/tauri/pull/3641)) on 2022-03-08 + +- Fix CSP usage on Linux when changing it via the `on_web_resource_request` handler. + - [f5efc248](https://www.github.com/tauri-apps/tauri/commit/f5efc248da511e0924c9673b947d5de7ef69ac45) fix(core): runtime CSP changes on Linux on 2022-03-07 + +- Improved the updater response validation and error messages. + - [dbc2873e](https://www.github.com/tauri-apps/tauri/commit/dbc2873e82dd56e13025f73281769fff323d32aa) feat(updater): improve validation and error messages, closes [#3761](https://www.github.com/tauri-apps/tauri/pull/3761) ([#3780](https://www.github.com/tauri-apps/tauri/pull/3780)) on 2022-03-27 + +- **Breaking change:** The `MenuItem::About` variant is now associated with a tuple value `(String, AboutMetadata)`. + - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28 + +- Replace multiple dependencies who's C code compiled concurrently and caused + the other ones to bloat compile time significantly. + +- `zstd` -> `brotli` + +- `blake3` -> a vendored version of the blake3 reference + +- `ring` -> `getrandom` + +See https://github.com/tauri-apps/tauri/pull/3773 for more information about +these specific choices. + +- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27 +- **Breaking change:** The `Window::hwnd` method now returns *HWND* from `windows-rs` crate instead of *c_void* on Windows. + - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23 +- Support window parenting on macOS + - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23 +- **Breaking change:** The updater default targets have been renamed to include better support for different architectures. + - [579312f8](https://www.github.com/tauri-apps/tauri/commit/579312f834f08dca15e51e4f43c1d0bb65b54a51) feat(updater): separate intel and apple silicon targets, closes [#3359](https://www.github.com/tauri-apps/tauri/pull/3359) ([#3739](https://www.github.com/tauri-apps/tauri/pull/3739)) on 2022-03-23 +- **Breaking change:** Removed `RunEvent::CloseRequested` and `RunEvent::WindowClosed` and added `RunEvent::WindowEvent`. + - [edad9f4f](https://www.github.com/tauri-apps/tauri/commit/edad9f4f55dcc69a06cd9d6d5a5068c94ecb77dd) refactor(core): add `RunEvent::WindowEvent` ([#3793](https://www.github.com/tauri-apps/tauri/pull/3793)) on 2022-03-28 +- **Breaking change:** Removed `window_label` from `RunEvent::ExitRequested`. + - [9ddf8d84](https://www.github.com/tauri-apps/tauri/commit/9ddf8d84a22cd6ccdce04bcc98b2b0f5fc54381a) fix(core): properly fire `WindowEvent::Destroyed`, closes [#3688](https://www.github.com/tauri-apps/tauri/pull/3688) ([#3778](https://www.github.com/tauri-apps/tauri/pull/3778)) on 2022-03-28 +- **Breaking change:** The `tauri://` events are no longer emitted to listeners using `Window::listen`. Use the `App::run` closure, `Window::on_window_event` and `Window::on_menu_event` instead. + - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15 +- The `App::setup` closure can now return a boxed error directly. + - [da1e8793](https://www.github.com/tauri-apps/tauri/commit/da1e879358895f7b190b1c1b20d23da23666a74b) feat(core): improve and cleanup the `Error` enum ([#3748](https://www.github.com/tauri-apps/tauri/pull/3748)) on 2022-03-22 +- Implement `Debug` for `tauri::State`. + - [0b49dd56](https://www.github.com/tauri-apps/tauri/commit/0b49dd566dae21c4dcb1cf110115aab982a7dab6) impl Debug for State closes [#3676](https://www.github.com/tauri-apps/tauri/pull/3676) ([#3677](https://www.github.com/tauri-apps/tauri/pull/3677)) on 2022-03-12 +- **Breaking change:** The `Manager::manage` function now returns a bool indicating whether the type is already managed or not. + - [263b45e1](https://www.github.com/tauri-apps/tauri/commit/263b45e1b4e72d6c99fc27e41d0c5e1d134f363b) refactor(core): return boolean on `Manager::manage` ([#3682](https://www.github.com/tauri-apps/tauri/pull/3682)) on 2022-03-13 +- Set the `Access-Control-Allow-Origin` header on the `tauri` protocol response with the initial webview URL as value. + - [1730b1a5](https://www.github.com/tauri-apps/tauri/commit/1730b1a51d1220e6c6a2eec405a3830cc3878224) feat(core): enable CORS on the tauri protocol ([#3750](https://www.github.com/tauri-apps/tauri/pull/3750)) on 2022-03-22 +- **Breaking change:** The `tauri_runtime` crate is no longer exported since its API is not stable. + - [1099a969](https://www.github.com/tauri-apps/tauri/commit/1099a9696e6639b46d736f8f3b446d2dfc4ef2f0) refactor(core): do not export `tauri_runtime` on `tauri` ([#3749](https://www.github.com/tauri-apps/tauri/pull/3749)) on 2022-03-22 +- Added `Temp` to the `BaseDirectory` enum. + - [266156a0](https://www.github.com/tauri-apps/tauri/commit/266156a0b08150b21140dd552c8bc252fe413cdd) feat(core): add `BaseDirectory::Temp` and `$TEMP` variable ([#3763](https://www.github.com/tauri-apps/tauri/pull/3763)) on 2022-03-24 +- Added `$TEMP` to the allowed variables to the filesystem and asset protocol scopes. + - [266156a0](https://www.github.com/tauri-apps/tauri/commit/266156a0b08150b21140dd552c8bc252fe413cdd) feat(core): add `BaseDirectory::Temp` and `$TEMP` variable ([#3763](https://www.github.com/tauri-apps/tauri/pull/3763)) on 2022-03-24 +- Update `wry` to `0.14` and `tao` to `0.7`. + - [f2d24ef2](https://www.github.com/tauri-apps/tauri/commit/f2d24ef2fbd95ec7d3433ba651964f4aa3b7f48c) chore(deps): update wry ([#1482](https://www.github.com/tauri-apps/tauri/pull/1482)) on 2021-04-14 + - [e267ebf1](https://www.github.com/tauri-apps/tauri/commit/e267ebf1f1009b99829e0a7d71519925f5792f9f) Apply Version Updates From Current Changes ([#1486](https://www.github.com/tauri-apps/tauri/pull/1486)) on 2021-04-14 + - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28 +- Added `updater` method to `App` and `AppHandle`, a builder to check for app updates. + - [4094494a](https://www.github.com/tauri-apps/tauri/commit/4094494a1b3125bf01676dabaa69e56cc8741d59) feat(core): add API to manually trigger updater check ([#3712](https://www.github.com/tauri-apps/tauri/pull/3712)) on 2022-03-17 + - [c64268f9](https://www.github.com/tauri-apps/tauri/commit/c64268f9274bdb7352da1a53184e487b03437dc2) feat(updater): expose builder, allow setting a custom version checker ([#3792](https://www.github.com/tauri-apps/tauri/pull/3792)) on 2022-03-28 +- Allow using a custom updater version checker via `App::updater().should_install()`. + - [c64268f9](https://www.github.com/tauri-apps/tauri/commit/c64268f9274bdb7352da1a53184e487b03437dc2) feat(updater): expose builder, allow setting a custom version checker ([#3792](https://www.github.com/tauri-apps/tauri/pull/3792)) on 2022-03-28 +- Added download progress events to the updater. + - [f0db3f9b](https://www.github.com/tauri-apps/tauri/commit/f0db3f9b8357dc304a8254426034c4d1733fbd45) feat(updater): add download progress events ([#3734](https://www.github.com/tauri-apps/tauri/pull/3734)) on 2022-03-18 +- Send updater events to the `App::run` closure. + - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15 +- Run the updater on startup even if no window was created. + - [c4ca80f9](https://www.github.com/tauri-apps/tauri/commit/c4ca80f919551cbcae53d931f860115c2d591d14) feat(core): use AppHandle instead of Window on the updater logic ([#3702](https://www.github.com/tauri-apps/tauri/pull/3702)) on 2022-03-15 +- Properly fire the window destroyed event. + - [9ddf8d84](https://www.github.com/tauri-apps/tauri/commit/9ddf8d84a22cd6ccdce04bcc98b2b0f5fc54381a) fix(core): properly fire `WindowEvent::Destroyed`, closes [#3688](https://www.github.com/tauri-apps/tauri/pull/3688) ([#3778](https://www.github.com/tauri-apps/tauri/pull/3778)) on 2022-03-28 +- Added `close_devtools` and `is_devtools_open` APIs to the `Window` struct. + - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28 +- Added the `WindowEvent::FileDrop` variant. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 +- Added a configuration flag for disallowing install downgrades on Windows. + **Breaking change:** The default behavior on Windows is now to allow downgrades. + - [8b807e09](https://www.github.com/tauri-apps/tauri/commit/8b807e09d6868f6bff8357f16d27b15bd1fccadd) refactor(bundler): allow downgrades, add option to disallow on Windows ([#3777](https://www.github.com/tauri-apps/tauri/pull/3777)) on 2022-03-27 + +## \[1.0.0-rc.4] + +- Run `AppHandle` cleanup code before restarting the application on the `process > relaunch` API. + - [9c65abce](https://www.github.com/tauri-apps/tauri/commit/9c65abce1bd3895b7f8d9d4d8a449ac4802fce24) feat(core): run cleanup code on the relaunch API ([#3629](https://www.github.com/tauri-apps/tauri/pull/3629)) on 2022-03-07 +- **Breaking change:** The `Builder#create_window` API now returns a Result validating the window label. + - [64e00542](https://www.github.com/tauri-apps/tauri/commit/64e0054299c95f10ef5a1a9d3f914bbaeff3d73f) refactor(core): do not panic on invalid window labels,[#3544](https://www.github.com/tauri-apps/tauri/pull/3544) ([#3596](https://www.github.com/tauri-apps/tauri/pull/3596)) on 2022-03-03 +- Added `tsp` config option under `tauri > bundle > windows`, which enables Time-Stamp Protocol (RFC 3161) for the timestamping + server under code signing on Windows if set to `true`. + - [bdd5f7c2](https://www.github.com/tauri-apps/tauri/commit/bdd5f7c2f03af4af8b60a9527e55bb18525d989b) fix: add support for Time-Stamping Protocol for Windows codesigning (fix [#3563](https://www.github.com/tauri-apps/tauri/pull/3563)) ([#3570](https://www.github.com/tauri-apps/tauri/pull/3570)) on 2022-03-07 +- Revert the `clap` usage back to the version 3.0 API. + - [2b554c38](https://www.github.com/tauri-apps/tauri/commit/2b554c38a5181b948d7674b6ef33e4049ca7d327) fix(core): revert to clap 3.0 API, allow deprecations, closes [#3549](https://www.github.com/tauri-apps/tauri/pull/3549) ([#3552](https://www.github.com/tauri-apps/tauri/pull/3552)) on 2022-02-24 +- The `tauri::api::process::Command` API now properly reads stdout and stderr messages that ends with a carriage return (`\r`) instead of just a newline (`\n`). + - [0a0de8ab](https://www.github.com/tauri-apps/tauri/commit/0a0de8ab6ed80b7722012e83636dff41a813b770) fix: read Command output ending with a carriage return, closes [#3508](https://www.github.com/tauri-apps/tauri/pull/3508) ([#3523](https://www.github.com/tauri-apps/tauri/pull/3523)) on 2022-02-24 +- Fixes filesystem and asset scope stripping the first component of the allowed path. + - [4d0e2ecc](https://www.github.com/tauri-apps/tauri/commit/4d0e2eccd9e0e1db16e6cc20613ffdc3dbe0474e) fix(core): scope should not strip the first path component, closes [#3592](https://www.github.com/tauri-apps/tauri/pull/3592) ([#3602](https://www.github.com/tauri-apps/tauri/pull/3602)) on 2022-03-03 +- Ignore trailing slashes on path scope validation. + - [929a83dd](https://www.github.com/tauri-apps/tauri/commit/929a83dd4d2614a119ab05122663669648e8c701) fix(core): ignore trailing slashes on scope validation, closes [#3580](https://www.github.com/tauri-apps/tauri/pull/3580) ([#3601](https://www.github.com/tauri-apps/tauri/pull/3601)) on 2022-03-03 +- Fixes `Command::output` and `Command::status` deadlock when running on async commands. + - [0163489e](https://www.github.com/tauri-apps/tauri/commit/0163489ed60ec0fe9486b5556ec0234499852a16) fix(core): `safe_block_on` usage on async contexts, closes [#3505](https://www.github.com/tauri-apps/tauri/pull/3505) ([#3513](https://www.github.com/tauri-apps/tauri/pull/3513)) on 2022-02-24 +- Update tray menu id map when `SystemTrayHandle::set_menu` is called. + - [da882431](https://www.github.com/tauri-apps/tauri/commit/da8824318affb50475f28b9c78c56da3fb048708) fix(core): update tray menu ids on `set_menu`, closes [#3608](https://www.github.com/tauri-apps/tauri/pull/3608) ([#3611](https://www.github.com/tauri-apps/tauri/pull/3611)) on 2022-03-04 +- Allow absolute paths on the filesystem APIs as long as it does not include parent directory components. + - [b744cd27](https://www.github.com/tauri-apps/tauri/commit/b744cd2758bbc5da39d6105fd82002dc6536dd16) feat: extend scopes with user selected paths, closes [#3591](https://www.github.com/tauri-apps/tauri/pull/3591) ([#3595](https://www.github.com/tauri-apps/tauri/pull/3595)) on 2022-03-03 +- **Breaking change:** The `tauri::api::file::Extract` API is now available when the `fs-extract-api` feature is enabled. + - [0f155898](https://www.github.com/tauri-apps/tauri/commit/0f1558980a0fb1d6c042988e173047f0590b6574) fix(core): docs.rs on Windows and macOS ([#3566](https://www.github.com/tauri-apps/tauri/pull/3566)) on 2022-03-02 +- Allow listening to events on the filesystem and asset scopes. + - [58070c1e](https://www.github.com/tauri-apps/tauri/commit/58070c1eb443c29b01638b7fc3f69aacdcc464a6) feat(core): filesystem and asset protocol scope events ([#3609](https://www.github.com/tauri-apps/tauri/pull/3609)) on 2022-03-04 +- Allow configuring forbidden paths on the asset and filesystem scopes. + - [983ccb81](https://www.github.com/tauri-apps/tauri/commit/983ccb815b1cb094ac681c2b36c69ec16f123b4e) feat(core): allow denying paths on the fs and asset scopes ([#3607](https://www.github.com/tauri-apps/tauri/pull/3607)) on 2022-03-03 +- Extend the allowed patterns for the filesystem and asset protocol when the user selects a path (dialog open and save commands and file drop on the window). + - [b744cd27](https://www.github.com/tauri-apps/tauri/commit/b744cd2758bbc5da39d6105fd82002dc6536dd16) feat: extend scopes with user selected paths, closes [#3591](https://www.github.com/tauri-apps/tauri/pull/3591) ([#3595](https://www.github.com/tauri-apps/tauri/pull/3595)) on 2022-03-03 +- The HTTP scope now matches the entire URL using a glob pattern instead of only its path. + - [944b124c](https://www.github.com/tauri-apps/tauri/commit/944b124ce04a15a415fcbb0afdfa78b0e900c97a) feat(core): enhance HTTP scope glob validation, closes [#3507](https://www.github.com/tauri-apps/tauri/pull/3507) ([#3515](https://www.github.com/tauri-apps/tauri/pull/3515)) on 2022-02-24 +- Parse window icons at compile time. + - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05 +- **Breaking change:** Move `ico` and `png` parsing behind `icon-ico` and `icon-png` Cargo features. + - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05 +- Return an error when creating a window with an invalid label instead of panicking. + - [64e00542](https://www.github.com/tauri-apps/tauri/commit/64e0054299c95f10ef5a1a9d3f914bbaeff3d73f) refactor(core): do not panic on invalid window labels,[#3544](https://www.github.com/tauri-apps/tauri/pull/3544) ([#3596](https://www.github.com/tauri-apps/tauri/pull/3596)) on 2022-03-03 +- Allows the configuration CSP to be an object mapping a directive name to its source list. + - [3fe0260f](https://www.github.com/tauri-apps/tauri/commit/3fe0260f4c7f47dba28b0d049c741d885bc6d356) feat(core): allow CSP configuration to be an object, ref [#3533](https://www.github.com/tauri-apps/tauri/pull/3533) ([#3603](https://www.github.com/tauri-apps/tauri/pull/3603)) on 2022-03-04 +- Allow range in the form of `bytes=0-*` on the asset protocol. + - [d06efc77](https://www.github.com/tauri-apps/tauri/commit/d06efc7704092a549c886be701122ad420db5543) fix(core): parse range `bytes=0-*`, closes [#3143](https://www.github.com/tauri-apps/tauri/pull/3143) ([#3516](https://www.github.com/tauri-apps/tauri/pull/3516)) on 2022-02-24 +- Reimplement endpoint to read file as string for performance. + - [834ccc51](https://www.github.com/tauri-apps/tauri/commit/834ccc51539401d36a7dfa1c0982623c9c446a4c) feat(core): reimplement `readTextFile` for performance ([#3631](https://www.github.com/tauri-apps/tauri/pull/3631)) on 2022-03-07 +- **Breaking change:** Renamed the `command` Cargo feature to `process-command-api`. + - [4e1af005](https://www.github.com/tauri-apps/tauri/commit/4e1af005a1fc80e911299b5fd6d6b0f3eb44f8e7) refactor(core): rename `command` feature to `process-command-api` ([#3594](https://www.github.com/tauri-apps/tauri/pull/3594)) on 2022-03-03 +- Disabled the default features for the `zip` crate. + - [5293445f](https://www.github.com/tauri-apps/tauri/commit/5293445f08f1484639ad9f56c45aad53039ba579) refactor(core): disable default features for the zip crate ([#3624](https://www.github.com/tauri-apps/tauri/pull/3624)) on 2022-03-06 +- The `cmd` field is no longer required on the shell scope for sidecars. + - [9b3b163b](https://www.github.com/tauri-apps/tauri/commit/9b3b163baa9b8eb2ef0bbc53f6303cc7a59a01af) feat(core): simplify scope definition for sidecars ([#3574](https://www.github.com/tauri-apps/tauri/pull/3574)) on 2022-03-02 +- Fixes a regression on the `unlisten` command. + - [76c791bd](https://www.github.com/tauri-apps/tauri/commit/76c791bd2b836d2055410e37e71716172a3f81ef) fix(core): regression on the unlisten function ([#3623](https://www.github.com/tauri-apps/tauri/pull/3623)) on 2022-03-06 +- Run `AppHandle` cleanup code before restarting the application when a new update is installed. + - [fce7d3bb](https://www.github.com/tauri-apps/tauri/commit/fce7d3bbae8d9e7e928a31b04a0a87dcaf4cd65f) feat(core): run app cleanup code before updater restart, closes [#3605](https://www.github.com/tauri-apps/tauri/pull/3605) ([#3616](https://www.github.com/tauri-apps/tauri/pull/3616)) on 2022-03-04 +- Added a `WindowBuilder` type. + - [141133a4](https://www.github.com/tauri-apps/tauri/commit/141133a414154631d42a0873c6abe2d76391b87d) feat(core): add WindowBuilder type ([#3598](https://www.github.com/tauri-apps/tauri/pull/3598)) on 2022-03-04 +- Added `WindowBuilder::on_web_resource_request`, which allows customizing the tauri custom protocol response. + - [3b13fda5](https://www.github.com/tauri-apps/tauri/commit/3b13fda56f515c708014c0396ed5ca295faaef84) feat(core): add `WindowBuilder::on_request`, closes [#3533](https://www.github.com/tauri-apps/tauri/pull/3533) ([#3618](https://www.github.com/tauri-apps/tauri/pull/3618)) on 2022-03-06 + +## \[1.0.0-rc.3] + +- `tauri::plugin::Builder` closures are no longer required to implement `Sync`. + - [fb7ee2c9](https://www.github.com/tauri-apps/tauri/commit/fb7ee2c987d9fca23f08bd470789069d9477c66e) drop Sync req from `setup` and `setup_with_config` ([#3471](https://www.github.com/tauri-apps/tauri/pull/3471)) on 2022-02-16 + - [b8e4d651](https://www.github.com/tauri-apps/tauri/commit/b8e4d651f9866b34bd3afcf2392812a18e1cee53) fix(core): drop all plugin builder Sync requirements ([#3490](https://www.github.com/tauri-apps/tauri/pull/3490)) on 2022-02-17 +- Added context to the file system endpoint errors. + - [06053833](https://www.github.com/tauri-apps/tauri/commit/060538331c138473159cf8fee0fcb7904ca33d3b) feat(core): add context to the filesystem APIs errors, closes [#3457](https://www.github.com/tauri-apps/tauri/pull/3457) ([#3480](https://www.github.com/tauri-apps/tauri/pull/3480)) on 2022-02-16 +- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`. + - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17 + +## \[1.0.0-rc.2] + +- Ease the requirements for plugin hooks. `setup` and `setup_with_config` can now be `FnOnce` and `on_webview_ready`, `on_event` and `on_page_load` can be `FnMut`. + - [fd557e98](https://www.github.com/tauri-apps/tauri/commit/fd557e984db17335a7c2ff10e6fe9c07065a95ed) Ease plugin hook restrictions ([#3404](https://www.github.com/tauri-apps/tauri/pull/3404)) on 2022-02-13 +- Fixes an issue with the updater when replacing the `{{target}}` and `{{current_version}}` variables due to percent-encoding on the `Url` that is parsed from the configuration. + - [20f0477f](https://www.github.com/tauri-apps/tauri/commit/20f0477f95b836cdf03ca66dadd9c0d3c95b198a) fix(core): updater not replacing variables, closes [#3428](https://www.github.com/tauri-apps/tauri/pull/3428) ([#3432](https://www.github.com/tauri-apps/tauri/pull/3432)) on 2022-02-13 + +## \[1.0.0-rc.1] + +- Change default value for the `freezePrototype` configuration to `false`. + - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12 + +## \[1.0.0-rc.0] + +- The dialog allowlist now includes flags for the `message`, `ask` and `confirm` APIs. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- - **Breaking change:** Renamed `tauri::Event` to `tauri::RunEvent` +- Exported `tauri::Event` and `tauri::EventHandler` so you can define a function and pass it to `Window::listen` +- [15358b18](https://www.github.com/tauri-apps/tauri/commit/15358b1895487cb9c258a8ca4d2336b4215e2a8f) Expose event interface. fixes [#2733](https://www.github.com/tauri-apps/tauri/pull/2733) ([#3321](https://www.github.com/tauri-apps/tauri/pull/3321)) on 2022-02-04 +- The `tauri::api` modules `http`, `notification`, `dialog`, and `process::Command` APIs are now hidden behind a feature flag, `http-api`, `notification`, `dialog` and `command`, respectively. + - [6feb5a0c](https://www.github.com/tauri-apps/tauri/commit/6feb5a0c50852a4792a9cc80967eab0fcf18609e) refactor(core): api feature flags, documentation ([#26](https://www.github.com/tauri-apps/tauri/pull/26)) on 2022-01-09 +- Add `title` option to file open/save dialogs. + - [e1d6a6e6](https://www.github.com/tauri-apps/tauri/commit/e1d6a6e6445637723e2331ca799a662e720e15a8) Create api-file-dialog-title.md ([#3235](https://www.github.com/tauri-apps/tauri/pull/3235)) on 2022-01-16 + - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10 +- Added `any_thread()` to the `tauri::Builder` to run applications on any thread (only exposed on Linux and Windows). + - [af44bf81](https://www.github.com/tauri-apps/tauri/commit/af44bf8168310cf77fbe102a53e7c433f11641a3) feat(core): allow app run on any thread on Linux & Windows, closes [#3172](https://www.github.com/tauri-apps/tauri/pull/3172) ([#3353](https://www.github.com/tauri-apps/tauri/pull/3353)) on 2022-02-07 +- Enable CORS on the `asset` protocol. + - [d28ac8aa](https://www.github.com/tauri-apps/tauri/commit/d28ac8aac0d19a70bb658f12e56330ec8ac4dda5) fix(core): enable CORS on the `asset` protocol, closes [#2965](https://www.github.com/tauri-apps/tauri/pull/2965) ([#2974](https://www.github.com/tauri-apps/tauri/pull/2974)) on 2021-12-09 +- The `asset://` custom protocol is only defined when either the `api-all`, `protocol-all` or `protocol-asset` feature flags are enabled. These feature flags are accessible with the `tauri.conf.json` allowlist. + - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \[TRI-026] \[TRI-010] \[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09 +- Expose the `asset_resolver` API on the `App` and `AppHandle` structs. + - [7c6c7adc](https://www.github.com/tauri-apps/tauri/commit/7c6c7adcc4c1ddd7406dd2d3b31541d5b962b385) feat(core): add `asset_resolver` API ([#2879](https://www.github.com/tauri-apps/tauri/pull/2879)) on 2021-11-12 +- **Breaking change:** Refactored the types returned from the `async_runtime` module. + - [a3537078](https://www.github.com/tauri-apps/tauri/commit/a3537078ddeaac7d26250f111f071072cf328b0b) feat(core): allow running along another tokio runtime, closes [#2838](https://www.github.com/tauri-apps/tauri/pull/2838) ([#2973](https://www.github.com/tauri-apps/tauri/pull/2973)) on 2021-12-08 +- Added `tauri::async_runtime::set` method, allowing to share your tokio runtime with Tauri. + - [a3537078](https://www.github.com/tauri-apps/tauri/commit/a3537078ddeaac7d26250f111f071072cf328b0b) feat(core): allow running along another tokio runtime, closes [#2838](https://www.github.com/tauri-apps/tauri/pull/2838) ([#2973](https://www.github.com/tauri-apps/tauri/pull/2973)) on 2021-12-08 +- Added `tauri::async_runtime::spawn_blocking` API. + - [a3537078](https://www.github.com/tauri-apps/tauri/commit/a3537078ddeaac7d26250f111f071072cf328b0b) feat(core): allow running along another tokio runtime, closes [#2838](https://www.github.com/tauri-apps/tauri/pull/2838) ([#2973](https://www.github.com/tauri-apps/tauri/pull/2973)) on 2021-12-08 +- The `callback` and `error` invoke fields, along with other `transformCallback` usages, are now validated to be numeric. + - [a48b8b18](https://www.github.com/tauri-apps/tauri/commit/a48b8b18d428bcc404d489daa690bbefe1f57311) feat(core): validate callbacks and event names \[TRI-038] \[TRI-020] ([#21](https://www.github.com/tauri-apps/tauri/pull/21)) on 2022-01-09 +- Change `Error::ParseCliArguments(clap::Error)` to `Error::ParseCliArguments(String)` because `clap::Error` is not `Send`. + - [1f988535](https://www.github.com/tauri-apps/tauri/commit/1f98853573a837dd0cfc2161b206a5033ec2da5e) chore(deps) Update Tauri Core ([#2480](https://www.github.com/tauri-apps/tauri/pull/2480)) on 2021-08-24 +- The `api::process::Command` APIs are now hidden behind the `command` feature flag. + - [eed01728](https://www.github.com/tauri-apps/tauri/commit/eed017287fed2ade689af4268e8b63b9c9f2e585) feat(core): add `shell > sidecar` allowlist and `process` feature flag \[TRI-037] ([#18](https://www.github.com/tauri-apps/tauri/pull/18)) on 2021-10-24 +- Add `tauri::api::path::log_dir` function to access the suggested log directory path. + - [acbb3ae7](https://www.github.com/tauri-apps/tauri/commit/acbb3ae7bb0165846b9456aea103269f027fc548) feat: add Log directory ([#2736](https://www.github.com/tauri-apps/tauri/pull/2736)) on 2021-10-16 + - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10 +- The `process`, `path` and `updater` APIs now takes a `tauri::Env` argument, used to force environment variables load on startup to prevent env var update attacks. + - [7209fdf7](https://www.github.com/tauri-apps/tauri/commit/7209fdf732ffe1893aaa9cd970ab6491a883d997) refactor(core): load APPIMAGE and APPDIR env vars on startup \[TRI-007] \[TRI-041] on 2022-01-09 +- Now `resolve()`, `join()` and `normalize()` from the `path` module, won't throw errors if the path doesn't exist, which matches NodeJS behavior. + - [fe381a0b](https://www.github.com/tauri-apps/tauri/commit/fe381a0bde86ebf4014007f6e21af4c1a9e58cef) fix: `join` no longer cares if path doesn't exist, closes [#2499](https://www.github.com/tauri-apps/tauri/pull/2499) ([#2548](https://www.github.com/tauri-apps/tauri/pull/2548)) on 2021-09-21 +- **Breaking change:** Return `Window` on `App` and `AppHandle`'s `create_window` function. + - [e15a8af8](https://www.github.com/tauri-apps/tauri/commit/e15a8af8434805da9716f3f840daa4e87b18b0a2) refactor(core): return `Window` on `create_window` API ([#3211](https://www.github.com/tauri-apps/tauri/pull/3211)) on 2022-01-13 + - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10 +- Apply `nonce` to `script` and `style` tags and set them on the `CSP` (`script-src` and `style-src` fetch directives). + - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09 +- The path returned from `tauri::api::process::current_binary` is now cached when loading the binary. + - [7c3db7a3](https://www.github.com/tauri-apps/tauri/commit/7c3db7a3811fd4de3e71c78cfd00894fa51ab786) cache current binary path much sooner ([#45](https://www.github.com/tauri-apps/tauri/pull/45)) on 2022-02-01 +- Added an API to use a custom invoke system to receive and respond to commands (`Builder#invoke_system`). + - [15164d93](https://www.github.com/tauri-apps/tauri/commit/15164d930a64807054057d83a2b6dae8540bcd9a) feat(core): allow defining a custom invoke system ([#2899](https://www.github.com/tauri-apps/tauri/pull/2899)) on 2021-11-16 +- Inject configured `CSP` on `data:` URLs. + - [8259cd64](https://www.github.com/tauri-apps/tauri/commit/8259cd64c2087b23b694a78aa98c43866762faec) feat(core): inject `CSP` on data URLs \[TRI-049] ([#16](https://www.github.com/tauri-apps/tauri/pull/16)) on 2022-01-09 +- Emit `tauri://*` events to Rust listeners. + - [4c4ab1eb](https://www.github.com/tauri-apps/tauri/commit/4c4ab1eb8b0b8b98ded402d0afb9dbca7ffe08e8) fix(core): trigger `tauri://*` events to Rust listeners, closes [#2901](https://www.github.com/tauri-apps/tauri/pull/2901) ([#2902](https://www.github.com/tauri-apps/tauri/pull/2902)) on 2021-11-16 +- Emit `tauri://window-created` event for windows created on the backend. + - [1dbd887a](https://www.github.com/tauri-apps/tauri/commit/1dbd887a6e0fc77b56e1b4d1b96dae8ddea1d5ea) fix(core): emit tauri://window-created event for windows created on Rust ([#3299](https://www.github.com/tauri-apps/tauri/pull/3299)) on 2022-02-04 +- Enable non-session cookie persistence on Linux. + - [d7c02a30](https://www.github.com/tauri-apps/tauri/commit/d7c02a30a56de79100804969138b379e703f0e07) feat(core): persist non-session cookies on Linux ([#3052](https://www.github.com/tauri-apps/tauri/pull/3052)) on 2021-12-09 +- Expose `tauri::api::ipc::{serialize_js_with, serialize_js}` functions. + - [5a94200f](https://www.github.com/tauri-apps/tauri/commit/5a94200f65feb4fddc79f0c68f8bc0a4067ad51a) feat(core): expose functions to serialize `serde::Serialize` values to JS ([#3354](https://www.github.com/tauri-apps/tauri/pull/3354)) on 2022-02-07 +- Resolve `asset` protocol HTTP request instead of panicking if the file does not exist or cannot be read. + - [03fc92c8](https://www.github.com/tauri-apps/tauri/commit/03fc92c8304efe897a09de6e82da2fea7f69ecab) fix(core): resolve request instead of panicking on asset protocol ([#3347](https://www.github.com/tauri-apps/tauri/pull/3347)) on 2022-02-06 +- Avoid `async_runtime::block_on` panics when used along another tokio runtime. + - [a3537078](https://www.github.com/tauri-apps/tauri/commit/a3537078ddeaac7d26250f111f071072cf328b0b) feat(core): allow running along another tokio runtime, closes [#2838](https://www.github.com/tauri-apps/tauri/pull/2838) ([#2973](https://www.github.com/tauri-apps/tauri/pull/2973)) on 2021-12-08 +- Prevent window closing if `tauri://close-requested` is listened on the JS layer. Users must call `appWindow.close()` manually when listening to that event. + - [74dff536](https://www.github.com/tauri-apps/tauri/commit/74dff536d4757820e68ab05301be83c0870c22ed) fix(core): emit `tauri://close-requested` to JS, closes [#2996](https://www.github.com/tauri-apps/tauri/pull/2996) ([#3041](https://www.github.com/tauri-apps/tauri/pull/3041)) on 2021-12-09 +- Fixes a deadlock when creating a window from a menu event handler. + - [9c82006b](https://www.github.com/tauri-apps/tauri/commit/9c82006b2fe166d20510183e36cee099bf96e8d9) fix(core): deadlock when creating window from menu handler, closes [#3110](https://www.github.com/tauri-apps/tauri/pull/3110) ([#3126](https://www.github.com/tauri-apps/tauri/pull/3126)) on 2021-12-28 +- Fixes the dialog `defaultPath` usage on Linux. + - [2212bd5d](https://www.github.com/tauri-apps/tauri/commit/2212bd5d75146f5a2df27cc2157a057642f626da) fix: dialog default path on Linux, closes [#3091](https://www.github.com/tauri-apps/tauri/pull/3091) ([#3123](https://www.github.com/tauri-apps/tauri/pull/3123)) on 2021-12-27 +- Fixes `WindowEvent::Focus` and `WindowEvent::Blur` events not firing. + - [3b33d67a](https://www.github.com/tauri-apps/tauri/commit/3b33d67aa4f48dcf4e32b3b8a5f45e83808efc2d) fix: re-adding focus/blur events for linux and macos (fix [#2485](https://www.github.com/tauri-apps/tauri/pull/2485)) ([#2489](https://www.github.com/tauri-apps/tauri/pull/2489)) on 2021-08-24 +- Fixes `tauri://focus` and `tauri://blur` events not firing. + - [3b33d67a](https://www.github.com/tauri-apps/tauri/commit/3b33d67aa4f48dcf4e32b3b8a5f45e83808efc2d) fix: re-adding focus/blur events for linux and macos (fix [#2485](https://www.github.com/tauri-apps/tauri/pull/2485)) ([#2489](https://www.github.com/tauri-apps/tauri/pull/2489)) on 2021-08-24 +- Use webview's inner_size instead of window's value to get the correct size on macOS. + - [4c0c780e](https://www.github.com/tauri-apps/tauri/commit/4c0c780e00d8851be38cb1c22f636d9e4ed34a23) fix(core): window's inner_size usage, closes [#2187](https://www.github.com/tauri-apps/tauri/pull/2187) ([#2690](https://www.github.com/tauri-apps/tauri/pull/2690)) on 2021-09-29 +- Fixes resource directory resolution on Linux. + - [1a28904b](https://www.github.com/tauri-apps/tauri/commit/1a28904b8ebea92e143d5dc21ebd209e9edec531) fix(core): resource path resolution on Linux, closes [#2493](https://www.github.com/tauri-apps/tauri/pull/2493) on 2021-08-22 +- Fixes the menu id mapping not reflecting the current window. + - [ac37b56e](https://www.github.com/tauri-apps/tauri/commit/ac37b56ef43c9e97039967a5fd99f0d2dccb5b5a) fix(core): menu id map not reflecting the current window menu ([#2726](https://www.github.com/tauri-apps/tauri/pull/2726)) on 2021-10-08 +- `Manager::once_global` and `Window::once` allow `FnOnce` callbacks. + - [d5400a3d](https://www.github.com/tauri-apps/tauri/commit/d5400a3d62ff2a37ccad20987dfea309725975a6) `once_global` and `once` accept FnOnce callbacks ([#3383](https://www.github.com/tauri-apps/tauri/pull/3383)) on 2022-02-10 +- Properly check if document is loaded before invoking commands. + - [000d126e](https://www.github.com/tauri-apps/tauri/commit/000d126e0e7231fb666b9ef53d6a1479dca774f7) fix(core): properly check if document is loaded, closes [#2716](https://www.github.com/tauri-apps/tauri/pull/2716) ([#2900](https://www.github.com/tauri-apps/tauri/pull/2900)) on 2021-11-16 +- Initialize system tray before windows so `tray_handle` can be accessed on command handlers. + - [dbe0d21b](https://www.github.com/tauri-apps/tauri/commit/dbe0d21b6fdc0f7d4c7b5e099cef805b60ef203e) fix(core): initialize system tray before app windows on 2021-08-31 +- Reimplement `remove_system_tray` on Windows to drop the `SystemTray` to run its cleanup code. + - [a03b8554](https://www.github.com/tauri-apps/tauri/commit/a03b85545a4b0b61a598a43eabe96e03565dcaf0) fix(core): tray not closing on Windows ([#3351](https://www.github.com/tauri-apps/tauri/pull/3351)) on 2022-02-07 +- Immediately listen to `tauri://window-created` event to catch it before the application triggers it. + - [878b8b9a](https://www.github.com/tauri-apps/tauri/commit/878b8b9a1fc825b1ea34f955e053311409a1468d) fix(core): immediately listen to window-created, closes [#3297](https://www.github.com/tauri-apps/tauri/pull/3297) ([#3298](https://www.github.com/tauri-apps/tauri/pull/3298)) on 2022-02-04 +- The `tauri::Window#emit` function now correctly sends the event to all windows that has a registered listener. + **Breaking change:** `Window#emit_and_trigger` and `Window#emit` now requires the payload to be cloneable. + - [9b340552](https://www.github.com/tauri-apps/tauri/commit/9b340552643a1d8b9311219101329ce23c9271ea) fix(core): window-specific event delivery, closes [#3302](https://www.github.com/tauri-apps/tauri/pull/3302) ([#3344](https://www.github.com/tauri-apps/tauri/pull/3344)) on 2022-02-06 +- Allow using a fixed version for the Webview2 runtime via the `tauri > bundle > windows > webviewFixedRuntimePath` config option. + - [85df94f2](https://www.github.com/tauri-apps/tauri/commit/85df94f2b0d40255812b42c5e32a70c4b45392df) feat(core): config for fixed webview2 runtime version path ([#27](https://www.github.com/tauri-apps/tauri/pull/27)) on 2021-11-02 +- The updater `pubkey` is now a required field for security reasons. Sign your updates with the `tauri signer` command. + - [d95cc831](https://www.github.com/tauri-apps/tauri/commit/d95cc83105dda52df7514e30e54f3676cdb374ee) feat: enforce updater public key \[TRI-015] ([#42](https://www.github.com/tauri-apps/tauri/pull/42)) on 2022-01-09 +- `tauri::api::HttpRequestBuilder::new` now returns a `Result` to validate the url. + - [0ad1c651](https://www.github.com/tauri-apps/tauri/commit/0ad1c6515f696fadefddbf133a9561836b3d5934) feat(core): add `http` allowlist scope \[TRI-008] ([#24](https://www.github.com/tauri-apps/tauri/pull/24)) on 2021-10-29 +- Added the `isolation` pattern. + - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) Co-authored-by: Lucas Fernandes Nogueira on 2022-01-17 +- Added `abort` method to `tauri::async_runtime::JoinHandle`. + - [ad169759](https://www.github.com/tauri-apps/tauri/commit/ad16975938afc9e87747de5fdcb0f07fc2d24811) feat: Add JoinHandle::abort() ([#2877](https://www.github.com/tauri-apps/tauri/pull/2877)) on 2021-11-13 +- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension. + +Here is the logic flow that determines if JSON or JSON5 will be used to parse the config: + +1. Check if `tauri.conf.json` exists + a. Parse it with `serde_json` + b. Parse it with `json5` if `serde_json` fails + c. Return original `serde_json` error if all above steps failed +2. Check if `tauri.conf.json5` exists + a. Parse it with `json5` + b. Return error if all above steps failed +3. Return error if all above steps failed + +- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03 +- Allow preventing opening the default browser on a click on an `` element via `stopImmediatePropagation()`. + - [10e3190f](https://www.github.com/tauri-apps/tauri/commit/10e3190fdd38893a720a51b7bfd06744e0306626) fix(core): do not use capture on \_blank link event handler, closes [#2791](https://www.github.com/tauri-apps/tauri/pull/2791) ([#3349](https://www.github.com/tauri-apps/tauri/pull/3349)) on 2022-02-07 +- The `run_return` API is now available on Linux. + - [8483fde9](https://www.github.com/tauri-apps/tauri/commit/8483fde975aac8833d2ce426e42fb40aeaeecba9) feat(core): expose `run_return` on Linux ([#3352](https://www.github.com/tauri-apps/tauri/pull/3352)) on 2022-02-07 +- Allow window, global shortcut and clipboard APIs to be called on the main thread. + - [2812c446](https://www.github.com/tauri-apps/tauri/commit/2812c4464b93a365ab955935d05b5cea8cb03aab) feat(core): window, shortcut and clipboard API calls on main thread ([#2659](https://www.github.com/tauri-apps/tauri/pull/2659)) on 2021-09-26 + - [d24fd8d1](https://www.github.com/tauri-apps/tauri/commit/d24fd8d10242da3da143a971d976b42ec4de6079) feat(tauri-runtime-wry): allow window creation and closing on the main thread ([#2668](https://www.github.com/tauri-apps/tauri/pull/2668)) on 2021-09-27 +- Add `Menu::with_items` constructor, taking an iterator of `MenuEntry`. + - [7cc95e10](https://www.github.com/tauri-apps/tauri/commit/7cc95e10ec66d8b155e9bb7f89cf73df56d1f107) feat(core): add `Menu::with_items`, closes [#2807](https://www.github.com/tauri-apps/tauri/pull/2807) ([#2966](https://www.github.com/tauri-apps/tauri/pull/2966)) on 2021-12-27 +- The updater now expects signatures created with the latest CLI release. + - [c2a6e8d7](https://www.github.com/tauri-apps/tauri/commit/c2a6e8d7e64284080129f09b3a0ae6cf93a87df0) chore(deps) Update Tauri Core ([#2746](https://www.github.com/tauri-apps/tauri/pull/2746)) on 2021-10-13 +- Change event loop callbacks definition to allow callers to move in mutable values. + - [bdbf905e](https://www.github.com/tauri-apps/tauri/commit/bdbf905e5d802b58693d2bd27582ce4269faf79c) Transformed event-loop callback to FnMut to allow mutable values ([#2667](https://www.github.com/tauri-apps/tauri/pull/2667)) on 2021-09-27 +- Fixes `Notification.requestPermission()` deadlock. + - [48f3768c](https://www.github.com/tauri-apps/tauri/commit/48f3768c41a2c68e2e097fcc1ef50e549c1dfb78) fix(core): `Notification.requestPermission()` deadlock regression on 2021-08-24 +- Added `Window#open_devtools` API. + - [55aa22de](https://www.github.com/tauri-apps/tauri/commit/55aa22de80c3de873e29bcffcb5b2fe236a637a6) feat(core): add `Window#open_devtools` API, closes [#1213](https://www.github.com/tauri-apps/tauri/pull/1213) ([#3350](https://www.github.com/tauri-apps/tauri/pull/3350)) on 2022-02-07 +- Add a `plugin::Builder` struct to make plugin creation more convenient. + - [9aed2996](https://www.github.com/tauri-apps/tauri/commit/9aed299621763a6e0a35d102cdf012da522dac35) feat: `plugin::Builder` closes [#2959](https://www.github.com/tauri-apps/tauri/pull/2959) ([#3005](https://www.github.com/tauri-apps/tauri/pull/3005)) on 2022-02-07 + - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10 +- Added `on_event` on the `Plugin` trait, which allows a plugin to react to the event loop. + - [cc2f39a2](https://www.github.com/tauri-apps/tauri/commit/cc2f39a29fc8852724aa3954ff0d42a36484929b) feat(core): add `on_event` hook on the `Plugin` trait ([#2656](https://www.github.com/tauri-apps/tauri/pull/2656)) on 2021-09-26 +- Prevent path traversal on the file system APIs. + - [4d89f60d](https://www.github.com/tauri-apps/tauri/commit/4d89f60d77a2abe7f3358cec00e15ecacf5e1148) refactor(core): prevent path traversal \[TRI-012] ([#35](https://www.github.com/tauri-apps/tauri/pull/35)) on 2021-12-06 +- **Breaking change:** Add `macos-private-api` feature flag, enabled via `tauri.conf.json > tauri > macOSPrivateApi`. + - [6ac21b3c](https://www.github.com/tauri-apps/tauri/commit/6ac21b3cef7f14358df38cc69ea3d277011accaf) feat: add private api feature flag ([#7](https://www.github.com/tauri-apps/tauri/pull/7)) on 2022-01-09 +- Add `raw_headers` to `tauri::api::http::ResponseData`. + - [b7a2345b](https://www.github.com/tauri-apps/tauri/commit/b7a2345b06ca0306988b4ba3d3deadd449e65af9) feat(core): add raw headers to HTTP API, closes [#2695](https://www.github.com/tauri-apps/tauri/pull/2695) ([#3053](https://www.github.com/tauri-apps/tauri/pull/3053)) on 2022-01-07 +- Implement `raw_window_handle::RawWindowHandle` for `tauri::Window` on `Windows` and `macOS`. The `tauri::api::dialog::window_parent` function was removed since now you can use the window directly. + - [e98c1af4](https://www.github.com/tauri-apps/tauri/commit/e98c1af44279a5ff6c8a6f0a506ecc219c9f77af) feat(core): expose message dialog APIs, fix window.confirm, implement HasRawWindowHandle for Window, closes [#2535](https://www.github.com/tauri-apps/tauri/pull/2535) ([#2700](https://www.github.com/tauri-apps/tauri/pull/2700)) on 2021-10-02 +- Refactor `create_tao_window` API to return `Weak` instead of `Arc`. + - [c1494b35](https://www.github.com/tauri-apps/tauri/commit/c1494b353233c6a9552d7ace962fdf8d5b1f199a) refactor: return Weak on create_tao_window on 2021-08-31 +- Added the `tauri::api::dialog::blocking` module. + - [4818531a](https://www.github.com/tauri-apps/tauri/commit/4818531aba47e126af91253d5d0eae3972b27d4c) refactor(core): add blocking dialog APIs, improve docs, closes [#3255](https://www.github.com/tauri-apps/tauri/pull/3255) ([#3270](https://www.github.com/tauri-apps/tauri/pull/3270)) on 2022-02-05 +- The notification endpoint now checks for the permission flag and requests if the value is not set. + - [239bba56](https://www.github.com/tauri-apps/tauri/commit/239bba56666c96acce24f30851f2d0719c95fc2c) refactor(core): check notification permission on the Rust endpoint \[TRI-017] ([#23](https://www.github.com/tauri-apps/tauri/pull/23)) on 2022-01-09 +- **Breaking change:** The `WindowEvent::CloseRequested` variant now includes `label` and `signal_tx` fields to allow preventing closing the window. + - [74dff536](https://www.github.com/tauri-apps/tauri/commit/74dff536d4757820e68ab05301be83c0870c22ed) fix(core): emit `tauri://close-requested` to JS, closes [#2996](https://www.github.com/tauri-apps/tauri/pull/2996) ([#3041](https://www.github.com/tauri-apps/tauri/pull/3041)) on 2021-12-09 +- **Breaking change:** Move `__currentWindow` and `__windows` values from `window.__TAURI__` to `window.__TAURI_METADATA__`. + - [f5109e0c](https://www.github.com/tauri-apps/tauri/commit/f5109e0c962e3d25404995194968bade1be33b16) fix(api): window label null instead of actual value, closes [#3295](https://www.github.com/tauri-apps/tauri/pull/3295) ([#3332](https://www.github.com/tauri-apps/tauri/pull/3332)) on 2022-02-04 +- Remove the `BaseDirectory::Current` enum variant for security reasons. + - [696dca58](https://www.github.com/tauri-apps/tauri/commit/696dca58a9f8ee127a1cf857eb848e09f5845d18) refactor(core): remove `BaseDirectory::Current` variant on 2022-01-26 +- **Breaking change:** Remove default webview window when `tauri.conf.json > tauri > windows` is not set. + - [c119060e](https://www.github.com/tauri-apps/tauri/commit/c119060e3d9a5a824639fb6b3c45a87e7a62e4e2) refactor(core): empty default value for config > tauri > windows ([#3380](https://www.github.com/tauri-apps/tauri/pull/3380)) on 2022-02-10 +- **Breaking change:** Renamed the `rpc` module to `ipc`. + - [3420aa50](https://www.github.com/tauri-apps/tauri/commit/3420aa5031b3274a95c6c5fa0f8683ca13213396) refactor: IPC handler \[TRI-019] ([#9](https://www.github.com/tauri-apps/tauri/pull/9)) on 2022-01-09 +- Expose `run_on_main_thread` APIs on `Window` and `AppHandle`. + - [53fdfe52](https://www.github.com/tauri-apps/tauri/commit/53fdfe52bb30d52653c72ca9f42506c3863dcf4a) feat(core): expose `run_on_main_thread` API ([#2711](https://www.github.com/tauri-apps/tauri/pull/2711)) on 2021-10-04 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 +- The minimum Rust version is now 1.57. + - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) Co-authored-by: Lucas Fernandes Nogueira on 2022-01-17 +- Scopes the `filesystem` APIs from the webview access using `tauri.conf.json > tauri > allowlist > fs > scope`. + Scopes the `asset` protocol access using `tauri.conf.json > tauri > allowlist > protocol > assetScope`. + Scopes the `http` APIs from the webview access using `tauri.conf.json > tauri > allowlist > http > scope`. + Scopes the `shell` execute API from the webview access using `tauri.conf.json > tauri > allowlist > shell > scope`. Additionally, check the `tauri.conf.json > tauri > bundle > externalBin` to prevent access to unknown sidecars. + - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \[TRI-026] \[TRI-010] \[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09 + - [0ad1c651](https://www.github.com/tauri-apps/tauri/commit/0ad1c6515f696fadefddbf133a9561836b3d5934) feat(core): add `http` allowlist scope \[TRI-008] ([#24](https://www.github.com/tauri-apps/tauri/pull/24)) on 2021-10-29 + - [d4db95e7](https://www.github.com/tauri-apps/tauri/commit/d4db95e7161e064d9463bc84d871a5391c3ce2d7) feat(core): shell execute API scope \[TRI-002] ([#36](https://www.github.com/tauri-apps/tauri/pull/36)) on 2021-12-01 +- `Builder#setup` closure type changed from `Fn` to `FnOnce`. + - [3f3599b9](https://www.github.com/tauri-apps/tauri/commit/3f3599b9cc238da06b36aff4c120a013a97fc3f0) refactor(core): change `setup` closure type to `FnOnce`, closes [#3061](https://www.github.com/tauri-apps/tauri/pull/3061) ([#3065](https://www.github.com/tauri-apps/tauri/pull/3065)) on 2021-12-27 +- The `tauri::api::shell::open`'s `with` argument is now an enum value instead of any string. + - [63921fad](https://www.github.com/tauri-apps/tauri/commit/63921fada436f010c33bb5e647bd67c6e549571c) refactor: change `tauri::api::open` `with` argument to an enum \[TRI-022] ([#19](https://www.github.com/tauri-apps/tauri/pull/19)) on 2022-01-09 +- The `shell` allowlist now includes a `sidecar` flag, which enables the use of the `shell` API to execute sidecars. + - [eed01728](https://www.github.com/tauri-apps/tauri/commit/eed017287fed2ade689af4268e8b63b9c9f2e585) feat(core): add `shell > sidecar` allowlist and `process` feature flag \[TRI-037] ([#18](https://www.github.com/tauri-apps/tauri/pull/18)) on 2021-10-24 +- **Breaking change:** The sidecar's target triple suffix is now removed at build time. + - [3035e458](https://www.github.com/tauri-apps/tauri/commit/3035e4581c161ec7f0bd6d9b42e9015cf1dd1d77) Remove target triple from sidecar bin paths, closes [#3355](https://www.github.com/tauri-apps/tauri/pull/3355) ([#3356](https://www.github.com/tauri-apps/tauri/pull/3356)) on 2022-02-07 +- Fix streaming of small files using the `asset` protocol. + - [151e629e](https://www.github.com/tauri-apps/tauri/commit/151e629ebf15ec5c068eb623e3dbc0ecdef1f816) fix(core): streaming of small files using `asset://`, closes [#2854](https://www.github.com/tauri-apps/tauri/pull/2854) ([#3039](https://www.github.com/tauri-apps/tauri/pull/3039)) on 2021-12-09 +- Add `set_menu` API on `tauri::SystemTrayHandle`. + - [0e4d12b5](https://www.github.com/tauri-apps/tauri/commit/0e4d12b541652d98c5a73de65034db8214e0363c) fix: [#2502](https://www.github.com/tauri-apps/tauri/pull/2502) Expose `set_menu` from tao through the TrayHandle struct ([#2532](https://www.github.com/tauri-apps/tauri/pull/2532)) on 2021-10-02 +- Adds `unlisten` function to the `Window` struct. + - [3a59f5f7](https://www.github.com/tauri-apps/tauri/commit/3a59f5f79e1b3ea08a224e6f2e0a56668d96cda3) Unlisten to an event on this window ([#2664](https://www.github.com/tauri-apps/tauri/pull/2664)) on 2021-09-28 +- Force updater endpoint URL to use `https` on release builds. + - [c077f449](https://www.github.com/tauri-apps/tauri/commit/c077f449270cffbf7956b1af81e1fb237ebf564a) feat: force endpoint URL to use https on release \[TRI-015] ([#41](https://www.github.com/tauri-apps/tauri/pull/41)) on 2022-01-09 +- Validate the `std::env::current_exe` return value if `APPDIR` or `APPIMAGE` environment variables are set. + - [6fbd6dba](https://www.github.com/tauri-apps/tauri/commit/6fbd6dba5290dc017ab0ba5a44cf4358b022836f) feat(core): validate `AppImage` execution when env vars are set \[TRI-041] ([#17](https://www.github.com/tauri-apps/tauri/pull/17)) on 2021-10-24 +- The event name is now validated. On a IPC message, it returns an error if it fails validation; on the Rust side, it panics. + It must include only alphanumeric characters, `-`, `/`, `:` and `_`. + - [a48b8b18](https://www.github.com/tauri-apps/tauri/commit/a48b8b18d428bcc404d489daa690bbefe1f57311) feat(core): validate callbacks and event names \[TRI-038] \[TRI-020] ([#21](https://www.github.com/tauri-apps/tauri/pull/21)) on 2022-01-09 +- The window label is now validated and must be alphanumeric, resulting in a panic if it isn't. + - [680554de](https://www.github.com/tauri-apps/tauri/commit/680554de3ef6b7fccf87c441ad355cfef7aab6fe) feat: validate window label \[TRI-021] ([#13](https://www.github.com/tauri-apps/tauri/pull/13)) on 2021-10-23 +- Allow `tauri.conf.json > package > version` to specify a path to a `package.json` file and pull the version from it. + - [46f2eae8](https://www.github.com/tauri-apps/tauri/commit/46f2eae8aad7c6a228eaf48480d5603dae6454b4) feat: allow config's version to be a path to package.json, closes [#2967](https://www.github.com/tauri-apps/tauri/pull/2967) ([#2971](https://www.github.com/tauri-apps/tauri/pull/2971)) on 2022-01-07 +- Added `clipboard` field on the `WebviewAttributes` struct, which must be set to `true` to enable clipboard access on the webview. + - [d42ccfb3](https://www.github.com/tauri-apps/tauri/commit/d42ccfb34f71851dfeb22fe74c83a8bdbddb5550) feat: add `clipboard` flag to `WebviewAttributes` \[TRI-032] ([#12](https://www.github.com/tauri-apps/tauri/pull/12)) on 2021-10-23 +- Replace all of the `winapi` crate references with the `windows` crate, and replace `webview2` and `webview2-sys` with `webview2-com` and `webview2-com-sys` built with the `windows` crate. This goes along with updates to the TAO and WRY `next` branches. + - [bb00d5bd](https://www.github.com/tauri-apps/tauri/commit/bb00d5bd6c9dfcb6bdd0d308dadb70e6c6aafe5c) Replace winapi with windows crate and use webview2-com instead of webview2 ([#2615](https://www.github.com/tauri-apps/tauri/pull/2615)) on 2021-09-24 +- Show `Ok/Cancel` buttons instead of `Yes/No` when executing `window.confirm`. + - [e98c1af4](https://www.github.com/tauri-apps/tauri/commit/e98c1af44279a5ff6c8a6f0a506ecc219c9f77af) feat(core): expose message dialog APIs, fix window.confirm, implement HasRawWindowHandle for Window, closes [#2535](https://www.github.com/tauri-apps/tauri/pull/2535) ([#2700](https://www.github.com/tauri-apps/tauri/pull/2700)) on 2021-10-02 +- Update the `windows` crate to 0.25.0, which comes with pre-built libraries. WRY and Tao can both reference the same types directly from the `windows` crate instead of sharing bindings in `webview2-com-sys`. + - [34be6cf3](https://www.github.com/tauri-apps/tauri/commit/34be6cf37a98ee7cbd66623ebddae08e5a6520fd) Update webview2-com and windows crates ([#2875](https://www.github.com/tauri-apps/tauri/pull/2875)) on 2021-11-11 + ## \[1.0.0-beta.8] - Fix missing asset protocol path.Now the protocol is `https://asset.localhost/path/to/file` on Windows. Lunix and macOS is still `asset://path/to/file`. - [994b5325](https://www.github.com/tauri-apps/tauri/commit/994b5325dd385f564b37fe1530c5d798dc925fff) fix: missing asset protocol path ([#2484](https://www.github.com/tauri-apps/tauri/pull/2484)) on 2021-08-23 -- **Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttibutes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure. +- **Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttributes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure. - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23 - Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing. - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23 @@ -30,10 +1085,10 @@ - [2088cd0f](https://www.github.com/tauri-apps/tauri/commit/2088cd0f24cd56ba427241136138c74bebee28f2) refactor(core): handle dialog threading internally, closes [#2223](https://www.github.com/tauri-apps/tauri/pull/2223) ([#2429](https://www.github.com/tauri-apps/tauri/pull/2429)) on 2021-08-14 - [60b1e260](https://www.github.com/tauri-apps/tauri/commit/60b1e260f511f50bbebceb6367f412c11f8dcf11) chore: adjust change file on 2021-08-16 -- **Breaking change**: The `Plugin` trait `initialize` method now takes an `AppHandle` reference instead of `App`. +- **Breaking change:** The `Plugin` trait `initialize` method now takes an `AppHandle` reference instead of `App`. - [c17532f7](https://www.github.com/tauri-apps/tauri/commit/c17532f7412bdcc57ae850c1251052ad1421fd67) refactor(core): change Plugin `initialize` signature, move register t… ([#2347](https://www.github.com/tauri-apps/tauri/pull/2347)) on 2021-08-03 -- **Breaking change**: Remove menu feature flag since there's no package dependency need to be installed on any platform anymore. +- **Breaking change:** Remove menu feature flag since there's no package dependency need to be installed on any platform anymore. - [f81ebddf](https://www.github.com/tauri-apps/tauri/commit/f81ebddfcc1aea0d4989706aef43538e8ea98bea) feat: remove menu feature flag ([#2415](https://www.github.com/tauri-apps/tauri/pull/2415)) on 2021-08-13 - Adds `set_activation_policy` API to the `tauri::App` struct (macOS only). @@ -248,8 +1303,8 @@ - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27 - Improve invoke key code injection performance time rewriting code at compile time. - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27 -- Enfore uniqueness of window label. - - [d18b5367](https://www.github.com/tauri-apps/tauri/commit/d18b5367a91fd53d408510b456897630c70abcca) feat(core): enfore label uniqueness, closes [#2067](https://www.github.com/tauri-apps/tauri/pull/2067) ([#2097](https://www.github.com/tauri-apps/tauri/pull/2097)) on 2021-06-27 +- Enforce uniqueness of window label. + - [d18b5367](https://www.github.com/tauri-apps/tauri/commit/d18b5367a91fd53d408510b456897630c70abcca) feat(core): enforce label uniqueness, closes [#2067](https://www.github.com/tauri-apps/tauri/pull/2067) ([#2097](https://www.github.com/tauri-apps/tauri/pull/2097)) on 2021-06-27 - `Window` is now `Send + Sync` on Windows. - [fe32afcc](https://www.github.com/tauri-apps/tauri/commit/fe32afcc933920d6282ae1d63b041b182278a031) fix(core): `Window` must be `Send + Sync` on Windows, closes [#2078](https://www.github.com/tauri-apps/tauri/pull/2078) ([#2093](https://www.github.com/tauri-apps/tauri/pull/2093)) on 2021-06-27 @@ -350,7 +1405,7 @@ - [5f351622](https://www.github.com/tauri-apps/tauri/commit/5f351622c7812ad1bb56ddb37364ccaa4124c24b) feat(core): add focus API to the WindowBuilder and WindowOptions, [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30 - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 -- **Breaking change**: The global shortcut API is now managed by `tao` so it cannot be accessed globally, the manager is now exposed on the `App` and `AppHandle` structs. +- **Breaking change:** The global shortcut API is now managed by `tao` so it cannot be accessed globally, the manager is now exposed on the `App` and `AppHandle` structs. - [3280c4aa](https://www.github.com/tauri-apps/tauri/commit/3280c4aa91e50a8ccdd561a8b48a12a4a13ea8d5) refactor(core): global shortcut is now provided by `tao` ([#2031](https://www.github.com/tauri-apps/tauri/pull/2031)) on 2021-06-21 - Hide `phf` crate export (not public API). @@ -379,7 +1434,7 @@ - Adds `accelerator` method to the `CustomMenuItem` struct to define a keyboard shortcut for the menu item. - [034c2601](https://www.github.com/tauri-apps/tauri/commit/034c26013bce0c7bbe6db067ea7fd24a53a5c998) feat(core): add `accelerator` method to `CustomMenuItem` ([#2043](https://www.github.com/tauri-apps/tauri/pull/2043)) on 2021-06-22 -- **Breaking change**: The `menu` API was not designed to have all the new features: submenus, item updates, disabled state... so we broke it before going to stable. +- **Breaking change:** The `menu` API was not designed to have all the new features: submenus, item updates, disabled state... so we broke it before going to stable. - [f7e9fe8f](https://www.github.com/tauri-apps/tauri/commit/f7e9fe8f3f7c83532713be6cc4ef84e8b127c208) refactor(core): new system tray and window menu APIs, closes [#1898](https://www.github.com/tauri-apps/tauri/pull/1898) ([#1944](https://www.github.com/tauri-apps/tauri/pull/1944)) on 2021-06-04 - Adds a `PathResolver` struct to simplify the usage of the `tauri::api::path::{app_dir, resource_dir}` APIs, accessible through the `App` and `AppHandle` `path_resolver` methods. @@ -412,7 +1467,7 @@ - [5525b03a](https://www.github.com/tauri-apps/tauri/commit/5525b03a78a2232c650043fbd9894ce1553cad41) feat(core): add `skip_taskbar` API to the WindowBuilder/WindowOptions on 2021-05-30 - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 -- **Breaking change**: The `system_tray` and `on_system_tray_event` APIs were not designed to have all the new features: submenus, item updates, click events, positioning... so we broke it before going to stable. +- **Breaking change:** The `system_tray` and `on_system_tray_event` APIs were not designed to have all the new features: submenus, item updates, click events, positioning... so we broke it before going to stable. - [f7e9fe8f](https://www.github.com/tauri-apps/tauri/commit/f7e9fe8f3f7c83532713be6cc4ef84e8b127c208) refactor(core): new system tray and window menu APIs, closes [#1898](https://www.github.com/tauri-apps/tauri/pull/1898) ([#1944](https://www.github.com/tauri-apps/tauri/pull/1944)) on 2021-06-04 - Fix loading url containing URI fragment @@ -665,7 +1720,7 @@ Note: The order that event handlers are called when triggered is still non-deter - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11 - Tauri now uses explicit Error variants with `thiserror` instead of relying on `anyhow`. - [156a0ad](https://www.github.com/tauri-apps/tauri/commit/156a0ad5cb0a152eaa0dd038a6b3dba68f03eb21) refactor(tauri): use explicit error types instead of anyhow ([#1209](https://www.github.com/tauri-apps/tauri/pull/1209)) on 2021-02-10 -- Align HTTP API types with the [documentation](https://tauri.studio/en/docs/api/js#http). +- Align HTTP API types with the [documentation](https://tauri.app/en/docs/api/js#http). - [2fc39fc](https://www.github.com/tauri-apps/tauri/commit/2fc39fc341771431078c20a95fa6b2affe5155c9) fix(api/http): correct types ([#1360](https://www.github.com/tauri-apps/tauri/pull/1360)) on 2021-03-17 - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11 - Replace `\` with `\\` in css assets that are lazy loaded. Since these are injected in a template literal, backslashes must be escaped. Backslashes are sometimes used for octal sequences in CSS. @@ -695,7 +1750,7 @@ Note: The order that event handlers are called when triggered is still non-deter - The HTTP API was improved with client caching and better payload and response types. - [a7bc472](https://www.github.com/tauri-apps/tauri/commit/a7bc472e994730071f960d09a12ac85296a080ae) refactor(core): improve HTTP API, closes [#1098](https://www.github.com/tauri-apps/tauri/pull/1098) ([#1237](https://www.github.com/tauri-apps/tauri/pull/1237)) on 2021-02-15 - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11 -- Added new Javascript API to extract `name`, `version`, `tauri version` from the running application. We exposed `relaunch` and `exit` as well to control your application state. +- Added new JavaScript API to extract `name`, `version`, `tauri version` from the running application. We exposed `relaunch` and `exit` as well to control your application state. - [e511d39](https://www.github.com/tauri-apps/tauri/commit/e511d3991041a974273a2674a9bf60230b7519ee) feat(api): Expose application metadata and functions to JS api - fix [#1387](https://www.github.com/tauri-apps/tauri/pull/1387) ([#1445](https://www.github.com/tauri-apps/tauri/pull/1445)) on 2021-04-08 - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11 - The event listener `once` kind was moved to a dedicated function. @@ -831,7 +1886,7 @@ https://github.com/GoogleChromeLabs/json-parse-benchmark ## \[0.7.3] - Properly run the loopback command on Windows. -- Properly ignore the ${distDir}/index.html asset from the asset embbeding. Previously every asset with name matching /(.+)index.html$/g were ignored. +- Properly ignore the ${distDir}/index.html asset from the asset embedding. Previously every asset with name matching /(.+)index.html$/g were ignored. ## \[0.7.2] diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 02f1188e9769..a7f369b691bd 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -1,271 +1,181 @@ [package] -authors = ["Tauri Programme within The Commons Conservancy"] -categories = ["gui", "web-programming"] -description = "Make tiny, secure apps for all desktop platforms with Tauri" -edition = "2021" -rust-version = "1.57" -exclude = [ - "/test", - "/.scripts", - ".license_template", - "CHANGELOG.md", - "/target", -] -homepage = "https://tauri.studio" -license = "Apache-2.0 OR MIT" name = "tauri" +version = "2.0.0-alpha.9" +description = "Make tiny, secure apps for all desktop platforms with Tauri" +exclude = [ "/test", "/.scripts", "CHANGELOG.md", "/target" ] readme = "README.md" -repository = "https://github.com/tauri-apps/tauri" -version = "1.0.0-beta.8" +links = "Tauri" + +# workspace defined package items +authors = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } [package.metadata.docs.rs] -default-features = false -features = ["compression", "wry", "isolation", "custom-protocol", "api-all", "cli", "updater", "system-tray", "devtools", "dox"] +no-default-features = true +features = [ + "wry", + "custom-protocol", + "system-tray", + "devtools", + "icon-png", + "protocol-asset", + "test", + "dox" +] rustdoc-args = [ "--cfg", "doc_cfg" ] default-target = "x86_64-unknown-linux-gnu" targets = [ "x86_64-pc-windows-msvc", "x86_64-unknown-linux-gnu", - "x86_64-apple-darwin", + "x86_64-apple-darwin" ] [package.metadata.cargo-udeps.ignore] -normal = ["attohttpc"] +normal = [ "reqwest" ] +build = [ "tauri-build" ] [dependencies] serde_json = { version = "1.0", features = [ "raw_value" ] } serde = { version = "1.0", features = [ "derive" ] } -tokio = { version = "1.16", features = [ "rt", "rt-multi-thread", "sync", "fs", "io-util" ] } -futures = "0.3" -uuid = { version = "0.8", features = [ "v4" ] } -url = { version = "2.2" } +tokio = { version = "1", features = [ "rt", "rt-multi-thread", "sync", "fs", "io-util" ] } +futures-util = "0.3" +uuid = { version = "1", features = [ "v4" ] } +url = { version = "2.3" } +anyhow = "1.0" thiserror = "1.0" -once_cell = "1.9" -tauri-runtime = { version = "0.2.1", path = "../tauri-runtime" } -tauri-macros = { version = "1.0.0-beta.5", path = "../tauri-macros" } -tauri-utils = { version = "1.0.0-beta.3", path = "../tauri-utils" } -tauri-runtime-wry = { version = "0.2.1", path = "../tauri-runtime-wry", optional = true } +once_cell = "1" +tauri-runtime = { version = "0.13.0-alpha.5", path = "../tauri-runtime" } +tauri-macros = { version = "2.0.0-alpha.5", path = "../tauri-macros" } +tauri-utils = { version = "2.0.0-alpha.5", features = [ "resources" ], path = "../tauri-utils" } +tauri-runtime-wry = { version = "0.13.0-alpha.5", path = "../tauri-runtime-wry", optional = true } rand = "0.8" -semver = "1.0" +semver = { version = "1.0", features = [ "serde" ] } serde_repr = "0.1" state = "0.5" -tar = "0.4" tempfile = "3" -zip = "0.5" -ignore = "0.4" -either = "1.6" -flate2 = "1.0" http = "0.2" -bincode = "1.3" dirs-next = "2.0" -percent-encoding = "2.1" -base64 = { version = "0.13", optional = true } -clap = { version = "3", optional = true } -notify-rust = { version = "4.5", optional = true } -reqwest = { version = "0.11", features = [ "json", "multipart" ], optional = true } -bytes = { version = "1", features = [ "serde" ], optional = true } -attohttpc = { version = "0.18", features = [ "json", "form" ], optional = true } -open = { version = "2.0", optional = true } -shared_child = { version = "1.0", optional = true } -os_pipe = { version = "1.0", optional = true } -rfd = { version = "0.7.0", features = [ "parent" ], optional = true } -raw-window-handle = "0.4.2" -minisign-verify = { version = "0.2", optional = true } -os_info = { version = "3.2.0", optional = true } -futures-lite = "1.12" -regex = { version = "1.5", optional = true } +percent-encoding = "2.2" +reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ] } +bytes = { version = "1", features = [ "serde" ] } +raw-window-handle = "0.5" glob = "0.3" -data-url = { version = "0.1", optional = true } +data-url = { version = "0.2", optional = true } serialize-to-javascript = "=0.1.1" +infer = { version = "0.9", optional = true } +png = { version = "0.17", optional = true } +ico = { version = "0.2.0", optional = true } [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] -gtk = { version = "0.15", features = [ "v3_20" ] } -glib = "0.15" +gtk = { version = "0.16", features = [ "v3_24" ] } +glib = "0.16" +webkit2gtk = { version = "0.19.1", features = [ "v2_38" ] } [target."cfg(target_os = \"macos\")".dependencies] embed_plist = "1.2" +cocoa = "0.24" +objc = "0.2" + +[target."cfg(windows)".dependencies] +webview2-com = "0.22" + + [target."cfg(windows)".dependencies.windows] + version = "0.44" + features = [ "Win32_Foundation" ] + +[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies] +log = "0.4" +heck = "0.4" + +[target."cfg(target_os = \"android\")".dependencies] +jni = "0.20" + +[target."cfg(target_os = \"ios\")".dependencies] +libc = "0.2" +objc = "0.2" +cocoa = "0.24" +swift-rs = "1.0.4" [build-dependencies] -cfg_aliases = "0.1.1" +heck = "0.4" +once_cell = "1" +tauri-build = { path = "../tauri-build/", version = "2.0.0-alpha.1" } [dev-dependencies] -mockito = "0.30" proptest = "1.0.0" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" -serde = {version = "1.0", features = ["derive"]} +serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" -tauri = {path = "."} -tokio-test = "0.4.2" -tokio = { version = "1.16", features = [ "full" ] } - -[target."cfg(windows)".dev-dependencies.windows] -version = "0.30.0" -features = [ - "Win32_Foundation", -] +tauri = { path = ".", default-features = false, features = [ "wry" ] } +tokio = { version = "1", features = [ "full" ] } +cargo_toml = "0.15" [features] -default = [ "wry", "compression" ] +default = [ "wry", "compression", "objc-exception" ] +test = [ ] compression = [ "tauri-macros/compression", "tauri-utils/compression" ] -wry = ["tauri-runtime-wry"] -isolation = ["tauri-utils/isolation", "tauri-macros/isolation"] -custom-protocol = ["tauri-macros/custom-protocol"] -updater = ["minisign-verify", "base64", "http-api", "dialog-ask"] -http-api = ["attohttpc"] -shell-open-api = [ "open", "regex", "tauri-macros/shell-scope" ] -reqwest-client = ["reqwest", "bytes"] -command = ["shared_child", "os_pipe"] -dialog = ["rfd"] -notification = ["notify-rust"] -cli = ["clap"] -system-tray = ["tauri-runtime/system-tray", "tauri-runtime-wry/system-tray"] -devtools = ["tauri-runtime/devtools", "tauri-runtime-wry/devtools"] -dox = ["tauri-runtime-wry/dox"] -macos-private-api = ["tauri-runtime/macos-private-api", "tauri-runtime-wry/macos-private-api"] -window-data-url = ["data-url"] -api-all = [ - "clipboard-all", - "dialog-all", - "fs-all", - "global-shortcut-all", - "http-all", - "notification-all", - "os-all", - "path-all", - "process-all", - "protocol-all", - "shell-all", - "window-all" +wry = [ "tauri-runtime-wry" ] +objc-exception = [ "tauri-runtime-wry/objc-exception" ] +linux-protocol-headers = [ "tauri-runtime-wry/linux-headers", "webkit2gtk/v2_36" ] +isolation = [ "tauri-utils/isolation", "tauri-macros/isolation" ] +custom-protocol = [ "tauri-macros/custom-protocol" ] +native-tls = [ "reqwest/native-tls" ] +native-tls-vendored = [ "reqwest/native-tls-vendored" ] +rustls-tls = [ "reqwest/rustls-tls" ] +system-tray = [ "tauri-runtime/system-tray", "tauri-runtime-wry/system-tray" ] +devtools = [ "tauri-runtime/devtools", "tauri-runtime-wry/devtools" ] +dox = [ "tauri-runtime-wry/dox" ] +process-relaunch-dangerous-allow-symlink-macos = [ "tauri-utils/process-relaunch-dangerous-allow-symlink-macos" ] +macos-private-api = [ + "tauri-runtime/macos-private-api", + "tauri-runtime-wry/macos-private-api" ] -clipboard-all = ["clipboard-write-text", "clipboard-read-text"] -clipboard-read-text = [] -clipboard-write-text = [] -dialog-all = ["dialog-open", "dialog-save", "dialog-message", "dialog-ask"] -dialog-ask = ["dialog"] -dialog-confirm = ["dialog"] -dialog-message = ["dialog"] -dialog-open = ["dialog"] -dialog-save = ["dialog"] -fs-all = [ - "fs-copy-file", - "fs-create-dir", - "fs-read-file", - "fs-read-dir", - "fs-remove-dir", - "fs-remove-file", - "fs-rename-file", - "fs-write-file" -] -fs-copy-file = [] -fs-create-dir = [] -fs-read-file = [] -fs-read-dir = [] -fs-remove-dir = [] -fs-remove-file = [] -fs-rename-file = [] -fs-write-file = [] -global-shortcut-all = [] -http-all = ["http-request"] -http-request = ["http-api"] -notification-all = ["notification", "dialog-ask"] -os-all = ["os_info"] -path-all = [] -process-all = ["process-relaunch", "process-exit"] -process-exit = [] -process-relaunch = [] -process-relaunch-dangerous-allow-symlink-macos = ["tauri-utils/process-relaunch-dangerous-allow-symlink-macos"] -protocol-all = ["protocol-asset"] -protocol-asset = [] -shell-all = ["shell-execute", "shell-sidecar", "shell-open"] -shell-execute = ["command", "regex", "tauri-macros/shell-scope"] -shell-sidecar = ["command", "regex", "tauri-macros/shell-scope"] -shell-open = ["shell-open-api"] -window-all = [ - "window-create", - "window-center", - "window-request-user-attention", - "window-set-resizable", - "window-set-title", - "window-maximize", - "window-unmaximize", - "window-minimize", - "window-unminimize", - "window-show", - "window-hide", - "window-close", - "window-set-decorations", - "window-set-always-on-top", - "window-set-size", - "window-set-min-size", - "window-set-max-size", - "window-set-position", - "window-set-fullscreen", - "window-set-focus", - "window-set-icon", - "window-set-skip-taskbar", - "window-start-dragging", - "window-print", -] -window-create = [ ] -window-center = [ ] -window-request-user-attention = [ ] -window-set-resizable = [ ] -window-set-title = [ ] -window-maximize = [ ] -window-unmaximize = [ ] -window-minimize = [ ] -window-unminimize = [ ] -window-show = [ ] -window-hide = [ ] -window-close = [ ] -window-set-decorations = [ ] -window-set-always-on-top = [ ] -window-set-size = [ ] -window-set-min-size = [ ] -window-set-max-size = [ ] -window-set-position = [ ] -window-set-fullscreen = [ ] -window-set-focus = [ ] -window-set-icon = [ ] -window-set-skip-taskbar = [ ] -window-start-dragging = [ ] -window-print = [ ] - -# features unrelated to api/endpoints +window-data-url = [ "data-url" ] +protocol-asset = [ ] config-json5 = [ "tauri-macros/config-json5" ] +config-toml = [ "tauri-macros/config-toml" ] +icon-ico = [ "infer", "ico" ] +icon-png = [ "infer", "png" ] [[example]] name = "commands" -path = "../../examples/commands/src-tauri/src/main.rs" +path = "../../examples/commands/main.rs" [[example]] name = "helloworld" -path = "../../examples/helloworld/src-tauri/src/main.rs" +path = "../../examples/helloworld/main.rs" [[example]] name = "multiwindow" -path = "../../examples/multiwindow/src-tauri/src/main.rs" -required-features = [ "window-create" ] +path = "../../examples/multiwindow/main.rs" + +[[example]] +name = "parent-window" +path = "../../examples/parent-window/main.rs" [[example]] name = "navigation" -path = "../../examples/navigation/src-tauri/src/main.rs" -required-features = [ "window-create" ] +path = "../../examples/navigation/main.rs" [[example]] name = "splashscreen" -path = "../../examples/splashscreen/src-tauri/src/main.rs" +path = "../../examples/splashscreen/main.rs" [[example]] name = "state" -path = "../../examples/state/src-tauri/src/main.rs" +path = "../../examples/state/main.rs" [[example]] name = "streaming" -path = "../../examples/streaming/src-tauri/src/main.rs" +path = "../../examples/streaming/main.rs" [[example]] name = "isolation" -path = "../../examples/isolation/src-tauri/src/main.rs" +path = "../../examples/isolation/main.rs" required-features = [ "isolation" ] diff --git a/core/tauri/README.md b/core/tauri/README.md index f53c76e77199..f0cf6e5a879b 100644 --- a/core/tauri/README.md +++ b/core/tauri/README.md @@ -2,38 +2,41 @@ -[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri) -[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) -[![devto](https://img.shields.io/badge/blog-dev.to-black.svg)](https://dev.to/tauri) - -![](https://img.shields.io/github/workflow/status/tauri-apps/tauri/test%20library?label=test%20library -) -[![devto](https://img.shields.io/badge/documentation-site-purple.svg)](https://tauri.studio) - +[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev) +[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri) +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield) +[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) +[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) -[![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri) +[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) -| Component | Version | -| --------- | ------------------------------------------- | -| tauri | [![](https://img.shields.io/crates/v/tauri?style=flat-square)](https://crates.io/crates/tauri) | +| Component | Version | +| --------- | ---------------------------------------------------------------------------------------------- | +| tauri | [![](https://img.shields.io/crates/v/tauri?style=flat-square)](https://crates.io/crates/tauri) | ## About Tauri + Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily. Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task. ## This module + This is the glue crate that holds everything together. It brings the runtimes, macros, utilities and API into one final product. It reads the `tauri.conf.json` file at compile time in order to bring in features and undertake actual configuration of the app (and even the `Cargo.toml` file in the project's folder). It handles script injection (for polyfills / prototype revision) at runtime, hosts the API for systems interaction, and even manages updating. To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. - ## Semver + **tauri** is following [Semantic Versioning 2.0](https://semver.org/). + ## Licenses + Code: (c) 2019 - 2021 - The Tauri Programme within The Commons Conservancy. MIT or MIT/Apache 2.0 where applicable. Logo: CC-BY-NC-ND + - Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) diff --git a/core/tauri/build.rs b/core/tauri/build.rs index db4d11d16583..29ea69f97a24 100644 --- a/core/tauri/build.rs +++ b/core/tauri/build.rs @@ -1,103 +1,153 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use cfg_aliases::cfg_aliases; +use heck::AsShoutySnakeCase; + +use once_cell::sync::OnceCell; + +use std::env::var_os; +use std::fs::read_dir; +use std::fs::read_to_string; +use std::fs::write; +use std::{ + env::var, + path::{Path, PathBuf}, + sync::Mutex, +}; + +static CHECKED_FEATURES: OnceCell>> = OnceCell::new(); + +// checks if the given Cargo feature is enabled. +fn has_feature(feature: &str) -> bool { + CHECKED_FEATURES + .get_or_init(Default::default) + .lock() + .unwrap() + .push(feature.to_string()); + + // when a feature is enabled, Cargo sets the `CARGO_FEATURE_` env var to 1 + // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts + std::env::var(format!("CARGO_FEATURE_{}", AsShoutySnakeCase(feature))) + .map(|x| x == "1") + .unwrap_or(false) +} + +// creates a cfg alias if `has_feature` is true. +// `alias` must be a snake case string. +fn alias(alias: &str, has_feature: bool) { + if has_feature { + println!("cargo:rustc-cfg={alias}"); + } +} fn main() { - cfg_aliases! { - custom_protocol: { feature = "custom-protocol" }, - dev: { not(feature = "custom-protocol") }, - - api_all: { feature = "api-all" }, - - // fs - fs_all: { any(api_all, feature = "fs-all") }, - fs_read_file: { any(fs_all, feature = "fs-read-file") }, - fs_write_file: { any(fs_all, feature = "fs-write-file") }, - fs_write_binary_file: { any(fs_all, feature = "fs-write-binary-file") }, - fs_read_dir: { any(fs_all, feature = "fs-read-dir") }, - fs_copy_file: { any(fs_all, feature = "fs-copy-file") }, - fs_create_dir: { any(fs_all, feature = "fs-create_dir") }, - fs_remove_dir: { any(fs_all, feature = "fs-remove-dir") }, - fs_remove_file: { any(fs_all, feature = "fs-remove-file") }, - fs_rename_file: { any(fs_all, feature = "fs-rename-file") }, - - // window - window_all: { any(api_all, feature = "window-all") }, - window_create: { any(window_all, feature = "window-create") }, - window_center: { any(window_all, feature = "window-center") }, - window_request_user_attention: { any(window_all, feature = "window-request-user-attention") }, - window_set_resizable: { any(window_all, feature = "window-set-resizable") }, - window_set_title: { any(window_all, feature = "window-set-title") }, - window_maximize: { any(window_all, feature = "window-maximize") }, - window_unmaximize: { any(window_all, feature = "window-unmaximize") }, - window_minimize: { any(window_all, feature = "window-minimize") }, - window_unminimize: { any(window_all, feature = "window-unminimize") }, - window_show: { any(window_all, feature = "window-show") }, - window_hide: { any(window_all, feature = "window-hide") }, - window_close: { any(window_all, feature = "window-close") }, - window_set_decorations: { any(window_all, feature = "window-set-decorations") }, - window_set_always_on_top: { any(window_all, feature = "window-set-always-on-top") }, - window_set_size: { any(window_all, feature = "window-set-size") }, - window_set_min_size: { any(window_all, feature = "window-set-min-size") }, - window_set_max_size: { any(window_all, feature = "window-set-max-size") }, - window_set_position: { any(window_all, feature = "window-set-position") }, - window_set_fullscreen: { any(window_all, feature = "window-set-fullscreen") }, - window_set_focus: { any(window_all, feature = "window-set-focus") }, - window_set_icon: { any(window_all, feature = "window-set-icon") }, - window_set_skip_taskbar: { any(window_all, feature = "window-set-skip-taskbar") }, - window_start_dragging: { any(window_all, feature = "window-start-dragging") }, - window_print: { any(window_all, feature = "window-print") }, - - // shell - shell_all: { any(api_all, feature = "shell-all") }, - shell_execute: { any(shell_all, feature = "shell-execute") }, - shell_sidecar: { any(shell_all, feature = "shell-sidecar") }, - shell_open: { any(shell_all, feature = "shell-open") }, - // helper for the shell scope functionality - shell_scope: { any(shell_execute, shell_sidecar, feature = "shell-open-api") }, - - // dialog - dialog_all: { any(api_all, feature = "dialog-all") }, - dialog_open: { any(dialog_all, feature = "dialog-open") }, - dialog_save: { any(dialog_all, feature = "dialog-save") }, - dialog_message: { any(dialog_all, feature = "dialog-message") }, - dialog_ask: { any(dialog_all, feature = "dialog-ask") }, - dialog_confirm: { any(dialog_all, feature = "dialog-confirm") }, - - // http - http_all: { any(api_all, feature = "http-all") }, - http_request: { any(http_all, feature = "http-request") }, - - // cli - cli: { feature = "cli" }, - - // notification - notification_all: { any(api_all, feature = "notification-all") }, - - // global shortcut - global_shortcut_all: { any(api_all, feature = "global_shortcut-all") }, - - // os - os_all: { any(api_all, feature = "os-all") }, - - // path - path_all: { any(api_all, feature = "path-all") }, - - // protocol - protocol_all: { any(api_all, feature = "protocol-all") }, - protocol_asset: { any(protocol_all, feature = "protocol-asset") }, - - // process - process_all: { any(api_all, feature = "process-all") }, - process_relaunch: { any(protocol_all, feature = "process-relaunch") }, - process_relaunch_dangerous_allow_symlink_macos: { feature = "process-relaunch-dangerous-allow-symlink-macos" }, - process_exit: { any(protocol_all, feature = "process-exit") }, - - // clipboard - clipboard_all: { any(api_all, feature = "clipboard-all") }, - clipboard_write_text: { any(protocol_all, feature = "clipboard-write-text") }, - clipboard_read_text: { any(protocol_all, feature = "clipboard-read-text") }, + alias("custom_protocol", has_feature("custom-protocol")); + alias("dev", !has_feature("custom-protocol")); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "ios" || target_os == "android"; + alias("desktop", !mobile); + alias("mobile", mobile); + + let checked_features_out_path = Path::new(&var("OUT_DIR").unwrap()).join("checked_features"); + std::fs::write( + checked_features_out_path, + CHECKED_FEATURES.get().unwrap().lock().unwrap().join(","), + ) + .expect("failed to write checked_features file"); + + // workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error + // see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864 + let target_env = std::env::var("CARGO_CFG_TARGET_ENV"); + let is_tauri_workspace = std::env::var("__TAURI_WORKSPACE__").map_or(false, |v| v == "true"); + if is_tauri_workspace && target_os == "windows" && Ok("msvc") == target_env.as_deref() { + add_manifest(); + } + + if target_os == "android" { + if let Ok(kotlin_out_dir) = std::env::var("WRY_ANDROID_KOTLIN_FILES_OUT_DIR") { + fn env_var(var: &str) -> String { + std::env::var(var).unwrap_or_else(|_| { + panic!( + "`{}` is not set, which is needed to generate the kotlin files for android.", + var + ) + }) + } + + let package = env_var("WRY_ANDROID_PACKAGE"); + let library = env_var("WRY_ANDROID_LIBRARY"); + + let kotlin_out_dir = PathBuf::from(&kotlin_out_dir) + .canonicalize() + .unwrap_or_else(move |_| { + panic!("Failed to canonicalize `WRY_ANDROID_KOTLIN_FILES_OUT_DIR` path {kotlin_out_dir}") + }); + + let kotlin_files_path = + PathBuf::from(env_var("CARGO_MANIFEST_DIR")).join("mobile/android-codegen"); + println!("cargo:rerun-if-changed={}", kotlin_files_path.display()); + let kotlin_files = + read_dir(kotlin_files_path).expect("failed to read Android codegen directory"); + + for file in kotlin_files { + let file = file.unwrap(); + + let content = read_to_string(file.path()) + .expect("failed to read kotlin file as string") + .replace("{{package}}", &package) + .replace("{{library}}", &library); + + let out_path = kotlin_out_dir.join(file.file_name()); + write(&out_path, content).expect("Failed to write kotlin file"); + println!("cargo:rerun-if-changed={}", out_path.display()); + } + } + + if let Some(project_dir) = var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) { + let tauri_proguard = include_str!("./mobile/proguard-tauri.pro").replace( + "$PACKAGE", + &var("WRY_ANDROID_PACKAGE").expect("missing `WRY_ANDROID_PACKAGE` environment variable"), + ); + std::fs::write( + project_dir.join("app").join("proguard-tauri.pro"), + tauri_proguard, + ) + .expect("failed to write proguard-tauri.pro"); + } + + let lib_path = + PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("mobile/android"); + println!("cargo:android_library_path={}", lib_path.display()); + } + + #[cfg(target_os = "macos")] + { + if target_os == "ios" { + let lib_path = + PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("mobile/ios-api"); + tauri_build::mobile::link_swift_library("Tauri", &lib_path); + println!("cargo:ios_library_path={}", lib_path.display()); + } } } + +fn add_manifest() { + static WINDOWS_MANIFEST_FILE: &str = "window-app-manifest.xml"; + + let manifest = std::env::current_dir() + .unwrap() + .join("../tauri-build/src") + .join(WINDOWS_MANIFEST_FILE); + + println!("cargo:rerun-if-changed={}", manifest.display()); + // Embed the Windows application manifest file. + println!("cargo:rustc-link-arg=/MANIFEST:EMBED"); + println!( + "cargo:rustc-link-arg=/MANIFESTINPUT:{}", + manifest.to_str().unwrap() + ); + // Turn linker warnings into errors. + println!("cargo:rustc-link-arg=/WX"); +} diff --git a/core/tauri/mobile/android-codegen/TauriActivity.kt b/core/tauri/mobile/android-codegen/TauriActivity.kt new file mode 100644 index 000000000000..b0ca3bba86a5 --- /dev/null +++ b/core/tauri/mobile/android-codegen/TauriActivity.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */ + +package {{package}} + +import android.os.Bundle +import app.tauri.plugin.PluginManager + +abstract class TauriActivity : WryActivity() { + var pluginManager: PluginManager = PluginManager(this) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (intent != null) { + pluginManager.onNewIntent(intent) + } + } +} diff --git a/core/tauri/mobile/android/.gitignore b/core/tauri/mobile/android/.gitignore new file mode 100644 index 000000000000..42afabfd2abe --- /dev/null +++ b/core/tauri/mobile/android/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/tauri/mobile/android/build.gradle.kts b/core/tauri/mobile/android/build.gradle.kts new file mode 100644 index 000000000000..d23ad0fb148c --- /dev/null +++ b/core/tauri/mobile/android/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri" + compileSdk = 33 + + defaultConfig { + minSdk = 21 + targetSdk = 33 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("proguard-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.7.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} diff --git a/core/tauri/mobile/android/proguard-rules.pro b/core/tauri/mobile/android/proguard-rules.pro new file mode 100644 index 000000000000..fed38c703624 --- /dev/null +++ b/core/tauri/mobile/android/proguard-rules.pro @@ -0,0 +1,25 @@ +-keep class app.tauri.** { + @app.tauri.JniMethod public ; + native ; +} + +-keep class app.tauri.plugin.JSArray { + public (...); +} + +-keepclassmembers class org.json.JSONArray { + public put(...); +} + +-keep class app.tauri.plugin.JSObject { + public (...); + public put(...); +} + +-keep @app.tauri.annotation.TauriPlugin public class * { + @app.tauri.annotation.Command public ; + @app.tauri.annotation.PermissionCallback ; + @app.tauri.annotation.ActivityCallback ; + @app.tauri.annotation.Permission ; + public (...); +} diff --git a/core/tauri/mobile/android/src/androidTest/java/app/tauri/ExampleInstrumentedTest.kt b/core/tauri/mobile/android/src/androidTest/java/app/tauri/ExampleInstrumentedTest.kt new file mode 100644 index 000000000000..791991038044 --- /dev/null +++ b/core/tauri/mobile/android/src/androidTest/java/app/tauri/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.test", appContext.packageName) + } +} diff --git a/core/tauri/mobile/android/src/main/AndroidManifest.xml b/core/tauri/mobile/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..9a40236b9471 --- /dev/null +++ b/core/tauri/mobile/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/FsUtils.kt b/core/tauri/mobile/android/src/main/java/app/tauri/FsUtils.kt new file mode 100644 index 000000000000..43d09a252642 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/FsUtils.kt @@ -0,0 +1,208 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri + +import android.content.ContentUris +import android.content.Context +import android.content.res.AssetManager +import android.database.Cursor +import android.net.Uri +import android.os.Environment +import android.provider.DocumentsContract +import android.provider.MediaStore +import android.provider.OpenableColumns +import java.io.File +import java.io.FileOutputStream +import kotlin.math.min + +internal class FsUtils { + companion object { + fun readAsset(assetManager: AssetManager, fileName: String): String { + assetManager.open(fileName).bufferedReader().use { + return it.readText() + } + } + + fun getFileUrlForUri(context: Context, uri: Uri): String? { + // DocumentProvider + if (DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + val docId: String = DocumentsContract.getDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + val type = split[0] + if ("primary".equals(type, ignoreCase = true)) { + return legacyPrimaryPath(split[1]) + } else { + val splitIndex = docId.indexOf(':', 1) + val tag = docId.substring(0, splitIndex) + val path = docId.substring(splitIndex + 1) + val nonPrimaryVolume = getPathToNonPrimaryVolume(context, tag) + if (nonPrimaryVolume != null) { + val result = "$nonPrimaryVolume/$path" + val file = File(result) + return if (file.exists() && file.canRead()) { + result + } else null + } + } + } else if (isDownloadsDocument(uri)) { + val id: String = DocumentsContract.getDocumentId(uri) + val contentUri: Uri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), + java.lang.Long.valueOf(id) + ) + return getDataColumn(context, contentUri, null, null) + } else if (isMediaDocument(uri)) { + val docId: String = DocumentsContract.getDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + val type = split[0] + var contentUri: Uri? = null + when (type) { + "image" -> { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } + "video" -> { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } + "audio" -> { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + } + val selection = "_id=?" + val selectionArgs = arrayOf(split[1]) + if (contentUri != null) { + return getDataColumn(context, contentUri, selection, selectionArgs) + } + } + } else if ("content".equals(uri.scheme, ignoreCase = true)) { + // Return the remote address + return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn( + context, + uri, + null, + null + ) + } else if ("file".equals(uri.scheme, ignoreCase = true)) { + return uri.path + } + return null + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + private fun getDataColumn( + context: Context, + uri: Uri, + selection: String?, + selectionArgs: Array? + ): String? { + var path: String? = null + var cursor: Cursor? = null + val column = "_data" + val projection = arrayOf(column) + try { + cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) + if (cursor != null && cursor.moveToFirst()) { + val index = cursor.getColumnIndexOrThrow(column) + path = cursor.getString(index) + } + } catch (ex: IllegalArgumentException) { + return getCopyFilePath(uri, context) + } finally { + cursor?.close() + } + return path ?: getCopyFilePath(uri, context) + } + + private fun getCopyFilePath(uri: Uri, context: Context): String? { + val cursor = context.contentResolver.query(uri, null, null, null, null)!! + val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + cursor.moveToFirst() + val name = cursor.getString(nameIndex) + val file = File(context.filesDir, name) + try { + val inputStream = context.contentResolver.openInputStream(uri) + val outputStream = FileOutputStream(file) + var read: Int + val maxBufferSize = 1024 * 1024 + val bufferSize = min(inputStream!!.available(), maxBufferSize) + val buffers = ByteArray(bufferSize) + while (inputStream.read(buffers).also { read = it } != -1) { + outputStream.write(buffers, 0, read) + } + inputStream.close() + outputStream.close() + } catch (e: Exception) { + return null + } finally { + cursor.close() + } + return file.path + } + + private fun legacyPrimaryPath(pathPart: String): String { + return Environment.getExternalStorageDirectory().toString() + "/" + pathPart + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + private fun isExternalStorageDocument(uri: Uri): Boolean { + return "com.android.externalstorage.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + private fun isDownloadsDocument(uri: Uri): Boolean { + return "com.android.providers.downloads.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + private fun isMediaDocument(uri: Uri): Boolean { + return "com.android.providers.media.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + private fun isGooglePhotosUri(uri: Uri): Boolean { + return "com.google.android.apps.photos.content" == uri.authority + } + + private fun getPathToNonPrimaryVolume(context: Context, tag: String): String? { + val volumes = context.externalCacheDirs + if (volumes != null) { + for (volume in volumes) { + if (volume != null) { + val path = volume.absolutePath + val index = path.indexOf(tag) + if (index != -1) { + return path.substring(0, index) + tag + } + } + } + } + return null + } + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/JniMethod.kt b/core/tauri/mobile/android/src/main/java/app/tauri/JniMethod.kt new file mode 100644 index 000000000000..d12778b5ffc1 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/JniMethod.kt @@ -0,0 +1,8 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri + +@Retention(AnnotationRetention.RUNTIME) +internal annotation class JniMethod diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/Logger.kt b/core/tauri/mobile/android/src/main/java/app/tauri/Logger.kt new file mode 100644 index 000000000000..a4789dd5032c --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/Logger.kt @@ -0,0 +1,85 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri + +// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/Logger.java + +import android.text.TextUtils; +import android.util.Log; + +class Logger { + companion object { + private const val LOG_TAG_CORE = "Tauri" + + fun tags(vararg subtags: String): String { + return if (subtags.isNotEmpty()) { + LOG_TAG_CORE + "/" + TextUtils.join("/", subtags) + } else LOG_TAG_CORE + } + + fun verbose(message: String) { + verbose(LOG_TAG_CORE, message) + } + + fun verbose(tag: String, message: String) { + if (!shouldLog()) { + return + } + Log.v(tag, message) + } + + fun debug(message: String) { + debug(LOG_TAG_CORE, message) + } + + fun debug(tag: String, message: String) { + if (!shouldLog()) { + return + } + Log.d(tag, message) + } + + fun info(message: String) { + info(LOG_TAG_CORE, message) + } + + fun info(tag: String, message: String) { + if (!shouldLog()) { + return + } + Log.i(tag, message) + } + + fun warn(message: String) { + warn(LOG_TAG_CORE, message) + } + + fun warn(tag: String, message: String) { + if (!shouldLog()) { + return + } + Log.w(tag, message) + } + + fun error(message: String) { + error(LOG_TAG_CORE, message, null) + } + + fun error(message: String, e: Throwable?) { + error(LOG_TAG_CORE, message, e) + } + + fun error(tag: String, message: String, e: Throwable?) { + if (!shouldLog()) { + return + } + Log.e(tag, message, e) + } + + private fun shouldLog(): Boolean { + return BuildConfig.DEBUG + } + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/PathPlugin.kt b/core/tauri/mobile/android/src/main/java/app/tauri/PathPlugin.kt new file mode 100644 index 000000000000..65147db5a410 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/PathPlugin.kt @@ -0,0 +1,78 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri + +import android.app.Activity +import android.os.Environment +import app.tauri.annotation.Command +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Plugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSObject + +@TauriPlugin +class PathPlugin(private val activity: Activity): Plugin(activity) { + private fun resolvePath(invoke: Invoke, path: String?) { + val obj = JSObject() + obj.put("path", path) + invoke.resolve(obj) + } + + @Command + fun getAudioDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath) + } + + @Command + fun getExternalCacheDir(invoke: Invoke) { + resolvePath(invoke, activity.externalCacheDir?.absolutePath) + } + + @Command + fun getConfigDir(invoke: Invoke) { + resolvePath(invoke, activity.dataDir.absolutePath) + } + + @Command + fun getDataDir(invoke: Invoke) { + resolvePath(invoke, activity.dataDir.absolutePath) + } + + @Command + fun getDocumentDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath) + } + + @Command + fun getDownloadDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath) + } + + @Command + fun getPictureDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.absolutePath) + } + + @Command + fun getPublicDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DCIM)?.absolutePath) + } + + @Command + fun getVideoDir(invoke: Invoke) { + resolvePath(invoke, activity.externalCacheDir?.absolutePath) + } + + @Command + fun getResourcesDir(invoke: Invoke) { + // TODO + resolvePath(invoke, activity.cacheDir.absolutePath) + } + + @Command + fun getCacheDir(invoke: Invoke) { + resolvePath(invoke, activity.cacheDir.absolutePath) + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/PermissionHelper.kt b/core/tauri/mobile/android/src/main/java/app/tauri/PermissionHelper.kt new file mode 100644 index 000000000000..e84ae9b3177e --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/PermissionHelper.kt @@ -0,0 +1,115 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri + +// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/PermissionHelper.java + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import androidx.core.app.ActivityCompat; +import java.util.ArrayList; + +object PermissionHelper { + /** + * Checks if a list of given permissions are all granted by the user + * + * @param permissions Permissions to check. + * @return True if all permissions are granted, false if at least one is not. + */ + fun hasPermissions(context: Context?, permissions: Array): Boolean { + for (perm in permissions) { + if (ActivityCompat.checkSelfPermission( + context!!, + perm + ) != PackageManager.PERMISSION_GRANTED + ) { + return false + } + } + return true + } + + /** + * Check whether the given permission has been defined in the AndroidManifest.xml + * + * @param permission A permission to check. + * @return True if the permission has been defined in the Manifest, false if not. + */ + fun hasDefinedPermission(context: Context, permission: String): Boolean { + var hasPermission = false + val requestedPermissions = getManifestPermissions(context) + if (requestedPermissions != null && requestedPermissions.isNotEmpty()) { + val requestedPermissionsList = listOf(*requestedPermissions) + val requestedPermissionsArrayList = ArrayList(requestedPermissionsList) + if (requestedPermissionsArrayList.contains(permission)) { + hasPermission = true + } + } + return hasPermission + } + + /** + * Check whether all of the given permissions have been defined in the AndroidManifest.xml + * @param context the app context + * @param permissions a list of permissions + * @return true only if all permissions are defined in the AndroidManifest.xml + */ + fun hasDefinedPermissions(context: Context, permissions: Array): Boolean { + for (permission in permissions) { + if (!hasDefinedPermission(context, permission)) { + return false + } + } + return true + } + + /** + * Get the permissions defined in AndroidManifest.xml + * + * @return The permissions defined in AndroidManifest.xml + */ + private fun getManifestPermissions(context: Context): Array? { + var requestedPermissions: Array? = null + try { + val pm = context.packageManager + val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pm.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong())) + } else { + @Suppress("DEPRECATION") + pm.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS) + } + if (packageInfo != null) { + requestedPermissions = packageInfo.requestedPermissions + } + } catch (_: Exception) { + } + return requestedPermissions + } + + /** + * Given a list of permissions, return a new list with the ones not present in AndroidManifest.xml + * + * @param neededPermissions The permissions needed. + * @return The permissions not present in AndroidManifest.xml + */ + fun getUndefinedPermissions(context: Context, neededPermissions: Array): Array { + val undefinedPermissions = ArrayList() + val requestedPermissions = getManifestPermissions(context) + if (requestedPermissions != null && requestedPermissions.isNotEmpty()) { + val requestedPermissionsList = listOf(*requestedPermissions) + val requestedPermissionsArrayList = ArrayList(requestedPermissionsList) + for (permission in neededPermissions) { + if (!requestedPermissionsArrayList.contains(permission)) { + undefinedPermissions.add(permission) + } + } + var undefinedPermissionArray = arrayOfNulls(undefinedPermissions.size) + undefinedPermissionArray = undefinedPermissions.toArray(undefinedPermissionArray) + return undefinedPermissionArray + } + return neededPermissions as Array + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/PermissionState.kt b/core/tauri/mobile/android/src/main/java/app/tauri/PermissionState.kt new file mode 100644 index 000000000000..4655c0dc03d0 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/PermissionState.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri + +import java.util.* + +enum class PermissionState(private val state: String) { + GRANTED("granted"), DENIED("denied"), PROMPT("prompt"), PROMPT_WITH_RATIONALE("prompt-with-rationale"); + + override fun toString(): String { + return state + } + + companion object { + fun byState(state: String): PermissionState { + return valueOf(state.uppercase(Locale.ROOT).replace('-', '_')) + } + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/annotation/ActivityCallback.kt b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/ActivityCallback.kt new file mode 100644 index 000000000000..7da7c5fb8785 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/ActivityCallback.kt @@ -0,0 +1,9 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.annotation + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class ActivityCallback diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/annotation/Permission.kt b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/Permission.kt new file mode 100644 index 000000000000..c5eb5ac73278 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/Permission.kt @@ -0,0 +1,19 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.annotation + +@Retention(AnnotationRetention.RUNTIME) +annotation class Permission( + /** + * An array of Android permission strings. + * Eg: {Manifest.permission.ACCESS_COARSE_LOCATION} + * or {"android.permission.ACCESS_COARSE_LOCATION"} + */ + val strings: Array = [], + /** + * An optional name to use instead of the Android permission string. + */ + val alias: String = "" +) diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/annotation/PermissionCallback.kt b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/PermissionCallback.kt new file mode 100644 index 000000000000..960d9f3a1c3d --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/PermissionCallback.kt @@ -0,0 +1,9 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.annotation + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class PermissionCallback diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/annotation/PluginMethod.kt b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/PluginMethod.kt new file mode 100644 index 000000000000..e102782aeb14 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/PluginMethod.kt @@ -0,0 +1,8 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.annotation + +@Retention(AnnotationRetention.RUNTIME) +annotation class Command diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/annotation/TauriPlugin.kt b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/TauriPlugin.kt new file mode 100644 index 000000000000..41beb5017509 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/TauriPlugin.kt @@ -0,0 +1,19 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.annotation + +import app.tauri.annotation.Permission + +/** + * Base annotation for all Plugins + */ +@Retention(AnnotationRetention.RUNTIME) +annotation class TauriPlugin( + /** + * Permissions this plugin needs, in order to make permission requests + * easy if the plugin only needs basic permission prompting + */ + val permissions: Array = [] +) diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Channel.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Channel.kt new file mode 100644 index 000000000000..eb7afa51a3bb --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Channel.kt @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +class Channel(val id: Long, private val handler: (data: JSObject) -> Unit) { + fun send(data: JSObject) { + handler(data) + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/InvalidPluginMethodException.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/InvalidPluginMethodException.kt new file mode 100644 index 000000000000..371b15388e51 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/InvalidPluginMethodException.kt @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +internal class InvalidCommandException : Exception { + constructor(s: String?) : super(s) {} + constructor(t: Throwable?) : super(t) {} + constructor(s: String?, t: Throwable?) : super(s, t) {} +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt new file mode 100644 index 000000000000..4bfb5bd908e0 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt @@ -0,0 +1,210 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +import app.tauri.Logger + +const val CHANNEL_PREFIX = "__CHANNEL__:" + +class Invoke( + val id: Long, + val command: String, + val callback: Long, + val error: Long, + private val sendResponse: (callback: Long, data: PluginResult?) -> Unit, + val data: JSObject) { + + fun resolve(data: JSObject?) { + val result = PluginResult(data) + sendResponse(callback, result) + } + + fun resolve() { + sendResponse(callback, null) + } + + fun reject(msg: String?, code: String?, ex: Exception?, data: JSObject?) { + val errorResult = PluginResult() + if (ex != null) { + Logger.error(Logger.tags("Plugin"), msg!!, ex) + } + try { + errorResult.put("message", msg) + errorResult.put("code", code) + if (null != data) { + errorResult.put("data", data) + } + } catch (jsonEx: Exception) { + Logger.error(Logger.tags("Plugin"), jsonEx.message!!, jsonEx) + } + sendResponse(error, errorResult) + } + + fun reject(msg: String?, ex: Exception?, data: JSObject?) { + reject(msg, null, ex, data) + } + + fun reject(msg: String?, code: String?, data: JSObject?) { + reject(msg, code, null, data) + } + + fun reject(msg: String?, code: String?, ex: Exception?) { + reject(msg, code, ex, null) + } + + fun reject(msg: String?, data: JSObject?) { + reject(msg, null, null, data) + } + + fun reject(msg: String?, ex: Exception?) { + reject(msg, null, ex, null) + } + + fun reject(msg: String?, code: String?) { + reject(msg, code, null, null) + } + + fun reject(msg: String?) { + reject(msg, null, null, null) + } + + fun getString(name: String): String? { + return getStringInternal(name, null) + } + + fun getString(name: String, defaultValue: String): String { + return getStringInternal(name, defaultValue)!! + } + + private fun getStringInternal(name: String, defaultValue: String?): String? { + val value = data.opt(name) ?: return defaultValue + return if (value is String) { + value + } else defaultValue + } + + fun getInt(name: String): Int? { + return getIntInternal(name, null) + } + + fun getInt(name: String, defaultValue: Int): Int { + return getIntInternal(name, defaultValue)!! + } + + private fun getIntInternal(name: String, defaultValue: Int?): Int? { + val value = data.opt(name) ?: return defaultValue + return if (value is Int) { + value + } else defaultValue + } + + fun getLong(name: String): Long? { + return getLongInternal(name, null) + } + + fun getLong(name: String, defaultValue: Long): Long { + return getLongInternal(name, defaultValue)!! + } + + private fun getLongInternal(name: String, defaultValue: Long?): Long? { + val value = data.opt(name) ?: return defaultValue + return if (value is Long) { + value + } else defaultValue + } + + fun getFloat(name: String): Float? { + return getFloatInternal(name, null) + } + + fun getFloat(name: String, defaultValue: Float): Float { + return getFloatInternal(name, defaultValue)!! + } + + private fun getFloatInternal(name: String, defaultValue: Float?): Float? { + val value = data.opt(name) ?: return defaultValue + if (value is Float) { + return value + } + if (value is Double) { + return value.toFloat() + } + return if (value is Int) { + value.toFloat() + } else defaultValue + } + + fun getDouble(name: String): Double? { + return getDoubleInternal(name, null) + } + + fun getDouble(name: String, defaultValue: Double): Double { + return getDoubleInternal(name, defaultValue)!! + } + + private fun getDoubleInternal(name: String, defaultValue: Double?): Double? { + val value = data.opt(name) ?: return defaultValue + if (value is Double) { + return value + } + if (value is Float) { + return value.toDouble() + } + return if (value is Int) { + value.toDouble() + } else defaultValue + } + + fun getBoolean(name: String): Boolean? { + return getBooleanInternal(name, null) + } + + fun getBoolean(name: String, defaultValue: Boolean): Boolean { + return getBooleanInternal(name, defaultValue)!! + } + + private fun getBooleanInternal(name: String, defaultValue: Boolean?): Boolean? { + val value = data.opt(name) ?: return defaultValue + return if (value is Boolean) { + value + } else defaultValue + } + + fun getObject(name: String): JSObject? { + return getObjectInternal(name, null) + } + + fun getObject(name: String, defaultValue: JSObject): JSObject { + return getObjectInternal(name, defaultValue)!! + } + + private fun getObjectInternal(name: String, defaultValue: JSObject?): JSObject? { + val value = data.opt(name) ?: return defaultValue + return if (value is JSObject) value else defaultValue + } + + fun getArray(name: String): JSArray? { + return getArrayInternal(name, null) + } + + fun getArray(name: String, defaultValue: JSArray): JSArray { + return getArrayInternal(name, defaultValue)!! + } + + private fun getArrayInternal(name: String, defaultValue: JSArray?): JSArray? { + val value = data.opt(name) ?: return defaultValue + return if (value is JSArray) value else defaultValue + } + + fun hasOption(name: String): Boolean { + return data.has(name) + } + + fun getChannel(name: String): Channel? { + val channelDef = getString(name, "") + val callback = channelDef.substring(CHANNEL_PREFIX.length).toLongOrNull() ?: return null + return Channel(callback) { res -> sendResponse(callback, PluginResult(res)) } + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/JSArray.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/JSArray.kt new file mode 100644 index 000000000000..6db42b86ca88 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/JSArray.kt @@ -0,0 +1,45 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +import org.json.JSONArray +import org.json.JSONException + +class JSArray : JSONArray { + constructor() : super() {} + constructor(json: String?) : super(json) {} + constructor(copyFrom: Collection<*>?) : super(copyFrom) {} + constructor(array: Any?) : super(array) {} + + @Suppress("UNCHECKED_CAST", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + @Throws(JSONException::class) + fun toList(): List { + val items: MutableList = ArrayList() + var o: Any? = null + for (i in 0 until this.length()) { + this.get(i).also { o = it } + try { + items.add(this.get(i) as E) + } catch (ex: Exception) { + throw JSONException("Not all items are instances of the given type") + } + } + return items + } + + companion object { + /** + * Create a new JSArray without throwing a error + */ + fun from(array: Any?): JSArray? { + try { + return JSArray(array) + } catch (ex: JSONException) { + // + } + return null + } + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/JSObject.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/JSObject.kt new file mode 100644 index 000000000000..3affc81aa735 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/JSObject.kt @@ -0,0 +1,152 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +import org.json.JSONException +import org.json.JSONObject + +class JSObject : JSONObject { + constructor() : super() + constructor(json: String) : super(json) + constructor(obj: JSONObject, names: Array) : super(obj, names) + + override fun getString(key: String): String { + return getString(key, "")!! + } + + fun getString(key: String, defaultValue: String?): String? { + try { + if (!super.isNull(key)) { + return super.getString(key) + } + } catch (_: JSONException) { + } + return defaultValue + } + + fun getInteger(key: String): Int? { + return getIntegerInternal(key, null) + } + + fun getInteger(key: String, defaultValue: Int): Int { + return getIntegerInternal(key, defaultValue)!! + } + + private fun getIntegerInternal(key: String, defaultValue: Int?): Int? { + try { + return super.getInt(key) + } catch (_: JSONException) { + } + return defaultValue + } + + override fun getBoolean(key: String): Boolean { + return getBooleanInternal(key, false)!! + } + + fun getBoolean(key: String, defaultValue: Boolean?): Boolean { + return getBooleanInternal(key, defaultValue)!! + } + + private fun getBooleanInternal(key: String, defaultValue: Boolean?): Boolean? { + try { + return super.getBoolean(key) + } catch (_: JSONException) { + } + return defaultValue + } + + fun getJSObject(name: String): JSObject? { + try { + return getJSObjectInternal(name, null) + } catch (_: JSONException) { + } + return null + } + + fun getJSObject(name: String, defaultValue: JSObject): JSObject { + return getJSObjectInternal(name, defaultValue)!! + } + + private fun getJSObjectInternal(name: String, defaultValue: JSObject?): JSObject? { + try { + val obj = get(name) + if (obj is JSONObject) { + val keysIter = obj.keys() + val keys: MutableList = ArrayList() + while (keysIter.hasNext()) { + keys.add(keysIter.next()) + } + return JSObject(obj, keys.toTypedArray()) + } + } catch (_: JSONException) { + } + return defaultValue + } + + override fun put(key: String, value: Boolean): JSObject { + try { + super.put(key, value) + } catch (_: JSONException) { + } + return this + } + + override fun put(key: String, value: Int): JSObject { + try { + super.put(key, value) + } catch (_: JSONException) { + } + return this + } + + override fun put(key: String, value: Long): JSObject { + try { + super.put(key, value) + } catch (_: JSONException) { + } + return this + } + + override fun put(key: String, value: Double): JSObject { + try { + super.put(key, value) + } catch (_: JSONException) { + } + return this + } + + override fun put(key: String, value: Any?): JSObject { + try { + super.put(key, value) + } catch (_: JSONException) { + } + return this + } + + fun put(key: String, value: String?): JSObject { + try { + super.put(key, value) + } catch (_: JSONException) { + } + return this + } + + companion object { + /** + * Convert a pathetic JSONObject into a JSObject + * @param obj + */ + @Throws(JSONException::class) + fun fromJSONObject(obj: JSONObject): JSObject { + val keysIter = obj.keys() + val keys: MutableList = ArrayList() + while (keysIter.hasNext()) { + keys.add(keysIter.next()) + } + return JSObject(obj, keys.toTypedArray()) + } + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Plugin.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Plugin.kt new file mode 100644 index 000000000000..5f1ee212fa41 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Plugin.kt @@ -0,0 +1,417 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.webkit.WebView +import androidx.core.app.ActivityCompat +import app.tauri.FsUtils +import app.tauri.Logger +import app.tauri.PermissionHelper +import app.tauri.PermissionState +import app.tauri.annotation.ActivityCallback +import app.tauri.annotation.Command +import app.tauri.annotation.PermissionCallback +import app.tauri.annotation.TauriPlugin +import org.json.JSONException +import java.util.* +import java.util.concurrent.CopyOnWriteArrayList + +abstract class Plugin(private val activity: Activity) { + var handle: PluginHandle? = null + private val listeners: MutableMap> = mutableMapOf() + + open fun load(webView: WebView) {} + + fun getConfig(): JSObject { + return handle!!.config + } + + /** + * Handle a new intent being received by the application + */ + open fun onNewIntent(intent: Intent) {} + + /** + * Start activity for result with the provided Intent and resolve calling the provided callback method name. + * + * If there is no registered activity callback for the method name passed in, the call will + * be rejected. Make sure a valid activity result callback method is registered using the + * [ActivityCallback] annotation. + * + * @param invoke the invoke object + * @param intent the intent used to start an activity + * @param callbackName the name of the callback to run when the launched activity is finished + */ + fun startActivityForResult(invoke: Invoke, intent: Intent, callbackName: String) { + handle!!.startActivityForResult(invoke, intent, callbackName) + } + + /** + * Get the plugin log tags. + * @param subTags + */ + protected fun getLogTag(vararg subTags: String): String { + return Logger.tags(*subTags) + } + + /** + * Gets a log tag with the plugin's class name as subTag. + */ + protected fun getLogTag(): String { + return Logger.tags(this.javaClass.simpleName) + } + + /** + * Convert an URI to an URL that can be loaded by the webview. + */ + fun assetUrl(u: Uri): String { + var path = FsUtils.getFileUrlForUri(activity, u) + if (path?.startsWith("file://") == true) { + path = path.replace("file://", "") + } + return "asset://localhost$path" + } + + fun trigger(event: String, payload: JSObject) { + val eventListeners = listeners[event] + if (!eventListeners.isNullOrEmpty()) { + val listeners = CopyOnWriteArrayList(eventListeners) + for (channel in listeners) { + channel.send(payload) + } + } + } + + @Command + open fun registerListener(invoke: Invoke) { + val event = invoke.getString("event") + val channel = invoke.getChannel("handler") + + if (event == null || channel == null) { + invoke.reject("`event` or `handler` not provided") + } else { + val eventListeners = listeners[event] + if (eventListeners.isNullOrEmpty()) { + listeners[event] = mutableListOf(channel) + } else { + eventListeners.add(channel) + } + } + + invoke.resolve() + } + + @Command + open fun removeListener(invoke: Invoke) { + val event = invoke.getString("event") + val channelId = invoke.getLong("channelId") + + if (event == null || channelId == null) { + invoke.reject("`event` or `channelId` not provided") + } else { + val eventListeners = listeners[event] + if (!eventListeners.isNullOrEmpty()) { + val c = eventListeners.find { c -> c.id == channelId } + if (c != null) { + eventListeners.remove(c) + } + } + } + + invoke.resolve() + } + + /** + * Exported plugin method for checking the granted status for each permission + * declared on the plugin. This plugin call responds with a mapping of permissions to + * the associated granted status. + */ + @Command + @PermissionCallback + open fun checkPermissions(invoke: Invoke) { + val permissionsResult: Map = getPermissionStates() + if (permissionsResult.isEmpty()) { + // if no permissions are defined on the plugin, resolve undefined + invoke.resolve() + } else { + val permissionsResultJSON = JSObject() + for ((key, value) in permissionsResult) { + permissionsResultJSON.put(key, value) + } + invoke.resolve(permissionsResultJSON) + } + } + + /** + * Exported plugin method to request all permissions for this plugin. + * To manually request permissions within a plugin use: + * [.requestAllPermissions], or + * [.requestPermissionForAlias], or + * [.requestPermissionForAliases] + * + * @param invoke + */ + @Command + open fun requestPermissions(invoke: Invoke) { + val annotation = handle?.annotation + if (annotation != null) { + // handle permission requests for plugins defined with @TauriPlugin + var permAliases: Array? = null + val autoGrantPerms: MutableSet = HashSet() + + // If call was made with a list of specific permission aliases to request, save them + // to be requested + val providedPerms: JSArray = invoke.getArray("permissions", JSArray()) + var providedPermsList: List? = null + try { + providedPermsList = providedPerms.toList() + } catch (ignore: JSONException) { + // do nothing + } + + // If call was made without any custom permissions, request all from plugin annotation + val aliasSet: MutableSet = HashSet() + if (providedPermsList.isNullOrEmpty()) { + for (perm in annotation.permissions) { + // If a permission is defined with no permission strings, separate it for auto-granting. + // Otherwise, the alias is added to the list to be requested. + if (perm.strings.isEmpty() || perm.strings.size == 1 && perm.strings[0] + .isEmpty() + ) { + if (!perm.alias.isEmpty()) { + autoGrantPerms.add(perm.alias) + } + } else { + aliasSet.add(perm.alias) + } + } + permAliases = aliasSet.toTypedArray() + } else { + for (perm in annotation.permissions) { + if (providedPermsList.contains(perm.alias)) { + aliasSet.add(perm.alias) + } + } + if (aliasSet.isEmpty()) { + invoke.reject("No valid permission alias was requested of this plugin.") + } else { + permAliases = aliasSet.toTypedArray() + } + } + if (!permAliases.isNullOrEmpty()) { + // request permissions using provided aliases or all defined on the plugin + requestPermissionForAliases(permAliases, invoke, "checkPermissions") + } else if (autoGrantPerms.isNotEmpty()) { + // if the plugin only has auto-grant permissions, return all as GRANTED + val permissionsResults = JSObject() + for (perm in autoGrantPerms) { + permissionsResults.put(perm, PermissionState.GRANTED.toString()) + } + invoke.resolve(permissionsResults) + } else { + // no permissions are defined on the plugin, resolve undefined + invoke.resolve() + } + } + } + + /** + * Checks if the given permission alias is correctly declared in AndroidManifest.xml + * @param alias a permission alias defined on the plugin + * @return true only if all permissions associated with the given alias are declared in the manifest + */ + fun isPermissionDeclared(alias: String): Boolean { + val annotation = handle?.annotation + if (annotation != null) { + for (perm in annotation.permissions) { + if (alias.equals(perm.alias, ignoreCase = true)) { + var result = true + for (permString in perm.strings) { + result = result && PermissionHelper.hasDefinedPermission(activity, permString) + } + return result + } + } + } + Logger.error( + String.format( + "isPermissionDeclared: No alias defined for %s " + "or missing @TauriPlugin annotation.", + alias + ) + ) + return false + } + + private fun permissionActivityResult( + invoke: Invoke, + permissionStrings: Array, + callbackName: String + ) { + handle!!.requestPermissions(invoke, permissionStrings, callbackName) + } + + /** + * Request all of the specified permissions in the TauriPlugin annotation (if any) + * + * If there is no registered permission callback for the Invoke passed in, the call will + * be rejected. Make sure a valid permission callback method is registered using the + * [PermissionCallback] annotation. + * + * @param invoke + * @param callbackName the name of the callback to run when the permission request is complete + */ + protected fun requestAllPermissions( + invoke: Invoke, + callbackName: String + ) { + val annotation = handle!!.annotation + if (annotation != null) { + val perms: HashSet = HashSet() + for (perm in annotation.permissions) { + perms.addAll(perm.strings) + } + permissionActivityResult(invoke, perms.toArray(arrayOfNulls(0)), callbackName) + } + } + + /** + * Request permissions using an alias defined on the plugin. + * + * If there is no registered permission callback for the Invoke passed in, the call will + * be rejected. Make sure a valid permission callback method is registered using the + * [PermissionCallback] annotation. + * + * @param alias an alias defined on the plugin + * @param invoke the invoke involved in originating the request + * @param callbackName the name of the callback to run when the permission request is complete + */ + protected fun requestPermissionForAlias( + alias: String, + call: Invoke, + callbackName: String + ) { + requestPermissionForAliases(arrayOf(alias), call, callbackName) + } + + /** + * Request permissions using aliases defined on the plugin. + * + * If there is no registered permission callback for the Invoke passed in, the call will + * be rejected. Make sure a valid permission callback method is registered using the + * [PermissionCallback] annotation. + * + * @param aliases a set of aliases defined on the plugin + * @param invoke the invoke involved in originating the request + * @param callbackName the name of the callback to run when the permission request is complete + */ + fun requestPermissionForAliases( + aliases: Array, + invoke: Invoke, + callbackName: String + ) { + if (aliases.isEmpty()) { + Logger.error("No permission alias was provided") + return + } + val permissions = getPermissionStringsForAliases(aliases) + if (permissions.isNotEmpty()) { + permissionActivityResult(invoke, permissions, callbackName) + } + } + + /** + * Gets the Android permission strings defined on the [TauriPlugin] annotation with + * the provided aliases. + * + * @param aliases aliases for permissions defined on the plugin + * @return Android permission strings associated with the provided aliases, if exists + */ + private fun getPermissionStringsForAliases(aliases: Array): Array { + val annotation = handle?.annotation + val perms: HashSet = HashSet() + if (annotation != null) { + for (perm in annotation.permissions) { + if (aliases.contains(perm.alias)) { + perms.addAll(perm.strings) + } + } + } + return perms.toArray(arrayOfNulls(0)) + } + + /** + * Get the permission state for the provided permission alias. + * + * @param alias the permission alias to get + * @return the state of the provided permission alias or null + */ + fun getPermissionState(alias: String): PermissionState? { + return getPermissionStates()[alias] + } + + /** + * Helper to check all permissions defined on a plugin and see the state of each. + * + * @return A mapping of permission aliases to the associated granted status. + */ + open fun getPermissionStates(): Map { + val permissionsResults: MutableMap = HashMap() + val annotation = handle?.annotation + if (annotation != null) { + for (perm in annotation.permissions) { + // If a permission is defined with no permission constants, return GRANTED for it. + // Otherwise, get its true state. + if (perm.strings.isEmpty() || perm.strings.size == 1 && perm.strings[0] + .isEmpty() + ) { + val key = perm.alias + if (key.isNotEmpty()) { + val existingResult = permissionsResults[key] + + // auto set permission state to GRANTED if the alias is empty. + if (existingResult == null) { + permissionsResults[key] = PermissionState.GRANTED + } + } + } else { + for (permString in perm.strings) { + val key = perm.alias.ifEmpty { permString } + var permissionStatus: PermissionState + if (ActivityCompat.checkSelfPermission( + activity, + permString + ) == PackageManager.PERMISSION_GRANTED + ) { + permissionStatus = PermissionState.GRANTED + } else { + permissionStatus = PermissionState.PROMPT + + // Check if there is a cached permission state for the "Never ask again" state + val prefs = + activity.getSharedPreferences("PluginPermStates", Activity.MODE_PRIVATE) + val state = prefs.getString(permString, null) + if (state != null) { + permissionStatus = PermissionState.byState(state) + } + } + val existingResult = permissionsResults[key] + + // multiple permissions with the same alias must all be true, otherwise all false. + if (existingResult == null || existingResult === PermissionState.GRANTED) { + permissionsResults[key] = permissionStatus + } + } + } + } + } + + return permissionsResults + } + +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginHandle.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginHandle.kt new file mode 100644 index 000000000000..08f4a0862519 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginHandle.kt @@ -0,0 +1,154 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +import android.app.Activity +import android.content.Intent +import android.content.SharedPreferences +import android.webkit.WebView +import androidx.core.app.ActivityCompat +import app.tauri.PermissionHelper +import app.tauri.PermissionState +import app.tauri.annotation.ActivityCallback +import app.tauri.annotation.Command +import app.tauri.annotation.PermissionCallback +import app.tauri.annotation.TauriPlugin +import java.lang.reflect.Method +import java.util.Arrays + + +class PluginHandle(private val manager: PluginManager, val name: String, val instance: Plugin, val config: JSObject) { + private val commands: HashMap = HashMap() + private val permissionCallbackMethods: HashMap = HashMap() + private val startActivityCallbackMethods: HashMap = HashMap() + var annotation: TauriPlugin? + var loaded = false + + init { + indexMethods() + instance.handle = this + annotation = instance.javaClass.getAnnotation(TauriPlugin::class.java) + } + + fun load(webView: WebView) { + instance.load(webView) + loaded = true + } + + fun startActivityForResult(invoke: Invoke, intent: Intent, callbackName: String) { + manager.startActivityForResult(intent) { result -> + val method = startActivityCallbackMethods[callbackName] + if (method != null) { + method.isAccessible = true + method(instance, invoke, result) + } + } + } + + fun requestPermissions( + invoke: Invoke, + permissions: Array, + callbackName: String + ) { + manager.requestPermissions(permissions) { result -> + if (validatePermissions(invoke, result)) { + val method = permissionCallbackMethods[callbackName] + if (method != null) { + method.isAccessible = true + method(instance, invoke) + } + } + } + } + + /** + * Saves permission states and rejects if permissions were not correctly defined in + * the AndroidManifest.xml file. + * + * @param permissions + * @return true if permissions were saved and defined correctly, false if not + */ + private fun validatePermissions( + invoke: Invoke, + permissions: Map + ): Boolean { + val activity = manager.activity + val prefs = + activity.getSharedPreferences("PluginPermStates", Activity.MODE_PRIVATE) + for ((permString, isGranted) in permissions) { + if (isGranted) { + // Permission granted. If previously denied, remove cached state + val state = prefs.getString(permString, null) + if (state != null) { + val editor: SharedPreferences.Editor = prefs.edit() + editor.remove(permString) + editor.apply() + } + } else { + val editor: SharedPreferences.Editor = prefs.edit() + if (ActivityCompat.shouldShowRequestPermissionRationale( + activity, + permString + ) + ) { + // Permission denied, can prompt again with rationale + editor.putString(permString, PermissionState.PROMPT_WITH_RATIONALE.toString()) + } else { + // Permission denied permanently, store this state for future reference + editor.putString(permString, PermissionState.DENIED.toString()) + } + editor.apply() + } + } + val permStrings = permissions.keys.toTypedArray() + if (!PermissionHelper.hasDefinedPermissions(activity, permStrings)) { + val builder = StringBuilder() + builder.append("Missing the following permissions in AndroidManifest.xml:\n") + val missing = PermissionHelper.getUndefinedPermissions(activity, permStrings) + for (perm in missing) { + builder.append( + """ + $perm + + """.trimIndent() + ) + } + invoke.reject(builder.toString()) + return false + } + return true + } + + @Throws( + InvalidCommandException::class, + IllegalAccessException::class + ) + fun invoke(invoke: Invoke) { + val methodMeta = commands[invoke.command] + ?: throw InvalidCommandException("No command " + invoke.command + " found for plugin " + instance.javaClass.name) + methodMeta.method.invoke(instance, invoke) + } + + private fun indexMethods() { + val methods = mutableListOf() + var pluginCursor: Class<*> = instance.javaClass + while (pluginCursor.name != Any::class.java.name) { + methods.addAll(listOf(*pluginCursor.declaredMethods)) + pluginCursor = pluginCursor.superclass + } + + for (method in methods) { + if (method.isAnnotationPresent(Command::class.java)) { + val command = method.getAnnotation(Command::class.java) ?: continue + val methodMeta = CommandData(method, command) + commands[method.name] = methodMeta + } else if (method.isAnnotationPresent(ActivityCallback::class.java)) { + startActivityCallbackMethods[method.name] = method + } else if (method.isAnnotationPresent(PermissionCallback::class.java)) { + permissionCallbackMethods[method.name] = method + } + } + } +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginManager.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginManager.kt new file mode 100644 index 000000000000..7dbab66429f8 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginManager.kt @@ -0,0 +1,143 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +import android.content.Context +import android.content.Intent +import android.webkit.WebView +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import app.tauri.FsUtils +import app.tauri.JniMethod +import app.tauri.Logger + +class PluginManager(val activity: AppCompatActivity) { + fun interface RequestPermissionsCallback { + fun onResult(permissions: Map) + } + + fun interface ActivityResultCallback { + fun onResult(result: ActivityResult) + } + + private val plugins: HashMap = HashMap() + private val startActivityForResultLauncher: ActivityResultLauncher + private val requestPermissionsLauncher: ActivityResultLauncher> + private var requestPermissionsCallback: RequestPermissionsCallback? = null + private var startActivityForResultCallback: ActivityResultCallback? = null + + init { + startActivityForResultLauncher = + activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult() + ) { result -> + if (startActivityForResultCallback != null) { + startActivityForResultCallback!!.onResult(result) + } + } + + requestPermissionsLauncher = + activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions() + ) { result -> + if (requestPermissionsCallback != null) { + requestPermissionsCallback!!.onResult(result) + } + } + } + + fun onNewIntent(intent: Intent) { + for (plugin in plugins.values) { + plugin.instance.onNewIntent(intent) + } + } + + fun startActivityForResult(intent: Intent, callback: ActivityResultCallback) { + startActivityForResultCallback = callback + startActivityForResultLauncher.launch(intent) + } + + fun requestPermissions( + permissionStrings: Array, + callback: RequestPermissionsCallback + ) { + requestPermissionsCallback = callback + requestPermissionsLauncher.launch(permissionStrings) + } + + @JniMethod + fun onWebViewCreated(webView: WebView) { + for ((_, plugin) in plugins) { + if (!plugin.loaded) { + plugin.load(webView) + } + } + } + + @JniMethod + fun load(webView: WebView?, name: String, plugin: Plugin, config: JSObject) { + val handle = PluginHandle(this, name, plugin, config) + plugins[name] = handle + if (webView != null) { + plugin.load(webView) + } + } + + @JniMethod + fun postIpcMessage(webView: WebView, pluginId: String, command: String, data: JSObject, callback: Long, error: Long) { + val invoke = Invoke(callback, command, callback, error, { fn, result -> + webView.evaluateJavascript("window['_$fn']($result)", null) + }, data) + + dispatchPluginMessage(invoke, pluginId) + } + + @JniMethod + fun runCommand(id: Int, pluginId: String, command: String, data: JSObject) { + val successId = 0L + val errorId = 1L + val invoke = Invoke(id.toLong(), command, successId, errorId, { fn, result -> + var success: PluginResult? = null + var error: PluginResult? = null + if (fn == successId) { + success = result + } else { + error = result + } + handlePluginResponse(id, success?.toString(), error?.toString()) + }, data) + + dispatchPluginMessage(invoke, pluginId) + } + + private fun dispatchPluginMessage(invoke: Invoke, pluginId: String) { + Logger.verbose( + Logger.tags("Plugin"), + "Tauri plugin: pluginId: $pluginId, command: ${invoke.command}" + ) + + try { + val plugin = plugins[pluginId] + if (plugin == null) { + invoke.reject("Plugin $pluginId not initialized") + } else { + plugins[pluginId]?.invoke(invoke) + } + } catch (e: Exception) { + invoke.reject(if (e.message?.isEmpty() != false) { e.toString() } else { e.message }) + } + } + + companion object { + fun loadConfig(context: Context, plugin: String): JSObject { + val tauriConfigJson = FsUtils.readAsset(context.assets, "tauri.conf.json") + val tauriConfig = JSObject(tauriConfigJson) + val plugins = tauriConfig.getJSObject("plugins", JSObject()) + return plugins.getJSObject(plugin, JSObject()) + } + } + + private external fun handlePluginResponse(id: Int, success: String?, error: String?) +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginMethodData.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginMethodData.kt new file mode 100644 index 000000000000..87b2543122b6 --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginMethodData.kt @@ -0,0 +1,16 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +import app.tauri.annotation.Command +import java.lang.reflect.Method + +class CommandData( + val method: Method, methodDecorator: Command +) { + + // The name of the method + val name: String = method.name +} diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginResult.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginResult.kt new file mode 100644 index 000000000000..142954ba989b --- /dev/null +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginResult.kt @@ -0,0 +1,67 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.plugin + +import android.annotation.SuppressLint +import app.tauri.Logger +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +class PluginResult @JvmOverloads constructor(json: JSObject? = JSObject()) { + private val json: JSObject + + init { + this.json = json ?: JSObject() + } + + fun put(name: String, value: Boolean): PluginResult { + return jsonPut(name, value) + } + + fun put(name: String, value: Double): PluginResult { + return jsonPut(name, value) + } + + fun put(name: String, value: Int): PluginResult { + return jsonPut(name, value) + } + + fun put(name: String, value: Long): PluginResult { + return jsonPut(name, value) + } + + /** + * Format a date as an ISO string + */ + @SuppressLint("SimpleDateFormat") + fun put(name: String, value: Date): PluginResult { + val tz: TimeZone = TimeZone.getTimeZone("UTC") + val df: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'") + df.timeZone = tz + return jsonPut(name, df.format(value)) + } + + fun put(name: String, value: Any?): PluginResult { + return jsonPut(name, value) + } + + fun put(name: String, value: PluginResult): PluginResult { + return jsonPut(name, value.json) + } + + private fun jsonPut(name: String, value: Any?): PluginResult { + try { + json.put(name, value) + } catch (ex: Exception) { + Logger.error(Logger.tags("Plugin"), "", ex) + } + return this + } + + override fun toString(): String { + return json.toString() + } +} diff --git a/core/tauri/mobile/android/src/test/java/app/tauri/ExampleUnitTest.kt b/core/tauri/mobile/android/src/test/java/app/tauri/ExampleUnitTest.kt new file mode 100644 index 000000000000..7db9cdf69f73 --- /dev/null +++ b/core/tauri/mobile/android/src/test/java/app/tauri/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/core/tauri/mobile/ios-api/.gitignore b/core/tauri/mobile/ios-api/.gitignore new file mode 100644 index 000000000000..5922fdaa5638 --- /dev/null +++ b/core/tauri/mobile/ios-api/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/core/tauri/mobile/ios-api/Package.swift b/core/tauri/mobile/ios-api/Package.swift new file mode 100644 index 000000000000..50bafbd805d5 --- /dev/null +++ b/core/tauri/mobile/ios-api/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "Tauri", + platforms: [ + .macOS(.v10_13), + .iOS(.v11), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "Tauri", + type: .static, + targets: ["Tauri"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(name: "SwiftRs", url: "https://github.com/Brendonovich/swift-rs", from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "Tauri", + dependencies: [ + .byName(name: "SwiftRs"), + ], + path: "Sources" + ), + .testTarget( + name: "TauriTests", + dependencies: ["Tauri"] + ), + ] +) diff --git a/core/tauri/mobile/ios-api/README.md b/core/tauri/mobile/ios-api/README.md new file mode 100644 index 000000000000..52c3f1c7fa45 --- /dev/null +++ b/core/tauri/mobile/ios-api/README.md @@ -0,0 +1,3 @@ +# Tauri + +Tauri iOS API. diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/Channel.swift b/core/tauri/mobile/ios-api/Sources/Tauri/Channel.swift new file mode 100644 index 000000000000..6722caec0f24 --- /dev/null +++ b/core/tauri/mobile/ios-api/Sources/Tauri/Channel.swift @@ -0,0 +1,17 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +public class Channel { + public let id: UInt64 + let handler: (JsonValue) -> Void + + public init(callback: UInt64, handler: @escaping (JsonValue) -> Void) { + self.id = callback + self.handler = handler + } + + public func send(_ data: JsonObject) { + handler(.dictionary(data)) + } +} diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift b/core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift new file mode 100644 index 000000000000..15ffb7ac2ff4 --- /dev/null +++ b/core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift @@ -0,0 +1,83 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import UIKit + +let CHANNEL_PREFIX = "__CHANNEL__:" + +@objc public class Invoke: NSObject, JSValueContainer, BridgedJSValueContainer { + public var dictionaryRepresentation: NSDictionary { + return data as NSDictionary + } + + public static var jsDateFormatter: ISO8601DateFormatter = { + return ISO8601DateFormatter() + }() + + public var command: String + var callback: UInt64 + var error: UInt64 + public var data: JSObject + var sendResponse: (UInt64, JsonValue?) -> Void + + public init(command: String, callback: UInt64, error: UInt64, sendResponse: @escaping (UInt64, JsonValue?) -> Void, data: JSObject?) { + self.command = command + self.callback = callback + self.error = error + self.data = data ?? [:] + self.sendResponse = sendResponse + } + + public func resolve() { + sendResponse(callback, nil) + } + + public func resolve(_ data: JsonObject) { + resolve(.dictionary(data)) + } + + public func resolve(_ data: JsonValue) { + sendResponse(callback, data) + } + + public func reject(_ message: String, _ code: String? = nil, _ error: Error? = nil, _ data: JsonValue? = nil) { + let payload: NSMutableDictionary = ["message": message, "code": code ?? "", "error": error ?? ""] + if let data = data { + switch data { + case .dictionary(let dict): + for entry in dict { + payload[entry.key] = entry.value + } + } + } + sendResponse(self.error, .dictionary(payload as! JsonObject)) + } + + public func unimplemented() { + unimplemented("not implemented") + } + + public func unimplemented(_ message: String) { + sendResponse(error, .dictionary(["message": message])) + } + + public func unavailable() { + unavailable("not available") + } + + public func unavailable(_ message: String) { + sendResponse(error, .dictionary(["message": message])) + } + + public func getChannel(_ key: String) -> Channel? { + let channelDef = getString(key, "") + guard let callback = UInt64(channelDef.components(separatedBy: CHANNEL_PREFIX)[1]) else { + return nil + } + return Channel(callback: callback, handler: { (res: JsonValue) -> Void in + self.sendResponse(callback, res) + }) + } +} diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/JSTypes.swift b/core/tauri/mobile/ios-api/Sources/Tauri/JSTypes.swift new file mode 100644 index 000000000000..d1c3fb07b485 --- /dev/null +++ b/core/tauri/mobile/ios-api/Sources/Tauri/JSTypes.swift @@ -0,0 +1,242 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +// declare our empty protocol, and conformance, for typing +public protocol JSValue { } +extension String: JSValue { } +extension Bool: JSValue { } +extension Int: JSValue { } +extension Float: JSValue { } +extension Double: JSValue { } +extension NSNumber: JSValue { } +extension NSNull: JSValue { } +extension Array: JSValue { } +extension Date: JSValue { } +extension Dictionary: JSValue where Key == String, Value == JSValue { } + +// convenience aliases +public typealias JSObject = [String: JSValue] +public typealias JSArray = [JSValue] + +// string types +public protocol JSStringContainer { + func getString(_ key: String, _ defaultValue: String) -> String + func getString(_ key: String) -> String? +} + +extension JSStringContainer { + public func getString(_ key: String, _ defaultValue: String) -> String { + return getString(key) ?? defaultValue + } +} + +// boolean types +public protocol JSBoolContainer { + func getBool(_ key: String, _ defaultValue: Bool) -> Bool + func getBool(_ key: String) -> Bool? +} + +extension JSBoolContainer { + public func getBool(_ key: String, _ defaultValue: Bool) -> Bool { + return getBool(key) ?? defaultValue + } +} + +// integer types +public protocol JSIntContainer { + func getInt(_ key: String, _ defaultValue: Int) -> Int + func getInt(_ key: String) -> Int? +} + +extension JSIntContainer { + public func getInt(_ key: String, _ defaultValue: Int) -> Int { + return getInt(key) ?? defaultValue + } +} + +// float types +public protocol JSFloatContainer { + func getFloat(_ key: String, _ defaultValue: Float) -> Float + func getFloat(_ key: String) -> Float? +} + +extension JSFloatContainer { + public func getFloat(_ key: String, _ defaultValue: Float) -> Float { + return getFloat(key) ?? defaultValue + } +} + +// double types +public protocol JSDoubleContainer { + func getDouble(_ key: String, _ defaultValue: Double) -> Double + func getDouble(_ key: String) -> Double? +} + +extension JSDoubleContainer { + public func getDouble(_ key: String, _ defaultValue: Double) -> Double { + return getDouble(key) ?? defaultValue + } +} + +// date types +public protocol JSDateContainer { + func getDate(_ key: String, _ defaultValue: Date) -> Date + func getDate(_ key: String) -> Date? +} + +extension JSDateContainer { + public func getDate(_ key: String, _ defaultValue: Date) -> Date { + return getDate(key) ?? defaultValue + } +} + +// array types +public protocol JSArrayContainer { + func getArray(_ key: String, _ defaultValue: JSArray) -> JSArray + func getArray(_ key: String, _ ofType: T.Type) -> [T]? + func getArray(_ key: String) -> JSArray? +} + +extension JSArrayContainer { + public func getArray(_ key: String, _ defaultValue: JSArray) -> JSArray { + return getArray(key) ?? defaultValue + } + + public func getArray(_ key: String, _ ofType: T.Type) -> [T]? { + return getArray(key) as? [T] + } +} + +// dictionary types +public protocol JSObjectContainer { + func getObject(_ key: String, _ defaultValue: JSObject) -> JSObject + func getObject(_ key: String) -> JSObject? +} + +extension JSObjectContainer { + public func getObject(_ key: String, _ defaultValue: JSObject) -> JSObject { + return getObject(key) ?? defaultValue + } +} + +public protocol JSValueContainer: JSStringContainer, JSBoolContainer, JSIntContainer, JSFloatContainer, + JSDoubleContainer, JSDateContainer, JSArrayContainer, JSObjectContainer { + static var jsDateFormatter: ISO8601DateFormatter { get } + var data: JSObject { get } +} + +extension JSValueContainer { + public func getValue(_ key: String) -> JSValue? { + return data[key] + } + + public func getString(_ key: String) -> String? { + return data[key] as? String + } + + public func getBool(_ key: String) -> Bool? { + return data[key] as? Bool + } + + public func getInt(_ key: String) -> Int? { + return data[key] as? Int + } + + public func getFloat(_ key: String) -> Float? { + if let floatValue = data[key] as? Float { + return floatValue + } else if let doubleValue = data[key] as? Double { + return Float(doubleValue) + } + return nil + } + + public func getDouble(_ key: String) -> Double? { + return data[key] as? Double + } + + public func getDate(_ key: String) -> Date? { + if let isoString = data[key] as? String { + return Self.jsDateFormatter.date(from: isoString) + } + return data[key] as? Date + } + + public func getArray(_ key: String) -> JSArray? { + return data[key] as? JSArray + } + + public func getObject(_ key: String) -> JSObject? { + return data[key] as? JSObject + } +} + +@objc protocol BridgedJSValueContainer: NSObjectProtocol { + static var jsDateFormatter: ISO8601DateFormatter { get } + var dictionaryRepresentation: NSDictionary { get } +} + +/* + Simply casting objects from foundation class clusters (such as __NSArrayM) + doesn't work with the JSValue protocol and will always fail. So we need to + recursively and explicitly convert each value in the dictionary. + */ +public enum JSTypes { } +extension JSTypes { + public static func coerceDictionaryToJSObject(_ dictionary: NSDictionary?, formattingDatesAsStrings: Bool = false) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } + + public static func coerceDictionaryToJSObject(_ dictionary: [AnyHashable: Any]?, formattingDatesAsStrings: Bool = false) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } + + public static func coerceArrayToJSArray(_ array: [Any]?, formattingDatesAsStrings: Bool = false) -> JSArray? { + return array?.compactMap { coerceToJSValue($0, formattingDates: formattingDatesAsStrings) } + } +} + +private let dateStringFormatter = ISO8601DateFormatter() + +// We need a large switch statement because we have a lot of types. +// swiftlint:disable:next cyclomatic_complexity +private func coerceToJSValue(_ value: Any?, formattingDates: Bool) -> JSValue? { + guard let value = value else { + return nil + } + switch value { + case let stringValue as String: + return stringValue + case let numberValue as NSNumber: + return numberValue + case let boolValue as Bool: + return boolValue + case let intValue as Int: + return intValue + case let floatValue as Float: + return floatValue + case let doubleValue as Double: + return doubleValue + case let dateValue as Date: + if formattingDates { + return dateStringFormatter.string(from: dateValue) + } + return dateValue + case let nullValue as NSNull: + return nullValue + case let arrayValue as NSArray: + return arrayValue.compactMap { coerceToJSValue($0, formattingDates: formattingDates) } + case let dictionaryValue as NSDictionary: + let keys = dictionaryValue.allKeys.compactMap { $0 as? String } + var result: JSObject = [:] + for key in keys { + result[key] = coerceToJSValue(dictionaryValue[key], formattingDates: formattingDates) + } + return result + default: + return nil + } +} diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/JsonValue.swift b/core/tauri/mobile/ios-api/Sources/Tauri/JsonValue.swift new file mode 100644 index 000000000000..7c863ded5db3 --- /dev/null +++ b/core/tauri/mobile/ios-api/Sources/Tauri/JsonValue.swift @@ -0,0 +1,58 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +public typealias JsonObject = [String: Any] + +public enum JsonValue { + case dictionary(JsonObject) + + enum SerializationError: Error { + case invalidObject + } + + public func jsonRepresentation(includingFields: JsonObject? = nil) throws -> String? { + switch self { + case .dictionary(var dictionary): + if let fields = includingFields { + dictionary.merge(fields) { (current, _) in current } + } + dictionary = prepare(dictionary: dictionary) + guard JSONSerialization.isValidJSONObject(dictionary) else { + throw SerializationError.invalidObject + } + let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) + return String(data: data, encoding: .utf8) + } + } + + private static let formatter = ISO8601DateFormatter() + + private func prepare(dictionary: JsonObject) -> JsonObject { + return dictionary.mapValues { (value) -> Any in + if let date = value as? Date { + return JsonValue.formatter.string(from: date) + } else if let aDictionary = value as? JsonObject { + return prepare(dictionary: aDictionary) + } else if let anArray = value as? [Any] { + return prepare(array: anArray) + } + return value + } + } + + private func prepare(array: [Any]) -> [Any] { + return array.map { (value) -> Any in + if let date = value as? Date { + return JsonValue.formatter.string(from: date) + } else if let aDictionary = value as? JsonObject { + return prepare(dictionary: aDictionary) + } else if let anArray = value as? [Any] { + return prepare(array: anArray) + } + return value + } + } +} diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/Logger.swift b/core/tauri/mobile/ios-api/Sources/Tauri/Logger.swift new file mode 100644 index 000000000000..115359f8ffbe --- /dev/null +++ b/core/tauri/mobile/ios-api/Sources/Tauri/Logger.swift @@ -0,0 +1,50 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import os.log +import UIKit + +/// Wrapper class for os_log function +public class Logger { + private static var _enabled = false + public static var enabled: Bool { + get { + #if DEBUG + return true + #else + return _enabled + #endif + } + set { + Logger._enabled = newValue + } + } + + static func log(_ items: Any..., category: String, type: OSLogType) { + if Logger.enabled { + var message = "" + let last = items.count - 1 + for (index, item) in items.enumerated() { + message += "\(item)" + if index != last { + message += " " + } + } + let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "-", category: category) + os_log("%{public}@", log: log, type: type, String(message.prefix(4068))) + } + } + + public static func debug(_ items: Any..., category: String = "app") { + Logger.log(items, category: category, type: OSLogType.debug) + } + + public static func info(_ items: Any..., category: String = "app") { + Logger.log(items, category: category, type: OSLogType.info) + } + + public static func error(_ items: Any..., category: String = "app") { + Logger.log(items, category: category, type: OSLogType.error) + } +} diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/Plugin/Plugin.swift b/core/tauri/mobile/ios-api/Sources/Tauri/Plugin/Plugin.swift new file mode 100644 index 000000000000..50179e2da09d --- /dev/null +++ b/core/tauri/mobile/ios-api/Sources/Tauri/Plugin/Plugin.swift @@ -0,0 +1,71 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import WebKit +import os.log + +open class Plugin: NSObject { + public let manager: PluginManager = PluginManager.shared + public var config: JSObject = [:] + private var listeners = [String: [Channel]]() + + internal func setConfig(_ config: JSObject) { + self.config = config + } + + @objc open func load(webview: WKWebView) {} + + @objc open func checkPermissions(_ invoke: Invoke) { + invoke.resolve() + } + + @objc open func requestPermissions(_ invoke: Invoke) { + invoke.resolve() + } + + public func trigger(_ event: String, data: JSObject) { + if let eventListeners = listeners[event] { + for channel in eventListeners { + channel.send(data) + } + } + } + + @objc func registerListener(_ invoke: Invoke) { + guard let event = invoke.getString("event") else { + invoke.reject("`event` not provided") + return + } + guard let channel = invoke.getChannel("handler") else { + invoke.reject("`handler` not provided") + return + } + + if var eventListeners = listeners[event] { + eventListeners.append(channel) + } else { + listeners[event] = [channel] + } + + invoke.resolve() + } + + @objc func removeListener(_ invoke: Invoke) { + guard let event = invoke.getString("event") else { + invoke.reject("`event` not provided") + return + } + + if let eventListeners = listeners[event] { + guard let channelId = invoke.getInt("channelId") else { + invoke.reject("`channelId` not provided") + return + } + + listeners[event] = eventListeners.filter { $0.id != channelId } + } + + invoke.resolve() + } +} diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/Tauri.swift b/core/tauri/mobile/ios-api/Sources/Tauri/Tauri.swift new file mode 100644 index 000000000000..b5f4ec6531e8 --- /dev/null +++ b/core/tauri/mobile/ios-api/Sources/Tauri/Tauri.swift @@ -0,0 +1,146 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import SwiftRs +import Foundation +import UIKit +import WebKit +import os.log + +class PluginHandle { + var instance: Plugin + var loaded = false + + init(plugin: Plugin) { + instance = plugin + } +} + +public class PluginManager { + static let shared: PluginManager = PluginManager() + public var viewController: UIViewController? + var plugins: [String: PluginHandle] = [:] + var ipcDispatchQueue = DispatchQueue(label: "ipc") + public var isSimEnvironment: Bool { + #if targetEnvironment(simulator) + return true + #else + return false + #endif + } + + public func assetUrl(fromLocalURL url: URL?) -> URL? { + guard let inputURL = url else { + return nil + } + + return URL(string: "asset://localhost")!.appendingPathComponent(inputURL.path) + } + + func onWebviewCreated(_ webview: WKWebView) { + for (_, handle) in plugins { + if (!handle.loaded) { + handle.instance.load(webview: webview) + } + } + } + + func load(name: String, plugin: P, config: JSObject, webview: WKWebView?) { + plugin.setConfig(config) + let handle = PluginHandle(plugin: plugin) + if let webview = webview { + handle.instance.load(webview: webview) + handle.loaded = true + } + plugins[name] = handle + } + + func invoke(name: String, invoke: Invoke) { + if let plugin = plugins[name] { + ipcDispatchQueue.async { + let selectorWithThrows = Selector(("\(invoke.command):error:")) + if plugin.instance.responds(to: selectorWithThrows) { + var error: NSError? = nil + withUnsafeMutablePointer(to: &error) { + let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows) + unsafeBitCast(methodIMP, to: (@convention(c)(Any?, Selector, Invoke, OpaquePointer) -> Void).self)(plugin.instance, selectorWithThrows, invoke, OpaquePointer($0)) + } + if let error = error { + invoke.reject("\(error)") + // TODO: app crashes without this leak + let _ = Unmanaged.passRetained(error) + } + } else { + let selector = Selector(("\(invoke.command):")) + if plugin.instance.responds(to: selector) { + plugin.instance.perform(selector, with: invoke) + } else { + invoke.reject("No command \(invoke.command) found for plugin \(name)") + } + } + } + } else { + invoke.reject("Plugin \(name) not initialized") + } + } +} + +extension PluginManager: NSCopying { + public func copy(with zone: NSZone? = nil) -> Any { + return self + } +} + +@_cdecl("register_plugin") +func registerPlugin(name: SRString, plugin: NSObject, config: NSDictionary?, webview: WKWebView?) { + PluginManager.shared.load( + name: name.toString(), + plugin: plugin as! Plugin, + config: JSTypes.coerceDictionaryToJSObject(config ?? [:], formattingDatesAsStrings: true)!, + webview: webview + ) +} + +@_cdecl("on_webview_created") +func onWebviewCreated(webview: WKWebView, viewController: UIViewController) { + PluginManager.shared.viewController = viewController + PluginManager.shared.onWebviewCreated(webview) +} + +@_cdecl("post_ipc_message") +func postIpcMessage(webview: WKWebView, name: SRString, command: SRString, data: NSDictionary, callback: UInt64, error: UInt64) { + let invoke = Invoke(command: command.toString(), callback: callback, error: error, sendResponse: { (fn: UInt64, payload: JsonValue?) -> Void in + var payloadJson: String + do { + try payloadJson = payload == nil ? "null" : payload!.jsonRepresentation() ?? "`Failed to serialize payload`" + } catch { + payloadJson = "`\(error)`" + } + webview.evaluateJavaScript("window['_\(fn)'](\(payloadJson))") + }, data: JSTypes.coerceDictionaryToJSObject(data, formattingDatesAsStrings: true)) + PluginManager.shared.invoke(name: name.toString(), invoke: invoke) +} + +@_cdecl("run_plugin_method") +func runCommand( + id: Int, + name: SRString, + command: SRString, + data: NSDictionary, + callback: @escaping @convention(c) (Int, Bool, UnsafePointer?) -> Void +) { + let callbackId: UInt64 = 0 + let errorId: UInt64 = 1 + let invoke = Invoke(command: command.toString(), callback: callbackId, error: errorId, sendResponse: { (fn: UInt64, payload: JsonValue?) -> Void in + let success = fn == callbackId + var payloadJson: String = "" + do { + try payloadJson = payload == nil ? "null" : payload!.jsonRepresentation() ?? "`Failed to serialize payload`" + } catch { + payloadJson = "`\(error)`" + } + callback(id, success, payloadJson.cString(using: String.Encoding.utf8)) + }, data: JSTypes.coerceDictionaryToJSObject(data, formattingDatesAsStrings: true)) + PluginManager.shared.invoke(name: name.toString(), invoke: invoke) +} diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/UiUtils.swift b/core/tauri/mobile/ios-api/Sources/Tauri/UiUtils.swift new file mode 100644 index 000000000000..6ca74b960e77 --- /dev/null +++ b/core/tauri/mobile/ios-api/Sources/Tauri/UiUtils.swift @@ -0,0 +1,15 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import UIKit + +public class UIUtils { + public static func centerPopover(rootViewController: UIViewController?, popoverController: UIViewController) { + if let viewController = rootViewController { + popoverController.popoverPresentationController?.sourceRect = CGRect(x: viewController.view.center.x, y: viewController.view.center.y, width: 0, height: 0) + popoverController.popoverPresentationController?.sourceView = viewController.view + popoverController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up + } + } +} diff --git a/core/tauri/mobile/ios-api/Tests/TauriTests/TauriTests.swift b/core/tauri/mobile/ios-api/Tests/TauriTests/TauriTests.swift new file mode 100644 index 000000000000..0681f06fd5e8 --- /dev/null +++ b/core/tauri/mobile/ios-api/Tests/TauriTests/TauriTests.swift @@ -0,0 +1,15 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import Tauri + +final class TauriTests: XCTestCase { + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + XCTAssertEqual(Tauri().text, "Hello, World!") + } +} diff --git a/core/tauri/mobile/proguard-tauri.pro b/core/tauri/mobile/proguard-tauri.pro new file mode 100644 index 000000000000..6bc0b4b1ca82 --- /dev/null +++ b/core/tauri/mobile/proguard-tauri.pro @@ -0,0 +1,5 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + +-keep class $PACKAGE.TauriActivity { + public app.tauri.plugin.PluginManager getPluginManager(); +} diff --git a/core/tauri/scripts/bundle.global.js b/core/tauri/scripts/bundle.global.js new file mode 100644 index 000000000000..3ae27b270d74 --- /dev/null +++ b/core/tauri/scripts/bundle.global.js @@ -0,0 +1,2 @@ +"use strict";var __TAURI_IIFE__=(()=>{var m=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var O=Object.prototype.hasOwnProperty;var p=(n,e)=>{for(var i in e)m(n,i,{get:e[i],enumerable:!0})},W=(n,e,i,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of E(e))!O.call(n,a)&&a!==i&&m(n,a,{get:()=>e[a],enumerable:!(o=C(e,a))||o.enumerable});return n};var N=n=>W(m({},"__esModule",{value:!0}),n);var P=(n,e,i)=>{if(!e.has(n))throw TypeError("Cannot "+i)};var _=(n,e,i)=>(P(n,e,"read from private field"),i?i.call(n):e.get(n)),D=(n,e,i)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,i)},w=(n,e,i,o)=>(P(n,e,"write to private field"),o?o.call(n,i):e.set(n,i),i);var hn={};p(hn,{event:()=>f,invoke:()=>fn,path:()=>h,tauri:()=>y});var f={};p(f,{TauriEvent:()=>b,emit:()=>F,listen:()=>I,once:()=>U});var y={};p(y,{Channel:()=>l,PluginListener:()=>g,addPluginListener:()=>L,convertFileSrc:()=>k,invoke:()=>t,transformCallback:()=>u});function T(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function u(n,e=!1){let i=T(),o=`_${i}`;return Object.defineProperty(window,o,{value:a=>(e&&Reflect.deleteProperty(window,o),n?.(a)),writable:!1,configurable:!0}),i}var c,l=class{constructor(){this.__TAURI_CHANNEL_MARKER__=!0;D(this,c,()=>{});this.id=u(e=>{_(this,c).call(this,e)})}set onmessage(e){w(this,c,e)}get onmessage(){return _(this,c)}toJSON(){return`__CHANNEL__:${this.id}`}};c=new WeakMap;var g=class{constructor(e,i,o){this.plugin=e,this.event=i,this.channelId=o}async unregister(){return t(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}};async function L(n,e,i){let o=new l;return o.onmessage=i,t(`plugin:${n}|register_listener`,{event:e,handler:o}).then(()=>new g(n,e,o.id))}async function t(n,e={}){return new Promise((i,o)=>{let a=u(d=>{i(d),Reflect.deleteProperty(window,`_${v}`)},!0),v=u(d=>{o(d),Reflect.deleteProperty(window,`_${a}`)},!0);window.__TAURI_IPC__({cmd:n,callback:a,error:v,...e})})}function k(n,e="asset"){let i=encodeURIComponent(n);return navigator.userAgent.includes("Windows")?`https://${e}.localhost/${i}`:`${e}://localhost/${i}`}var b=(s=>(s.WINDOW_RESIZED="tauri://resize",s.WINDOW_MOVED="tauri://move",s.WINDOW_CLOSE_REQUESTED="tauri://close-requested",s.WINDOW_CREATED="tauri://window-created",s.WINDOW_DESTROYED="tauri://destroyed",s.WINDOW_FOCUS="tauri://focus",s.WINDOW_BLUR="tauri://blur",s.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",s.WINDOW_THEME_CHANGED="tauri://theme-changed",s.WINDOW_FILE_DROP="tauri://file-drop",s.WINDOW_FILE_DROP_HOVER="tauri://file-drop-hover",s.WINDOW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled",s.MENU="tauri://menu",s))(b||{});async function A(n,e){await t("plugin:event|unlisten",{event:n,eventId:e})}async function I(n,e,i){return t("plugin:event|listen",{event:n,windowLabel:i?.target,handler:u(e)}).then(o=>async()=>A(n,o))}async function U(n,e,i){return I(n,o=>{e(o),A(n,o.id).catch(()=>{})},i)}async function F(n,e,i){await t("plugin:event|emit",{event:n,windowLabel:i?.target,payload:e})}var h={};p(h,{BaseDirectory:()=>R,appCacheDir:()=>S,appConfigDir:()=>x,appDataDir:()=>$,appLocalDataDir:()=>H,appLogDir:()=>sn,audioDir:()=>M,basename:()=>_n,cacheDir:()=>V,configDir:()=>j,dataDir:()=>z,delimiter:()=>un,desktopDir:()=>G,dirname:()=>dn,documentDir:()=>q,downloadDir:()=>J,executableDir:()=>K,extname:()=>mn,fontDir:()=>Q,homeDir:()=>Y,isAbsolute:()=>yn,join:()=>gn,localDataDir:()=>Z,normalize:()=>ln,pictureDir:()=>X,publicDir:()=>B,resolve:()=>pn,resolveResource:()=>en,resourceDir:()=>nn,runtimeDir:()=>rn,sep:()=>cn,tempDir:()=>an,templateDir:()=>tn,videoDir:()=>on});var R=(r=>(r[r.Audio=1]="Audio",r[r.Cache=2]="Cache",r[r.Config=3]="Config",r[r.Data=4]="Data",r[r.LocalData=5]="LocalData",r[r.Document=6]="Document",r[r.Download=7]="Download",r[r.Picture=8]="Picture",r[r.Public=9]="Public",r[r.Video=10]="Video",r[r.Resource=11]="Resource",r[r.Temp=12]="Temp",r[r.AppConfig=13]="AppConfig",r[r.AppData=14]="AppData",r[r.AppLocalData=15]="AppLocalData",r[r.AppCache=16]="AppCache",r[r.AppLog=17]="AppLog",r[r.Desktop=18]="Desktop",r[r.Executable=19]="Executable",r[r.Font=20]="Font",r[r.Home=21]="Home",r[r.Runtime=22]="Runtime",r[r.Template=23]="Template",r))(R||{});async function x(){return t("plugin:path|resolve_directory",{directory:13})}async function $(){return t("plugin:path|resolve_directory",{directory:14})}async function H(){return t("plugin:path|resolve_directory",{directory:15})}async function S(){return t("plugin:path|resolve_directory",{directory:16})}async function M(){return t("plugin:path|resolve_directory",{directory:1})}async function V(){return t("plugin:path|resolve_directory",{directory:2})}async function j(){return t("plugin:path|resolve_directory",{directory:3})}async function z(){return t("plugin:path|resolve_directory",{directory:4})}async function G(){return t("plugin:path|resolve_directory",{directory:18})}async function q(){return t("plugin:path|resolve_directory",{directory:6})}async function J(){return t("plugin:path|resolve_directory",{directory:7})}async function K(){return t("plugin:path|resolve_directory",{directory:19})}async function Q(){return t("plugin:path|resolve_directory",{directory:20})}async function Y(){return t("plugin:path|resolve_directory",{directory:21})}async function Z(){return t("plugin:path|resolve_directory",{directory:5})}async function X(){return t("plugin:path|resolve_directory",{directory:8})}async function B(){return t("plugin:path|resolve_directory",{directory:9})}async function nn(){return t("plugin:path|resolve_directory",{directory:11})}async function en(n){return t("plugin:path|resolve_directory",{directory:11,path:n})}async function rn(){return t("plugin:path|resolve_directory",{directory:22})}async function tn(){return t("plugin:path|resolve_directory",{directory:23})}async function on(){return t("plugin:path|resolve_directory",{directory:10})}async function sn(){return t("plugin:path|resolve_directory",{directory:17})}async function an(n){return t("plugin:path|resolve_directory",{directory:12})}function cn(){return window.__TAURI__.path.__sep}function un(){return window.__TAURI__.path.__delimiter}async function pn(...n){return t("plugin:path|resolve",{paths:n})}async function ln(n){return t("plugin:path|normalize",{path:n})}async function gn(...n){return t("plugin:path|join",{paths:n})}async function dn(n){return t("plugin:path|dirname",{path:n})}async function mn(n){return t("plugin:path|extname",{path:n})}async function _n(n,e){return t("plugin:path|basename",{path:n,ext:e})}async function yn(n){return t("plugin:path|isAbsolute",{path:n})}var fn=t;return N(hn);})(); +window.__TAURI__ = __TAURI_IIFE__ diff --git a/core/tauri/scripts/bundle.js b/core/tauri/scripts/bundle.js deleted file mode 100644 index 204f81f47ec7..000000000000 --- a/core/tauri/scripts/bundle.js +++ /dev/null @@ -1 +0,0 @@ -function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&_setPrototypeOf(e,t)}function _setPrototypeOf(e,t){return(_setPrototypeOf=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function _createSuper(e){var t=_isNativeReflectConstruct();return function(){var r,n=_getPrototypeOf(e);if(t){var a=_getPrototypeOf(this).constructor;r=Reflect.construct(n,arguments,a)}else r=n.apply(this,arguments);return _possibleConstructorReturn(this,r)}}function _possibleConstructorReturn(e,t){if(t&&("object"===_typeof(t)||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return _assertThisInitialized(e)}function _assertThisInitialized(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function _isNativeReflectConstruct(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function _getPrototypeOf(e){return(_getPrototypeOf=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function _createForOfIteratorHelper(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,a=function(){};return{s:a,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,u=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return i=e.done,e},e:function(e){u=!0,o=e},f:function(){try{i||null==r.return||r.return()}finally{if(u)throw o}}}}function _unsupportedIterableToArray(e,t){if(e){if("string"==typeof e)return _arrayLikeToArray(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r=0;--o){var i=this.tryEntries[o],u=i.completion;if("root"===i.tryLoc)return a("end");if(i.tryLoc<=this.prev){var s=n.call(i,"catchLoc"),c=n.call(i,"finallyLoc");if(s&&c){if(this.prev=0;--r){var a=this.tryEntries[r];if(a.tryLoc<=this.prev&&n.call(a,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),P(r),d}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var r=this.tryEntries[t];if(r.tryLoc===e){var n=r.completion;if("throw"===n.type){var a=n.arg;P(r)}return a}}throw new Error("illegal catch attempt")},delegateYield:function(e,r,n){return this.delegate={iterator:O(e),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=t),d}},e}("object"===("undefined"==typeof module?"undefined":_typeof(module))?module.exports:{});try{regeneratorRuntime=t}catch(e){"object"===("undefined"==typeof globalThis?"undefined":_typeof(globalThis))?globalThis.regeneratorRuntime=t:Function("r","regeneratorRuntime = r")(t)}function r(e){for(var t=void 0,r=e[0],n=1;n1&&void 0!==arguments[1]&&arguments[1],a=n(),o="_".concat(a);return Object.defineProperty(window,o,{value:function(n){return t&&Reflect.deleteProperty(window,o),r([e,"optionalCall",function(e){return e(n)}])},writable:!1,configurable:!0}),a}function o(e){return i.apply(this,arguments)}function i(){return(i=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=n.length>1&&void 0!==n[1]?n[1]:{},e.abrupt("return",new Promise((function(e,n){var o=a((function(t){e(t),Reflect.deleteProperty(window,i)}),!0),i=a((function(e){n(e),Reflect.deleteProperty(window,o)}),!0);window.__TAURI_IPC__(_objectSpread({cmd:t,callback:o,error:i},r))})));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var u=Object.freeze({__proto__:null,transformCallback:a,invoke:o,convertFileSrc:function(e){return navigator.userAgent.includes("Windows")?"https://asset.localhost/".concat(e):"asset://".concat(e)}});function s(e){return c.apply(this,arguments)}function c(){return(c=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",o("tauri",t));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function p(){return(p=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"App",message:{cmd:"getAppVersion"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function l(){return(l=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"App",message:{cmd:"getAppName"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function f(){return(f=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"App",message:{cmd:"getTauriVersion"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var h=Object.freeze({__proto__:null,getName:function(){return l.apply(this,arguments)},getVersion:function(){return p.apply(this,arguments)},getTauriVersion:function(){return f.apply(this,arguments)}});function m(){return(m=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Cli",message:{cmd:"cliMatches"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var d=Object.freeze({__proto__:null,getMatches:function(){return m.apply(this,arguments)}});function y(){return(y=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Clipboard",message:{cmd:"writeText",data:t}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function g(){return(g=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Clipboard",message:{cmd:"readText"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var _=Object.freeze({__proto__:null,writeText:function(e){return y.apply(this,arguments)},readText:function(){return g.apply(this,arguments)}});function w(){return(w=_asyncToGenerator(regeneratorRuntime.mark((function e(){var t,r=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return"object"===_typeof(t=r.length>0&&void 0!==r[0]?r[0]:{})&&Object.freeze(t),e.abrupt("return",s({__tauriModule:"Dialog",message:{cmd:"openDialog",options:t}}));case 3:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function v(){return(v=_asyncToGenerator(regeneratorRuntime.mark((function e(){var t,r=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return"object"===_typeof(t=r.length>0&&void 0!==r[0]?r[0]:{})&&Object.freeze(t),e.abrupt("return",s({__tauriModule:"Dialog",message:{cmd:"saveDialog",options:t}}));case 3:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function b(){return(b=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Dialog",message:{cmd:"messageDialog",message:t}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function R(){return(R=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Dialog",message:{cmd:"askDialog",title:r,message:t}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function k(){return(k=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Dialog",message:{cmd:"confirmDialog",title:r,message:t}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var x=Object.freeze({__proto__:null,open:function(){return w.apply(this,arguments)},save:function(){return v.apply(this,arguments)},message:function(e){return b.apply(this,arguments)},ask:function(e,t){return R.apply(this,arguments)},confirm:function(e,t){return k.apply(this,arguments)}});function T(e){return G.apply(this,arguments)}function G(){return(G=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Event",message:{cmd:"unlisten",eventId:t}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function P(e,t,r){return M.apply(this,arguments)}function M(){return(M=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r,n){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,s({__tauriModule:"Event",message:{cmd:"emit",event:t,windowLabel:r,payload:"string"==typeof n?n:JSON.stringify(n)}});case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function O(e,t,r){return j.apply(this,arguments)}function j(){return(j=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r,n){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Event",message:{cmd:"listen",event:t,windowLabel:r,handler:a(n)}}).then((function(e){return _asyncToGenerator(regeneratorRuntime.mark((function t(){return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.abrupt("return",T(e));case 1:case"end":return t.stop()}}),t)})))})));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function A(e,t,r){return C.apply(this,arguments)}function C(){return(C=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r,n){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",O(t,r,(function(e){n(e),T(e.id).catch((function(){}))})));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function S(e,t){return D.apply(this,arguments)}function D(){return(D=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",O(t,null,r));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function z(e,t){return L.apply(this,arguments)}function L(){return(L=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",A(t,null,r));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function E(e,t){return W.apply(this,arguments)}function W(){return(W=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",P(t,void 0,r));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var F,I=Object.freeze({__proto__:null,listen:S,once:z,emit:E});function N(){return(N=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=n.length>1&&void 0!==n[1]?n[1]:{},e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"readFile",path:t,options:r}}).then((function(e){return(new TextDecoder).decode(new Uint8Array(e))})));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function U(){return(U=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n,a=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=a.length>1&&void 0!==a[1]?a[1]:{},e.next=3,s({__tauriModule:"Fs",message:{cmd:"readFile",path:t,options:r}});case 3:return n=e.sent,e.abrupt("return",Uint8Array.from(n));case 5:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function H(){return(H=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return"object"===_typeof(r=n.length>1&&void 0!==n[1]?n[1]:{})&&Object.freeze(r),"object"===_typeof(t)&&Object.freeze(t),e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"writeFile",path:t.path,contents:Array.from((new TextEncoder).encode(t.contents)),options:r}}));case 4:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function q(){return(q=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return"object"===_typeof(r=n.length>1&&void 0!==n[1]?n[1]:{})&&Object.freeze(r),"object"===_typeof(t)&&Object.freeze(t),e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"writeFile",path:t.path,contents:Array.from(t.contents),options:r}}));case 4:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function B(){return(B=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=n.length>1&&void 0!==n[1]?n[1]:{},e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"readDir",path:t,options:r}}));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function V(){return(V=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=n.length>1&&void 0!==n[1]?n[1]:{},e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"createDir",path:t,options:r}}));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function J(){return(J=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=n.length>1&&void 0!==n[1]?n[1]:{},e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"removeDir",path:t,options:r}}));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function K(){return(K=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){var n,a=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=a.length>2&&void 0!==a[2]?a[2]:{},e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"copyFile",source:t,destination:r,options:n}}));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Y(){return(Y=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r,n=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=n.length>1&&void 0!==n[1]?n[1]:{},e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"removeFile",path:t,options:r}}));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function $(){return($=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){var n,a=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=a.length>2&&void 0!==a[2]?a[2]:{},e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"renameFile",oldPath:t,newPath:r,options:n}}));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}!function(e){e[e.Audio=1]="Audio";e[e.Cache=2]="Cache";e[e.Config=3]="Config";e[e.Data=4]="Data";e[e.LocalData=5]="LocalData";e[e.Desktop=6]="Desktop";e[e.Document=7]="Document";e[e.Download=8]="Download";e[e.Executable=9]="Executable";e[e.Font=10]="Font";e[e.Home=11]="Home";e[e.Picture=12]="Picture";e[e.Public=13]="Public";e[e.Runtime=14]="Runtime";e[e.Template=15]="Template";e[e.Video=16]="Video";e[e.Resource=17]="Resource";e[e.App=18]="App";e[e.Log=19]="Log"}(F||(F={}));var Q=Object.freeze({__proto__:null,get BaseDirectory(){return F},get Dir(){return F},readTextFile:function(e){return N.apply(this,arguments)},readBinaryFile:function(e){return U.apply(this,arguments)},writeFile:function(e){return H.apply(this,arguments)},writeBinaryFile:function(e){return q.apply(this,arguments)},readDir:function(e){return B.apply(this,arguments)},createDir:function(e){return V.apply(this,arguments)},removeDir:function(e){return J.apply(this,arguments)},copyFile:function(e,t){return K.apply(this,arguments)},removeFile:function(e){return Y.apply(this,arguments)},renameFile:function(e,t){return $.apply(this,arguments)}});function X(){return(X=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"GlobalShortcut",message:{cmd:"register",shortcut:t,handler:a(r)}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Z(){return(Z=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"GlobalShortcut",message:{cmd:"registerAll",shortcuts:t,handler:a(r)}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function ee(){return(ee=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"GlobalShortcut",message:{cmd:"isRegistered",shortcut:t}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function te(){return(te=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"GlobalShortcut",message:{cmd:"unregister",shortcut:t}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function re(){return(re=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"GlobalShortcut",message:{cmd:"unregisterAll"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var ne,ae=Object.freeze({__proto__:null,register:function(e,t){return X.apply(this,arguments)},registerAll:function(e,t){return Z.apply(this,arguments)},isRegistered:function(e){return ee.apply(this,arguments)},unregister:function(e){return te.apply(this,arguments)},unregisterAll:function(){return re.apply(this,arguments)}});function oe(e,t){return null!=e?e:t()}function ie(e){for(var t=void 0,r=e[0],n=1;n=200&&this.status<300,this.headers=t.headers,this.rawHeaders=t.rawHeaders,this.data=t.data})),ce=function(){function e(t){_classCallCheck(this,e),this.id=t}var t,r,n,a,o,i,u;return _createClass(e,[{key:"drop",value:(u=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Http",message:{cmd:"dropClient",client:this.id}}));case 1:case"end":return e.stop()}}),e,this)}))),function(){return u.apply(this,arguments)})},{key:"request",value:(i=_asyncToGenerator(regeneratorRuntime.mark((function e(t){var r;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return(r=!t.responseType||t.responseType===ne.JSON)&&(t.responseType=ne.Text),e.abrupt("return",s({__tauriModule:"Http",message:{cmd:"httpRequest",client:this.id,options:t}}).then((function(e){var t=new se(e);if(r){try{t.data=JSON.parse(t.data)}catch(e){if(t.ok&&""===t.data)t.data={};else if(t.ok)throw Error("Failed to parse response `".concat(t.data,"` as JSON: ").concat(e,";\n try setting the `responseType` option to `ResponseType.Text` or `ResponseType.Binary` if the API does not return a JSON response."))}return t}return t})));case 3:case"end":return e.stop()}}),e,this)}))),function(e){return i.apply(this,arguments)})},{key:"get",value:(o=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",this.request(_objectSpread({method:"GET",url:t},r)));case 1:case"end":return e.stop()}}),e,this)}))),function(e,t){return o.apply(this,arguments)})},{key:"post",value:(a=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r,n){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",this.request(_objectSpread({method:"POST",url:t,body:r},n)));case 1:case"end":return e.stop()}}),e,this)}))),function(e,t,r){return a.apply(this,arguments)})},{key:"put",value:(n=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r,n){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",this.request(_objectSpread({method:"PUT",url:t,body:r},n)));case 1:case"end":return e.stop()}}),e,this)}))),function(e,t,r){return n.apply(this,arguments)})},{key:"patch",value:(r=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",this.request(_objectSpread({method:"PATCH",url:t},r)));case 1:case"end":return e.stop()}}),e,this)}))),function(e,t){return r.apply(this,arguments)})},{key:"delete",value:(t=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",this.request(_objectSpread({method:"DELETE",url:t},r)));case 1:case"end":return e.stop()}}),e,this)}))),function(e,r){return t.apply(this,arguments)})}]),e}();function pe(e){return le.apply(this,arguments)}function le(){return(le=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Http",message:{cmd:"createClient",options:t}}).then((function(e){return new ce(e)})));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var fe=null;function he(){return(he=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(null!==fe){e.next=4;break}return e.next=3,pe();case 3:fe=e.sent;case 4:return e.abrupt("return",fe.request(_objectSpread({url:t,method:oe(ie([r,"optionalAccess",function(e){return e.method}]),(function(){return"GET"}))},r)));case 5:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var me=Object.freeze({__proto__:null,getClient:pe,fetch:function(e,t){return he.apply(this,arguments)},Body:ue,Client:ce,Response:se,get ResponseType(){return ne}});function de(){return(de=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if("default"===window.Notification.permission){e.next=2;break}return e.abrupt("return",Promise.resolve("granted"===window.Notification.permission));case 2:return e.abrupt("return",s({__tauriModule:"Notification",message:{cmd:"isNotificationPermissionGranted"}}));case 3:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function ye(){return(ye=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",window.Notification.requestPermission());case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var ge=Object.freeze({__proto__:null,sendNotification:function(e){"string"==typeof e?new window.Notification(e):new window.Notification(e.title,e)},requestPermission:function(){return ye.apply(this,arguments)},isPermissionGranted:function(){return de.apply(this,arguments)}});function _e(){return navigator.appVersion.includes("Win")}function we(){return(we=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.App}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function ve(){return(ve=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Audio}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function be(){return(be=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Cache}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Re(){return(Re=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Config}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function ke(){return(ke=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Data}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function xe(){return(xe=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Desktop}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Te(){return(Te=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Document}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ge(){return(Ge=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Download}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Pe(){return(Pe=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Executable}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Me(){return(Me=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Font}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Oe(){return(Oe=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Home}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function je(){return(je=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.LocalData}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ae(){return(Ae=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Picture}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ce(){return(Ce=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Public}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Se(){return(Se=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Resource}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function De(){return(De=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Runtime}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function ze(){return(ze=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Template}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Le(){return(Le=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Video}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ee(){return(Ee=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:F.Log}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var We=_e()?"\\":"/",Fe=_e()?";":":";function Ie(){return(Ie=_asyncToGenerator(regeneratorRuntime.mark((function e(){var t,r,n,a=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:for(t=a.length,r=new Array(t),n=0;n0&&void 0!==r[0]?r[0]:0,e.abrupt("return",s({__tauriModule:"Process",message:{cmd:"exit",exitCode:t}}));case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ye(){return(Ye=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Process",message:{cmd:"relaunch"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var $e=Object.freeze({__proto__:null,exit:function(){return Ke.apply(this,arguments)},relaunch:function(){return Ye.apply(this,arguments)}});function Qe(e,t){return null!=e?e:t()}function Xe(e,t){return Ze.apply(this,arguments)}function Ze(){return(Ze=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){var n,o,i=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=i.length>2&&void 0!==i[2]?i[2]:[],o=i.length>3?i[3]:void 0,"object"===_typeof(n)&&Object.freeze(n),e.abrupt("return",s({__tauriModule:"Shell",message:{cmd:"execute",program:r,args:n,options:o,onEventFn:a(t)}}));case 4:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var et=function(){function e(){_classCallCheck(this,e),e.prototype.__init.call(this)}return _createClass(e,[{key:"__init",value:function(){this.eventListeners=Object.create(null)}},{key:"addEventListener",value:function(e,t){e in this.eventListeners?this.eventListeners[e].push(t):this.eventListeners[e]=[t]}},{key:"_emit",value:function(e,t){if(e in this.eventListeners){var r,n=_createForOfIteratorHelper(this.eventListeners[e]);try{for(n.s();!(r=n.n()).done;){(0,r.value)(t)}}catch(e){n.e(e)}finally{n.f()}}}},{key:"on",value:function(e,t){return this.addEventListener(e,t),this}}]),e}(),tt=function(){function e(t){_classCallCheck(this,e),this.pid=t}var t,r;return _createClass(e,[{key:"write",value:(r=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Shell",message:{cmd:"stdinWrite",pid:this.pid,buffer:"string"==typeof t?t:Array.from(t)}}));case 1:case"end":return e.stop()}}),e,this)}))),function(e){return r.apply(this,arguments)})},{key:"kill",value:(t=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Shell",message:{cmd:"killChild",pid:this.pid}}));case 1:case"end":return e.stop()}}),e,this)}))),function(){return t.apply(this,arguments)})}]),e}(),rt=function(e){_inherits(a,e);var t,r,n=_createSuper(a);function a(e){var t,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],o=arguments.length>2?arguments[2]:void 0;return _classCallCheck(this,a),t=n.call(this),a.prototype.__init2.call(_assertThisInitialized(t)),a.prototype.__init3.call(_assertThisInitialized(t)),t.program=e,t.args="string"==typeof r?[r]:r,t.options=Qe(o,(function(){return{}})),t}return _createClass(a,[{key:"__init2",value:function(){this.stdout=new et}},{key:"__init3",value:function(){this.stderr=new et}},{key:"spawn",value:(r=_asyncToGenerator(regeneratorRuntime.mark((function e(){var t=this;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",Xe((function(e){switch(e.event){case"Error":t._emit("error",e.payload);break;case"Terminated":t._emit("close",e.payload);break;case"Stdout":t.stdout._emit("data",e.payload);break;case"Stderr":t.stderr._emit("data",e.payload)}}),this.program,this.args,this.options).then((function(e){return new tt(e)})));case 1:case"end":return e.stop()}}),e,this)}))),function(){return r.apply(this,arguments)})},{key:"execute",value:(t=_asyncToGenerator(regeneratorRuntime.mark((function e(){var t=this;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",new Promise((function(e,r){t.on("error",r);var n=[],a=[];t.stdout.on("data",(function(e){n.push(e)})),t.stderr.on("data",(function(e){a.push(e)})),t.on("close",(function(t){e({code:t.code,signal:t.signal,stdout:n.join("\n"),stderr:a.join("\n")})})),t.spawn().catch(r)})));case 1:case"end":return e.stop()}}),e)}))),function(){return t.apply(this,arguments)})}],[{key:"sidecar",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],r=arguments.length>2?arguments[2]:void 0,n=new a(e,t,r);return n.options.sidecar=!0,n}}]),a}(et);function nt(){return(nt=_asyncToGenerator(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Shell",message:{cmd:"open",path:t,with:r}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var at=Object.freeze({__proto__:null,Command:rt,Child:tt,open:function(e,t){return nt.apply(this,arguments)}});function ot(e){for(var t=void 0,r=e[0],n=1;n1&&void 0!==arguments[1]?arguments[1]:{};return _classCallCheck(this,r),n=t.call(this,e),ct([a,"optionalAccess",function(e){return e.skip}])||s({__tauriModule:"Window",message:{cmd:"createWebview",data:{options:_objectSpread({label:e},a)}}}).then(_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",n.emit("tauri://created"));case 1:case"end":return e.stop()}}),e)})))).catch(function(){var e=_asyncToGenerator(regeneratorRuntime.mark((function e(t){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",n.emit("tauri://error",t));case 1:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}()),n}return _createClass(r,null,[{key:"getByLabel",value:function(e){return dt().some((function(t){return t.label===e}))?new r(e,{skip:!0}):null}}]),r}(_t),vt=new wt(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0});function bt(){return(bt=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"currentMonitor"}}}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Rt(){return(Rt=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"primaryMonitor"}}}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function kt(){return(kt=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"availableMonitors"}}}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var xt=Object.freeze({__proto__:null,WebviewWindow:wt,WebviewWindowHandle:gt,WindowManager:_t,getCurrent:function(){return new wt(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0})},getAll:dt,appWindow:vt,LogicalSize:lt,PhysicalSize:ft,LogicalPosition:ht,PhysicalPosition:mt,get UserAttentionType(){return pt},currentMonitor:function(){return bt.apply(this,arguments)},primaryMonitor:function(){return Rt.apply(this,arguments)},availableMonitors:function(){return kt.apply(this,arguments)}}),Tt=_e()?"\r\n":"\n";function Gt(){return(Gt=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Os",message:{cmd:"platform"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Pt(){return(Pt=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Os",message:{cmd:"version"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Mt(){return(Mt=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Os",message:{cmd:"osType"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ot(){return(Ot=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Os",message:{cmd:"arch"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function jt(){return(jt=_asyncToGenerator(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",s({__tauriModule:"Os",message:{cmd:"tempdir"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var At=Object.freeze({__proto__:null,EOL:Tt,platform:function(){return Gt.apply(this,arguments)},version:function(){return Pt.apply(this,arguments)},type:function(){return Mt.apply(this,arguments)},arch:function(){return Ot.apply(this,arguments)},tempdir:function(){return jt.apply(this,arguments)}}),Ct=o;e.app=h,e.cli=d,e.clipboard=_,e.dialog=x,e.event=I,e.fs=Q,e.globalShortcut=ae,e.http=me,e.invoke=Ct,e.notification=ge,e.os=At,e.path=Je,e.process=$e,e.shell=at,e.tauri=u,e.updater=st,e.window=xt,Object.defineProperty(e,"__esModule",{value:!0})})); diff --git a/core/tauri/scripts/core.js b/core/tauri/scripts/core.js index af90118b52b5..9af28ee1b610 100644 --- a/core/tauri/scripts/core.js +++ b/core/tauri/scripts/core.js @@ -1,8 +1,8 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -;(function () { +; (function () { function uid() { return window.crypto.getRandomValues(new Uint32Array(1))[0] } @@ -52,11 +52,11 @@ return new Promise(function (resolve, reject) { var callback = window.__TAURI__.transformCallback(function (r) { resolve(r) - delete window[error] + delete window[`_${error}`] }, true) var error = window.__TAURI__.transformCallback(function (e) { reject(e) - delete window[callback] + delete window[`_${callback}`] }, true) if (typeof cmd === 'string') { @@ -85,199 +85,4 @@ } }) } - - // open links with the Tauri API - function __openLinks() { - document.querySelector('body').addEventListener( - 'click', - function (e) { - var target = e.target - while (target != null) { - if (target.matches('a')) { - if ( - target.href && - target.href.startsWith('http') && - target.target === '_blank' - ) { - window.__TAURI_INVOKE__('tauri', { - __tauriModule: 'Shell', - message: { - cmd: 'open', - path: target.href - } - }) - e.preventDefault() - } - break - } - target = target.parentElement - } - } - ) - } - - if ( - document.readyState === 'complete' || - document.readyState === 'interactive' - ) { - __openLinks() - } else { - window.addEventListener( - 'DOMContentLoaded', - function () { - __openLinks() - }, - true - ) - } - - // drag region - document.addEventListener('mousedown', (e) => { - if (e.target.hasAttribute('data-tauri-drag-region') && e.buttons === 1) { - // start dragging if the element has a `tauri-drag-region` data attribute and maximize on double-clicking it - window.__TAURI_INVOKE__('tauri', { - __tauriModule: 'Window', - message: { - cmd: 'manage', - data: { - cmd: { - type: e.detail === 2 ? '__toggleMaximize' : 'startDragging' - } - } - } - }) - } - }) - - listen('tauri://window-created', function (event) { - if (event.payload) { - var windowLabel = event.payload.label - window.__TAURI_METADATA__.__windows.push({ - label: windowLabel - }) - } - }) - - let permissionSettable = false - let permissionValue = 'default' - - function isPermissionGranted() { - if (window.Notification.permission !== 'default') { - return Promise.resolve(window.Notification.permission === 'granted') - } - return window.__TAURI_INVOKE__('tauri', { - __tauriModule: 'Notification', - message: { - cmd: 'isNotificationPermissionGranted' - } - }) - } - - function setNotificationPermission(value) { - permissionSettable = true - window.Notification.permission = value - permissionSettable = false - } - - function requestPermission() { - return window - .__TAURI_INVOKE__('tauri', { - __tauriModule: 'Notification', - message: { - cmd: 'requestNotificationPermission' - } - }) - .then(function (permission) { - setNotificationPermission(permission) - return permission - }) - } - - function sendNotification(options) { - if (typeof options === 'object') { - Object.freeze(options) - } - - return window.__TAURI_INVOKE__('tauri', { - __tauriModule: 'Notification', - message: { - cmd: 'notification', - options: - typeof options === 'string' - ? { - title: options - } - : options - } - }) - } - - window.Notification = function (title, options) { - var opts = options || {} - sendNotification( - Object.assign(opts, { - title: title - }) - ) - } - - window.Notification.requestPermission = requestPermission - - Object.defineProperty(window.Notification, 'permission', { - enumerable: true, - get: function () { - return permissionValue - }, - set: function (v) { - if (!permissionSettable) { - throw new Error('Readonly property') - } - permissionValue = v - } - }) - - isPermissionGranted().then(function (response) { - if (response === null) { - setNotificationPermission('default') - } else { - setNotificationPermission(response ? 'granted' : 'denied') - } - }) - - window.alert = function (message) { - window.__TAURI_INVOKE__('tauri', { - __tauriModule: 'Dialog', - message: { - cmd: 'messageDialog', - message: message - } - }) - } - - window.confirm = function (message) { - return window.__TAURI_INVOKE__('tauri', { - __tauriModule: 'Dialog', - message: { - cmd: 'confirmDialog', - message: message - } - }) - } - - // window.print works on Linux/Windows; need to use the API on macOS - if (navigator.userAgent.includes('Mac')) { - window.print = function () { - return window.__TAURI_INVOKE__('tauri', { - __tauriModule: 'Window', - message: { - cmd: 'manage', - data: { - cmd: { - type: 'print' - } - } - } - }) - } - } })() diff --git a/core/tauri/scripts/freeze_prototype.js b/core/tauri/scripts/freeze_prototype.js index 6c17c8e79ae0..85b0598b51b3 100644 --- a/core/tauri/scripts/freeze_prototype.js +++ b/core/tauri/scripts/freeze_prototype.js @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT diff --git a/core/tauri/scripts/hotkey.js b/core/tauri/scripts/hotkey.js new file mode 100644 index 000000000000..0bf6f6d73649 --- /dev/null +++ b/core/tauri/scripts/hotkey.js @@ -0,0 +1,6 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/*! hotkeys-js v3.8.7 | MIT (c) 2021 kenny wong | http://jaywcjlove.github.io/hotkeys */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).hotkeys=t()}(this,function(){"use strict";var e="undefined"!=typeof navigator&&0 { + if (val instanceof Map) { + let o = {}; + val.forEach((v, k) => o[k] = v); + return o; + } else if (val instanceof Object && '__TAURI_CHANNEL_MARKER__' in val && typeof val.id === 'number') { + return `__CHANNEL__:${val.id}` + } else { + return val; + } + }) +}) diff --git a/core/tauri/src/api/cli.rs b/core/tauri/src/api/cli.rs deleted file mode 100644 index 27cdde1f3f46..000000000000 --- a/core/tauri/src/api/cli.rs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Types and functions related to CLI arguments. - -use crate::{ - utils::config::{CliArg, CliConfig}, - PackageInfo, -}; - -use clap::{App, Arg, ArgMatches, ErrorKind}; -use serde::Serialize; -use serde_json::Value; -use std::collections::HashMap; - -#[macro_use] -mod macros; - -/// The resolution of a argument match. -#[derive(Default, Debug, Serialize)] -#[non_exhaustive] -pub struct ArgData { - /// - [`Value::Bool`] if it's a flag, - /// - [`Value::Array`] if it's multiple, - /// - [`Value::String`] if it has value, - /// - [`Value::Null`] otherwise. - pub value: Value, - /// The number of occurrences of the argument. - /// e.g. `./app --arg 1 --arg 2 --arg 2 3 4` results in three occurrences. - pub occurrences: u64, -} - -/// The matched subcommand. -#[derive(Default, Debug, Serialize)] -#[non_exhaustive] -pub struct SubcommandMatches { - /// The subcommand name. - pub name: String, - /// The subcommand argument matches. - pub matches: Matches, -} - -/// The argument matches of a command. -#[derive(Default, Debug, Serialize)] -#[non_exhaustive] -pub struct Matches { - /// Data structure mapping each found arg with its resolution. - pub args: HashMap, - /// The matched subcommand if found. - pub subcommand: Option>, -} - -impl Matches { - /// Set a arg match. - pub(crate) fn set_arg(&mut self, name: String, value: ArgData) { - self.args.insert(name, value); - } - - /// Sets the subcommand matches. - pub(crate) fn set_subcommand(&mut self, name: String, matches: Matches) { - self.subcommand = Some(Box::new(SubcommandMatches { name, matches })); - } -} - -/// Gets the argument matches of the CLI definition. -pub fn get_matches(cli: &CliConfig, package_info: &PackageInfo) -> crate::api::Result { - let about = cli - .description() - .unwrap_or(&package_info.description.to_string()) - .to_string(); - let app = get_app(package_info, &package_info.name, Some(&about), cli); - match app.try_get_matches() { - Ok(matches) => Ok(get_matches_internal(cli, &matches)), - Err(e) => match e.kind { - ErrorKind::DisplayHelp => { - let mut matches = Matches::default(); - let help_text = e.to_string(); - matches.args.insert( - "help".to_string(), - ArgData { - value: Value::String(help_text), - occurrences: 0, - }, - ); - Ok(matches) - } - ErrorKind::DisplayVersion => { - let mut matches = Matches::default(); - matches - .args - .insert("version".to_string(), Default::default()); - Ok(matches) - } - _ => Err(e.into()), - }, - } -} - -fn get_matches_internal(config: &CliConfig, matches: &ArgMatches) -> Matches { - let mut cli_matches = Matches::default(); - map_matches(config, matches, &mut cli_matches); - - if let Some((subcommand_name, subcommand_matches)) = matches.subcommand() { - let mut subcommand_cli_matches = Matches::default(); - map_matches( - config.subcommands().unwrap().get(subcommand_name).unwrap(), - subcommand_matches, - &mut subcommand_cli_matches, - ); - cli_matches.set_subcommand(subcommand_name.to_string(), subcommand_cli_matches); - } - - cli_matches -} - -fn map_matches(config: &CliConfig, matches: &ArgMatches, cli_matches: &mut Matches) { - if let Some(args) = config.args() { - for arg in args { - let occurrences = matches.occurrences_of(arg.name.clone()); - let value = if occurrences == 0 || !arg.takes_value.unwrap_or(false) { - Value::Bool(occurrences > 0) - } else if arg.multiple.unwrap_or(false) { - matches - .values_of(arg.name.clone()) - .map(|v| { - let mut values = Vec::new(); - for value in v { - values.push(Value::String(value.to_string())); - } - Value::Array(values) - }) - .unwrap_or(Value::Null) - } else { - matches - .value_of(arg.name.clone()) - .map(|v| Value::String(v.to_string())) - .unwrap_or(Value::Null) - }; - - cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences }); - } - } -} - -fn get_app<'a>( - package_info: &'a PackageInfo, - command_name: &'a str, - about: Option<&'a String>, - config: &'a CliConfig, -) -> App<'a> { - let mut app = App::new(command_name) - .author(package_info.authors) - .version(&*package_info.version); - - if let Some(about) = about { - app = app.about(&**about); - } - if let Some(long_description) = config.long_description() { - app = app.long_about(&**long_description); - } - if let Some(before_help) = config.before_help() { - app = app.before_help(&**before_help); - } - if let Some(after_help) = config.after_help() { - app = app.after_help(&**after_help); - } - - if let Some(args) = config.args() { - for arg in args { - let arg_name = arg.name.as_ref(); - app = app.arg(get_arg(arg_name, arg)); - } - } - - if let Some(subcommands) = config.subcommands() { - for (subcommand_name, subcommand) in subcommands { - let clap_subcommand = get_app( - package_info, - subcommand_name, - subcommand.description(), - subcommand, - ); - app = app.subcommand(clap_subcommand); - } - } - - app -} - -fn get_arg<'a>(arg_name: &'a str, arg: &'a CliArg) -> Arg<'a> { - let mut clap_arg = Arg::new(arg_name); - - if arg.index.is_none() { - clap_arg = clap_arg.long(arg_name); - if let Some(short) = arg.short { - clap_arg = clap_arg.short(short); - } - } - - clap_arg = bind_string_arg!(arg, clap_arg, description, help); - clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_help); - clap_arg = bind_value_arg!(arg, clap_arg, takes_value); - if let Some(value) = arg.multiple { - clap_arg = clap_arg.multiple_values(value); - } - clap_arg = bind_value_arg!(arg, clap_arg, multiple_occurrences); - clap_arg = bind_value_arg!(arg, clap_arg, number_of_values); - clap_arg = bind_string_slice_arg!(arg, clap_arg, possible_values); - clap_arg = bind_value_arg!(arg, clap_arg, min_values); - clap_arg = bind_value_arg!(arg, clap_arg, max_values); - clap_arg = bind_value_arg!(arg, clap_arg, required); - clap_arg = bind_string_arg!( - arg, - clap_arg, - required_unless_present, - required_unless_present - ); - clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_all); - clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_any); - clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with); - if let Some(value) = &arg.conflicts_with_all { - let v: Vec<&str> = value.iter().map(|x| &**x).collect(); - clap_arg = clap_arg.conflicts_with_all(&v); - } - clap_arg = bind_string_arg!(arg, clap_arg, requires, requires); - if let Some(value) = &arg.requires_all { - let v: Vec<&str> = value.iter().map(|x| &**x).collect(); - clap_arg = clap_arg.requires_all(&v); - } - clap_arg = bind_if_arg!(arg, clap_arg, requires_if); - clap_arg = bind_if_arg!(arg, clap_arg, required_if_eq); - clap_arg = bind_value_arg!(arg, clap_arg, require_equals); - clap_arg = bind_value_arg!(arg, clap_arg, index); - - clap_arg -} diff --git a/core/tauri/src/api/cli/macros.rs b/core/tauri/src/api/cli/macros.rs deleted file mode 100644 index 33ce701308d0..000000000000 --- a/core/tauri/src/api/cli/macros.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -macro_rules! bind_string_arg { - ($arg:expr, $clap_arg:expr, $arg_name:ident, $clap_field:ident) => {{ - let arg = $arg; - let mut clap_arg = $clap_arg; - if let Some(value) = &arg.$arg_name { - clap_arg = clap_arg.$clap_field(value.as_str()); - } - clap_arg - }}; -} - -macro_rules! bind_value_arg { - ($arg:expr, $clap_arg:expr, $field:ident) => {{ - let arg = $arg; - let mut clap_arg = $clap_arg; - if let Some(value) = arg.$field { - clap_arg = clap_arg.$field(value); - } - clap_arg - }}; -} - -macro_rules! bind_string_slice_arg { - ($arg:expr, $clap_arg:expr, $field:ident) => {{ - let arg = $arg; - let mut clap_arg = $clap_arg; - if let Some(value) = &arg.$field { - let v: Vec<&str> = value.iter().map(|x| &**x).collect(); - clap_arg = clap_arg.$field(v); - } - clap_arg - }}; -} - -macro_rules! bind_if_arg { - ($arg:expr, $clap_arg:expr, $field:ident) => {{ - let arg = $arg; - let mut clap_arg = $clap_arg; - if let Some(value) = &arg.$field { - let v: Vec<&str> = value.iter().map(|x| &**x).collect(); - clap_arg = clap_arg.$field(&v[0], &v[1]); - } - clap_arg - }}; -} diff --git a/core/tauri/src/api/dialog.rs b/core/tauri/src/api/dialog.rs deleted file mode 100644 index 2d6fa1c28e0e..000000000000 --- a/core/tauri/src/api/dialog.rs +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Use native message and file open/save dialogs. -//! -//! This module exposes non-blocking APIs on its root, relying on callback closures -//! to give results back. This is particularly useful when running dialogs from the main thread. -//! When using on asynchronous contexts such as async commands, the [`blocking`] APIs are recommended. - -pub use nonblocking::*; - -#[cfg(not(target_os = "linux"))] -macro_rules! run_dialog { - ($e:expr, $h: ident) => {{ - std::thread::spawn(move || { - let response = $e; - $h(response); - }); - }}; -} - -#[cfg(target_os = "linux")] -macro_rules! run_dialog { - ($e:expr, $h: ident) => {{ - std::thread::spawn(move || { - let context = glib::MainContext::default(); - context.invoke_with_priority(glib::PRIORITY_HIGH, move || { - let response = $e; - $h(response); - }); - }); - }}; -} - -macro_rules! run_dialog_sync { - ($e:expr) => {{ - let (tx, rx) = sync_channel(0); - let cb = move |response| { - tx.send(response).unwrap(); - }; - run_dialog!($e, cb); - rx.recv().unwrap() - }}; -} - -macro_rules! file_dialog_builder { - () => { - /// The file dialog builder. - /// - /// Constructs file picker dialogs that can select single/multiple files or directories. - #[derive(Debug, Default)] - pub struct FileDialogBuilder(rfd::FileDialog); - - impl FileDialogBuilder { - /// Gets the default file dialog builder. - pub fn new() -> Self { - Default::default() - } - - /// Add file extension filter. Takes in the name of the filter, and list of extensions - #[must_use] - pub fn add_filter(mut self, name: impl AsRef, extensions: &[&str]) -> Self { - self.0 = self.0.add_filter(name.as_ref(), extensions); - self - } - - /// Set starting directory of the dialog. - #[must_use] - pub fn set_directory>(mut self, directory: P) -> Self { - self.0 = self.0.set_directory(directory); - self - } - - /// Set starting file name of the dialog. - #[must_use] - pub fn set_file_name(mut self, file_name: &str) -> Self { - self.0 = self.0.set_file_name(file_name); - self - } - - /// Sets the parent window of the dialog. - #[must_use] - pub fn set_parent(mut self, parent: &W) -> Self { - self.0 = self.0.set_parent(parent); - self - } - - /// Set the title of the dialog. - #[must_use] - pub fn set_title(mut self, title: &str) -> Self { - self.0 = self.0.set_title(title); - self - } - } - }; -} - -/// Blocking interfaces for the dialog APIs. -/// -/// The blocking APIs will block the current thread to execute instead of relying on callback closures, -/// which makes them easier to use. -/// -/// **NOTE:** You cannot block the main thread when executing the dialog APIs, so you must use the [`tauri::api::dialog`] methods instead. -/// Examples of main thread context are the [`tauri::App::run`] closure and non-async commmands. -pub mod blocking { - use crate::{Runtime, Window}; - use std::path::{Path, PathBuf}; - use std::sync::mpsc::sync_channel; - - file_dialog_builder!(); - - impl FileDialogBuilder { - /// Shows the dialog to select a single file. - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// fn my_command() { - /// let file_path = FileDialogBuilder::new().pick_file(); - /// // do something with the optional file path here - /// // the file path is `None` if the user closed the dialog - /// } - /// ``` - pub fn pick_file(self) -> Option { - run_dialog_sync!(self.0.pick_file()) - } - - /// Shows the dialog to select multiple files. - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// fn my_command() { - /// let file_path = FileDialogBuilder::new().pick_files(); - /// // do something with the optional file paths here - /// // the file paths value is `None` if the user closed the dialog - /// } - /// ``` - pub fn pick_files(self) -> Option> { - run_dialog_sync!(self.0.pick_files()) - } - - /// Shows the dialog to select a single folder. - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// fn my_command() { - /// let folder_path = FileDialogBuilder::new().pick_folder(); - /// // do something with the optional folder path here - /// // the folder path is `None` if the user closed the dialog - /// } - /// ``` - pub fn pick_folder(self) -> Option { - run_dialog_sync!(self.0.pick_folder()) - } - - /// Shows the dialog to save a file. - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// fn my_command() { - /// let file_path = FileDialogBuilder::new().save_file(); - /// // do something with the optional file path here - /// // the file path is `None` if the user closed the dialog - /// } - /// ``` - pub fn save_file(self) -> Option { - run_dialog_sync!(self.0.save_file()) - } - } - - /// Displays a dialog with a message and an optional title with a "yes" and a "no" button and wait for it to be closed. - /// - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::ask; - /// # let app = tauri::Builder::default().build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")).unwrap(); - /// # let window = tauri::Manager::get_window(&app, "main").unwrap(); - /// let answer = ask(Some(&window), "Tauri", "Is Tauri awesome?"); - /// // do something with `answer` - /// ``` - #[allow(unused_variables)] - pub fn ask( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - ) -> bool { - run_message_dialog(parent_window, title, message, rfd::MessageButtons::YesNo) - } - - /// Displays a dialog with a message and an optional title with an "ok" and a "cancel" button and wait for it to be closed. - /// - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::confirm; - /// # let app = tauri::Builder::default().build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")).unwrap(); - /// # let window = tauri::Manager::get_window(&app, "main").unwrap(); - /// let answer = confirm(Some(&window), "Tauri", "Are you sure?"); - /// // do something with `answer` - /// ``` - #[allow(unused_variables)] - pub fn confirm( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - ) -> bool { - run_message_dialog(parent_window, title, message, rfd::MessageButtons::OkCancel) - } - - /// Displays a message dialog and wait for it to be closed. - /// - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::message; - /// # let app = tauri::Builder::default().build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")).unwrap(); - /// # let window = tauri::Manager::get_window(&app, "main").unwrap(); - /// message(Some(&window), "Tauri", "Tauri is awesome!"); - /// ``` - #[allow(unused_variables)] - pub fn message( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - ) { - let _ = run_message_dialog(parent_window, title, message, rfd::MessageButtons::Ok); - } - - #[allow(unused_variables)] - fn run_message_dialog( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - buttons: rfd::MessageButtons, - ) -> bool { - let (tx, rx) = sync_channel(1); - super::nonblocking::run_message_dialog( - parent_window, - title, - message, - buttons, - move |response| { - tx.send(response).unwrap(); - }, - ); - rx.recv().unwrap() - } -} - -mod nonblocking { - use crate::{Runtime, Window}; - use std::path::{Path, PathBuf}; - - file_dialog_builder!(); - - impl FileDialogBuilder { - /// Shows the dialog to select a single file. - /// This is not a blocking operation, - /// and should be used when running on the main thread to avoid deadlocks with the event loop. - /// - /// For usage in other contexts such as commands, prefer [`Self::pick_file`]. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; - /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) - /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().pick_file(|file_path| { - /// // do something with the optional file path here - /// // the file path is `None` if the user closed the dialog - /// }) - /// }) - /// ``` - pub fn pick_file) + Send + 'static>(self, f: F) { - run_dialog!(self.0.pick_file(), f) - } - - /// Shows the dialog to select multiple files. - /// This is not a blocking operation, - /// and should be used when running on the main thread to avoid deadlocks with the event loop. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; - /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) - /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().pick_files(|file_paths| { - /// // do something with the optional file paths here - /// // the file paths value is `None` if the user closed the dialog - /// }) - /// }) - /// ``` - pub fn pick_files>) + Send + 'static>(self, f: F) { - run_dialog!(self.0.pick_files(), f) - } - - /// Shows the dialog to select a single folder. - /// This is not a blocking operation, - /// and should be used when running on the main thread to avoid deadlocks with the event loop. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; - /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) - /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().pick_folder(|folder_path| { - /// // do something with the optional folder path here - /// // the folder path is `None` if the user closed the dialog - /// }) - /// }) - /// ``` - pub fn pick_folder) + Send + 'static>(self, f: F) { - run_dialog!(self.0.pick_folder(), f) - } - - /// Shows the dialog to save a file. - /// - /// This is not a blocking operation, - /// and should be used when running on the main thread to avoid deadlocks with the event loop. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; - /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) - /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().save_file(|file_path| { - /// // do something with the optional file path here - /// // the file path is `None` if the user closed the dialog - /// }) - /// }) - /// ``` - pub fn save_file) + Send + 'static>(self, f: F) { - run_dialog!(self.0.save_file(), f) - } - } - - /// Displays a non-blocking dialog with a message and an optional title with a "yes" and a "no" button. - /// - /// This is not a blocking operation, - /// and should be used when running on the main thread to avoid deadlocks with the event loop. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::ask; - /// # let app = tauri::Builder::default().build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")).unwrap(); - /// # let window = tauri::Manager::get_window(&app, "main").unwrap(); - /// ask(Some(&window), "Tauri", "Is Tauri awesome?", |answer| { - /// // do something with `answer` - /// }); - /// ``` - #[allow(unused_variables)] - pub fn ask( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - f: F, - ) { - run_message_dialog(parent_window, title, message, rfd::MessageButtons::YesNo, f) - } - - /// Displays a non-blocking dialog with a message and an optional title with an "ok" and a "cancel" button. - /// - /// This is not a blocking operation, - /// and should be used when running on the main thread to avoid deadlocks with the event loop. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::confirm; - /// # let app = tauri::Builder::default().build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")).unwrap(); - /// # let window = tauri::Manager::get_window(&app, "main").unwrap(); - /// confirm(Some(&window), "Tauri", "Are you sure?", |answer| { - /// // do something with `answer` - /// }); - /// ``` - #[allow(unused_variables)] - pub fn confirm( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - f: F, - ) { - run_message_dialog( - parent_window, - title, - message, - rfd::MessageButtons::OkCancel, - f, - ) - } - - /// Displays a non-blocking message dialog. - /// - /// This is not a blocking operation, - /// and should be used when running on the main thread to avoid deadlocks with the event loop. - /// - /// # Example - /// - /// ```rust,no_run - /// use tauri::api::dialog::message; - /// # let app = tauri::Builder::default().build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")).unwrap(); - /// # let window = tauri::Manager::get_window(&app, "main").unwrap(); - /// message(Some(&window), "Tauri", "Tauri is awesome!"); - /// ``` - #[allow(unused_variables)] - pub fn message( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - ) { - run_message_dialog( - parent_window, - title, - message, - rfd::MessageButtons::Ok, - |_| {}, - ) - } - - #[allow(unused_variables)] - pub(crate) fn run_message_dialog( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - buttons: rfd::MessageButtons, - f: F, - ) { - let title = title.as_ref().to_string(); - let message = message.as_ref().to_string(); - #[allow(unused_mut)] - let mut builder = rfd::MessageDialog::new() - .set_title(&title) - .set_description(&message) - .set_buttons(buttons) - .set_level(rfd::MessageLevel::Info); - - #[cfg(any(windows, target_os = "macos"))] - { - if let Some(window) = parent_window { - builder = builder.set_parent(window); - } - } - - run_dialog!(builder.show(), f) - } -} diff --git a/core/tauri/src/api/dir.rs b/core/tauri/src/api/dir.rs index aa4deb27841b..7a23eec70bec 100644 --- a/core/tauri/src/api/dir.rs +++ b/core/tauri/src/api/dir.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ use serde::Serialize; use std::{ - fs::{self, metadata}, + fs::{self, metadata, symlink_metadata}, path::{Path, PathBuf}, }; use tempfile::{self, tempdir}; @@ -31,8 +31,36 @@ pub fn is_dir>(path: P) -> crate::api::Result { metadata(path).map(|md| md.is_dir()).map_err(Into::into) } +fn is_symlink>(path: P) -> crate::api::Result { + // TODO: remove the different implementation once we raise tauri's MSRV to at least 1.58 + #[cfg(windows)] + let ret = symlink_metadata(path) + .map(|md| md.is_symlink()) + .map_err(Into::into); + + #[cfg(not(windows))] + let ret = symlink_metadata(path) + .map(|md| md.file_type().is_symlink()) + .map_err(Into::into); + + ret +} + /// Reads a directory. Can perform recursive operations. pub fn read_dir>(path: P, recursive: bool) -> crate::api::Result> { + read_dir_with_options(path, recursive, ReadDirOptions { scope: None }) +} + +#[derive(Clone, Copy)] +pub(crate) struct ReadDirOptions<'a> { + pub scope: Option<&'a crate::FsScope>, +} + +pub(crate) fn read_dir_with_options>( + path: P, + recursive: bool, + options: ReadDirOptions<'_>, +) -> crate::api::Result> { let mut files_and_dirs: Vec = vec![]; for entry in fs::read_dir(path)? { let path = entry?.path(); @@ -42,11 +70,16 @@ pub fn read_dir>(path: P, recursive: bool) -> crate::api::Result< files_and_dirs.push(DiskEntry { path: path.clone(), children: if flag { - Some(if recursive { - read_dir(&path_as_string, true)? - } else { - vec![] - }) + Some( + if recursive + && (!is_symlink(&path_as_string)? + || options.scope.map(|s| s.is_allowed(&path)).unwrap_or(true)) + { + read_dir_with_options(&path_as_string, true, options)? + } else { + vec![] + }, + ) } else { None }, @@ -193,7 +226,7 @@ mod test { fn check_test_dir() { // create a callback closure that takes in a TempDir type and prints it. let callback = |td: &tempfile::TempDir| { - println!("{:?}", td); + println!("{td:?}"); }; // execute the with_temp_dir function on the callback diff --git a/core/tauri/src/api/error.rs b/core/tauri/src/api/error.rs index d5dd2bfa4c30..0fce6a32c26e 100644 --- a/core/tauri/src/api/error.rs +++ b/core/tauri/src/api/error.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -6,94 +6,13 @@ #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { - /// Command error. - #[error("Command Error: {0}")] - Command(String), - /// The extract archive error. - #[error("Extract Error: {0}")] - Extract(String), - /// The path operation error. - #[error("Path Error: {0}")] - Path(String), - /// The path StripPrefixError error. - #[error("Path Error: {0}")] - PathPrefix(#[from] std::path::StripPrefixError), - /// Error showing the dialog. - #[error("Dialog Error: {0}")] - Dialog(String), - /// The dialog operation was cancelled by the user. - #[error("user cancelled the dialog")] - DialogCancelled, - /// The network error. - #[cfg(all(feature = "http-api", not(feature = "reqwest-client")))] - #[error("Network Error: {0}")] - Network(#[from] attohttpc::Error), - /// The network error. - #[cfg(feature = "reqwest-client")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "reqwest-client")))] - #[error("Network Error: {0}")] - Network(#[from] reqwest::Error), - /// HTTP method error. - #[error(transparent)] - HttpMethod(#[from] http::method::InvalidMethod), - /// Invalid HTTP header value. - #[cfg(feature = "reqwest-client")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "reqwest-client")))] - #[error(transparent)] - HttpHeaderValue(#[from] http::header::InvalidHeaderValue), - /// Invalid HTTP header value. - #[error(transparent)] - HttpHeader(#[from] http::header::InvalidHeaderName), - /// Failed to serialize header value as string. - #[error(transparent)] - Utf8(#[from] std::string::FromUtf8Error), - /// HTTP form to must be an object. - #[error("http form must be an object")] - InvalidHttpForm, /// Semver error. #[error(transparent)] Semver(#[from] semver::Error), /// JSON error. #[error(transparent)] Json(#[from] serde_json::Error), - /// Bincode error. - #[error(transparent)] - Bincode(#[from] Box), /// IO error. #[error(transparent)] Io(#[from] std::io::Error), - /// Ignore error. - #[error("failed to walkdir: {0}")] - Ignore(#[from] ignore::Error), - /// ZIP error. - #[error(transparent)] - Zip(#[from] zip::result::ZipError), - /// Notification error. - #[cfg(notification_all)] - #[error(transparent)] - Notification(#[from] notify_rust::error::Error), - /// Url error. - #[error(transparent)] - Url(#[from] url::ParseError), - /// failed to detect the current platform. - #[error("failed to detect platform: {0}")] - FailedToDetectPlatform(String), - /// CLI argument parsing error. - #[cfg(feature = "cli")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "cli")))] - #[error("failed to parse CLI arguments: {0}")] - ParseCliArguments(String), - /// Shell error. - #[error("shell error: {0}")] - Shell(String), - /// Unknown program name. - #[error("unknown program name: {0}")] - UnknownProgramName(String), -} - -#[cfg(feature = "cli")] -impl From for Error { - fn from(error: clap::Error) -> Self { - Self::ParseCliArguments(error.to_string()) - } } diff --git a/core/tauri/src/api/file.rs b/core/tauri/src/api/file.rs index 1699c7c351b5..46ace7fa3ac8 100644 --- a/core/tauri/src/api/file.rs +++ b/core/tauri/src/api/file.rs @@ -1,23 +1,17 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! Types and functions related to file operations. -mod extract; -mod file_move; - use std::{fs, path::Path}; -pub use extract::*; -pub use file_move::*; - -/// Reads a string file. +/// Reads the entire contents of a file into a string. pub fn read_string>(file: P) -> crate::api::Result { fs::read_to_string(file).map_err(Into::into) } -/// Reads a binary file. +/// Reads the entire contents of a file into a bytes vector. pub fn read_binary>(file: P) -> crate::api::Result> { fs::read(file).map_err(Into::into) } @@ -25,6 +19,7 @@ pub fn read_binary>(file: P) -> crate::api::Result> { #[cfg(test)] mod test { use super::*; + #[cfg(not(windows))] use crate::api::Error; #[test] @@ -48,9 +43,8 @@ mod test { assert!(res.is_err()); + #[cfg(not(windows))] if let Error::Io(e) = res.unwrap_err() { - #[cfg(windows)] - assert_eq!(e.to_string(), "Access is denied. (os error 5)".to_string()); #[cfg(not(windows))] assert_eq!(e.to_string(), "Is a directory (os error 21)".to_string()); } @@ -60,15 +54,9 @@ mod test { fn check_read_binary() { let file = String::from("test/api/test_binary"); - #[cfg(windows)] - let expected_vec = vec![ - 35, 33, 47, 98, 105, 110, 47, 98, 97, 115, 104, 13, 10, 13, 10, 101, 99, 104, 111, 32, 34, - 72, 101, 108, 108, 111, 32, 116, 104, 101, 114, 101, 34, - ]; - #[cfg(not(windows))] let expected_vec = vec![ - 35, 33, 47, 98, 105, 110, 47, 98, 97, 115, 104, 10, 10, 101, 99, 104, 111, 32, 34, 72, 101, - 108, 108, 111, 32, 116, 104, 101, 114, 101, 34, + 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 0, 0, 255, 255, 255, 0, 0, 0, 33, 249, 4, 1, 0, 0, + 0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59, ]; let res = read_binary(file); diff --git a/core/tauri/src/api/file/extract.rs b/core/tauri/src/api/file/extract.rs deleted file mode 100644 index 3b19a0328473..000000000000 --- a/core/tauri/src/api/file/extract.rs +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use either::{self, Either}; -use std::{ - fs, - io::{self, Read, Seek}, - path::{self, Path, PathBuf}, -}; - -/// The supported archive formats. -#[derive(Debug, Clone, Copy, PartialEq)] -#[non_exhaustive] -pub enum ArchiveFormat { - /// Tar archive. - Tar(Option), - /// Plain archive. - Plain(Option), - /// Zip archive. - Zip, -} - -/// The supported compression types. -#[derive(Debug, Clone, Copy, PartialEq)] -#[non_exhaustive] -pub enum Compression { - /// Gz compression (e.g. `.tar.gz` archives) - Gz, -} - -/// The extract manager to retrieve files from archives. -#[derive(Debug)] -pub struct Extract { - reader: R, - archive_format: ArchiveFormat, -} - -impl Extract { - /// Create archive from reader. - pub fn from_cursor(mut reader: R, archive_format: ArchiveFormat) -> Extract { - if reader.seek(io::SeekFrom::Start(0)).is_err() { - #[cfg(debug_assertions)] - eprintln!("Could not seek to start of the file"); - } - Extract { - reader, - archive_format, - } - } - - /// Get the archive content. - pub fn files(&mut self) -> crate::api::Result> { - let reader = &mut self.reader; - let mut all_files = Vec::new(); - if reader.seek(io::SeekFrom::Start(0)).is_err() { - #[cfg(debug_assertions)] - eprintln!("Could not seek to start of the file"); - } - match self.archive_format { - ArchiveFormat::Plain(compression) | ArchiveFormat::Tar(compression) => { - let reader = Self::get_archive_reader(reader, compression); - match self.archive_format { - ArchiveFormat::Tar(_) => { - let mut archive = tar::Archive::new(reader); - for entry in archive.entries()?.flatten() { - if let Ok(path) = entry.path() { - all_files.push(path.to_path_buf()); - } - } - } - _ => unreachable!(), - }; - } - - ArchiveFormat::Zip => { - let archive = zip::ZipArchive::new(reader)?; - for entry in archive.file_names() { - all_files.push(PathBuf::from(entry)); - } - } - } - - Ok(all_files) - } - - // Get the reader based on the compression type. - fn get_archive_reader( - source: &mut R, - compression: Option, - ) -> Either<&mut R, flate2::read::GzDecoder<&mut R>> { - if source.seek(io::SeekFrom::Start(0)).is_err() { - #[cfg(debug_assertions)] - eprintln!("Could not seek to start of the file"); - } - match compression { - Some(Compression::Gz) => Either::Right(flate2::read::GzDecoder::new(source)), - None => Either::Left(source), - } - } - - /// Extract an entire source archive into a specified path. If the source is a single compressed - /// file and not an archive, it will be extracted into a file with the same name inside of - /// `into_dir`. - pub fn extract_into(&mut self, into_dir: &path::Path) -> crate::api::Result<()> { - let reader = &mut self.reader; - if reader.seek(io::SeekFrom::Start(0)).is_err() { - #[cfg(debug_assertions)] - eprintln!("Could not seek to start of the file"); - } - match self.archive_format { - ArchiveFormat::Plain(compression) | ArchiveFormat::Tar(compression) => { - let mut reader = Self::get_archive_reader(reader, compression); - match self.archive_format { - ArchiveFormat::Plain(_) => { - match fs::create_dir_all(into_dir) { - Ok(_) => (), - Err(e) => { - if e.kind() != io::ErrorKind::AlreadyExists { - return Err(e.into()); - } - } - } - - let mut out_file = fs::File::create(&into_dir)?; - io::copy(&mut reader, &mut out_file)?; - } - ArchiveFormat::Tar(_) => { - let mut archive = tar::Archive::new(reader); - archive.unpack(into_dir)?; - } - _ => unreachable!(), - }; - } - - ArchiveFormat::Zip => { - let mut archive = zip::ZipArchive::new(reader)?; - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - // Decode the file name from raw bytes instead of using file.name() directly. - // file.name() uses String::from_utf8_lossy() which may return messy characters - // such as: τê▒Σ║ñµÿô.app/, that does not work as expected. - // Here we require the file name must be a valid UTF-8. - let file_name = String::from_utf8(file.name_raw().to_vec())?; - let out_path = into_dir.join(&file_name); - if file.is_dir() { - fs::create_dir_all(&out_path)?; - } else { - if let Some(out_path_parent) = out_path.parent() { - fs::create_dir_all(&out_path_parent)?; - } - let mut out_file = fs::File::create(&out_path)?; - io::copy(&mut file, &mut out_file)?; - } - // Get and Set permissions - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - if let Some(mode) = file.unix_mode() { - fs::set_permissions(&out_path, fs::Permissions::from_mode(mode))?; - } - } - } - } - } - Ok(()) - } - - /// Extract a single file from a source and extract it `into_path`. - /// If it's a directory, the target will be created, if it's a file, it'll be extracted at this location. - /// Note: You need to include the complete path, with file name and extension. - pub fn extract_file>( - &mut self, - into_path: &path::Path, - file_to_extract: T, - ) -> crate::api::Result<()> { - let file_to_extract = file_to_extract.as_ref(); - let reader = &mut self.reader; - - match self.archive_format { - ArchiveFormat::Plain(compression) | ArchiveFormat::Tar(compression) => { - let mut reader = Self::get_archive_reader(reader, compression); - match self.archive_format { - ArchiveFormat::Plain(_) => { - match fs::create_dir_all(into_path) { - Ok(_) => (), - Err(e) => { - if e.kind() != io::ErrorKind::AlreadyExists { - return Err(e.into()); - } - } - } - let mut out_file = fs::File::create(into_path)?; - io::copy(&mut reader, &mut out_file)?; - } - ArchiveFormat::Tar(_) => { - let mut archive = tar::Archive::new(reader); - let mut entry = archive - .entries()? - .filter_map(|e| e.ok()) - .find(|e| e.path().ok().filter(|p| p == file_to_extract).is_some()) - .ok_or_else(|| { - crate::api::Error::Extract(format!( - "Could not find the required path in the archive: {:?}", - file_to_extract - )) - })?; - - // determine if it's a file or a directory - if entry.header().entry_type() == tar::EntryType::Directory { - // this is a directory, lets create it - match fs::create_dir_all(into_path) { - Ok(_) => (), - Err(e) => { - if e.kind() != io::ErrorKind::AlreadyExists { - return Err(e.into()); - } - } - } - } else { - let mut out_file = fs::File::create(into_path)?; - io::copy(&mut entry, &mut out_file)?; - - // make sure we set permissions - if let Ok(mode) = entry.header().mode() { - set_perms(into_path, Some(&mut out_file), mode, true)?; - } - } - } - _ => { - panic!("Unreasonable code"); - } - }; - } - ArchiveFormat::Zip => { - let mut archive = zip::ZipArchive::new(reader)?; - let mut file = archive.by_name( - file_to_extract - .to_str() - .expect("Could not convert file to str"), - )?; - - if file.is_dir() { - // this is a directory, lets create it - match fs::create_dir_all(into_path) { - Ok(_) => (), - Err(e) => { - if e.kind() != io::ErrorKind::AlreadyExists { - return Err(e.into()); - } - } - } - } else { - let mut out_file = fs::File::create(into_path)?; - io::copy(&mut file, &mut out_file)?; - } - } - } - - Ok(()) - } -} - -fn set_perms( - dst: &Path, - f: Option<&mut std::fs::File>, - mode: u32, - preserve: bool, -) -> crate::api::Result<()> { - _set_perms(dst, f, mode, preserve).map_err(|_| { - crate::api::Error::Extract(format!( - "failed to set permissions to {:o} \ - for `{}`", - mode, - dst.display() - )) - }) -} - -#[cfg(unix)] -fn _set_perms( - dst: &Path, - f: Option<&mut std::fs::File>, - mode: u32, - preserve: bool, -) -> io::Result<()> { - use std::os::unix::prelude::*; - - let mode = if preserve { mode } else { mode & 0o777 }; - let perm = fs::Permissions::from_mode(mode as _); - match f { - Some(f) => f.set_permissions(perm), - None => fs::set_permissions(dst, perm), - } -} - -#[cfg(windows)] -fn _set_perms( - dst: &Path, - f: Option<&mut std::fs::File>, - mode: u32, - _preserve: bool, -) -> io::Result<()> { - if mode & 0o200 == 0o200 { - return Ok(()); - } - match f { - Some(f) => { - let mut perm = f.metadata()?.permissions(); - perm.set_readonly(true); - f.set_permissions(perm) - } - None => { - let mut perm = fs::metadata(dst)?.permissions(); - perm.set_readonly(true); - fs::set_permissions(dst, perm) - } - } -} diff --git a/core/tauri/src/api/file/file_move.rs b/core/tauri/src/api/file/file_move.rs deleted file mode 100644 index d6be67689d99..000000000000 --- a/core/tauri/src/api/file/file_move.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use ignore::WalkBuilder; -use std::{fs, path}; - -/// Moves a file from the given path to the specified destination. -/// -/// `source` and `dest` must be on the same filesystem. -/// If `replace_using_temp` is specified, the destination file will be -/// replaced using the given temporary path. -/// -/// * Errors: -/// * Io - copying / renaming -#[derive(Debug)] -pub struct Move<'a> { - source: &'a path::Path, - temp: Option<&'a path::Path>, -} -impl<'a> Move<'a> { - /// Specify source file - pub fn from_source(source: &'a path::Path) -> Move<'a> { - Self { source, temp: None } - } - - /// If specified and the destination file already exists, the "destination" - /// file will be moved to the given temporary location before the "source" - /// file is moved to the "destination" file. - /// - /// In the event of an `io` error while renaming "source" to "destination", - /// the temporary file will be moved back to "destination". - /// - /// The `temp` dir must be explicitly provided since `rename` operations require - /// files to live on the same filesystem. - pub fn replace_using_temp(&mut self, temp: &'a path::Path) -> &mut Self { - self.temp = Some(temp); - self - } - - /// Move source file to specified destination (replace whole directory) - pub fn to_dest(&self, dest: &path::Path) -> crate::api::Result<()> { - match self.temp { - None => { - fs::rename(self.source, dest)?; - } - Some(temp) => { - if dest.exists() { - fs::rename(dest, temp)?; - if let Err(e) = fs::rename(self.source, dest) { - fs::rename(temp, dest)?; - return Err(e.into()); - } - } else { - fs::rename(self.source, dest)?; - } - } - }; - Ok(()) - } - - /// Walk in the source and copy all files and create directories if needed by - /// replacing existing elements. (equivalent to a cp -R) - pub fn walk_to_dest(&self, dest: &path::Path) -> crate::api::Result<()> { - match self.temp { - None => { - // got no temp -- no need to backup - walkdir_and_copy(self.source, dest)?; - } - Some(temp) => { - if dest.exists() { - // we got temp and our dest exist, lets make a backup - // of current files - walkdir_and_copy(dest, temp)?; - - if let Err(e) = walkdir_and_copy(self.source, dest) { - // if we got something wrong we reset the dest with our backup - fs::rename(temp, dest)?; - return Err(e); - } - } else { - // got temp but dest didnt exist - walkdir_and_copy(self.source, dest)?; - } - } - }; - Ok(()) - } -} -// Walk into the source and create directories, and copy files -// Overwriting existing items but keeping untouched the files in the dest -// not provided in the source. -fn walkdir_and_copy(source: &path::Path, dest: &path::Path) -> crate::api::Result<()> { - let walkdir = WalkBuilder::new(source).hidden(false).build(); - - for entry in walkdir { - // Check if it's a file - - let element = entry?; - let metadata = element.metadata()?; - let destination = dest.join(element.path().strip_prefix(&source)?); - - // we make sure it's a directory and destination doesnt exist - if metadata.is_dir() && !&destination.exists() { - fs::create_dir_all(&destination)?; - } - - // we make sure it's a file - if metadata.is_file() { - fs::copy(element.path(), destination)?; - } - } - Ok(()) -} diff --git a/core/tauri/src/api/http.rs b/core/tauri/src/api/http.rs deleted file mode 100644 index 3a3a18f25de1..000000000000 --- a/core/tauri/src/api/http.rs +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Types and functions related to HTTP request. - -use http::{header::HeaderName, Method}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use url::Url; - -use std::{collections::HashMap, path::PathBuf, time::Duration}; - -/// The builder of [`Client`]. -#[derive(Debug, Clone, Default, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ClientBuilder { - /// Max number of redirections to follow. - pub max_redirections: Option, - /// Connect timeout in seconds for the request. - pub connect_timeout: Option, -} - -impl ClientBuilder { - /// Creates a new client builder with the default options. - pub fn new() -> Self { - Default::default() - } - - /// Sets the maximum number of redirections. - #[must_use] - pub fn max_redirections(mut self, max_redirections: usize) -> Self { - self.max_redirections = Some(max_redirections); - self - } - - /// Sets the connection timeout. - #[must_use] - pub fn connect_timeout(mut self, connect_timeout: u64) -> Self { - self.connect_timeout = Some(connect_timeout); - self - } - - /// Builds the Client. - #[cfg(not(feature = "reqwest-client"))] - pub fn build(self) -> crate::api::Result { - Ok(Client(self)) - } - - /// Builds the Client. - #[cfg(feature = "reqwest-client")] - pub fn build(self) -> crate::api::Result { - let mut client_builder = reqwest::Client::builder(); - - if let Some(max_redirections) = self.max_redirections { - client_builder = client_builder.redirect(reqwest::redirect::Policy::limited(max_redirections)) - } - - if let Some(connect_timeout) = self.connect_timeout { - client_builder = client_builder.connect_timeout(Duration::from_secs(connect_timeout)); - } - - let client = client_builder.build()?; - Ok(Client(client)) - } -} - -/// The HTTP client based on [`reqwest`]. -#[cfg(feature = "reqwest-client")] -#[derive(Debug, Clone)] -pub struct Client(reqwest::Client); - -/// The HTTP client. -#[cfg(not(feature = "reqwest-client"))] -#[derive(Debug, Clone)] -pub struct Client(ClientBuilder); - -#[cfg(not(feature = "reqwest-client"))] -impl Client { - /// Executes an HTTP request - /// - /// The response will be transformed to String. - /// If reading the response as binary, the byte array will be serialized using [`serde_json`]. - pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result { - let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?; - - let mut request_builder = attohttpc::RequestBuilder::try_new(method, &request.url)?; - - if let Some(query) = request.query { - request_builder = request_builder.params(&query); - } - - if let Some(headers) = request.headers { - for (header, header_value) in headers.iter() { - request_builder = request_builder.header( - HeaderName::from_bytes(header.as_bytes())?, - header_value.as_bytes(), - ); - } - } - - if let Some(timeout) = request.timeout { - request_builder = request_builder.timeout(Duration::from_secs(timeout)); - } - - let response = if let Some(body) = request.body { - match body { - Body::Bytes(data) => request_builder.body(attohttpc::body::Bytes(data)).send()?, - Body::Text(text) => request_builder.body(attohttpc::body::Bytes(text)).send()?, - Body::Json(json) => request_builder.json(&json)?.send()?, - Body::Form(form_body) => { - let mut form = Vec::new(); - for (name, part) in form_body.0 { - match part { - FormPart::Bytes(bytes) => form.push((name, serde_json::to_string(&bytes)?)), - FormPart::File(file_path) => form.push((name, serde_json::to_string(&file_path)?)), - FormPart::Text(text) => form.push((name, text)), - } - } - request_builder.form(&form)?.send()? - } - } - } else { - request_builder.send()? - }; - - Ok(Response( - request.response_type.unwrap_or(ResponseType::Json), - response, - request.url, - )) - } -} - -#[cfg(feature = "reqwest-client")] -impl Client { - /// Executes an HTTP request - /// - /// The response will be transformed to String. - /// If reading the response as binary, the byte array will be serialized using serde_json. - pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result { - let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?; - - let mut request_builder = self.0.request(method, request.url.as_str()); - - if let Some(query) = request.query { - request_builder = request_builder.query(&query); - } - - if let Some(timeout) = request.timeout { - request_builder = request_builder.timeout(Duration::from_secs(timeout)); - } - - if let Some(body) = request.body { - request_builder = match body { - Body::Bytes(data) => request_builder.body(bytes::Bytes::from(data)), - Body::Text(text) => request_builder.body(bytes::Bytes::from(text)), - Body::Json(json) => request_builder.json(&json), - Body::Form(form_body) => { - let mut form = Vec::new(); - for (name, part) in form_body.0 { - match part { - FormPart::Bytes(bytes) => form.push((name, serde_json::to_string(&bytes)?)), - FormPart::File(file_path) => form.push((name, serde_json::to_string(&file_path)?)), - FormPart::Text(text) => form.push((name, text)), - } - } - request_builder.form(&form) - } - }; - } - - let mut http_request = request_builder.build()?; - if let Some(headers) = request.headers { - for (header, value) in headers.iter() { - http_request.headers_mut().insert( - HeaderName::from_bytes(header.as_bytes())?, - http::header::HeaderValue::from_bytes(value.as_bytes())?, - ); - } - } - - let response = self.0.execute(http_request).await?; - - Ok(Response( - request.response_type.unwrap_or(ResponseType::Json), - response, - )) - } -} - -#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)] -#[repr(u16)] -#[non_exhaustive] -/// The HTTP response type. -pub enum ResponseType { - /// Read the response as JSON - Json = 1, - /// Read the response as text - Text, - /// Read the response as binary - Binary, -} - -/// [`FormBody`] data types. -#[derive(Debug, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum FormPart { - /// A file path value. - File(PathBuf), - /// A string value. - Text(String), - /// A byte array value. - Bytes(Vec), -} - -/// Form body definition. -#[derive(Debug, Deserialize)] -pub struct FormBody(HashMap); - -impl FormBody { - /// Creates a new form body. - pub fn new(data: HashMap) -> Self { - Self(data) - } -} - -/// A body for the request. -#[derive(Debug, Deserialize)] -#[serde(tag = "type", content = "payload")] -#[non_exhaustive] -pub enum Body { - /// A multipart formdata body. - Form(FormBody), - /// A JSON body. - Json(Value), - /// A text string body. - Text(String), - /// A byte array body. - Bytes(Vec), -} - -/// The builder for a HTTP request. -/// -/// # Examples -/// ```no_run -/// use tauri::api::http::{ HttpRequestBuilder, ResponseType, ClientBuilder }; -/// async fn run() { -/// let client = ClientBuilder::new() -/// .max_redirections(3) -/// .build() -/// .unwrap(); -/// let mut request_builder = HttpRequestBuilder::new("GET", "http://example.com").unwrap(); -/// let request = request_builder.response_type(ResponseType::Text); -/// -/// if let Ok(response) = client.send(request).await { -/// println!("got response"); -/// } else { -/// println!("Something Happened!"); -/// } -/// } -/// ``` -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct HttpRequestBuilder { - /// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE) - pub method: String, - /// The request URL - pub url: Url, - /// The request query params - pub query: Option>, - /// The request headers - pub headers: Option>, - /// The request body - pub body: Option, - /// Timeout for the whole request - pub timeout: Option, - /// The response type (defaults to Json) - pub response_type: Option, -} - -impl HttpRequestBuilder { - /// Initializes a new instance of the HttpRequestrequest_builder. - pub fn new(method: impl Into, url: impl AsRef) -> crate::api::Result { - Ok(Self { - method: method.into(), - url: Url::parse(url.as_ref())?, - query: None, - headers: None, - body: None, - timeout: None, - response_type: None, - }) - } - - /// Sets the request parameters. - #[must_use] - pub fn query(mut self, query: HashMap) -> Self { - self.query = Some(query); - self - } - - /// Sets the request headers. - #[must_use] - pub fn headers(mut self, headers: HashMap) -> Self { - self.headers = Some(headers); - self - } - - /// Sets the request body. - #[must_use] - pub fn body(mut self, body: Body) -> Self { - self.body = Some(body); - self - } - - /// Sets the general request timeout. - #[must_use] - pub fn timeout(mut self, timeout: u64) -> Self { - self.timeout = Some(timeout); - self - } - - /// Sets the type of the response. Interferes with the way we read the response. - #[must_use] - pub fn response_type(mut self, response_type: ResponseType) -> Self { - self.response_type = Some(response_type); - self - } -} - -/// The HTTP response. -#[cfg(feature = "reqwest-client")] -#[derive(Debug)] -pub struct Response(ResponseType, reqwest::Response); -/// The HTTP response. -#[cfg(not(feature = "reqwest-client"))] -#[derive(Debug)] -pub struct Response(ResponseType, attohttpc::Response, Url); - -impl Response { - /// Reads the response as raw bytes. - pub async fn bytes(self) -> crate::api::Result { - let status = self.1.status().as_u16(); - #[cfg(feature = "reqwest-client")] - let data = self.1.bytes().await?.to_vec(); - #[cfg(not(feature = "reqwest-client"))] - let data = self.1.bytes()?; - Ok(RawResponse { status, data }) - } - - /// Reads the response and returns its info. - pub async fn read(self) -> crate::api::Result { - #[cfg(feature = "reqwest-client")] - let url = self.1.url().clone(); - #[cfg(not(feature = "reqwest-client"))] - let url = self.2; - - let mut headers = HashMap::new(); - let mut raw_headers = HashMap::new(); - for (name, value) in self.1.headers() { - headers.insert( - name.as_str().to_string(), - String::from_utf8(value.as_bytes().to_vec())?, - ); - raw_headers.insert( - name.as_str().to_string(), - self - .1 - .headers() - .get_all(name) - .into_iter() - .map(|v| String::from_utf8(v.as_bytes().to_vec()).map_err(Into::into)) - .collect::>>()?, - ); - } - let status = self.1.status().as_u16(); - - #[cfg(feature = "reqwest-client")] - let data = match self.0 { - ResponseType::Json => self.1.json().await?, - ResponseType::Text => Value::String(self.1.text().await?), - ResponseType::Binary => serde_json::to_value(&self.1.bytes().await?)?, - }; - - #[cfg(not(feature = "reqwest-client"))] - let data = match self.0 { - ResponseType::Json => self.1.json()?, - ResponseType::Text => Value::String(self.1.text()?), - ResponseType::Binary => serde_json::to_value(&self.1.bytes()?)?, - }; - - Ok(ResponseData { - url, - status, - headers, - raw_headers, - data, - }) - } -} - -/// A response with raw bytes. -#[non_exhaustive] -#[derive(Debug)] -pub struct RawResponse { - /// Response status code. - pub status: u16, - /// Response bytes. - pub data: Vec, -} - -/// The response data. -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ResponseData { - /// Response URL. Useful if it followed redirects. - pub url: Url, - /// Response status code. - pub status: u16, - /// Response headers. - pub headers: HashMap, - /// Response raw headers. - pub raw_headers: HashMap>, - /// Response data. - pub data: Value, -} - -#[cfg(test)] -mod test { - use super::ClientBuilder; - use quickcheck::{Arbitrary, Gen}; - - impl Arbitrary for ClientBuilder { - fn arbitrary(g: &mut Gen) -> Self { - Self { - max_redirections: Option::arbitrary(g), - connect_timeout: Option::arbitrary(g), - } - } - } -} diff --git a/core/tauri/src/api/ipc.rs b/core/tauri/src/api/ipc.rs index e643ad444241..e4fccc99e45c 100644 --- a/core/tauri/src/api/ipc.rs +++ b/core/tauri/src/api/ipc.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -10,9 +10,73 @@ use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; pub use serialize_to_javascript::Options as SerializeOptions; use serialize_to_javascript::Serialized; +use tauri_macros::default_runtime; + +use crate::{ + command::{CommandArg, CommandItem}, + InvokeError, Runtime, Window, +}; + +const CHANNEL_PREFIX: &str = "__CHANNEL__:"; + +/// An IPC channel. +#[default_runtime(crate::Wry, wry)] +pub struct Channel { + id: CallbackFn, + window: Window, +} + +impl Clone for Channel { + fn clone(&self) -> Self { + Self { + id: self.id, + window: self.window.clone(), + } + } +} + +impl Serialize for Channel { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!("{CHANNEL_PREFIX}{}", self.id.0)) + } +} + +impl Channel { + /// Sends the given data through the channel. + pub fn send(&self, data: &S) -> crate::Result<()> { + let js = format_callback(self.id, data)?; + self.window.eval(&js) + } +} + +impl<'de, R: Runtime> CommandArg<'de, R> for Channel { + /// Grabs the [`Window`] from the [`CommandItem`] and returns the associated [`Channel`]. + fn from_command(command: CommandItem<'de, R>) -> Result { + let name = command.name; + let arg = command.key; + let window = command.message.window(); + let value: String = + Deserialize::deserialize(command).map_err(|e| crate::Error::InvalidArgs(name, arg, e))?; + if let Some(callback_id) = value + .split_once(CHANNEL_PREFIX) + .and_then(|(_prefix, id)| id.parse().ok()) + { + return Ok(Channel { + id: CallbackFn(callback_id), + window, + }); + } + Err(InvokeError::from_anyhow(anyhow::anyhow!( + "invalid channel value `{value}`, expected a string in the `{CHANNEL_PREFIX}ID` format" + ))) + } +} /// The `Callback` type is the return value of the `transformCallback` JavaScript function. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct CallbackFn(pub usize); /// The information about this is quite limited. On Chrome/Edge and Firefox, [the maximum string size is approximately 1 GB](https://stackoverflow.com/a/34958490). @@ -48,7 +112,7 @@ const MIN_JSON_PARSE_LEN: usize = 10_240; /// 2. JavaScript engines not accepting anything except another unescaped, literal single quote /// character to end a string that was opened with it. /// -/// # Example +/// # Examples /// /// ``` /// use tauri::api::ipc::{serialize_js_with, SerializeOptions}; @@ -57,7 +121,7 @@ const MIN_JSON_PARSE_LEN: usize = 10_240; /// bar: String, /// } /// let foo = Foo { bar: "x".repeat(20_000).into() }; -/// let value = serialize_js_with(&foo, SerializeOptions::default(), |v| format!("console.log({})", v)).unwrap(); +/// let value = serialize_js_with(&foo, SerializeOptions::default(), |v| format!("console.log({v})")).unwrap(); /// assert_eq!(value, format!("console.log(JSON.parse('{{\"bar\":\"{}\"}}'))", foo.bar)); /// ``` pub fn serialize_js_with String>( @@ -103,7 +167,7 @@ pub fn serialize_js_with String>( /// /// For usage in functions where performance is more important than code readability, see [`serialize_js_with`]. /// -/// # Example +/// # Examples /// ```rust,no_run /// use tauri::{Manager, api::ipc::serialize_js}; /// use serde::Serialize; @@ -125,7 +189,7 @@ pub fn serialize_js_with String>( /// "console.log({}, {})", /// serialize_js(&Foo { bar: "bar".to_string() }).unwrap(), /// serialize_js(&Bar { baz: 0 }).unwrap()), -/// ).unwrap(); +/// )?; /// Ok(()) /// }); /// ``` @@ -179,8 +243,7 @@ pub fn format_callback( }} else {{ console.warn("[TAURI] Couldn't find callback id {fn} in window. This happens when the app is reloaded while Rust is running an asynchronous operation.") }}"#, - fn = function_name.0, - arg = arg + fn = function_name.0 ) }) } @@ -242,22 +305,22 @@ mod test { } let raw_str = "T".repeat(MIN_JSON_PARSE_LEN); - assert_eq!(serialize_js(&raw_str).unwrap(), format!("\"{}\"", raw_str)); + assert_eq!(serialize_js(&raw_str).unwrap(), format!("\"{raw_str}\"")); assert_eq!( serialize_js(&JsonObj { value: raw_str.clone() }) .unwrap(), - format!("JSON.parse('{{\"value\":\"{}\"}}')", raw_str) + format!("JSON.parse('{{\"value\":\"{raw_str}\"}}')") ); assert_eq!( serialize_js(&JsonObj { - value: format!("\"{}\"", raw_str) + value: format!("\"{raw_str}\"") }) .unwrap(), - format!("JSON.parse('{{\"value\":\"\\\\\"{}\\\\\"\"}}')", raw_str) + format!("JSON.parse('{{\"value\":\"\\\\\"{raw_str}\\\\\"\"}}')") ); let dangerous_json = RawValue::from_string( @@ -279,9 +342,9 @@ mod test { assert_eq!(escape_single_quoted_json_test, result); } - // check abritrary strings in the format callback function + // check arbitrary strings in the format callback function #[quickcheck] - fn qc_formating(f: CallbackFn, a: String) -> bool { + fn qc_formatting(f: CallbackFn, a: String) -> bool { // call format callback let fc = format_callback(f, &a).unwrap(); fc.contains(&format!( diff --git a/core/tauri/src/api/mod.rs b/core/tauri/src/api/mod.rs index 83aa6f414747..f3d24f4c39b4 100644 --- a/core/tauri/src/api/mod.rs +++ b/core/tauri/src/api/mod.rs @@ -1,37 +1,14 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! The Tauri API interface. -#[cfg(feature = "dialog")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "dialog")))] -pub mod dialog; pub mod dir; pub mod file; -#[cfg(feature = "http-api")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "http-api")))] -pub mod http; pub mod ipc; -pub mod path; -pub mod process; -#[cfg(feature = "shell-open-api")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "shell-open-api")))] -pub mod shell; pub mod version; -#[cfg(feature = "cli")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "cli")))] -pub mod cli; - -#[cfg(feature = "cli")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "cli")))] -pub use clap; - -#[cfg(feature = "notification")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "notification")))] -pub mod notification; - mod error; /// The error type of Tauri API module. diff --git a/core/tauri/src/api/notification.rs b/core/tauri/src/api/notification.rs deleted file mode 100644 index e93076988b6f..000000000000 --- a/core/tauri/src/api/notification.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Types and functions related to desktop notifications. - -#[cfg(windows)] -use std::path::MAIN_SEPARATOR; - -/// The desktop notification definition. -/// -/// Allows you to construct a Notification data and send it. -/// -/// # Example -/// ``` -/// use tauri::api::notification::Notification; -/// // shows a notification with the given title and body -/// Notification::new("studio.tauri.example") -/// .title("New message") -/// .body("You've got a new message.") -/// .show(); -/// ``` -#[allow(dead_code)] -#[derive(Debug, Default)] -pub struct Notification { - /// The notification body. - body: Option, - /// The notification title. - title: Option, - /// The notification icon. - icon: Option, - /// The notification identifier - identifier: String, -} - -impl Notification { - /// Initializes a instance of a Notification. - pub fn new(identifier: impl Into) -> Self { - Self { - identifier: identifier.into(), - ..Default::default() - } - } - - /// Sets the notification body. - #[must_use] - pub fn body(mut self, body: impl Into) -> Self { - self.body = Some(body.into()); - self - } - - /// Sets the notification title. - #[must_use] - pub fn title(mut self, title: impl Into) -> Self { - self.title = Some(title.into()); - self - } - - /// Sets the notification icon. - #[must_use] - pub fn icon(mut self, icon: impl Into) -> Self { - self.icon = Some(icon.into()); - self - } - - /// Shows the notification. - pub fn show(self) -> crate::api::Result<()> { - let mut notification = notify_rust::Notification::new(); - if let Some(body) = self.body { - notification.body(&body); - } - if let Some(title) = self.title { - notification.summary(&title); - } - if let Some(icon) = self.icon { - notification.icon(&icon); - } - #[cfg(windows)] - { - let exe = tauri_utils::platform::current_exe()?; - let exe_dir = exe.parent().expect("failed to get exe directory"); - let curr_dir = exe_dir.display().to_string(); - // set the notification's System.AppUserModel.ID only when running the installed app - if !(curr_dir.ends_with(format!("{S}target{S}debug", S = MAIN_SEPARATOR).as_str()) - || curr_dir.ends_with(format!("{S}target{S}release", S = MAIN_SEPARATOR).as_str())) - { - notification.app_id(&self.identifier); - } - } - - crate::async_runtime::spawn(async move { - notification.show().expect("failed to show notification"); - }); - - Ok(()) - } -} diff --git a/core/tauri/src/api/os.rs b/core/tauri/src/api/os.rs new file mode 100644 index 000000000000..c5ea565a8d0e --- /dev/null +++ b/core/tauri/src/api/os.rs @@ -0,0 +1,10 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Types and functions related to operating system operations. + +/// Returns `Some(String)` with a `BCP-47` language tag inside. If the locale couldn’t be obtained, `None` is returned instead. +pub fn locale() -> Option { + sys_locale::get_locale() +} diff --git a/core/tauri/src/api/path.rs b/core/tauri/src/api/path.rs deleted file mode 100644 index ab0ec5838e57..000000000000 --- a/core/tauri/src/api/path.rs +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Types and functions related to file system path operations. - -use std::path::{Component, Path, PathBuf}; - -use crate::{Config, Env, PackageInfo}; - -use serde_repr::{Deserialize_repr, Serialize_repr}; - -/// A base directory to be used in [`resolve_path`]. -/// -/// The base directory is the optional root of a file system operation. -/// If informed by the API call, all paths will be relative to the path of the given directory. -/// -/// For more information, check the [`dirs_next` documentation](https://docs.rs/dirs_next/). -#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)] -#[repr(u16)] -#[non_exhaustive] -pub enum BaseDirectory { - /// The Audio directory. - Audio = 1, - /// The Cache directory. - Cache, - /// The Config directory. - Config, - /// The Data directory. - Data, - /// The LocalData directory. - LocalData, - /// The Desktop directory. - Desktop, - /// The Document directory. - Document, - /// The Download directory. - Download, - /// The Executable directory. - Executable, - /// The Font directory. - Font, - /// The Home directory. - Home, - /// The Picture directory. - Picture, - /// The Public directory. - Public, - /// The Runtime directory. - Runtime, - /// The Template directory. - Template, - /// The Video directory. - Video, - /// The Resource directory. - Resource, - /// The default App config directory. - /// Resolves to [`BaseDirectory::Config`]. - App, - /// The Log directory. - /// Resolves to [`BaseDirectory::Home/Library/Logs/{bundle_identifier}`] on macOS - /// and [`BaseDirectory::Config/{bundle_identifier}/logs`] on linux and windows. - Log, -} - -impl BaseDirectory { - /// Gets the variable that represents this [`BaseDirectory`] for string paths. - pub fn variable(self) -> &'static str { - match self { - Self::Audio => "$AUDIO", - Self::Cache => "$CACHE", - Self::Config => "$CONFIG", - Self::Data => "$DATA", - Self::LocalData => "$LOCALDATA", - Self::Desktop => "$DESKTOP", - Self::Document => "$DOCUMENT", - Self::Download => "$DOWNLOAD", - Self::Executable => "$EXE", - Self::Font => "$FONT", - Self::Home => "$HOME", - Self::Picture => "$PICTURE", - Self::Public => "$PUBLIC", - Self::Runtime => "$RUNTIME", - Self::Template => "$TEMPLATE", - Self::Video => "$VIDEO", - Self::Resource => "$RESOURCE", - Self::App => "$APP", - Self::Log => "$LOG", - } - } - - /// Gets the [`BaseDirectory`] associated with the given variable, or [`None`] if the variable doesn't match any. - pub fn from_variable(variable: &str) -> Option { - let res = match variable { - "$AUDIO" => Self::Audio, - "$CACHE" => Self::Cache, - "$CONFIG" => Self::Config, - "$DATA" => Self::Data, - "$LOCALDATA" => Self::LocalData, - "$DESKTOP" => Self::Desktop, - "$DOCUMENT" => Self::Document, - "$DOWNLOAD" => Self::Download, - "$EXE" => Self::Executable, - "$FONT" => Self::Font, - "$HOME" => Self::Home, - "$PICTURE" => Self::Picture, - "$PUBLIC" => Self::Public, - "$RUNTIME" => Self::Runtime, - "$TEMPLATE" => Self::Template, - "$VIDEO" => Self::Video, - "$RESOURCE" => Self::Resource, - "$APP" => Self::App, - "$LOG" => Self::Log, - _ => return None, - }; - Some(res) - } -} - -/// Parse the given path, resolving a [`BaseDirectory`] variable if the path starts with one. -pub fn parse>( - config: &Config, - package_info: &PackageInfo, - env: &Env, - path: P, -) -> crate::api::Result { - let mut p = PathBuf::new(); - let mut components = path.as_ref().components(); - if let Some(Component::Normal(str)) = components.next() { - if let Some(base_directory) = BaseDirectory::from_variable(&str.to_string_lossy()) { - p.push(resolve_path( - config, - package_info, - env, - "", - Some(base_directory), - )?); - } else { - p.push(str); - } - } - - for component in components { - if let Component::ParentDir = component { - continue; - } - p.push(component); - } - - Ok(p) -} - -/// Resolves the path with the optional base directory. -/// -/// # Example -/// ``` -/// use tauri::{api::path::{resolve_path, BaseDirectory}, PackageInfo}; -/// // we use the default config and a mock PackageInfo, but in an actual app you should get the -/// // Config created from `tauri.conf.json` and the app's PackageInfo instance. -/// let path = resolve_path( -/// &Default::default(), -/// &PackageInfo { -/// name: "app".into(), -/// version: "1.0.0".into(), -/// authors: "tauri", -/// description: "a tauri test", -/// }, -/// &Default::default(), -/// "path/to/something", -/// Some(BaseDirectory::Config) -/// ).expect("failed to resolve path"); -/// // path is equal to "/home/${whoami}/.config/path/to/something" on Linux -/// ``` -pub fn resolve_path>( - config: &Config, - package_info: &PackageInfo, - env: &Env, - path: P, - dir: Option, -) -> crate::api::Result { - if let Some(base_dir) = dir { - let resolve_resource = matches!(base_dir, BaseDirectory::Resource); - let base_dir_path = match base_dir { - BaseDirectory::Audio => audio_dir(), - BaseDirectory::Cache => cache_dir(), - BaseDirectory::Config => config_dir(), - BaseDirectory::Data => data_dir(), - BaseDirectory::LocalData => local_data_dir(), - BaseDirectory::Desktop => desktop_dir(), - BaseDirectory::Document => document_dir(), - BaseDirectory::Download => download_dir(), - BaseDirectory::Executable => executable_dir(), - BaseDirectory::Font => font_dir(), - BaseDirectory::Home => home_dir(), - BaseDirectory::Picture => picture_dir(), - BaseDirectory::Public => public_dir(), - BaseDirectory::Runtime => runtime_dir(), - BaseDirectory::Template => template_dir(), - BaseDirectory::Video => video_dir(), - BaseDirectory::Resource => resource_dir(package_info, env), - BaseDirectory::App => app_dir(config), - BaseDirectory::Log => log_dir(config), - }; - if let Some(mut base_dir_path_value) = base_dir_path { - // use the same path resolution mechanism as the bundler's resource injection algorithm - if resolve_resource { - let mut resource_path = PathBuf::new(); - for component in path.as_ref().components() { - match component { - Component::Prefix(_) => {} - Component::RootDir => resource_path.push("_root_"), - Component::CurDir => {} - Component::ParentDir => resource_path.push("_up_"), - Component::Normal(p) => resource_path.push(p), - } - } - base_dir_path_value.push(resource_path); - } else { - base_dir_path_value.push(path); - } - Ok(base_dir_path_value) - } else { - Err(crate::api::Error::Path( - "unable to determine base dir path".to_string(), - )) - } - } else { - let mut dir_path = PathBuf::new(); - dir_path.push(path); - Ok(dir_path) - } -} - -/// Returns the path to the user's audio directory. -pub fn audio_dir() -> Option { - dirs_next::audio_dir() -} - -/// Returns the path to the user's cache directory. -pub fn cache_dir() -> Option { - dirs_next::cache_dir() -} - -/// Returns the path to the user's config directory. -pub fn config_dir() -> Option { - dirs_next::config_dir() -} - -/// Returns the path to the user's data directory. -pub fn data_dir() -> Option { - dirs_next::data_dir() -} - -/// Returns the path to the user's local data directory. -pub fn local_data_dir() -> Option { - dirs_next::data_local_dir() -} - -/// Returns the path to the user's desktop directory. -pub fn desktop_dir() -> Option { - dirs_next::desktop_dir() -} - -/// Returns the path to the user's document directory. -pub fn document_dir() -> Option { - dirs_next::document_dir() -} - -/// Returns the path to the user's download directory. -pub fn download_dir() -> Option { - dirs_next::download_dir() -} - -/// Returns the path to the user's executable directory. -pub fn executable_dir() -> Option { - dirs_next::executable_dir() -} - -/// Returns the path to the user's font directory. -pub fn font_dir() -> Option { - dirs_next::font_dir() -} - -/// Returns the path to the user's home directory. -pub fn home_dir() -> Option { - dirs_next::home_dir() -} - -/// Returns the path to the user's picture directory. -pub fn picture_dir() -> Option { - dirs_next::picture_dir() -} - -/// Returns the path to the user's public directory. -pub fn public_dir() -> Option { - dirs_next::public_dir() -} - -/// Returns the path to the user's runtime directory. -pub fn runtime_dir() -> Option { - dirs_next::runtime_dir() -} - -/// Returns the path to the user's template directory. -pub fn template_dir() -> Option { - dirs_next::template_dir() -} - -/// Returns the path to the user's video dir -pub fn video_dir() -> Option { - dirs_next::video_dir() -} - -/// Returns the path to the resource directory of this app. -pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> Option { - crate::utils::platform::resource_dir(package_info, env).ok() -} - -/// Returns the path to the suggested directory for your app config files. -pub fn app_dir(config: &Config) -> Option { - dirs_next::config_dir().map(|dir| dir.join(&config.tauri.bundle.identifier)) -} - -/// Returns the path to the suggested log directory. -pub fn log_dir(config: &Config) -> Option { - #[cfg(target_os = "macos")] - let path = dirs_next::home_dir().map(|dir| { - dir - .join("Library/Logs") - .join(&config.tauri.bundle.identifier) - }); - - #[cfg(not(target_os = "macos"))] - let path = - dirs_next::config_dir().map(|dir| dir.join(&config.tauri.bundle.identifier).join("logs")); - - path -} diff --git a/core/tauri/src/api/process.rs b/core/tauri/src/api/process.rs deleted file mode 100644 index 72544ea775fa..000000000000 --- a/core/tauri/src/api/process.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Types and functions related to child processes management. - -use crate::Env; - -use std::path::PathBuf; - -#[cfg(feature = "command")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "command")))] -mod command; -#[cfg(feature = "command")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "command")))] -pub use command::*; - -/// Finds the current running binary's path. -/// -/// With exception to any following platform-specific behavior, the path is cached as soon as -/// possible, and then used repeatedly instead of querying for a new path every time this function -/// is called. -/// -/// # Platform-specific behavior -/// -/// ## Linux -/// -/// On Linux, this function will **attempt** to detect if it's currently running from a -/// valid [AppImage] and use that path instead. -/// -/// ## macOS -/// -/// On `macOS`, this function will return an error if the original path contained any symlinks -/// due to less protection on macOS regarding symlinks. This behavior can be disabled by setting the -/// `process-relaunch-dangerous-allow-symlink-macos` feature, although it is *highly discouraged*. -/// -/// # Security -/// -/// See [`tauri_utils::platform::current_exe`] for possible security implications. -/// -/// [AppImage]: https://appimage.org/ -pub fn current_binary(_env: &Env) -> std::io::Result { - // if we are running from an AppImage, we ONLY want the set AppImage path - #[cfg(target_os = "linux")] - if let Some(app_image_path) = &_env.appimage { - return Ok(PathBuf::from(app_image_path)); - } - - tauri_utils::platform::current_exe() -} - -/// Restarts the currently running binary. -/// -/// See [`current_binary`] for platform specific behavior, and -/// [`tauri_utils::platform::current_exe`] for possible security implications. -pub fn restart(env: &Env) { - use std::process::{exit, Command}; - - if let Ok(path) = current_binary(env) { - Command::new(path) - .spawn() - .expect("application failed to start"); - } - - exit(0); -} diff --git a/core/tauri/src/api/process/command.rs b/core/tauri/src/api/process/command.rs deleted file mode 100644 index d81116aaf36a..000000000000 --- a/core/tauri/src/api/process/command.rs +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::{ - collections::HashMap, - io::{BufRead, BufReader, Write}, - path::PathBuf, - process::{Command as StdCommand, Stdio}, - sync::{Arc, Mutex, RwLock}, - thread::spawn, -}; - -#[cfg(unix)] -use std::os::unix::process::ExitStatusExt; -#[cfg(windows)] -use std::os::windows::process::CommandExt; - -#[cfg(windows)] -const CREATE_NO_WINDOW: u32 = 0x0800_0000; - -use crate::async_runtime::{block_on as block_on_task, channel, Receiver}; -use os_pipe::{pipe, PipeWriter}; -use serde::Serialize; -use shared_child::SharedChild; -use tauri_utils::platform; - -type ChildStore = Arc>>>; - -fn commands() -> &'static ChildStore { - use once_cell::sync::Lazy; - static STORE: Lazy = Lazy::new(Default::default); - &STORE -} - -/// Kills all child processes created with [`Command`]. -/// By default it's called before the [`crate::App`] exits. -pub fn kill_children() { - for child in commands().lock().unwrap().values() { - let _ = child.kill(); - } -} - -/// Payload for the [`CommandEvent::Terminated`] command event. -#[derive(Debug, Clone, Serialize)] -pub struct TerminatedPayload { - /// Exit code of the process. - pub code: Option, - /// If the process was terminated by a signal, represents that signal. - pub signal: Option, -} - -/// A event sent to the command callback. -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "event", content = "payload")] -#[non_exhaustive] -pub enum CommandEvent { - /// Stderr line. - Stderr(String), - /// Stdout line. - Stdout(String), - /// An error happened. - Error(String), - /// Command process terminated. - Terminated(TerminatedPayload), -} - -macro_rules! get_std_command { - ($self: ident) => {{ - let mut command = StdCommand::new($self.program); - command.args(&$self.args); - command.stdout(Stdio::piped()); - command.stdin(Stdio::piped()); - command.stderr(Stdio::piped()); - if $self.env_clear { - command.env_clear(); - } - command.envs($self.env); - if let Some(current_dir) = $self.current_dir { - command.current_dir(current_dir); - } - #[cfg(windows)] - command.creation_flags(CREATE_NO_WINDOW); - command - }}; -} - -/// The type to spawn commands. -#[derive(Debug)] -pub struct Command { - program: String, - args: Vec, - env_clear: bool, - env: HashMap, - current_dir: Option, -} - -/// Spawned child process. -#[derive(Debug)] -pub struct CommandChild { - inner: Arc, - stdin_writer: PipeWriter, -} - -impl CommandChild { - /// Writes to process stdin. - pub fn write(&mut self, buf: &[u8]) -> crate::api::Result<()> { - self.stdin_writer.write_all(buf)?; - Ok(()) - } - - /// Sends a kill signal to the child. - pub fn kill(self) -> crate::api::Result<()> { - self.inner.kill()?; - Ok(()) - } - - /// Returns the process pid. - pub fn pid(&self) -> u32 { - self.inner.id() - } -} - -/// Describes the result of a process after it has terminated. -#[derive(Debug)] -pub struct ExitStatus { - code: Option, -} - -impl ExitStatus { - /// Returns the exit code of the process, if any. - pub fn code(&self) -> Option { - self.code - } - - /// Returns true if exit status is zero. Signal termination is not considered a success, and success is defined as a zero exit status. - pub fn success(&self) -> bool { - self.code == Some(0) - } -} - -/// The output of a finished process. -#[derive(Debug)] -pub struct Output { - /// The status (exit code) of the process. - pub status: ExitStatus, - /// The data that the process wrote to stdout. - pub stdout: String, - /// The data that the process wrote to stderr. - pub stderr: String, -} - -fn relative_command_path(command: String) -> crate::Result { - match platform::current_exe()?.parent() { - #[cfg(windows)] - Some(exe_dir) => Ok(format!("{}\\{}.exe", exe_dir.display(), command)), - #[cfg(not(windows))] - Some(exe_dir) => Ok(format!("{}/{}", exe_dir.display(), command)), - None => Err(crate::api::Error::Command("Could not evaluate executable dir".to_string()).into()), - } -} - -impl Command { - /// Creates a new Command for launching the given program. - pub fn new>(program: S) -> Self { - Self { - program: program.into(), - args: Default::default(), - env_clear: false, - env: Default::default(), - current_dir: None, - } - } - - /// Creates a new Command for launching the given sidecar program. - /// - /// A sidecar program is a embedded external binary in order to make your application work - /// or to prevent users having to install additional dependencies (e.g. Node.js, Python, etc). - pub fn new_sidecar>(program: S) -> crate::Result { - Ok(Self::new(relative_command_path(program.into())?)) - } - - /// Appends arguments to the command. - #[must_use] - pub fn args(mut self, args: I) -> Self - where - I: IntoIterator, - S: AsRef, - { - for arg in args { - self.args.push(arg.as_ref().to_string()); - } - self - } - - /// Clears the entire environment map for the child process. - #[must_use] - pub fn env_clear(mut self) -> Self { - self.env_clear = true; - self - } - - /// Adds or updates multiple environment variable mappings. - #[must_use] - pub fn envs(mut self, env: HashMap) -> Self { - self.env = env; - self - } - - /// Sets the working directory for the child process. - #[must_use] - pub fn current_dir(mut self, current_dir: PathBuf) -> Self { - self.current_dir.replace(current_dir); - self - } - - /// Spawns the command. - pub fn spawn(self) -> crate::api::Result<(Receiver, CommandChild)> { - let mut command = get_std_command!(self); - let (stdout_reader, stdout_writer) = pipe()?; - let (stderr_reader, stderr_writer) = pipe()?; - let (stdin_reader, stdin_writer) = pipe()?; - command.stdout(stdout_writer); - command.stderr(stderr_writer); - command.stdin(stdin_reader); - - let shared_child = SharedChild::spawn(&mut command)?; - let child = Arc::new(shared_child); - let child_ = child.clone(); - let guard = Arc::new(RwLock::new(())); - - commands().lock().unwrap().insert(child.id(), child.clone()); - - let (tx, rx) = channel(1); - - let tx_ = tx.clone(); - let guard_ = guard.clone(); - spawn(move || { - let _lock = guard_.read().unwrap(); - let reader = BufReader::new(stdout_reader); - for line in reader.lines() { - let tx_ = tx_.clone(); - block_on_task(async move { - let _ = match line { - Ok(line) => tx_.send(CommandEvent::Stdout(line)).await, - Err(e) => tx_.send(CommandEvent::Error(e.to_string())).await, - }; - }); - } - }); - - let tx_ = tx.clone(); - let guard_ = guard.clone(); - spawn(move || { - let _lock = guard_.read().unwrap(); - let reader = BufReader::new(stderr_reader); - for line in reader.lines() { - let tx_ = tx_.clone(); - block_on_task(async move { - let _ = match line { - Ok(line) => tx_.send(CommandEvent::Stderr(line)).await, - Err(e) => tx_.send(CommandEvent::Error(e.to_string())).await, - }; - }); - } - }); - - spawn(move || { - let _ = match child_.wait() { - Ok(status) => { - let _l = guard.write().unwrap(); - commands().lock().unwrap().remove(&child_.id()); - let _ = block_on_task(async move { - tx.send(CommandEvent::Terminated(TerminatedPayload { - code: status.code(), - #[cfg(windows)] - signal: None, - #[cfg(unix)] - signal: status.signal(), - })) - .await - }); - } - Err(e) => { - let _l = guard.write().unwrap(); - let _ = block_on_task(async move { tx.send(CommandEvent::Error(e.to_string())).await }); - } - }; - }); - - Ok(( - rx, - CommandChild { - inner: child, - stdin_writer, - }, - )) - } - - /// Executes a command as a child process, waiting for it to finish and collecting its exit status. - /// Stdin, stdout and stderr are ignored. - pub fn status(self) -> crate::api::Result { - let (mut rx, _child) = self.spawn()?; - let code = crate::async_runtime::safe_block_on(async move { - let mut code = None; - #[allow(clippy::collapsible_match)] - while let Some(event) = rx.recv().await { - if let CommandEvent::Terminated(payload) = event { - code = payload.code; - } - } - code - }); - Ok(ExitStatus { code }) - } - - /// Executes the command as a child process, waiting for it to finish and collecting all of its output. - /// Stdin is ignored. - pub fn output(self) -> crate::api::Result { - let (mut rx, _child) = self.spawn()?; - - let output = crate::async_runtime::safe_block_on(async move { - let mut code = None; - let mut stdout = String::new(); - let mut stderr = String::new(); - while let Some(event) = rx.recv().await { - match event { - CommandEvent::Terminated(payload) => { - code = payload.code; - } - CommandEvent::Stdout(line) => { - stdout.push_str(line.as_str()); - stdout.push('\n'); - } - CommandEvent::Stderr(line) => { - stderr.push_str(line.as_str()); - stderr.push('\n'); - } - CommandEvent::Error(_) => {} - } - } - Output { - status: ExitStatus { code }, - stdout, - stderr, - } - }); - - Ok(output) - } -} - -// tests for the commands functions. -#[cfg(test)] -mod test { - #[cfg(not(windows))] - use super::*; - - #[cfg(not(windows))] - #[test] - fn test_cmd_output() { - // create a command to run cat. - let cmd = Command::new("cat").args(&["test/api/test.txt"]); - let (mut rx, _) = cmd.spawn().unwrap(); - - crate::async_runtime::block_on(async move { - while let Some(event) = rx.recv().await { - match event { - CommandEvent::Terminated(payload) => { - assert_eq!(payload.code, Some(0)); - } - CommandEvent::Stdout(line) => { - assert_eq!(line, "This is a test doc!".to_string()); - } - _ => {} - } - } - }); - } - - #[cfg(not(windows))] - #[test] - // test the failure case - fn test_cmd_fail() { - let cmd = Command::new("cat").args(&["test/api/"]); - let (mut rx, _) = cmd.spawn().unwrap(); - - crate::async_runtime::block_on(async move { - while let Some(event) = rx.recv().await { - match event { - CommandEvent::Terminated(payload) => { - assert_eq!(payload.code, Some(1)); - } - CommandEvent::Stderr(line) => { - assert_eq!(line, "cat: test/api/: Is a directory".to_string()); - } - _ => {} - } - } - }); - } -} diff --git a/core/tauri/src/api/shell.rs b/core/tauri/src/api/shell.rs deleted file mode 100644 index 075621e23932..000000000000 --- a/core/tauri/src/api/shell.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Types and functions related to shell. - -use crate::ShellScope; -use std::str::FromStr; - -/// Program to use on the [`open()`] call. -pub enum Program { - /// Use the `open` program. - Open, - /// Use the `start` program. - Start, - /// Use the `xdg-open` program. - XdgOpen, - /// Use the `gio` program. - Gio, - /// Use the `gnome-open` program. - GnomeOpen, - /// Use the `kde-open` program. - KdeOpen, - /// Use the `wslview` program. - WslView, - /// Use the `Firefox` program. - Firefox, - /// Use the `Google Chrome` program. - Chrome, - /// Use the `Chromium` program. - Chromium, - /// Use the `Safari` program. - Safari, -} - -impl FromStr for Program { - type Err = super::Error; - - fn from_str(s: &str) -> Result { - let p = match s.to_lowercase().as_str() { - "open" => Self::Open, - "start" => Self::Start, - "xdg-open" => Self::XdgOpen, - "gio" => Self::Gio, - "gnome-open" => Self::GnomeOpen, - "kde-open" => Self::KdeOpen, - "wslview" => Self::WslView, - "firefox" => Self::Firefox, - "chrome" | "google chrome" => Self::Chrome, - "chromium" => Self::Chromium, - "safari" => Self::Safari, - _ => return Err(super::Error::UnknownProgramName(s.to_string())), - }; - Ok(p) - } -} - -impl Program { - pub(crate) fn name(self) -> &'static str { - match self { - Self::Open => "open", - Self::Start => "start", - Self::XdgOpen => "xdg-open", - Self::Gio => "gio", - Self::GnomeOpen => "gnome-open", - Self::KdeOpen => "kde-open", - Self::WslView => "wslview", - - #[cfg(target_os = "macos")] - Self::Firefox => "Firefox", - #[cfg(not(target_os = "macos"))] - Self::Firefox => "firefox", - - #[cfg(target_os = "macos")] - Self::Chrome => "Google Chrome", - #[cfg(not(target_os = "macos"))] - Self::Chrome => "google-chrome", - - #[cfg(target_os = "macos")] - Self::Chromium => "Chromium", - #[cfg(not(target_os = "macos"))] - Self::Chromium => "chromium", - - #[cfg(target_os = "macos")] - Self::Safari => "Safari", - #[cfg(not(target_os = "macos"))] - Self::Safari => "safari", - } - } -} - -/// Opens path or URL with program specified in `with`, or system default if `None`. -/// -/// The path will be matched against the shell open validation regex, defaulting to `^https?://`. -/// A custom validation regex may be supplied in the config in `tauri > allowlist > scope > open`. -pub fn open(scope: &ShellScope, path: String, with: Option) -> crate::api::Result<()> { - scope - .open(&path, with) - .map_err(|err| crate::api::Error::Shell(format!("failed to open: {}", err))) -} diff --git a/core/tauri/src/api/version.rs b/core/tauri/src/api/version.rs index e36fc4eb0f19..3a192ae2e77c 100644 --- a/core/tauri/src/api/version.rs +++ b/core/tauri/src/api/version.rs @@ -1,14 +1,32 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -//! Types and functions related to semantics versioning. +//! Compare two semantic versions. +//! +//! [Semantic Versioning](https://semver.org) is a guideline for how version numbers are assigned and incremented. +//! The functions on this module are helpers around [semver](https://docs.rs/semver/latest/semver/). use semver::Version; use std::cmp::Ordering; -/// Compare two semver versions -pub fn compare(first: &str, second: &str) -> crate::api::Result { +/// Compare two semver versions. +/// +/// If the `first` semver is greater, returns -1. +/// If the `second` semver is greater, returns 1. +/// If they are equal, returns 0. +/// +/// # Examples +/// +/// ``` +/// use tauri::api::version::compare; +/// assert_eq!(compare("0.15.0", "0.15.5").unwrap(), 1); +/// assert_eq!(compare("0.15.10", "0.15.9").unwrap(), -1); +/// assert_eq!(compare("0.15.10", "0.16.10").unwrap(), 1); +/// assert_eq!(compare("1.57.0", "2.17.4").unwrap(), 1); +/// assert_eq!(compare("0.0.0", "0.0.0").unwrap(), 0); +/// ``` +pub fn compare(first: &str, second: &str) -> crate::api::Result { let v1 = Version::parse(first)?; let v2 = Version::parse(second)?; match v1.cmp(&v2) { @@ -18,7 +36,19 @@ pub fn compare(first: &str, second: &str) -> crate::api::Result { } } -/// Check if the "second" semver is compatible with the "first" +/// Check if the "second" semver is compatible with the "first". +/// +/// # Examples +/// +/// ``` +/// use tauri::api::version::is_compatible; +/// assert!(is_compatible("0.15.0", "0.15.5").unwrap()); +/// assert!(!is_compatible("0.15.0", "0.16.5").unwrap()); +/// +/// assert!(is_compatible("1.5.0", "1.5.10").unwrap()); +/// assert!(is_compatible("1.54.0", "1.55.0").unwrap()); +/// assert!(!is_compatible("2.17.0", "3.17.0").unwrap()); +/// ``` pub fn is_compatible(first: &str, second: &str) -> crate::api::Result { let first = Version::parse(first)?; let second = Version::parse(second)?; @@ -33,28 +63,69 @@ pub fn is_compatible(first: &str, second: &str) -> crate::api::Result { }) } -/// Check if a the "other" version is a major bump from the "current" +/// Check if a the "other" version is a major bump from the "current". +/// +/// # Examples +/// +/// ``` +/// use tauri::api::version::is_major; +/// assert!(is_major("1.0.0", "2.0.0").unwrap()); +/// assert!(is_major("1.5.0", "2.17.10").unwrap()); +/// assert!(is_major("0.5.0", "2.17.10").unwrap()); +/// assert!(!is_major("1.1.5", "1.2.5").unwrap()); +/// assert!(!is_major("0.14.0", "0.15.0").unwrap()); +/// ``` pub fn is_major(current: &str, other: &str) -> crate::api::Result { let current = Version::parse(current)?; let other = Version::parse(other)?; Ok(other.major > current.major) } -/// Check if a the "other" version is a minor bump from the "current" +/// Check if a the "other" version is a minor bump from the "current". +/// +/// # Examples +/// +/// ``` +/// use tauri::api::version::is_minor; +/// assert!(is_minor("0.15.10", "0.16.110").unwrap()); +/// assert!(is_minor("1.0.0", "1.1.1").unwrap()); +/// assert!(!is_minor("2.1.9", "3.2.0").unwrap()); +/// assert!(!is_minor("1.0.0", "1.0.10").unwrap()); +/// ``` pub fn is_minor(current: &str, other: &str) -> crate::api::Result { let current = Version::parse(current)?; let other = Version::parse(other)?; Ok(current.major == other.major && other.minor > current.minor) } -/// Check if a the "other" version is a patch bump from the "current" +/// Check if a the "other" version is a patch bump from the "current". +/// +/// # Examples +/// +/// ``` +/// use tauri::api::version::is_patch; +/// assert!(is_patch("0.15.0", "0.15.1").unwrap()); +/// assert!(is_patch("1.0.0", "1.0.1").unwrap()); +/// assert!(!is_patch("2.2.0", "2.3.1").unwrap()); +/// assert!(!is_patch("2.2.1", "1.1.0").unwrap()); +/// ``` pub fn is_patch(current: &str, other: &str) -> crate::api::Result { let current = Version::parse(current)?; let other = Version::parse(other)?; Ok(current.major == other.major && current.minor == other.minor && other.patch > current.patch) } -/// Check if a version is greater than the current +/// Check if a version is greater than the current. +/// +/// # Examples +/// +/// ``` +/// use tauri::api::version::is_greater; +/// assert!(is_greater("0.15.10", "0.16.0").unwrap()); +/// assert!(is_greater("1.0.0", "1.0.1").unwrap()); +/// assert!(is_greater("1.1.9", "1.2.0").unwrap()); +/// assert!(is_greater("1.0.0", "2.0.0").unwrap()); +/// ``` pub fn is_greater(current: &str, other: &str) -> crate::api::Result { Ok(Version::parse(other)? > Version::parse(current)?) } diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index a3a664eeee86..7128c8c0ca6d 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1,8 +1,8 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#[cfg(feature = "system-tray")] +#[cfg(all(desktop, feature = "system-tray"))] pub(crate) mod tray; use crate::{ @@ -15,44 +15,45 @@ use crate::{ plugin::{Plugin, PluginStore}, runtime::{ http::{Request as HttpRequest, Response as HttpResponse}, - webview::{WebviewAttributes, WindowBuilder}, - window::{PendingWindow, WindowEvent}, - Dispatch, ExitRequestedEventAction, RunEvent as RuntimeRunEvent, Runtime, + webview::WebviewAttributes, + window::{PendingWindow, WindowEvent as RuntimeWindowEvent}, + ExitRequestedEventAction, RunEvent as RuntimeRunEvent, }, - scope::FsScope, + scope::IpcScope, sealed::{ManagerBase, RuntimeOrDispatch}, - utils::config::{Config, WindowUrl}, + utils::config::Config, utils::{assets::Assets, Env}, - Context, Invoke, InvokeError, InvokeResponse, Manager, Scopes, StateManager, Window, + Context, DeviceEventFilter, EventLoopMessage, Icon, Invoke, InvokeError, InvokeResponse, Manager, + Runtime, Scopes, StateManager, Theme, Window, }; -#[cfg(shell_scope)] -use crate::scope::ShellScope; +#[cfg(feature = "protocol-asset")] +use crate::scope::FsScope; +use raw_window_handle::HasRawDisplayHandle; use tauri_macros::default_runtime; +use tauri_runtime::window::{ + dpi::{PhysicalPosition, PhysicalSize}, + FileDropEvent, +}; use tauri_utils::PackageInfo; use std::{ collections::HashMap, - path::PathBuf, + fmt, sync::{mpsc::Sender, Arc, Weak}, }; use crate::runtime::menu::{Menu, MenuId, MenuIdRef}; use crate::runtime::RuntimeHandle; -#[cfg(feature = "system-tray")] -use crate::runtime::{Icon, SystemTrayEvent as RuntimeSystemTrayEvent}; - -#[cfg(feature = "updater")] -use crate::updater; #[cfg(target_os = "macos")] use crate::ActivationPolicy; pub(crate) type GlobalMenuEventListener = Box) + Send + Sync>; pub(crate) type GlobalWindowEventListener = Box) + Send + Sync>; -#[cfg(feature = "system-tray")] +#[cfg(all(desktop, feature = "system-tray"))] type SystemTrayEventListener = Box, tray::SystemTrayEvent) + Send + Sync>; /// Api exposed on the `ExitRequested` event. @@ -67,7 +68,7 @@ impl ExitRequestApi { } /// Api exposed on the `CloseRequested` event. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CloseRequestApi(Sender); impl CloseRequestApi { @@ -77,7 +78,78 @@ impl CloseRequestApi { } } +/// An event from a window. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum WindowEvent { + /// The size of the window has changed. Contains the client area's new dimensions. + Resized(PhysicalSize), + /// The position of the window has changed. Contains the window's new position. + Moved(PhysicalPosition), + /// The window has been requested to close. + #[non_exhaustive] + CloseRequested { + /// An API modify the behavior of the close requested event. + api: CloseRequestApi, + }, + /// The window has been destroyed. + Destroyed, + /// The window gained or lost focus. + /// + /// The parameter is true if the window has gained focus, and false if it has lost focus. + Focused(bool), + /// The window's scale factor has changed. + /// + /// The following user actions can cause DPI changes: + /// + /// - Changing the display's resolution. + /// - Changing the display's scale factor (e.g. in Control Panel on Windows). + /// - Moving the window to a display with a different scale factor. + #[non_exhaustive] + ScaleFactorChanged { + /// The new scale factor. + scale_factor: f64, + /// The window inner size. + new_inner_size: PhysicalSize, + }, + /// An event associated with the file drop action. + FileDrop(FileDropEvent), + /// The system window theme has changed. Only delivered if the window [`theme`](`crate::window::WindowBuilder#method.theme`) is `None`. + /// + /// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme. + /// + /// ## Platform-specific + /// + /// - **Linux**: Not supported. + ThemeChanged(Theme), +} + +impl From for WindowEvent { + fn from(event: RuntimeWindowEvent) -> Self { + match event { + RuntimeWindowEvent::Resized(size) => Self::Resized(size), + RuntimeWindowEvent::Moved(position) => Self::Moved(position), + RuntimeWindowEvent::CloseRequested { signal_tx } => Self::CloseRequested { + api: CloseRequestApi(signal_tx), + }, + RuntimeWindowEvent::Destroyed => Self::Destroyed, + RuntimeWindowEvent::Focused(flag) => Self::Focused(flag), + RuntimeWindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + } => Self::ScaleFactorChanged { + scale_factor, + new_inner_size, + }, + RuntimeWindowEvent::FileDrop(event) => Self::FileDrop(event), + RuntimeWindowEvent::ThemeChanged(theme) => Self::ThemeChanged(theme), + } + } +} + /// An application event, triggered from the event loop. +/// +/// See [`App::run`](crate::App#method.run) for usage examples. #[derive(Debug)] #[non_exhaustive] pub enum RunEvent { @@ -86,22 +158,17 @@ pub enum RunEvent { /// The app is about to exit #[non_exhaustive] ExitRequested { - /// The label of the window that requested the exit. - /// It is the last window managed by tauri. - window_label: String, /// Event API api: ExitRequestApi, }, - /// Window close was requested by the user. + /// An event associated with a window. #[non_exhaustive] - CloseRequested { + WindowEvent { /// The window label. label: String, - /// Event API. - api: CloseRequestApi, + /// The detailed event. + event: WindowEvent, }, - /// Window closed. - WindowClosed(String), /// Application ready. Ready, /// Sent if the event loop is being resumed. @@ -112,6 +179,12 @@ pub enum RunEvent { MainEventsCleared, } +impl From for RunEvent { + fn from(event: EventLoopMessage) -> Self { + match event {} + } +} + /// A menu event that was triggered on a window. #[default_runtime(crate::Wry, wry)] #[derive(Debug)] @@ -152,31 +225,6 @@ impl GlobalWindowEvent { } } -/// The path resolver is a helper for the application-specific [`crate::api::path`] APIs. -#[derive(Debug, Clone)] -pub struct PathResolver { - env: Env, - config: Arc, - package_info: PackageInfo, -} - -impl PathResolver { - /// Returns the path to the resource directory of this app. - pub fn resource_dir(&self) -> Option { - crate::api::path::resource_dir(&self.package_info, &self.env) - } - - /// Returns the path to the suggested directory for your app config files. - pub fn app_dir(&self) -> Option { - crate::api::path::app_dir(&self.config) - } - - /// Returns the path to the suggested log directory. - pub fn log_dir(&self) -> Option { - crate::api::path::log_dir(&self.config) - } -} - /// The asset resolver is a helper to access the [`tauri_utils::assets::Assets`] interface. #[derive(Debug, Clone)] pub struct AssetResolver { @@ -196,14 +244,11 @@ impl AssetResolver { #[default_runtime(crate::Wry, wry)] #[derive(Debug)] pub struct AppHandle { - runtime_handle: R::Handle, - manager: WindowManager, - global_shortcut_manager: R::GlobalShortcutManager, - clipboard_manager: R::ClipboardManager, - #[cfg(feature = "system-tray")] - tray_handle: Option>, + pub(crate) runtime_handle: R::Handle, + pub(crate) manager: WindowManager, } +/// APIs specific to the wry runtime. #[cfg(feature = "wry")] impl AppHandle { /// Create a new tao window using a callback. The event loop must be running at this point. @@ -224,7 +269,10 @@ impl AppHandle { ) -> crate::Result<()> { self .runtime_handle - .send_event(tauri_runtime_wry::Message::Window(window_id, message)) + .send_event(tauri_runtime_wry::Message::Window( + self.runtime_handle.window_id(window_id), + message, + )) .map_err(Into::into) } } @@ -234,10 +282,6 @@ impl Clone for AppHandle { Self { runtime_handle: self.runtime_handle.clone(), manager: self.manager.clone(), - global_shortcut_manager: self.global_shortcut_manager.clone(), - clipboard_manager: self.clipboard_manager.clone(), - #[cfg(feature = "system-tray")] - tray_handle: self.tray_handle.clone(), } } } @@ -258,14 +302,31 @@ impl AppHandle { .map_err(Into::into) } - /// Removes the system tray. - #[cfg(all(windows, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))] - fn remove_system_tray(&self) -> crate::Result<()> { - self.runtime_handle.remove_system_tray().map_err(Into::into) - } - - /// Adds a plugin to the runtime. + /// Adds a Tauri application plugin. + /// This function can be used to register a plugin that is loaded dynamically e.g. after login. + /// For plugins that are created when the app is started, prefer [`Builder::plugin`]. + /// + /// See [`Builder::plugin`] for more information. + /// + /// # Examples + /// + /// ``` + /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime}; + /// + /// fn init_plugin() -> TauriPlugin { + /// PluginBuilder::new("dummy").build() + /// } + /// + /// tauri::Builder::default() + /// .setup(move |app| { + /// let handle = app.handle(); + /// std::thread::spawn(move || { + /// handle.plugin(init_plugin()); + /// }); + /// + /// Ok(()) + /// }); + /// ``` pub fn plugin + 'static>(&self, mut plugin: P) -> crate::Result<()> { plugin .initialize( @@ -289,21 +350,60 @@ impl AppHandle { Ok(()) } - /// Exits the app + /// Removes the plugin with the given name. + /// + /// # Examples + /// + /// ``` + /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin, Plugin}, Runtime}; + /// + /// fn init_plugin() -> TauriPlugin { + /// PluginBuilder::new("dummy").build() + /// } + /// + /// let plugin = init_plugin(); + /// // `.name()` requires the `PLugin` trait import + /// let plugin_name = plugin.name(); + /// tauri::Builder::default() + /// .plugin(plugin) + /// .setup(move |app| { + /// let handle = app.handle(); + /// std::thread::spawn(move || { + /// handle.remove_plugin(plugin_name); + /// }); + /// + /// Ok(()) + /// }); + /// ``` + pub fn remove_plugin(&self, plugin: &'static str) -> bool { + self + .manager() + .inner + .plugins + .lock() + .unwrap() + .unregister(plugin) + } + + /// Exits the app. This is the same as [`std::process::exit`], but it performs cleanup on this application. pub fn exit(&self, exit_code: i32) { self.cleanup_before_exit(); std::process::exit(exit_code); } + /// Restarts the app. This is the same as [`crate::process::restart`], but it performs cleanup on this application. + pub fn restart(&self) { + self.cleanup_before_exit(); + crate::process::restart(&self.env()); + } + /// Runs necessary cleanup tasks before exiting the process fn cleanup_before_exit(&self) { - #[cfg(shell_execute)] - { - crate::api::process::kill_children(); - } #[cfg(all(windows, feature = "system-tray"))] { - let _ = self.remove_system_tray(); + for tray in self.manager().trays().values() { + let _ = tray.destroy(); + } } } } @@ -318,7 +418,7 @@ impl ManagerBase for AppHandle { RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone()) } - fn app_handle(&self) -> AppHandle { + fn managed_app_handle(&self) -> AppHandle { self.clone() } } @@ -327,17 +427,24 @@ impl ManagerBase for AppHandle { /// /// This type implements [`Manager`] which allows for manipulation of global application items. #[default_runtime(crate::Wry, wry)] -#[derive(Debug)] pub struct App { runtime: Option, + pending_windows: Option>>, + setup: Option>, manager: WindowManager, - global_shortcut_manager: R::GlobalShortcutManager, - clipboard_manager: R::ClipboardManager, - #[cfg(feature = "system-tray")] - tray_handle: Option>, handle: AppHandle, } +impl fmt::Debug for App { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("App") + .field("runtime", &self.runtime) + .field("manager", &self.manager) + .field("handle", &self.handle) + .finish() + } +} + impl Manager for App {} impl ManagerBase for App { fn manager(&self) -> &WindowManager { @@ -345,73 +452,105 @@ impl ManagerBase for App { } fn runtime(&self) -> RuntimeOrDispatch<'_, R> { - RuntimeOrDispatch::Runtime(self.runtime.as_ref().unwrap()) + if let Some(runtime) = self.runtime.as_ref() { + RuntimeOrDispatch::Runtime(runtime) + } else { + self.handle.runtime() + } } - fn app_handle(&self) -> AppHandle { + fn managed_app_handle(&self) -> AppHandle { self.handle() } } +/// APIs specific to the wry runtime. +#[cfg(feature = "wry")] +impl App { + /// Adds a [`tauri_runtime_wry::Plugin`] using its [`tauri_runtime_wry::PluginBuilder`]. + /// + /// # Stability + /// + /// This API is unstable. + pub fn wry_plugin + Send + 'static>( + &mut self, + plugin: P, + ) where +

>::Plugin: Send, + { + self.handle.runtime_handle.plugin(plugin); + } +} + macro_rules! shared_app_impl { ($app: ty) => { impl $app { - /// Creates a new webview window. + /// Gets a handle to the first system tray. /// - /// Data URLs are only supported with the `window-data-url` feature flag. - pub fn create_window( - &self, - label: impl Into, - url: WindowUrl, - setup: F, - ) -> crate::Result> - where - F: FnOnce( - ::WindowBuilder, - WebviewAttributes, - ) -> ( - ::WindowBuilder, - WebviewAttributes, - ), - { - let (window_builder, webview_attributes) = setup( - ::WindowBuilder::new(), - WebviewAttributes::new(url), - ); - self.create_new_window(PendingWindow::new( - window_builder, - webview_attributes, - label, - )) - } - - #[cfg(feature = "system-tray")] + /// Prefer [`Self::tray_handle_by_id`] when multiple system trays are created. + /// + /// # Examples + /// ``` + /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let app_handle = app.handle(); + /// SystemTray::new() + /// .with_menu( + /// SystemTrayMenu::new() + /// .add_item(CustomMenuItem::new("quit", "Quit")) + /// .add_item(CustomMenuItem::new("open", "Open")) + /// ) + /// .on_event(move |event| { + /// let tray_handle = app_handle.tray_handle(); + /// }) + /// .build(app)?; + /// Ok(()) + /// }); + /// ``` + #[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - /// Gets a handle handle to the system tray. pub fn tray_handle(&self) -> tray::SystemTrayHandle { self - .tray_handle - .clone() - .expect("tray not configured; use the `Builder#system_tray` API first.") - } - - /// The path resolver for the application. - pub fn path_resolver(&self) -> PathResolver { - PathResolver { - env: self.state::().inner().clone(), - config: self.manager.config(), - package_info: self.manager.package_info().clone(), - } + .manager() + .trays() + .values() + .next() + .cloned() + .expect("tray not configured; use the `Builder#system_tray`, `App#system_tray` or `AppHandle#system_tray` APIs first.") } - /// Gets a copy of the global shortcut manager instance. - pub fn global_shortcut_manager(&self) -> R::GlobalShortcutManager { - self.global_shortcut_manager.clone() - } - /// Gets a copy of the clipboard manager instance. - pub fn clipboard_manager(&self) -> R::ClipboardManager { - self.clipboard_manager.clone() + /// Gets a handle to a system tray by its id. + /// + /// ``` + /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let app_handle = app.handle(); + /// let tray_id = "my-tray"; + /// SystemTray::new() + /// .with_id(tray_id) + /// .with_menu( + /// SystemTrayMenu::new() + /// .add_item(CustomMenuItem::new("quit", "Quit")) + /// .add_item(CustomMenuItem::new("open", "Open")) + /// ) + /// .on_event(move |event| { + /// let tray_handle = app_handle.tray_handle_by_id(tray_id).unwrap(); + /// }) + /// .build(app)?; + /// Ok(()) + /// }); + /// ``` + #[cfg(all(desktop, feature = "system-tray"))] + #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] + pub fn tray_handle_by_id(&self, id: &str) -> Option> { + self + .manager() + .get_tray(id) } /// Gets the app's configuration, defined on the `tauri.conf.json` file. @@ -430,6 +569,33 @@ macro_rules! shared_app_impl { manager: self.manager.clone(), } } + + /// Returns the default window icon. + pub fn default_window_icon(&self) -> Option<&Icon> { + self.manager.inner.default_window_icon.as_ref() + } + + /// Shows the application, but does not automatically focus it. + #[cfg(target_os = "macos")] + pub fn show(&self) -> crate::Result<()> { + match self.runtime() { + RuntimeOrDispatch::Runtime(r) => r.show(), + RuntimeOrDispatch::RuntimeHandle(h) => h.show()?, + _ => unreachable!(), + } + Ok(()) + } + + /// Hides the application. + #[cfg(target_os = "macos")] + pub fn hide(&self) -> crate::Result<()> { + match self.runtime() { + RuntimeOrDispatch::Runtime(r) => r.hide(), + RuntimeOrDispatch::RuntimeHandle(h) => h.hide()?, + _ => unreachable!(), + } + Ok(()) + } } }; } @@ -438,6 +604,12 @@ shared_app_impl!(App); shared_app_impl!(AppHandle); impl App { + fn register_core_plugins(&self) -> crate::Result<()> { + self.handle.plugin(crate::path::init())?; + self.handle.plugin(crate::event::init())?; + Ok(()) + } + /// Gets a handle to the application instance. pub fn handle(&self) -> AppHandle { self.handle.clone() @@ -445,8 +617,8 @@ impl App { /// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default. /// - /// # Example - /// ```rust,no_run + /// # Examples + /// ```,no_run /// let mut app = tauri::Builder::default() /// // on an actual app, remove the string argument /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) @@ -465,10 +637,39 @@ impl App { .set_activation_policy(activation_policy); } + /// Change the device event filter mode. + /// + /// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`] + /// will ignore them by default for unfocused windows on Windows. This method allows changing + /// the filter to explicitly capture them again. + /// + /// ## Platform-specific + /// + /// - ** Linux / macOS / iOS / Android**: Unsupported. + /// + /// # Examples + /// ```,no_run + /// let mut app = tauri::Builder::default() + /// // on an actual app, remove the string argument + /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .expect("error while building tauri application"); + /// app.set_device_event_filter(tauri::DeviceEventFilter::Always); + /// app.run(|_app_handle, _event| {}); + /// ``` + /// + /// [`tao`]: https://crates.io/crates/tao + pub fn set_device_event_filter(&mut self, filter: DeviceEventFilter) { + self + .runtime + .as_mut() + .unwrap() + .set_device_event_filter(filter); + } + /// Runs the application. /// - /// # Example - /// ```rust,no_run + /// # Examples + /// ```,no_run /// let app = tauri::Builder::default() /// // on an actual app, remove the string argument /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) @@ -484,14 +685,25 @@ impl App { let app_handle = self.handle(); let manager = self.manager.clone(); self.runtime.take().unwrap().run(move |event| match event { + RuntimeRunEvent::Ready => { + if let Err(e) = setup(&mut self) { + panic!("Failed to setup app: {e}"); + } + on_event_loop_event( + &app_handle, + RuntimeRunEvent::Ready, + &manager, + Some(&mut callback), + ); + } RuntimeRunEvent::Exit => { - app_handle.cleanup_before_exit(); on_event_loop_event( &app_handle, RuntimeRunEvent::Exit, &manager, Some(&mut callback), ); + app_handle.cleanup_before_exit(); } _ => { on_event_loop_event(&app_handle, event, &manager, Some(&mut callback)); @@ -502,11 +714,11 @@ impl App { /// Runs a iteration of the runtime event loop and immediately return. /// /// Note that when using this API, app cleanup is not automatically done. - /// The cleanup calls [`crate::api::process::kill_children`] so you may want to call that function before exiting the application. + /// The cleanup calls [`crate::process::kill_children`] so you may want to call that function before exiting the application. /// Additionally, the cleanup calls [AppHandle#remove_system_tray](`AppHandle#method.remove_system_tray`) (Windows only). /// - /// # Example - /// ```rust,no_run + /// # Examples + /// ```no_run /// let mut app = tauri::Builder::default() /// // on an actual app, remove the string argument /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) @@ -518,6 +730,7 @@ impl App { /// } /// } /// ``` + #[cfg(desktop)] pub fn run_iteration(&mut self) -> crate::runtime::RunIteration { let manager = self.manager.clone(); let app_handle = self.handle(); @@ -532,65 +745,10 @@ impl App { } } -#[cfg(feature = "updater")] -impl App { - /// Runs the updater hook with built-in dialog. - fn run_updater_dialog(&self, window: Window) { - let updater_config = self.manager.config().tauri.updater.clone(); - let package_info = self.manager.package_info().clone(); - - crate::async_runtime::spawn(async move { - updater::check_update_with_dialog(updater_config, package_info, window).await - }); - } - - /// Listen updater events when dialog are disabled. - fn listen_updater_events(&self, window: Window) { - let updater_config = self.manager.config().tauri.updater.clone(); - updater::listener(updater_config, self.manager.package_info().clone(), &window); - } - - fn run_updater(&self, main_window: Option>) { - if let Some(main_window) = main_window { - let event_window = main_window.clone(); - let updater_config = self.manager.config().tauri.updater.clone(); - // check if updater is active or not - if updater_config.dialog && updater_config.active { - // if updater dialog is enabled spawn a new task - self.run_updater_dialog(main_window.clone()); - let config = self.manager.config().tauri.updater.clone(); - let package_info = self.manager.package_info().clone(); - // When dialog is enabled, if user want to recheck - // if an update is available after first start - // invoke the Event `tauri://update` from JS or rust side. - main_window.listen(updater::EVENT_CHECK_UPDATE, move |_msg| { - let window = event_window.clone(); - let package_info = package_info.clone(); - let config = config.clone(); - // re-spawn task inside tokyo to launch the download - // we don't need to emit anything as everything is handled - // by the process (user is asked to restart at the end) - // and it's handled by the updater - crate::async_runtime::spawn(async move { - updater::check_update_with_dialog(config, package_info, window).await - }); - }); - } else if updater_config.active { - // we only listen for `tauri://update` - // once we receive the call, we check if an update is available or not - // if there is a new update we emit `tauri://update-available` with details - // this is the user responsabilities to display dialog and ask if user want to install - // to install the update you need to invoke the Event `tauri://update-install` - self.listen_updater_events(main_window); - } - } - } -} - /// Builds a Tauri application. /// -/// # Example -/// ```rust,no_run +/// # Examples +/// ```,no_run /// tauri::Builder::default() /// // on an actual app, remove the string argument /// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) @@ -600,14 +758,13 @@ impl App { pub struct Builder { /// A flag indicating that the runtime must be started on an environment that supports the event loop not on the main thread. #[cfg(any(windows, target_os = "linux"))] - #[cfg_attr(doc_cfg, doc(any(windows, target_os = "linux")))] runtime_any_thread: bool, /// The JS message handler. invoke_handler: Box>, /// The JS message responder. - invoke_responder: Arc>, + pub(crate) invoke_responder: Arc>, /// The script that initializes the `window.__TAURI_POST_MESSAGE__` function. invoke_initialization_script: String, @@ -619,7 +776,7 @@ pub struct Builder { on_page_load: Box>, /// windows to create when starting up. - pending_windows: Vec>, + pending_windows: Vec>, /// All passed plugins plugins: PluginStore, @@ -633,6 +790,10 @@ pub struct Builder { /// The menu set to all windows. menu: Option

, + /// Enable macOS default menu creation. + #[allow(unused)] + enable_macos_default_menu: bool, + /// Menu event handlers that listens to all windows. menu_event_listeners: Vec>, @@ -640,12 +801,15 @@ pub struct Builder { window_event_listeners: Vec>, /// The app system tray. - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] system_tray: Option, /// System tray event handlers. - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] system_tray_event_listeners: Vec>, + + /// The device event filter. + device_event_filter: DeviceEventFilter, } impl Builder { @@ -655,22 +819,24 @@ impl Builder { #[cfg(any(windows, target_os = "linux"))] runtime_any_thread: false, setup: Box::new(|_| Ok(())), - invoke_handler: Box::new(|_| ()), + invoke_handler: Box::new(|_| false), invoke_responder: Arc::new(window_invoke_responder), invoke_initialization_script: - "Object.defineProperty(window, '__TAURI_POST_MESSAGE__', { value: (message) => window.ipc.postMessage(JSON.stringify(message)) })".into(), + format!("Object.defineProperty(window, '__TAURI_POST_MESSAGE__', {{ value: (message) => window.ipc.postMessage({}(message)) }})", crate::manager::STRINGIFY_IPC_MESSAGE_FN), on_page_load: Box::new(|_, _| ()), pending_windows: Default::default(), plugins: PluginStore::default(), uri_scheme_protocols: Default::default(), state: StateManager::new(), menu: None, + enable_macos_default_menu: true, menu_event_listeners: Vec::new(), window_event_listeners: Vec::new(), - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] system_tray: None, - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] system_tray_event_listeners: Vec::new(), + device_event_filter: Default::default(), } } @@ -678,9 +844,10 @@ impl Builder { /// /// ## Platform-specific /// - /// - **macOS**: on macOS the application *must* be executed on the main thread, so this function is not exposed. + /// - **macOS:** on macOS the application *must* be executed on the main thread, so this function is not exposed. #[cfg(any(windows, target_os = "linux"))] - #[cfg_attr(doc_cfg, doc(any(windows, target_os = "linux")))] + #[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))] + #[must_use] pub fn any_thread(mut self) -> Self { self.runtime_any_thread = true; self @@ -688,8 +855,8 @@ impl Builder { /// Defines the JS message handler callback. /// - /// # Example - /// ```rust,no_run + /// # Examples + /// ``` /// #[tauri::command] /// fn command_1() -> String { /// return "hello world".to_string(); @@ -703,7 +870,7 @@ impl Builder { #[must_use] pub fn invoke_handler(mut self, invoke_handler: F) -> Self where - F: Fn(Invoke) + Send + Sync + 'static, + F: Fn(Invoke) -> bool + Send + Sync + 'static, { self.invoke_handler = Box::new(invoke_handler); self @@ -727,23 +894,20 @@ impl Builder { /// Defines the setup hook. /// - /// # Example - /// ```rust,no_run + /// # Examples + /// ``` /// use tauri::Manager; /// tauri::Builder::default() /// .setup(|app| { /// let main_window = app.get_window("main").unwrap(); - #[cfg_attr( - feature = "dialog", - doc = r#" tauri::api::dialog::blocking::message(Some(&main_window), "Hello", "Welcome back!");"# - )] + /// main_window.set_title("Tauri!"); /// Ok(()) /// }); /// ``` #[must_use] pub fn setup(mut self, setup: F) -> Self where - F: FnOnce(&mut App) -> Result<(), Box> + Send + 'static, + F: FnOnce(&mut App) -> Result<(), Box> + Send + 'static, { self.setup = Box::new(setup); self @@ -759,7 +923,47 @@ impl Builder { self } - /// Adds a plugin to the runtime. + /// Adds a Tauri application plugin. + /// + /// A plugin is created using the [`crate::plugin::Builder`] struct.Check its documentation for more information. + /// + /// # Examples + /// + /// ``` + /// mod plugin { + /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, RunEvent, Runtime}; + /// + /// // this command can be called in the frontend using `invoke('plugin:window|do_something')`. + /// #[tauri::command] + /// async fn do_something(app: tauri::AppHandle, window: tauri::Window) -> Result<(), String> { + /// println!("command called"); + /// Ok(()) + /// } + /// pub fn init() -> TauriPlugin { + /// PluginBuilder::new("window") + /// .setup(|app, api| { + /// // initialize the plugin here + /// Ok(()) + /// }) + /// .on_event(|app, event| { + /// match event { + /// RunEvent::Ready => { + /// println!("app is ready"); + /// } + /// RunEvent::WindowEvent { label, event, .. } => { + /// println!("window {} received an event: {:?}", label, event); + /// } + /// _ => (), + /// } + /// }) + /// .invoke_handler(tauri::generate_handler![do_something]) + /// .build() + /// } + /// } + /// + /// tauri::Builder::default() + /// .plugin(plugin::init()); + /// ``` #[must_use] pub fn plugin + 'static>(mut self, plugin: P) -> Self { self.plugins.register(plugin); @@ -771,10 +975,10 @@ impl Builder { /// This method can be called any number of times as long as each call /// refers to a different `T`. /// - /// Managed state can be retrieved by any request handler via the - /// [`State`](crate::State) request guard. In particular, if a value of type `T` + /// Managed state can be retrieved by any command handler via the + /// [`State`](crate::State) guard. In particular, if a value of type `T` /// is managed by Tauri, adding `State` to the list of arguments in a - /// request handler instructs Tauri to retrieve the managed value. + /// command handler instructs Tauri to retrieve the managed value. /// /// # Panics /// @@ -784,38 +988,42 @@ impl Builder { /// /// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability: /// - /// ```rust,no_run + /// ```,no_run /// use std::{collections::HashMap, sync::Mutex}; /// use tauri::State; /// // here we use Mutex to achieve interior mutability - /// struct Storage(Mutex>); + /// struct Storage { + /// store: Mutex>, + /// } /// struct Connection; - /// struct DbConnection(Mutex>); + /// struct DbConnection { + /// db: Mutex>, + /// } /// /// #[tauri::command] /// fn connect(connection: State) { /// // initialize the connection, mutating the state with interior mutability - /// *connection.0.lock().unwrap() = Some(Connection {}); + /// *connection.db.lock().unwrap() = Some(Connection {}); /// } /// /// #[tauri::command] /// fn storage_insert(key: u64, value: String, storage: State) { /// // mutate the storage behind the Mutex - /// storage.0.lock().unwrap().insert(key, value); + /// storage.store.lock().unwrap().insert(key, value); /// } /// /// tauri::Builder::default() - /// .manage(Storage(Default::default())) - /// .manage(DbConnection(Default::default())) + /// .manage(Storage { store: Default::default() }) + /// .manage(DbConnection { db: Default::default() }) /// .invoke_handler(tauri::generate_handler![connect, storage_insert]) /// // on an actual app, remove the string argument /// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) /// .expect("error while running tauri application"); /// ``` /// - /// # Example + /// # Examples /// - /// ```rust,no_run + /// ```,no_run /// use tauri::State; /// /// struct MyInt(isize); @@ -847,52 +1055,27 @@ impl Builder { let type_name = std::any::type_name::(); assert!( self.state.set(state), - "state for type '{}' is already being managed", - type_name + "state for type '{type_name}' is already being managed", ); self } - /// Creates a new webview window. + /// Sets the given system tray to be built before the app runs. + /// + /// Prefer the [`SystemTray#method.build`](crate::SystemTray#method.build) method to create the tray at runtime instead. + /// + /// # Examples + /// ``` + /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; /// - /// # Example - /// ```rust,no_run - /// use tauri::WindowBuilder; /// tauri::Builder::default() - /// .create_window("main", tauri::WindowUrl::default(), |win, webview| { - /// let win = win - /// .title("My Main Window") - /// .resizable(true) - /// .inner_size(800.0, 550.0) - /// .min_inner_size(400.0, 200.0); - /// return (win, webview); - /// }); + /// .system_tray(SystemTray::new().with_menu( + /// SystemTrayMenu::new() + /// .add_item(CustomMenuItem::new("quit", "Quit")) + /// .add_item(CustomMenuItem::new("open", "Open")) + /// )); /// ``` - #[must_use] - pub fn create_window(mut self, label: impl Into, url: WindowUrl, setup: F) -> Self - where - F: FnOnce( - ::WindowBuilder, - WebviewAttributes, - ) -> ( - ::WindowBuilder, - WebviewAttributes, - ), - { - let (window_builder, webview_attributes) = setup( - ::WindowBuilder::new(), - WebviewAttributes::new(url), - ); - self.pending_windows.push(PendingWindow::new( - window_builder, - webview_attributes, - label, - )); - self - } - - /// Adds the icon configured on `tauri.conf.json` to the system tray with the specified menu items. - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] #[must_use] pub fn system_tray(mut self, system_tray: tray::SystemTray) -> Self { @@ -902,8 +1085,8 @@ impl Builder { /// Sets the menu to use on all windows. /// - /// # Example - /// ```rust,no_run + /// # Examples + /// ``` /// use tauri::{MenuEntry, Submenu, MenuItem, Menu, CustomMenuItem}; /// /// tauri::Builder::default() @@ -924,29 +1107,40 @@ impl Builder { self } + /// Enable or disable the default menu on macOS. Enabled by default. + /// + /// # Examples + /// ``` + /// use tauri::{MenuEntry, Submenu, MenuItem, Menu, CustomMenuItem}; + /// + /// tauri::Builder::default() + /// .enable_macos_default_menu(false); + /// ``` + #[must_use] + pub fn enable_macos_default_menu(mut self, enable: bool) -> Self { + self.enable_macos_default_menu = enable; + self + } + /// Registers a menu event handler for all windows. /// - /// # Example - /// ```rust,no_run + /// # Examples + /// ``` /// use tauri::{Menu, MenuEntry, Submenu, CustomMenuItem, api, Manager}; /// tauri::Builder::default() /// .menu(Menu::with_items([ /// MenuEntry::Submenu(Submenu::new( /// "File", /// Menu::with_items([ - /// CustomMenuItem::new("New", "New").into(), - /// CustomMenuItem::new("Learn More", "Learn More").into(), + /// CustomMenuItem::new("new", "New").into(), + /// CustomMenuItem::new("learn-more", "Learn More").into(), /// ]), /// )), /// ])) /// .on_menu_event(|event| { /// match event.menu_item_id() { - /// "Learn More" => { - /// // open in browser (requires the `shell-open-api` feature) - #[cfg_attr( - feature = "shell-open-api", - doc = r#" api::shell::open(&event.window().shell_scope(), "https://github.com/tauri-apps/tauri".to_string(), None).unwrap();"# - )] + /// "learn-more" => { + /// // open a link in the browser using tauri-plugin-shell /// } /// id => { /// // do something with other events @@ -966,8 +1160,8 @@ impl Builder { /// Registers a window event handler for all windows. /// - /// # Example - /// ```rust,no_run + /// # Examples + /// ``` /// tauri::Builder::default() /// .on_window_event(|event| match event.event() { /// tauri::WindowEvent::Focused(focused) => { @@ -990,13 +1184,15 @@ impl Builder { /// Registers a system tray event handler. /// - /// # Example - /// ```rust,no_run - /// use tauri::Manager; + /// Prefer the [`SystemTray#method.on_event`](crate::SystemTray#method.on_event) method when creating a tray at runtime instead. + /// + /// # Examples + /// ``` + /// use tauri::{Manager, SystemTrayEvent}; /// tauri::Builder::default() /// .on_system_tray_event(|app, event| match event { /// // show window with id "main" when the tray is left clicked - /// tauri::SystemTrayEvent::LeftClick { .. } => { + /// SystemTrayEvent::LeftClick { .. } => { /// let window = app.get_window("main").unwrap(); /// window.show().unwrap(); /// window.set_focus().unwrap(); @@ -1004,7 +1200,7 @@ impl Builder { /// _ => {} /// }); /// ``` - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] #[must_use] pub fn on_system_tray_event< @@ -1047,47 +1243,35 @@ impl Builder { self } + /// Change the device event filter mode. + /// + /// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`] + /// will ignore them by default for unfocused windows on Windows. This method allows changing + /// the filter to explicitly capture them again. + /// + /// ## Platform-specific + /// + /// - ** Linux / macOS / iOS / Android**: Unsupported. + /// + /// # Examples + /// ```,no_run + /// tauri::Builder::default() + /// .device_event_filter(tauri::DeviceEventFilter::Always); + /// ``` + /// + /// [`tao`]: https://crates.io/crates/tao + pub fn device_event_filter(mut self, filter: DeviceEventFilter) -> Self { + self.device_event_filter = filter; + self + } + /// Builds the application. #[allow(clippy::type_complexity)] pub fn build(mut self, context: Context) -> crate::Result> { - #[cfg(feature = "system-tray")] - let system_tray_icon = { - let icon = context.system_tray_icon.clone(); - - // check the icon format if the system tray is configured - if self.system_tray.is_some() { - use std::io::{Error, ErrorKind}; - #[cfg(target_os = "linux")] - if let Some(Icon::Raw(_)) = icon { - return Err(crate::Error::InvalidIcon(Box::new(Error::new( - ErrorKind::InvalidInput, - "system tray icons on linux must be a file path", - )))); - } - - #[cfg(not(target_os = "linux"))] - if let Some(Icon::File(_)) = icon { - return Err(crate::Error::InvalidIcon(Box::new(Error::new( - ErrorKind::InvalidInput, - "system tray icons on non-linux platforms must be the raw bytes", - )))); - } - } - - icon - }; - - #[cfg(all(feature = "system-tray", target_os = "macos"))] - let system_tray_icon_as_template = context - .config - .tauri - .system_tray - .as_ref() - .map(|t| t.icon_as_template) - .unwrap_or_default(); - - #[cfg(shell_scope)] - let shell_scope = context.shell_scope.clone(); + #[cfg(target_os = "macos")] + if self.menu.is_none() && self.enable_macos_default_menu { + self.menu = Some(Menu::os_default(&context.package_info().name)); + } let manager = WindowManager::with_handlers( context, @@ -1103,86 +1287,66 @@ impl Builder { // set up all the windows defined in the config for config in manager.config().tauri.windows.clone() { - let url = config.url.clone(); let label = config.label.clone(); - let file_drop_enabled = config.file_drop_enabled; - - let mut webview_attributes = WebviewAttributes::new(url); - if !file_drop_enabled { - webview_attributes = webview_attributes.disable_file_drop_handler(); - } - + let webview_attributes = WebviewAttributes::from(&config); self.pending_windows.push(PendingWindow::with_config( config, webview_attributes, label, - )); + )?); } #[cfg(any(windows, target_os = "linux"))] - let runtime = if self.runtime_any_thread { + let mut runtime = if self.runtime_any_thread { R::new_any_thread()? } else { R::new()? }; #[cfg(not(any(windows, target_os = "linux")))] - let runtime = R::new()?; + let mut runtime = R::new()?; + + runtime.set_device_event_filter(self.device_event_filter); let runtime_handle = runtime.handle(); - let global_shortcut_manager = runtime.global_shortcut_manager(); - let clipboard_manager = runtime.clipboard_manager(); + #[allow(unused_mut)] let mut app = App { runtime: Some(runtime), + pending_windows: Some(self.pending_windows), + setup: Some(self.setup), manager: manager.clone(), - global_shortcut_manager: global_shortcut_manager.clone(), - clipboard_manager: clipboard_manager.clone(), - #[cfg(feature = "system-tray")] - tray_handle: None, handle: AppHandle { runtime_handle, manager, - global_shortcut_manager, - clipboard_manager, - #[cfg(feature = "system-tray")] - tray_handle: None, }, }; + app.register_core_plugins()?; + let env = Env::default(); + app.manage(env); + app.manage(Scopes { - fs: FsScope::for_fs_api( - &app.manager.config(), - app.package_info(), - &env, - &app.config().tauri.allowlist.fs.scope, - ), - #[cfg(protocol_asset)] - asset_protocol: FsScope::for_fs_api( - &app.manager.config(), - app.package_info(), - &env, - &app.config().tauri.allowlist.protocol.asset_scope, - ), - #[cfg(http_request)] - http: crate::scope::HttpScope::for_http_api(&app.config().tauri.allowlist.http.scope), - #[cfg(shell_scope)] - shell: ShellScope::new(shell_scope), + ipc: IpcScope::new(&app.config()), + #[cfg(feature = "protocol-asset")] + asset_protocol: FsScope::for_fs_api(&app, &app.config().tauri.security.asset_protocol.scope)?, }); - app.manage(env); #[cfg(windows)] { - if let Some(w) = &app + if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } = &app .manager .config() .tauri .bundle .windows - .webview_fixed_runtime_path + .webview_install_mode { - if let Some(resource_dir) = app.path_resolver().resource_dir() { - std::env::set_var("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", resource_dir.join(w)); + if let Ok(resource_dir) = app.path().resource_dir() { + std::env::set_var( + "WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", + resource_dir.join(path), + ); } else { #[cfg(debug_assertions)] eprintln!( @@ -1192,156 +1356,137 @@ impl Builder { } } - #[cfg(feature = "system-tray")] - if let Some(system_tray) = self.system_tray { - let mut ids = HashMap::new(); - if let Some(menu) = system_tray.menu() { - tray::get_menu_ids(&mut ids, menu); - } - let mut tray = tray::SystemTray::new(); - if let Some(menu) = system_tray.menu { - tray = tray.with_menu(menu); + #[cfg(all(desktop, feature = "system-tray"))] + { + if let Some(tray) = self.system_tray { + tray.build(&app)?; } - #[cfg(not(target_os = "macos"))] - let tray_handler = app - .runtime - .as_ref() - .unwrap() - .system_tray( - tray.with_icon( - system_tray - .icon - .or(system_tray_icon) - .expect("tray icon not found; please configure it on tauri.conf.json"), - ), - ) - .expect("failed to run tray"); - - #[cfg(target_os = "macos")] - let tray_handler = app - .runtime - .as_ref() - .unwrap() - .system_tray( - tray - .with_icon( - system_tray - .icon - .or(system_tray_icon) - .expect("tray icon not found; please configure it on tauri.conf.json"), - ) - .with_icon_as_template(system_tray_icon_as_template), - ) - .expect("failed to run tray"); - - let tray_handle = tray::SystemTrayHandle { - ids: Arc::new(ids.clone()), - inner: tray_handler, - }; - app.tray_handle.replace(tray_handle.clone()); - app.handle.tray_handle.replace(tray_handle); for listener in self.system_tray_event_listeners { let app_handle = app.handle(); - let ids = ids.clone(); let listener = Arc::new(std::sync::Mutex::new(listener)); app .runtime .as_mut() .unwrap() - .on_system_tray_event(move |event| { - let app_handle = app_handle.clone(); - let event = match event { - RuntimeSystemTrayEvent::MenuItemClick(id) => tray::SystemTrayEvent::MenuItemClick { - id: ids.get(id).unwrap().clone(), - }, - RuntimeSystemTrayEvent::LeftClick { position, size } => { - tray::SystemTrayEvent::LeftClick { - position: *position, - size: *size, - } - } - RuntimeSystemTrayEvent::RightClick { position, size } => { - tray::SystemTrayEvent::RightClick { - position: *position, - size: *size, - } - } - RuntimeSystemTrayEvent::DoubleClick { position, size } => { - tray::SystemTrayEvent::DoubleClick { - position: *position, - size: *size, - } - } - }; - let listener = listener.clone(); - listener.lock().unwrap()(&app_handle, event); + .on_system_tray_event(move |tray_id, event| { + if let Some((tray_id, tray)) = app_handle.manager().get_tray_by_runtime_id(tray_id) { + let app_handle = app_handle.clone(); + let event = tray::SystemTrayEvent::from_runtime_event(event, tray_id, &tray.ids); + let listener = listener.clone(); + listener.lock().unwrap()(&app_handle, event); + } }); } } app.manager.initialize_plugins(&app.handle())?; - let window_labels = self - .pending_windows + Ok(app) + } + + /// Runs the configured Tauri application. + pub fn run(self, context: Context) -> crate::Result<()> { + self.build(context)?.run(|_, _| {}); + Ok(()) + } +} + +unsafe impl HasRawDisplayHandle for AppHandle { + fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { + self.runtime_handle.raw_display_handle() + } +} + +unsafe impl HasRawDisplayHandle for App { + fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { + self.handle.raw_display_handle() + } +} + +fn setup(app: &mut App) -> crate::Result<()> { + let pending_windows = app.pending_windows.take(); + if let Some(pending_windows) = pending_windows { + let window_labels = pending_windows .iter() .map(|p| p.label.clone()) .collect::>(); - #[cfg(feature = "updater")] - let mut main_window = None; - - for pending in self.pending_windows { + for pending in pending_windows { let pending = app .manager .prepare_window(app.handle.clone(), pending, &window_labels)?; - let detached = app.runtime.as_ref().unwrap().create_window(pending)?; - let _window = app.manager.attach_window(app.handle(), detached); - #[cfg(feature = "updater")] - if main_window.is_none() { - main_window = Some(_window); + let window_effects = pending.webview_attributes.window_effects.clone(); + let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app.handle().runtime() { + runtime.create_window(pending)? + } else { + // the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle + unreachable!() + }; + let window = app.manager.attach_window(app.handle(), detached); + + if let Some(effects) = window_effects { + crate::vibrancy::set_window_effects(&window, Some(effects))?; } } - - (self.setup)(&mut app).map_err(|e| crate::Error::Setup(e))?; - - #[cfg(feature = "updater")] - app.run_updater(main_window); - - Ok(app) } - /// Runs the configured Tauri application. - pub fn run(self, context: Context) -> crate::Result<()> { - self.build(context)?.run(|_, _| {}); - Ok(()) + if let Some(setup) = app.setup.take() { + (setup)(app).map_err(|e| crate::Error::Setup(e.into()))?; } + + Ok(()) } fn on_event_loop_event, RunEvent) + 'static>( app_handle: &AppHandle, - event: RuntimeRunEvent, + event: RuntimeRunEvent, manager: &WindowManager, callback: Option<&mut F>, ) { - if let RuntimeRunEvent::WindowClose(label) = &event { + if let RuntimeRunEvent::WindowEvent { + label, + event: RuntimeWindowEvent::Destroyed, + } = &event + { manager.on_window_close(label); } let event = match event { RuntimeRunEvent::Exit => RunEvent::Exit, - RuntimeRunEvent::ExitRequested { window_label, tx } => RunEvent::ExitRequested { - window_label, + RuntimeRunEvent::ExitRequested { tx } => RunEvent::ExitRequested { api: ExitRequestApi(tx), }, - RuntimeRunEvent::CloseRequested { label, signal_tx } => RunEvent::CloseRequested { + RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent { label, - api: CloseRequestApi(signal_tx), + event: event.into(), }, - RuntimeRunEvent::WindowClose(label) => RunEvent::WindowClosed(label), - RuntimeRunEvent::Ready => RunEvent::Ready, + RuntimeRunEvent::Ready => { + // set the app icon in development + #[cfg(all(dev, target_os = "macos"))] + unsafe { + use cocoa::{ + appkit::NSImage, + base::{id, nil}, + foundation::NSData, + }; + use objc::*; + if let Some(icon) = app_handle.manager.inner.app_icon.clone() { + let ns_app: id = msg_send![class!(NSApplication), sharedApplication]; + let data = NSData::dataWithBytes_length_( + nil, + icon.as_ptr() as *const std::os::raw::c_void, + icon.len() as u64, + ); + let app_icon = NSImage::initWithData_(NSImage::alloc(nil), data); + let _: () = msg_send![ns_app, setApplicationIconImage: app_icon]; + } + } + RunEvent::Ready + } RuntimeRunEvent::Resumed => RunEvent::Resumed, RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared, + RuntimeRunEvent::UserEvent(t) => t.into(), _ => unimplemented!(), }; @@ -1378,8 +1523,5 @@ mod tests { crate::test_utils::assert_send::>(); crate::test_utils::assert_sync::>(); } - - crate::test_utils::assert_send::(); - crate::test_utils::assert_sync::(); } } diff --git a/core/tauri/src/app/tray.rs b/core/tauri/src/app/tray.rs index 7239b33cc77b..b88b932c7e37 100644 --- a/core/tauri/src/app/tray.rs +++ b/core/tauri/src/app/tray.rs @@ -1,18 +1,32 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -pub use crate::runtime::{ - menu::{ - MenuHash, MenuId, MenuIdRef, MenuUpdate, SystemTrayMenu, SystemTrayMenuEntry, TrayHandle, +pub use crate::{ + runtime::{ + menu::{ + MenuHash, MenuId, MenuIdRef, MenuUpdate, SystemTrayMenu, SystemTrayMenuEntry, TrayHandle, + }, + window::dpi::{PhysicalPosition, PhysicalSize}, + RuntimeHandle, SystemTrayEvent as RuntimeSystemTrayEvent, }, - window::dpi::{PhysicalPosition, PhysicalSize}, - Icon, Runtime, SystemTray, + Icon, Runtime, }; +use crate::{sealed::RuntimeOrDispatch, Manager}; +use rand::distributions::{Alphanumeric, DistString}; use tauri_macros::default_runtime; +use tauri_runtime::TrayId; +use tauri_utils::debug_eprintln; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{hash_map::DefaultHasher, HashMap}, + fmt, + hash::{Hash, Hasher}, + sync::{Arc, Mutex}, +}; + +type TrayEventHandler = dyn Fn(SystemTrayEvent) + Send + Sync + 'static; pub(crate) fn get_menu_ids(map: &mut HashMap, menu: &SystemTrayMenu) { for item in &menu.items { @@ -26,6 +40,436 @@ pub(crate) fn get_menu_ids(map: &mut HashMap, menu: &SystemTra } } +/// Represents a System Tray instance. +#[derive(Clone)] +#[non_exhaustive] +pub struct SystemTray { + /// The tray identifier. Defaults to a random string. + pub id: String, + /// The tray icon. + pub icon: Option, + /// The tray menu. + pub menu: Option, + /// Whether the icon is a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) icon or not. + #[cfg(target_os = "macos")] + pub icon_as_template: bool, + /// Whether the menu should appear when the tray receives a left click. Defaults to `true` + #[cfg(target_os = "macos")] + pub menu_on_left_click: bool, + on_event: Option>, + // TODO: icon_as_template and menu_on_left_click should be an Option instead :( + #[cfg(target_os = "macos")] + menu_on_left_click_set: bool, + #[cfg(target_os = "macos")] + icon_as_template_set: bool, + #[cfg(target_os = "macos")] + title: Option, + tooltip: Option, +} + +impl fmt::Debug for SystemTray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("SystemTray"); + d.field("id", &self.id) + .field("icon", &self.icon) + .field("menu", &self.menu); + #[cfg(target_os = "macos")] + { + d.field("icon_as_template", &self.icon_as_template) + .field("menu_on_left_click", &self.menu_on_left_click); + } + d.finish() + } +} + +impl Default for SystemTray { + fn default() -> Self { + Self { + id: Alphanumeric.sample_string(&mut rand::thread_rng(), 16), + icon: None, + menu: None, + on_event: None, + #[cfg(target_os = "macos")] + icon_as_template: false, + #[cfg(target_os = "macos")] + menu_on_left_click: false, + #[cfg(target_os = "macos")] + icon_as_template_set: false, + #[cfg(target_os = "macos")] + menu_on_left_click_set: false, + #[cfg(target_os = "macos")] + title: None, + tooltip: None, + } + } +} + +impl SystemTray { + /// Creates a new system tray that only renders an icon. + /// + /// # Examples + /// + /// ``` + /// use tauri::SystemTray; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let tray_handle = SystemTray::new().build(app)?; + /// Ok(()) + /// }); + /// ``` + pub fn new() -> Self { + Default::default() + } + + pub(crate) fn menu(&self) -> Option<&SystemTrayMenu> { + self.menu.as_ref() + } + + /// Sets the tray identifier, used to retrieve its handle and to identify a tray event source. + /// + /// # Examples + /// + /// ``` + /// use tauri::SystemTray; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let tray_handle = SystemTray::new() + /// .with_id("tray-id") + /// .build(app)?; + /// Ok(()) + /// }); + /// ``` + #[must_use] + pub fn with_id>(mut self, id: I) -> Self { + self.id = id.into(); + self + } + + /// Sets the tray [`Icon`]. + /// + /// # Examples + /// + /// ``` + /// use tauri::{Icon, SystemTray}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let tray_handle = SystemTray::new() + /// // dummy and invalid Rgba icon; see the Icon documentation for more information + /// .with_icon(Icon::Rgba { rgba: Vec::new(), width: 0, height: 0 }) + /// .build(app)?; + /// Ok(()) + /// }); + /// ``` + #[must_use] + pub fn with_icon>(mut self, icon: I) -> Self + where + I::Error: std::error::Error, + { + match icon.try_into() { + Ok(icon) => { + self.icon.replace(icon); + } + Err(e) => { + debug_eprintln!("Failed to load tray icon: {}", e); + } + } + self + } + + /// Sets the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). + /// + /// Images you mark as template images should consist of only black and clear colors. + /// You can use the alpha channel in the image to adjust the opacity of black content. + /// + /// # Examples + /// + /// ``` + /// use tauri::SystemTray; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let mut tray_builder = SystemTray::new(); + /// #[cfg(target_os = "macos")] + /// { + /// tray_builder = tray_builder.with_icon_as_template(true); + /// } + /// let tray_handle = tray_builder.build(app)?; + /// Ok(()) + /// }); + /// ``` + #[cfg(target_os = "macos")] + #[must_use] + pub fn with_icon_as_template(mut self, is_template: bool) -> Self { + self.icon_as_template_set = true; + self.icon_as_template = is_template; + self + } + + /// Sets whether the menu should appear when the tray receives a left click. Defaults to `true`. + /// + /// # Examples + /// + /// ``` + /// use tauri::SystemTray; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let mut tray_builder = SystemTray::new(); + /// #[cfg(target_os = "macos")] + /// { + /// tray_builder = tray_builder.with_menu_on_left_click(false); + /// } + /// let tray_handle = tray_builder.build(app)?; + /// Ok(()) + /// }); + /// ``` + #[cfg(target_os = "macos")] + #[must_use] + pub fn with_menu_on_left_click(mut self, menu_on_left_click: bool) -> Self { + self.menu_on_left_click_set = true; + self.menu_on_left_click = menu_on_left_click; + self + } + + /// Sets the menu title` + /// + /// # Examples + /// + /// ``` + /// use tauri::SystemTray; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let mut tray_builder = SystemTray::new(); + /// #[cfg(target_os = "macos")] + /// { + /// tray_builder = tray_builder.with_title("My App"); + /// } + /// let tray_handle = tray_builder.build(app)?; + /// Ok(()) + /// }); + /// ``` + #[cfg(target_os = "macos")] + #[must_use] + pub fn with_title(mut self, title: &str) -> Self { + self.title = Some(title.to_owned()); + self + } + + /// Sets the tray icon tooltip. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported + /// + /// # Examples + /// + /// ``` + /// use tauri::SystemTray; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let tray_handle = SystemTray::new().with_tooltip("My App").build(app)?; + /// Ok(()) + /// }); + /// ``` + #[must_use] + pub fn with_tooltip(mut self, tooltip: &str) -> Self { + self.tooltip = Some(tooltip.to_owned()); + self + } + + /// Sets the event listener for this system tray. + /// + /// # Examples + /// + /// ``` + /// use tauri::{Icon, Manager, SystemTray, SystemTrayEvent}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle(); + /// let id = "tray-id"; + /// SystemTray::new() + /// .with_id(id) + /// .on_event(move |event| { + /// let tray_handle = handle.tray_handle_by_id(id).unwrap(); + /// match event { + /// // show window with id "main" when the tray is left clicked + /// SystemTrayEvent::LeftClick { .. } => { + /// let window = handle.get_window("main").unwrap(); + /// window.show().unwrap(); + /// window.set_focus().unwrap(); + /// } + /// _ => {} + /// } + /// }) + /// .build(app)?; + /// Ok(()) + /// }); + /// ``` + #[must_use] + pub fn on_event(mut self, f: F) -> Self { + self.on_event.replace(Arc::new(f)); + self + } + + /// Sets the menu to show when the system tray is right clicked. + /// + /// # Examples + /// + /// ``` + /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let tray_handle = SystemTray::new() + /// .with_menu( + /// SystemTrayMenu::new() + /// .add_item(CustomMenuItem::new("quit", "Quit")) + /// .add_item(CustomMenuItem::new("open", "Open")) + /// ) + /// .build(app)?; + /// Ok(()) + /// }); + /// ``` + #[must_use] + pub fn with_menu(mut self, menu: SystemTrayMenu) -> Self { + self.menu.replace(menu); + self + } + + /// Builds and shows the system tray. + /// + /// # Examples + /// + /// ``` + /// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let tray_handle = SystemTray::new() + /// .with_menu( + /// SystemTrayMenu::new() + /// .add_item(CustomMenuItem::new("quit", "Quit")) + /// .add_item(CustomMenuItem::new("open", "Open")) + /// ) + /// .build(app)?; + /// + /// tray_handle.get_item("quit").set_enabled(false); + /// Ok(()) + /// }); + /// ``` + pub fn build>( + mut self, + manager: &M, + ) -> crate::Result> { + let mut ids = HashMap::new(); + if let Some(menu) = self.menu() { + get_menu_ids(&mut ids, menu); + } + let ids = Arc::new(Mutex::new(ids)); + + if self.icon.is_none() { + if let Some(tray_icon) = &manager.manager().inner.tray_icon { + self = self.with_icon(tray_icon.clone()); + } + } + #[cfg(target_os = "macos")] + { + if !self.icon_as_template_set { + self.icon_as_template = manager + .config() + .tauri + .system_tray + .as_ref() + .map_or(false, |t| t.icon_as_template); + } + if !self.menu_on_left_click_set { + self.menu_on_left_click = manager + .config() + .tauri + .system_tray + .as_ref() + .map_or(false, |t| t.menu_on_left_click); + } + if self.title.is_none() { + self.title = manager + .config() + .tauri + .system_tray + .as_ref() + .and_then(|t| t.title.clone()) + } + } + + let tray_id = self.id.clone(); + + let mut runtime_tray = tauri_runtime::SystemTray::new(); + runtime_tray = runtime_tray.with_id(hash(&self.id)); + if let Some(i) = self.icon { + runtime_tray = runtime_tray.with_icon(i); + } + + if let Some(menu) = self.menu { + runtime_tray = runtime_tray.with_menu(menu); + } + + if let Some(on_event) = self.on_event { + let ids_ = ids.clone(); + let tray_id_ = tray_id.clone(); + runtime_tray = runtime_tray.on_event(move |event| { + on_event(SystemTrayEvent::from_runtime_event( + event, + tray_id_.clone(), + &ids_, + )) + }); + } + + #[cfg(target_os = "macos")] + { + runtime_tray = runtime_tray.with_icon_as_template(self.icon_as_template); + runtime_tray = runtime_tray.with_menu_on_left_click(self.menu_on_left_click); + if let Some(title) = self.title { + runtime_tray = runtime_tray.with_title(&title); + } + } + + if let Some(tooltip) = self.tooltip { + runtime_tray = runtime_tray.with_tooltip(&tooltip); + } + + let id = runtime_tray.id; + let tray_handler = match manager.runtime() { + RuntimeOrDispatch::Runtime(r) => r.system_tray(runtime_tray), + RuntimeOrDispatch::RuntimeHandle(h) => h.system_tray(runtime_tray), + RuntimeOrDispatch::Dispatch(_) => manager + .app_handle() + .runtime_handle + .system_tray(runtime_tray), + }?; + + let tray_handle = SystemTrayHandle { + id, + ids, + inner: tray_handler, + }; + manager.manager().attach_tray(tray_id, tray_handle.clone()); + + Ok(tray_handle) + } +} + +fn hash(id: &str) -> MenuHash { + let mut hasher = DefaultHasher::new(); + id.hash(&mut hasher); + hasher.finish() as MenuHash +} + /// System tray event. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] #[non_exhaustive] @@ -33,6 +477,8 @@ pub enum SystemTrayEvent { /// Tray context menu item was clicked. #[non_exhaustive] MenuItemClick { + /// The tray id. + tray_id: String, /// The id of the menu item. id: MenuId, }, @@ -43,6 +489,8 @@ pub enum SystemTrayEvent { /// - **Linux:** Unsupported #[non_exhaustive] LeftClick { + /// The tray id. + tray_id: String, /// The position of the tray icon. position: PhysicalPosition, /// The size of the tray icon. @@ -56,6 +504,8 @@ pub enum SystemTrayEvent { /// - **macOS:** `Ctrl` + `Left click` fire this event. #[non_exhaustive] RightClick { + /// The tray id. + tray_id: String, /// The position of the tray icon. position: PhysicalPosition, /// The size of the tray icon. @@ -69,6 +519,8 @@ pub enum SystemTrayEvent { /// #[non_exhaustive] DoubleClick { + /// The tray id. + tray_id: String, /// The position of the tray icon. position: PhysicalPosition, /// The size of the tray icon. @@ -76,17 +528,49 @@ pub enum SystemTrayEvent { }, } +impl SystemTrayEvent { + pub(crate) fn from_runtime_event( + event: &RuntimeSystemTrayEvent, + tray_id: String, + menu_ids: &Arc>>, + ) -> Self { + match event { + RuntimeSystemTrayEvent::MenuItemClick(id) => Self::MenuItemClick { + tray_id, + id: menu_ids.lock().unwrap().get(id).unwrap().clone(), + }, + RuntimeSystemTrayEvent::LeftClick { position, size } => Self::LeftClick { + tray_id, + position: *position, + size: *size, + }, + RuntimeSystemTrayEvent::RightClick { position, size } => Self::RightClick { + tray_id, + position: *position, + size: *size, + }, + RuntimeSystemTrayEvent::DoubleClick { position, size } => Self::DoubleClick { + tray_id, + position: *position, + size: *size, + }, + } + } +} + /// A handle to a system tray. Allows updating the context menu items. #[default_runtime(crate::Wry, wry)] #[derive(Debug)] pub struct SystemTrayHandle { - pub(crate) ids: Arc>, + pub(crate) id: TrayId, + pub(crate) ids: Arc>>, pub(crate) inner: R::TrayHandler, } impl Clone for SystemTrayHandle { fn clone(&self) -> Self { Self { + id: self.id, ids: self.ids.clone(), inner: self.inner.clone(), } @@ -113,7 +597,9 @@ impl Clone for SystemTrayMenuItemHandle { impl SystemTrayHandle { /// Gets a handle to the menu item that has the specified `id`. pub fn get_item(&self, id: MenuIdRef<'_>) -> SystemTrayMenuItemHandle { - for (raw, item_id) in self.ids.iter() { + let ids = self.ids.lock().unwrap(); + let iter = ids.iter(); + for (raw, item_id) in iter { if item_id == id { return SystemTrayMenuItemHandle { id: *raw, @@ -124,14 +610,31 @@ impl SystemTrayHandle { panic!("item id not found") } - /// Updates the tray icon. Must be a [`Icon::File`] on Linux and a [`Icon::Raw`] on Windows and macOS. + /// Attempts to get a handle to the menu item that has the specified `id`, return an error if `id` is not found. + pub fn try_get_item(&self, id: MenuIdRef<'_>) -> Option> { + self + .ids + .lock() + .unwrap() + .iter() + .find(|i| i.1 == id) + .map(|i| SystemTrayMenuItemHandle { + id: *i.0, + tray_handler: self.inner.clone(), + }) + } + /// Updates the tray icon. pub fn set_icon(&self, icon: Icon) -> crate::Result<()> { - self.inner.set_icon(icon).map_err(Into::into) + self.inner.set_icon(icon.try_into()?).map_err(Into::into) } /// Updates the tray menu. pub fn set_menu(&self, menu: SystemTrayMenu) -> crate::Result<()> { - self.inner.set_menu(menu).map_err(Into::into) + let mut ids = HashMap::new(); + get_menu_ids(&mut ids, &menu); + self.inner.set_menu(menu)?; + *self.ids.lock().unwrap() = ids; + Ok(()) } /// Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color. @@ -142,6 +645,26 @@ impl SystemTrayHandle { .set_icon_as_template(is_template) .map_err(Into::into) } + + /// Adds the title to the tray menu + #[cfg(target_os = "macos")] + pub fn set_title(&self, title: &str) -> crate::Result<()> { + self.inner.set_title(title).map_err(Into::into) + } + + /// Set the tooltip for this tray icon. + /// + /// ## Platform-specific: + /// + /// - **Linux:** Unsupported + pub fn set_tooltip(&self, tooltip: &str) -> crate::Result<()> { + self.inner.set_tooltip(tooltip).map_err(Into::into) + } + + /// Destroys this system tray. + pub fn destroy(&self) -> crate::Result<()> { + self.inner.destroy().map_err(Into::into) + } } impl SystemTrayMenuItemHandle { @@ -169,6 +692,7 @@ impl SystemTrayMenuItemHandle { .map_err(Into::into) } + /// Sets the native image for this item. #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] pub fn set_native_image(&self, image: crate::NativeImage) -> crate::Result<()> { diff --git a/core/tauri/src/asset_protocol.rs b/core/tauri/src/asset_protocol.rs new file mode 100644 index 000000000000..ec961a9a1169 --- /dev/null +++ b/core/tauri/src/asset_protocol.rs @@ -0,0 +1,226 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::path::SafePathBuf; +use crate::scope::FsScope; +use rand::RngCore; +use std::io::SeekFrom; +use tauri_runtime::http::HttpRange; +use tauri_runtime::http::{ + header::*, status::StatusCode, MimeType, Request, Response, ResponseBuilder, +}; +use tauri_utils::debug_eprintln; +use tokio::fs::File; +use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; +use url::Position; +use url::Url; + +pub fn asset_protocol_handler( + request: &Request, + scope: FsScope, + window_origin: String, +) -> Result> { + let parsed_path = Url::parse(request.uri())?; + let filtered_path = &parsed_path[..Position::AfterPath]; + let path = filtered_path + .strip_prefix("asset://localhost/") + // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows + // where `$P` is not `localhost/*` + .unwrap_or(""); + let path = percent_encoding::percent_decode(path.as_bytes()) + .decode_utf8_lossy() + .to_string(); + + if let Err(e) = SafePathBuf::new(path.clone().into()) { + debug_eprintln!("asset protocol path \"{}\" is not valid: {}", path, e); + return ResponseBuilder::new().status(403).body(Vec::new()); + } + + if !scope.is_allowed(&path) { + debug_eprintln!("asset protocol not configured to allow the path: {}", path); + return ResponseBuilder::new().status(403).body(Vec::new()); + } + + let mut resp = ResponseBuilder::new().header("Access-Control-Allow-Origin", &window_origin); + + let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move { + let mut file = File::open(&path).await?; + + // get file length + let len = { + let old_pos = file.stream_position().await?; + let len = file.seek(SeekFrom::End(0)).await?; + file.seek(SeekFrom::Start(old_pos)).await?; + len + }; + + // get file mime type + let (mime_type, read_bytes) = { + let nbytes = len.min(8192); + let mut magic_buf = Vec::with_capacity(nbytes as usize); + let old_pos = file.stream_position().await?; + (&mut file).take(nbytes).read_to_end(&mut magic_buf).await?; + file.seek(SeekFrom::Start(old_pos)).await?; + ( + MimeType::parse(&magic_buf, &path), + // return the `magic_bytes` if we read the whole file + // to avoid reading it again later if this is not a range request + if len < 8192 { Some(magic_buf) } else { None }, + ) + }; + + Ok::<(File, u64, String, Option>), anyhow::Error>((file, len, mime_type, read_bytes)) + })?; + + resp = resp.header(CONTENT_TYPE, &mime_type); + + // handle 206 (partial range) http requests + let response = if let Some(range_header) = request + .headers() + .get("range") + .and_then(|r| r.to_str().map(|r| r.to_string()).ok()) + { + resp = resp.header(ACCEPT_RANGES, "bytes"); + + let not_satisfiable = || { + ResponseBuilder::new() + .status(StatusCode::RANGE_NOT_SATISFIABLE) + .header(CONTENT_RANGE, format!("bytes */{len}")) + .body(vec![]) + }; + + // parse range header + let ranges = if let Ok(ranges) = HttpRange::parse(&range_header, len) { + ranges + .iter() + // map the output to spec range , example: 0-499 + .map(|r| (r.start, r.start + r.length - 1)) + .collect::>() + } else { + return not_satisfiable(); + }; + + /// The Maximum bytes we send in one range + const MAX_LEN: u64 = 1000 * 1024; + + // single-part range header + if ranges.len() == 1 { + let &(start, mut end) = ranges.first().unwrap(); + + // check if a range is not satisfiable + // + // this should be already taken care of by the range parsing library + // but checking here again for extra assurance + if start >= len || end >= len || end < start { + return not_satisfiable(); + } + + // adjust end byte for MAX_LEN + end = start + (end - start).min(len - start).min(MAX_LEN - 1); + + // calculate number of bytes needed to be read + let nbytes = end + 1 - start; + + let buf = crate::async_runtime::safe_block_on(async move { + let mut buf = Vec::with_capacity(nbytes as usize); + file.seek(SeekFrom::Start(start)).await?; + file.take(nbytes).read_to_end(&mut buf).await?; + Ok::, anyhow::Error>(buf) + })?; + + resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}")); + resp = resp.header(CONTENT_LENGTH, end + 1 - start); + resp = resp.status(StatusCode::PARTIAL_CONTENT); + resp.body(buf) + } else { + let ranges = ranges + .iter() + .filter_map(|&(start, mut end)| { + // filter out unsatisfiable ranges + // + // this should be already taken care of by the range parsing library + // but checking here again for extra assurance + if start >= len || end >= len || end < start { + None + } else { + // adjust end byte for MAX_LEN + end = start + (end - start).min(len - start).min(MAX_LEN - 1); + Some((start, end)) + } + }) + .collect::>(); + + let boundary = random_boundary(); + let boundary_sep = format!("\r\n--{boundary}\r\n"); + let boundary_closer = format!("\r\n--{boundary}\r\n"); + + resp = resp.header( + CONTENT_TYPE, + format!("multipart/byteranges; boundary={boundary}"), + ); + + let buf = crate::async_runtime::safe_block_on(async move { + // multi-part range header + let mut buf = Vec::new(); + + for (end, start) in ranges { + // a new range is being written, write the range boundary + buf.write_all(boundary_sep.as_bytes()).await?; + + // write the needed headers `Content-Type` and `Content-Range` + buf + .write_all(format!("{CONTENT_TYPE}: {mime_type}\r\n").as_bytes()) + .await?; + buf + .write_all(format!("{CONTENT_RANGE}: bytes {start}-{end}/{len}\r\n").as_bytes()) + .await?; + + // write the separator to indicate the start of the range body + buf.write_all("\r\n".as_bytes()).await?; + + // calculate number of bytes needed to be read + let nbytes = end + 1 - start; + + let mut local_buf = Vec::with_capacity(nbytes as usize); + file.seek(SeekFrom::Start(start)).await?; + (&mut file).take(nbytes).read_to_end(&mut local_buf).await?; + buf.extend_from_slice(&local_buf); + } + // all ranges have been written, write the closing boundary + buf.write_all(boundary_closer.as_bytes()).await?; + + Ok::, anyhow::Error>(buf) + })?; + resp.body(buf) + } + } else { + // avoid reading the file if we already read it + // as part of mime type detection + let buf = if let Some(b) = read_bytes { + b + } else { + crate::async_runtime::safe_block_on(async move { + let mut local_buf = Vec::with_capacity(len as usize); + file.read_to_end(&mut local_buf).await?; + Ok::, anyhow::Error>(local_buf) + })? + }; + resp = resp.header(CONTENT_LENGTH, len); + resp.body(buf) + }; + + response +} + +fn random_boundary() -> String { + let mut x = [0_u8; 30]; + rand::thread_rng().fill_bytes(&mut x); + (x[..]) + .iter() + .map(|&x| format!("{x:x}")) + .fold(String::new(), |mut a, x| { + a.push_str(x.as_str()); + a + }) +} diff --git a/core/tauri/src/async_runtime.rs b/core/tauri/src/async_runtime.rs index 0ee1e14c3d2f..01c7083be479 100644 --- a/core/tauri/src/async_runtime.rs +++ b/core/tauri/src/async_runtime.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -7,10 +7,9 @@ //! Tauri uses [`tokio`] Runtime to initialize code, such as //! [`Plugin::initialize`](../plugin/trait.Plugin.html#method.initialize) and [`crate::Builder::setup`] hooks. //! This module also re-export some common items most developers need from [`tokio`]. If there's -//! one you need isn't here, you could use types in [`tokio`] dierectly. +//! one you need isn't here, you could use types in [`tokio`] directly. //! For custom command handlers, it's recommended to use a plain `async fn` command. -use futures_lite::future::FutureExt; use once_cell::sync::OnceCell; pub use tokio::{ runtime::{Handle as TokioHandle, Runtime as TokioRuntime}, @@ -103,7 +102,10 @@ impl Runtime { F::Output: Send + 'static, { match self { - Self::Tokio(r) => JoinHandle::Tokio(r.spawn(task)), + Self::Tokio(r) => { + let _guard = r.enter(); + JoinHandle::Tokio(tokio::spawn(task)) + } } } @@ -156,7 +158,7 @@ impl Future for JoinHandle { type Output = crate::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.get_mut() { - Self::Tokio(t) => t.poll(cx).map_err(|e| crate::Error::JoinError(Box::new(e))), + Self::Tokio(t) => Pin::new(t).poll(cx).map_err(Into::into), } } } @@ -193,7 +195,10 @@ impl RuntimeHandle { F::Output: Send + 'static, { match self { - Self::Tokio(h) => JoinHandle::Tokio(h.spawn(task)), + Self::Tokio(h) => { + let _guard = h.enter(); + JoinHandle::Tokio(tokio::spawn(task)) + } } } @@ -215,10 +220,10 @@ fn default_runtime() -> GlobalRuntime { } /// Sets the runtime to use to execute asynchronous tasks. -/// For convinience, this method takes a [`TokioHandle`]. +/// For convenience, this method takes a [`TokioHandle`]. /// Note that you cannot drop the underlying [`TokioRuntime`]. /// -/// # Example +/// # Examples /// /// ```rust /// #[tokio::main] @@ -285,10 +290,11 @@ where F: Future + Send + 'static, F::Output: Send + 'static, { - if tokio::runtime::Handle::try_current().is_ok() { + if let Ok(handle) = tokio::runtime::Handle::try_current() { let (tx, rx) = std::sync::mpsc::sync_channel(1); - spawn(async move { - tx.send(task.await).unwrap(); + let handle_ = handle.clone(); + handle.spawn_blocking(move || { + tx.send(handle_.block_on(task)).unwrap(); }); rx.recv().unwrap() } else { @@ -333,8 +339,7 @@ mod tests { 5 }); join.abort(); - if let crate::Error::JoinError(raw_box) = join.await.unwrap_err() { - let raw_error = raw_box.downcast::().unwrap(); + if let crate::Error::JoinError(raw_error) = join.await.unwrap_err() { assert!(raw_error.is_cancelled()); } else { panic!("Abort did not result in the expected `JoinError`"); diff --git a/core/tauri/src/command.rs b/core/tauri/src/command.rs index b5122a19fa20..7f392d480826 100644 --- a/core/tauri/src/command.rs +++ b/core/tauri/src/command.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -8,8 +8,8 @@ //! attribute macro along the way and used by [`crate::generate_handler`] macro. use crate::hooks::InvokeError; -use crate::runtime::Runtime; use crate::InvokeMessage; +use crate::Runtime; use serde::de::Visitor; use serde::{Deserialize, Deserializer}; @@ -49,9 +49,10 @@ pub trait CommandArg<'de, R: Runtime>: Sized { /// Automatically implement [`CommandArg`] for any type that can be deserialized. impl<'de, D: Deserialize<'de>, R: Runtime> CommandArg<'de, R> for D { - fn from_command(command: CommandItem<'de, R>) -> Result { + fn from_command(command: CommandItem<'de, R>) -> Result { + let name = command.name; let arg = command.key; - Self::deserialize(command).map_err(|e| crate::Error::InvalidArgs(arg, e).into()) + Self::deserialize(command).map_err(|e| crate::Error::InvalidArgs(name, arg, e).into()) } } @@ -154,8 +155,8 @@ impl<'de, R: Runtime> Deserializer<'de> for CommandItem<'de, R> { /// Nothing in this module is considered stable. #[doc(hidden)] pub mod private { - use crate::{runtime::Runtime, InvokeError, InvokeResolver}; - use futures::{FutureExt, TryFutureExt}; + use crate::{InvokeError, InvokeResolver, Runtime}; + use futures_util::{FutureExt, TryFutureExt}; use serde::Serialize; use serde_json::Value; use std::future::Future; diff --git a/core/tauri/src/endpoints.rs b/core/tauri/src/endpoints.rs deleted file mode 100644 index fd59d2357195..000000000000 --- a/core/tauri/src/endpoints.rs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::{ - hooks::{InvokeError, InvokeMessage, InvokeResolver}, - runtime::Runtime, - Config, Invoke, PackageInfo, Window, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; - -use std::sync::Arc; - -mod app; -mod cli; -mod clipboard; -mod dialog; -mod event; -#[allow(unused_imports)] -mod file_system; -mod global_shortcut; -mod http; -mod notification; -mod operating_system; -mod path; -mod process; -mod shell; -mod window; - -/// The context passed to the invoke handler. -pub struct InvokeContext { - pub window: Window, - pub config: Arc, - pub package_info: PackageInfo, -} - -#[cfg(test)] -impl Clone for InvokeContext { - fn clone(&self) -> Self { - Self { - window: self.window.clone(), - config: self.config.clone(), - package_info: self.package_info.clone(), - } - } -} - -/// The response for a JS `invoke` call. -pub struct InvokeResponse { - json: crate::Result, -} - -impl From for InvokeResponse { - fn from(value: T) -> Self { - Self { - json: serde_json::to_value(value).map_err(Into::into), - } - } -} - -#[derive(Deserialize)] -#[serde(tag = "module", content = "message")] -enum Module { - App(app::Cmd), - Process(process::Cmd), - Fs(file_system::Cmd), - Os(operating_system::Cmd), - Path(path::Cmd), - Window(Box), - Shell(shell::Cmd), - Event(event::Cmd), - Dialog(dialog::Cmd), - Cli(cli::Cmd), - Notification(notification::Cmd), - Http(http::Cmd), - GlobalShortcut(global_shortcut::Cmd), - Clipboard(clipboard::Cmd), -} - -impl Module { - fn run( - self, - window: Window, - resolver: InvokeResolver, - config: Arc, - package_info: PackageInfo, - ) { - let context = InvokeContext { - window, - config, - package_info, - }; - match self { - Self::App(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Process(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Fs(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Path(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Os(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Window(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .await - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Shell(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Event(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Dialog(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Cli(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Notification(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Http(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .await - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::GlobalShortcut(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - Self::Clipboard(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from) - }), - } - } -} - -pub(crate) fn handle( - module: String, - invoke: Invoke, - config: Arc, - package_info: &PackageInfo, -) { - let Invoke { message, resolver } = invoke; - let InvokeMessage { - mut payload, - window, - .. - } = message; - - if let JsonValue::Object(ref mut obj) = payload { - obj.insert("module".to_string(), JsonValue::String(module)); - } - - match serde_json::from_value::(payload) { - Ok(module) => module.run(window, resolver, config, package_info.clone()), - Err(e) => resolver.reject(e.to_string()), - } -} diff --git a/core/tauri/src/endpoints/app.rs b/core/tauri/src/endpoints/app.rs deleted file mode 100644 index 8ff5cecb7b0a..000000000000 --- a/core/tauri/src/endpoints/app.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::CommandModule; - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -#[allow(clippy::enum_variant_names)] -pub enum Cmd { - /// Get Application Version - GetAppVersion, - /// Get Application Name - GetAppName, - /// Get Tauri Version - GetTauriVersion, -} - -impl Cmd { - fn get_app_version(context: InvokeContext) -> crate::Result { - Ok(context.package_info.version) - } - - fn get_app_name(context: InvokeContext) -> crate::Result { - Ok(context.package_info.name) - } - - fn get_tauri_version(_context: InvokeContext) -> crate::Result<&'static str> { - Ok(env!("CARGO_PKG_VERSION")) - } -} diff --git a/core/tauri/src/endpoints/cli.rs b/core/tauri/src/endpoints/cli.rs deleted file mode 100644 index c720f58f4113..000000000000 --- a/core/tauri/src/endpoints/cli.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::{InvokeContext, InvokeResponse}; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// The get CLI matches API. - CliMatches, -} - -impl Cmd { - #[module_command_handler(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)")] - fn cli_matches(context: InvokeContext) -> crate::Result { - if let Some(cli) = &context.config.tauri.cli { - crate::api::cli::get_matches(cli, &context.package_info) - .map(Into::into) - .map_err(Into::into) - } else { - Err(crate::Error::ApiNotAllowlisted("CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)".into())) - } - } -} - -#[cfg(test)] -mod tests { - #[tauri_macros::module_command_test(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)")] - #[quickcheck_macros::quickcheck] - fn cli_matches() { - let res = super::Cmd::cli_matches(crate::test::mock_invoke_context()); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } -} diff --git a/core/tauri/src/endpoints/clipboard.rs b/core/tauri/src/endpoints/clipboard.rs deleted file mode 100644 index 53b60a79c348..000000000000 --- a/core/tauri/src/endpoints/clipboard.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -#[cfg(any(clipboard_write_text, clipboard_read_text))] -use crate::runtime::ClipboardManager; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", content = "data", rename_all = "camelCase")] -pub enum Cmd { - /// Write a text string to the clipboard. - WriteText(String), - /// Read clipboard content as text. - ReadText, -} - -impl Cmd { - #[module_command_handler(clipboard_write_text, "clipboard > writeText")] - fn write_text(context: InvokeContext, text: String) -> crate::Result<()> { - Ok( - context - .window - .app_handle - .clipboard_manager() - .write_text(text)?, - ) - } - - #[module_command_handler(clipboard_read_text, "clipboard > readText")] - fn read_text(context: InvokeContext) -> crate::Result> { - Ok(context.window.app_handle.clipboard_manager().read_text()?) - } -} - -#[cfg(test)] -mod tests { - #[tauri_macros::module_command_test(clipboard_write_text, "clipboard > writeText")] - #[quickcheck_macros::quickcheck] - fn write_text(text: String) { - let ctx = crate::test::mock_invoke_context(); - super::Cmd::write_text(ctx.clone(), text.clone()).unwrap(); - assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text)); - } - - #[tauri_macros::module_command_test(clipboard_read_text, "clipboard > readText")] - #[quickcheck_macros::quickcheck] - fn read_text() { - let ctx = crate::test::mock_invoke_context(); - assert_eq!(super::Cmd::read_text(ctx.clone()).unwrap(), None); - let text = "Tauri!".to_string(); - super::Cmd::write_text(ctx.clone(), text.clone()).unwrap(); - assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text)); - } -} diff --git a/core/tauri/src/endpoints/dialog.rs b/core/tauri/src/endpoints/dialog.rs deleted file mode 100644 index 9c72165ca348..000000000000 --- a/core/tauri/src/endpoints/dialog.rs +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::{InvokeContext, InvokeResponse}; -#[cfg(any(dialog_open, dialog_save))] -use crate::api::dialog::blocking::FileDialogBuilder; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -use std::path::PathBuf; - -#[allow(dead_code)] -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DialogFilter { - name: String, - extensions: Vec, -} - -/// The options for the open dialog API. -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct OpenDialogOptions { - /// The title of the dialog window. - pub title: Option, - /// The filters of the dialog. - #[serde(default)] - pub filters: Vec, - /// Whether the dialog allows multiple selection or not. - #[serde(default)] - pub multiple: bool, - /// Whether the dialog is a directory selection (`true` value) or file selection (`false` value). - #[serde(default)] - pub directory: bool, - /// The initial path of the dialog. - pub default_path: Option, -} - -/// The options for the save dialog API. -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SaveDialogOptions { - /// The title of the dialog window. - pub title: Option, - /// The filters of the dialog. - #[serde(default)] - pub filters: Vec, - /// The initial path of the dialog. - pub default_path: Option, -} - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -#[allow(clippy::enum_variant_names)] -pub enum Cmd { - /// The open dialog API. - OpenDialog { - options: OpenDialogOptions, - }, - /// The save dialog API. - SaveDialog { - options: SaveDialogOptions, - }, - MessageDialog { - message: String, - }, - AskDialog { - title: Option, - message: String, - }, - ConfirmDialog { - title: Option, - message: String, - }, -} - -impl Cmd { - #[module_command_handler(dialog_open, "dialog > open")] - #[allow(unused_variables)] - fn open_dialog( - context: InvokeContext, - options: OpenDialogOptions, - ) -> crate::Result { - let mut dialog_builder = FileDialogBuilder::new(); - #[cfg(any(windows, target_os = "macos"))] - { - dialog_builder = dialog_builder.set_parent(&context.window); - } - if let Some(default_path) = options.default_path { - dialog_builder = set_default_path(dialog_builder, default_path); - } - for filter in options.filters { - let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect(); - dialog_builder = dialog_builder.add_filter(filter.name, &extensions); - } - - let res = if options.directory { - dialog_builder.pick_folder().into() - } else if options.multiple { - dialog_builder.pick_files().into() - } else { - dialog_builder.pick_file().into() - }; - - Ok(res) - } - - #[module_command_handler(dialog_save, "dialog > save")] - #[allow(unused_variables)] - fn save_dialog( - context: InvokeContext, - options: SaveDialogOptions, - ) -> crate::Result> { - let mut dialog_builder = FileDialogBuilder::new(); - #[cfg(any(windows, target_os = "macos"))] - { - dialog_builder = dialog_builder.set_parent(&context.window); - } - if let Some(default_path) = options.default_path { - dialog_builder = set_default_path(dialog_builder, default_path); - } - for filter in options.filters { - let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect(); - dialog_builder = dialog_builder.add_filter(filter.name, &extensions); - } - - Ok(dialog_builder.save_file()) - } - - #[module_command_handler(dialog_message, "dialog > message")] - fn message_dialog(context: InvokeContext, message: String) -> crate::Result<()> { - crate::api::dialog::blocking::message( - Some(&context.window), - &context.window.app_handle.package_info().name, - message, - ); - Ok(()) - } - - #[module_command_handler(dialog_ask, "dialog > ask")] - fn ask_dialog( - context: InvokeContext, - title: Option, - message: String, - ) -> crate::Result { - Ok(crate::api::dialog::blocking::ask( - Some(&context.window), - title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()), - message, - )) - } - - #[module_command_handler(dialog_confirm, "dialog > confirm")] - fn confirm_dialog( - context: InvokeContext, - title: Option, - message: String, - ) -> crate::Result { - Ok(crate::api::dialog::blocking::confirm( - Some(&context.window), - title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()), - message, - )) - } -} - -#[cfg(any(dialog_open, dialog_save))] -fn set_default_path( - mut dialog_builder: FileDialogBuilder, - default_path: PathBuf, -) -> FileDialogBuilder { - if default_path.is_file() || !default_path.exists() { - if let (Some(parent), Some(file_name)) = (default_path.parent(), default_path.file_name()) { - dialog_builder = dialog_builder.set_directory(parent); - dialog_builder = dialog_builder.set_file_name(&file_name.to_string_lossy().to_string()); - } else { - dialog_builder = dialog_builder.set_directory(default_path); - } - dialog_builder - } else { - dialog_builder.set_directory(default_path) - } -} - -#[cfg(test)] -mod tests { - use super::{OpenDialogOptions, SaveDialogOptions}; - use quickcheck::{Arbitrary, Gen}; - - impl Arbitrary for OpenDialogOptions { - fn arbitrary(g: &mut Gen) -> Self { - Self { - filters: Vec::new(), - multiple: bool::arbitrary(g), - directory: bool::arbitrary(g), - default_path: Option::arbitrary(g), - title: Option::arbitrary(g), - } - } - } - - impl Arbitrary for SaveDialogOptions { - fn arbitrary(g: &mut Gen) -> Self { - Self { - filters: Vec::new(), - default_path: Option::arbitrary(g), - title: Option::arbitrary(g), - } - } - } - - #[tauri_macros::module_command_test(dialog_open, "dialog > open")] - #[quickcheck_macros::quickcheck] - fn open_dialog(_options: OpenDialogOptions) {} - - #[tauri_macros::module_command_test(dialog_save, "dialog > save")] - #[quickcheck_macros::quickcheck] - fn save_dialog(_options: SaveDialogOptions) {} -} diff --git a/core/tauri/src/endpoints/event.rs b/core/tauri/src/endpoints/event.rs deleted file mode 100644 index 1f4d806933be..000000000000 --- a/core/tauri/src/endpoints/event.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -use crate::{ - api::ipc::CallbackFn, - event::is_event_name_valid, - event::{listen_js, unlisten_js}, - runtime::window::is_label_valid, - sealed::ManagerBase, - Manager, Runtime, -}; -use serde::{de::Deserializer, Deserialize}; -use tauri_macros::CommandModule; - -pub struct EventId(String); - -impl<'de> Deserialize<'de> for EventId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let event_id = String::deserialize(deserializer)?; - if is_event_name_valid(&event_id) { - Ok(EventId(event_id)) - } else { - Err(serde::de::Error::custom( - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.", - )) - } - } -} - -pub struct WindowLabel(String); - -impl<'de> Deserialize<'de> for WindowLabel { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let event_id = String::deserialize(deserializer)?; - if is_label_valid(&event_id) { - Ok(WindowLabel(event_id)) - } else { - Err(serde::de::Error::custom( - "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`.", - )) - } - } -} - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// Listen to an event. - #[serde(rename_all = "camelCase")] - Listen { - event: EventId, - window_label: Option, - handler: CallbackFn, - }, - /// Unlisten to an event. - #[serde(rename_all = "camelCase")] - Unlisten { event_id: u64 }, - /// Emit an event to the webview associated with the given window. - /// If the window_label is omitted, the event will be triggered on all listeners. - #[serde(rename_all = "camelCase")] - Emit { - event: EventId, - window_label: Option, - payload: Option, - }, -} - -impl Cmd { - fn listen( - context: InvokeContext, - event: EventId, - window_label: Option, - handler: CallbackFn, - ) -> crate::Result { - let event_id = rand::random(); - - let window_label = window_label.map(|l| l.0); - - context.window.eval(&listen_js( - context.window.manager().event_listeners_object_name(), - format!("'{}'", event.0), - event_id, - window_label.clone(), - format!("window['_{}']", handler.0), - ))?; - - context - .window - .register_js_listener(window_label, event.0, event_id); - - Ok(event_id) - } - - fn unlisten(context: InvokeContext, event_id: u64) -> crate::Result<()> { - context.window.eval(&unlisten_js( - context.window.manager().event_listeners_object_name(), - event_id, - ))?; - context.window.unregister_js_listener(event_id); - Ok(()) - } - - fn emit( - context: InvokeContext, - event: EventId, - window_label: Option, - payload: Option, - ) -> crate::Result<()> { - // dispatch the event to Rust listeners - context.window.trigger(&event.0, payload.clone()); - - if let Some(target) = window_label { - context.window.emit_to(&target.0, &event.0, payload)?; - } else { - context.window.emit_all(&event.0, payload)?; - } - Ok(()) - } -} diff --git a/core/tauri/src/endpoints/file_system.rs b/core/tauri/src/endpoints/file_system.rs deleted file mode 100644 index 4d8bd7e21c76..000000000000 --- a/core/tauri/src/endpoints/file_system.rs +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::{ - api::{dir, file, path::BaseDirectory}, - scope::Scopes, - Config, Env, Manager, PackageInfo, Runtime, Window, -}; - -use super::InvokeContext; -use serde::{ - de::{Deserializer, Error as DeError}, - Deserialize, Serialize, -}; -use tauri_macros::{module_command_handler, CommandModule}; - -use std::fmt::{Debug, Formatter}; -use std::{ - fs, - fs::File, - io::Write, - path::{Component, Path}, - sync::Arc, -}; - -#[derive(Clone, Debug)] -pub struct SafePathBuf(std::path::PathBuf); - -impl AsRef for SafePathBuf { - fn as_ref(&self) -> &Path { - self.0.as_ref() - } -} - -impl<'de> Deserialize<'de> for SafePathBuf { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let path = std::path::PathBuf::deserialize(deserializer)?; - if path.components().any(|x| { - matches!( - x, - Component::ParentDir | Component::RootDir | Component::Prefix(_) - ) - }) { - Err(DeError::custom("cannot traverse directory")) - } else { - Ok(SafePathBuf(path)) - } - } -} - -/// The options for the directory functions on the file system API. -#[derive(Debug, Clone, Deserialize)] -pub struct DirOperationOptions { - /// Whether the API should recursively perform the operation on the directory. - #[serde(default)] - pub recursive: bool, - /// The base directory of the operation. - /// The directory path of the BaseDirectory will be the prefix of the defined directory path. - pub dir: Option, -} - -/// The options for the file functions on the file system API. -#[derive(Debug, Clone, Deserialize)] -pub struct FileOperationOptions { - /// The base directory of the operation. - /// The directory path of the BaseDirectory will be the prefix of the defined file path. - pub dir: Option, -} - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// The read text file API. - ReadFile { - path: SafePathBuf, - options: Option, - }, - /// The write file API. - WriteFile { - path: SafePathBuf, - contents: Vec, - options: Option, - }, - /// The read dir API. - ReadDir { - path: SafePathBuf, - options: Option, - }, - /// The copy file API. - CopyFile { - source: SafePathBuf, - destination: SafePathBuf, - options: Option, - }, - /// The create dir API. - CreateDir { - path: SafePathBuf, - options: Option, - }, - /// The remove dir API. - RemoveDir { - path: SafePathBuf, - options: Option, - }, - /// The remove file API. - RemoveFile { - path: SafePathBuf, - options: Option, - }, - /// The rename file API. - #[serde(rename_all = "camelCase")] - RenameFile { - old_path: SafePathBuf, - new_path: SafePathBuf, - options: Option, - }, -} - -impl Cmd { - #[module_command_handler(fs_read_file, "fs > readFile")] - fn read_file( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> crate::Result> { - file::read_binary(resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - options.and_then(|o| o.dir), - )?) - .map_err(crate::Error::FailedToExecuteApi) - } - - #[module_command_handler(fs_write_file, "fs > writeFile")] - fn write_file( - context: InvokeContext, - path: SafePathBuf, - contents: Vec, - options: Option, - ) -> crate::Result<()> { - File::create(resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - options.and_then(|o| o.dir), - )?) - .map_err(Into::into) - .and_then(|mut f| f.write_all(&contents).map_err(|err| err.into())) - } - - #[module_command_handler(fs_read_dir, "fs > readDir")] - fn read_dir( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> crate::Result> { - let (recursive, dir) = if let Some(options_value) = options { - (options_value.recursive, options_value.dir) - } else { - (false, None) - }; - dir::read_dir( - resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - dir, - )?, - recursive, - ) - .map_err(crate::Error::FailedToExecuteApi) - } - - #[module_command_handler(fs_copy_file, "fs > copyFile")] - fn copy_file( - context: InvokeContext, - source: SafePathBuf, - destination: SafePathBuf, - options: Option, - ) -> crate::Result<()> { - let (src, dest) = match options.and_then(|o| o.dir) { - Some(dir) => ( - resolve_path( - &context.config, - &context.package_info, - &context.window, - source, - Some(dir.clone()), - )?, - resolve_path( - &context.config, - &context.package_info, - &context.window, - destination, - Some(dir), - )?, - ), - None => (source, destination), - }; - fs::copy(src, dest)?; - Ok(()) - } - - #[module_command_handler(fs_create_dir, "fs > createDir")] - fn create_dir( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> crate::Result<()> { - let (recursive, dir) = if let Some(options_value) = options { - (options_value.recursive, options_value.dir) - } else { - (false, None) - }; - let resolved_path = resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - dir, - )?; - if recursive { - fs::create_dir_all(resolved_path)?; - } else { - fs::create_dir(resolved_path)?; - } - - Ok(()) - } - - #[module_command_handler(fs_remove_dir, "fs > removeDir")] - fn remove_dir( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> crate::Result<()> { - let (recursive, dir) = if let Some(options_value) = options { - (options_value.recursive, options_value.dir) - } else { - (false, None) - }; - let resolved_path = resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - dir, - )?; - if recursive { - fs::remove_dir_all(resolved_path)?; - } else { - fs::remove_dir(resolved_path)?; - } - - Ok(()) - } - - #[module_command_handler(fs_remove_file, "fs > removeFile")] - fn remove_file( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> crate::Result<()> { - let resolved_path = resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - options.and_then(|o| o.dir), - )?; - fs::remove_file(resolved_path)?; - Ok(()) - } - - #[module_command_handler(fs_rename_file, "fs > renameFile")] - fn rename_file( - context: InvokeContext, - old_path: SafePathBuf, - new_path: SafePathBuf, - options: Option, - ) -> crate::Result<()> { - let (old, new) = match options.and_then(|o| o.dir) { - Some(dir) => ( - resolve_path( - &context.config, - &context.package_info, - &context.window, - old_path, - Some(dir.clone()), - )?, - resolve_path( - &context.config, - &context.package_info, - &context.window, - new_path, - Some(dir), - )?, - ), - None => (old_path, new_path), - }; - fs::rename(old, new).map_err(crate::Error::Io) - } -} - -#[allow(dead_code)] -fn resolve_path( - config: &Config, - package_info: &PackageInfo, - window: &Window, - path: SafePathBuf, - dir: Option, -) -> crate::Result { - let env = window.state::().inner(); - match crate::api::path::resolve_path(config, package_info, env, path, dir) { - Ok(path) => { - if window.state::().fs.is_allowed(&path) { - Ok(SafePathBuf(path)) - } else { - Err(crate::Error::PathNotAllowed(path)) - } - } - Err(e) => Err(e.into()), - } -} - -#[cfg(test)] -mod tests { - use super::{BaseDirectory, DirOperationOptions, FileOperationOptions, SafePathBuf}; - - use quickcheck::{Arbitrary, Gen}; - - use std::path::PathBuf; - - impl Arbitrary for super::SafePathBuf { - fn arbitrary(g: &mut Gen) -> Self { - Self(PathBuf::arbitrary(g)) - } - - fn shrink(&self) -> Box> { - Box::new(self.0.shrink().map(SafePathBuf)) - } - } - - impl Arbitrary for BaseDirectory { - fn arbitrary(g: &mut Gen) -> Self { - if bool::arbitrary(g) { - BaseDirectory::App - } else { - BaseDirectory::Resource - } - } - } - - impl Arbitrary for FileOperationOptions { - fn arbitrary(g: &mut Gen) -> Self { - Self { - dir: Option::arbitrary(g), - } - } - } - - impl Arbitrary for DirOperationOptions { - fn arbitrary(g: &mut Gen) -> Self { - Self { - recursive: bool::arbitrary(g), - dir: Option::arbitrary(g), - } - } - } - - #[tauri_macros::module_command_test(fs_read_file, "fs > readFile")] - #[quickcheck_macros::quickcheck] - fn read_file(path: SafePathBuf, options: Option) { - let res = super::Cmd::read_file(crate::test::mock_invoke_context(), path, options); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } - - #[tauri_macros::module_command_test(fs_write_file, "fs > writeFile")] - #[quickcheck_macros::quickcheck] - fn write_file(path: SafePathBuf, contents: Vec, options: Option) { - let res = super::Cmd::write_file(crate::test::mock_invoke_context(), path, contents, options); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } - - #[tauri_macros::module_command_test(fs_read_dir, "fs > readDir")] - #[quickcheck_macros::quickcheck] - fn read_dir(path: SafePathBuf, options: Option) { - let res = super::Cmd::read_dir(crate::test::mock_invoke_context(), path, options); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } - - #[tauri_macros::module_command_test(fs_copy_file, "fs > copyFile")] - #[quickcheck_macros::quickcheck] - fn copy_file( - source: SafePathBuf, - destination: SafePathBuf, - options: Option, - ) { - let res = super::Cmd::copy_file( - crate::test::mock_invoke_context(), - source, - destination, - options, - ); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } - - #[tauri_macros::module_command_test(fs_create_dir, "fs > createDir")] - #[quickcheck_macros::quickcheck] - fn create_dir(path: SafePathBuf, options: Option) { - let res = super::Cmd::create_dir(crate::test::mock_invoke_context(), path, options); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } - - #[tauri_macros::module_command_test(fs_remove_dir, "fs > removeDir")] - #[quickcheck_macros::quickcheck] - fn remove_dir(path: SafePathBuf, options: Option) { - let res = super::Cmd::remove_dir(crate::test::mock_invoke_context(), path, options); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } - - #[tauri_macros::module_command_test(fs_remove_file, "fs > removeFile")] - #[quickcheck_macros::quickcheck] - fn remove_file(path: SafePathBuf, options: Option) { - let res = super::Cmd::remove_file(crate::test::mock_invoke_context(), path, options); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } - - #[tauri_macros::module_command_test(fs_rename_file, "fs > renameFile")] - #[quickcheck_macros::quickcheck] - fn rename_file( - old_path: SafePathBuf, - new_path: SafePathBuf, - options: Option, - ) { - let res = super::Cmd::rename_file( - crate::test::mock_invoke_context(), - old_path, - new_path, - options, - ); - assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_)))); - } -} diff --git a/core/tauri/src/endpoints/global_shortcut.rs b/core/tauri/src/endpoints/global_shortcut.rs deleted file mode 100644 index 416c48542df8..000000000000 --- a/core/tauri/src/endpoints/global_shortcut.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -use crate::{api::ipc::CallbackFn, Runtime}; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -#[cfg(global_shortcut_all)] -use crate::runtime::GlobalShortcutManager; - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// Register a global shortcut. - Register { - shortcut: String, - handler: CallbackFn, - }, - /// Register a list of global shortcuts. - RegisterAll { - shortcuts: Vec, - handler: CallbackFn, - }, - /// Unregister a global shortcut. - Unregister { shortcut: String }, - /// Unregisters all registered shortcuts. - UnregisterAll, - /// Determines whether the given hotkey is registered or not. - IsRegistered { shortcut: String }, -} - -impl Cmd { - #[module_command_handler(global_shortcut_all, "globalShortcut > all")] - fn register( - context: InvokeContext, - shortcut: String, - handler: CallbackFn, - ) -> crate::Result<()> { - let mut manager = context.window.app_handle.global_shortcut_manager(); - register_shortcut(context.window, &mut manager, shortcut, handler)?; - Ok(()) - } - - #[module_command_handler(global_shortcut_all, "globalShortcut > all")] - fn register_all( - context: InvokeContext, - shortcuts: Vec, - handler: CallbackFn, - ) -> crate::Result<()> { - let mut manager = context.window.app_handle.global_shortcut_manager(); - for shortcut in shortcuts { - register_shortcut(context.window.clone(), &mut manager, shortcut, handler)?; - } - Ok(()) - } - - #[module_command_handler(global_shortcut_all, "globalShortcut > all")] - fn unregister(context: InvokeContext, shortcut: String) -> crate::Result<()> { - context - .window - .app_handle - .global_shortcut_manager() - .unregister(&shortcut)?; - Ok(()) - } - - #[module_command_handler(global_shortcut_all, "globalShortcut > all")] - fn unregister_all(context: InvokeContext) -> crate::Result<()> { - context - .window - .app_handle - .global_shortcut_manager() - .unregister_all()?; - Ok(()) - } - - #[module_command_handler(global_shortcut_all, "globalShortcut > all")] - fn is_registered(context: InvokeContext, shortcut: String) -> crate::Result { - Ok( - context - .window - .app_handle - .global_shortcut_manager() - .is_registered(&shortcut)?, - ) - } -} - -#[cfg(global_shortcut_all)] -fn register_shortcut( - window: crate::Window, - manager: &mut R::GlobalShortcutManager, - shortcut: String, - handler: CallbackFn, -) -> crate::Result<()> { - let accelerator = shortcut.clone(); - manager.register(&shortcut, move || { - let callback_string = crate::api::ipc::format_callback(handler, &accelerator) - .expect("unable to serialize shortcut string to json"); - let _ = window.eval(callback_string.as_str()); - })?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use crate::api::ipc::CallbackFn; - - #[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all")] - #[quickcheck_macros::quickcheck] - fn register(shortcut: String, handler: CallbackFn) { - let ctx = crate::test::mock_invoke_context(); - super::Cmd::register(ctx.clone(), shortcut.clone(), handler).unwrap(); - assert!(super::Cmd::is_registered(ctx, shortcut).unwrap()); - } - - #[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all")] - #[quickcheck_macros::quickcheck] - fn register_all(shortcuts: Vec, handler: CallbackFn) { - let ctx = crate::test::mock_invoke_context(); - super::Cmd::register_all(ctx.clone(), shortcuts.clone(), handler).unwrap(); - for shortcut in shortcuts { - assert!(super::Cmd::is_registered(ctx.clone(), shortcut).unwrap(),); - } - } - - #[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all")] - #[quickcheck_macros::quickcheck] - fn unregister(shortcut: String) { - let ctx = crate::test::mock_invoke_context(); - super::Cmd::register(ctx.clone(), shortcut.clone(), CallbackFn(0)).unwrap(); - super::Cmd::unregister(ctx.clone(), shortcut.clone()).unwrap(); - assert!(!super::Cmd::is_registered(ctx, shortcut).unwrap()); - } - - #[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all")] - #[quickcheck_macros::quickcheck] - fn unregister_all() { - let shortcuts = vec!["CTRL+X".to_string(), "SUPER+C".to_string(), "D".to_string()]; - let ctx = crate::test::mock_invoke_context(); - super::Cmd::register_all(ctx.clone(), shortcuts.clone(), CallbackFn(0)).unwrap(); - super::Cmd::unregister_all(ctx.clone()).unwrap(); - for shortcut in shortcuts { - assert!(!super::Cmd::is_registered(ctx.clone(), shortcut).unwrap(),); - } - } - - #[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all")] - #[quickcheck_macros::quickcheck] - fn is_registered(shortcut: String) { - let ctx = crate::test::mock_invoke_context(); - assert!(!super::Cmd::is_registered(ctx.clone(), shortcut.clone()).unwrap(),); - super::Cmd::register(ctx.clone(), shortcut.clone(), CallbackFn(0)).unwrap(); - assert!(super::Cmd::is_registered(ctx, shortcut).unwrap()); - } -} diff --git a/core/tauri/src/endpoints/http.rs b/core/tauri/src/endpoints/http.rs deleted file mode 100644 index 59c693316a2e..000000000000 --- a/core/tauri/src/endpoints/http.rs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -#[cfg(http_request)] -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; - -#[cfg(http_request)] -use crate::api::http::{ClientBuilder, HttpRequestBuilder, ResponseData}; -#[cfg(not(http_request))] -type ClientBuilder = (); -#[cfg(not(http_request))] -type HttpRequestBuilder = (); -#[cfg(not(http_request))] -type ResponseData = (); - -type ClientId = u32; -#[cfg(http_request)] -type ClientStore = Arc>>; - -#[cfg(http_request)] -fn clients() -> &'static ClientStore { - use once_cell::sync::Lazy; - static STORE: Lazy = Lazy::new(Default::default); - &STORE -} - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[cmd(async)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// Create a new HTTP client. - CreateClient { options: Option }, - /// Drop a HTTP client. - DropClient { client: ClientId }, - /// The HTTP request API. - HttpRequest { - client: ClientId, - options: Box, - }, -} - -impl Cmd { - #[module_command_handler(http_request, "http > request")] - async fn create_client( - _context: InvokeContext, - options: Option, - ) -> crate::Result { - let client = options.unwrap_or_default().build()?; - let mut store = clients().lock().unwrap(); - let id = rand::random::(); - store.insert(id, client); - Ok(id) - } - - #[module_command_handler(http_request, "http > request")] - async fn drop_client( - _context: InvokeContext, - client: ClientId, - ) -> crate::Result<()> { - let mut store = clients().lock().unwrap(); - store.remove(&client); - Ok(()) - } - - #[module_command_handler(http_request, "http > request")] - async fn http_request( - context: InvokeContext, - client_id: ClientId, - options: Box, - ) -> crate::Result { - use crate::Manager; - if context - .window - .state::() - .http - .is_allowed(&options.url) - { - let client = clients() - .lock() - .unwrap() - .get(&client_id) - .ok_or(crate::Error::HttpClientNotInitialized)? - .clone(); - let response = client.send(*options).await?; - Ok(response.read().await?) - } else { - Err(crate::Error::UrlNotAllowed(options.url)) - } - } -} - -#[cfg(test)] -mod tests { - use super::{ClientBuilder, ClientId}; - - #[tauri_macros::module_command_test(http_request, "http > request", async)] - #[quickcheck_macros::quickcheck] - fn create_client(options: Option) { - assert!(crate::async_runtime::block_on(super::Cmd::create_client( - crate::test::mock_invoke_context(), - options - )) - .is_ok()); - } - - #[tauri_macros::module_command_test(http_request, "http > request", async)] - #[quickcheck_macros::quickcheck] - fn drop_client(client_id: ClientId) { - crate::async_runtime::block_on(async move { - assert!( - super::Cmd::drop_client(crate::test::mock_invoke_context(), client_id) - .await - .is_ok() - ); - let id = super::Cmd::create_client(crate::test::mock_invoke_context(), None) - .await - .unwrap(); - assert!( - super::Cmd::drop_client(crate::test::mock_invoke_context(), id) - .await - .is_ok() - ); - }); - } -} diff --git a/core/tauri/src/endpoints/notification.rs b/core/tauri/src/endpoints/notification.rs deleted file mode 100644 index c0b4b78eb0a1..000000000000 --- a/core/tauri/src/endpoints/notification.rs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -#[cfg(notification_all)] -use crate::{api::notification::Notification, Env, Manager}; - -// `Granted` response from `request_permission`. Matches the Web API return value. -#[cfg(notification_all)] -const PERMISSION_GRANTED: &str = "granted"; -// `Denied` response from `request_permission`. Matches the Web API return value. -const PERMISSION_DENIED: &str = "denied"; - -/// The options for the notification API. -#[derive(Debug, Clone, Deserialize)] -pub struct NotificationOptions { - /// The notification title. - pub title: String, - /// The notification body. - pub body: Option, - /// The notification icon. - pub icon: Option, -} - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// The show notification API. - Notification { options: NotificationOptions }, - /// The request notification permission API. - RequestNotificationPermission, - /// The notification permission check API. - IsNotificationPermissionGranted, -} - -impl Cmd { - #[module_command_handler(notification_all, "notification > all")] - fn notification( - context: InvokeContext, - options: NotificationOptions, - ) -> crate::Result<()> { - let allowed = match is_permission_granted(&context) { - Some(p) => p, - None => request_permission(&context), - }; - if !allowed { - return Err(crate::Error::NotificationNotAllowed); - } - let mut notification = - Notification::new(context.config.tauri.bundle.identifier.clone()).title(options.title); - if let Some(body) = options.body { - notification = notification.body(body); - } - if let Some(icon) = options.icon { - notification = notification.icon(icon); - } - notification.show()?; - Ok(()) - } - - #[cfg(notification_all)] - fn request_notification_permission( - context: InvokeContext, - ) -> crate::Result<&'static str> { - if request_permission(&context) { - Ok(PERMISSION_GRANTED) - } else { - Ok(PERMISSION_DENIED) - } - } - - #[cfg(not(notification_all))] - fn request_notification_permission( - _context: InvokeContext, - ) -> crate::Result<&'static str> { - Ok(PERMISSION_DENIED) - } - - #[cfg(notification_all)] - fn is_notification_permission_granted( - context: InvokeContext, - ) -> crate::Result> { - if let Some(allow_notification) = is_permission_granted(&context) { - Ok(Some(allow_notification)) - } else { - Ok(None) - } - } - - #[cfg(not(notification_all))] - fn is_notification_permission_granted( - _context: InvokeContext, - ) -> crate::Result> { - Ok(Some(false)) - } -} - -#[cfg(notification_all)] -fn request_permission(context: &InvokeContext) -> bool { - let mut settings = crate::settings::read_settings( - &context.config, - &context.package_info, - context.window.state::().inner(), - ); - if let Some(allow_notification) = settings.allow_notification { - return allow_notification; - } - let answer = crate::api::dialog::blocking::ask( - Some(&context.window), - "Permissions", - "This app wants to show notifications. Do you allow?", - ); - - settings.allow_notification = Some(answer); - let _ = crate::settings::write_settings( - &context.config, - &context.package_info, - context.window.state::().inner(), - settings, - ); - - answer -} - -#[cfg(notification_all)] -fn is_permission_granted(context: &InvokeContext) -> Option { - crate::settings::read_settings( - &context.config, - &context.package_info, - context.window.state::().inner(), - ) - .allow_notification -} - -#[cfg(test)] -mod tests { - use super::NotificationOptions; - - use quickcheck::{Arbitrary, Gen}; - - impl Arbitrary for NotificationOptions { - fn arbitrary(g: &mut Gen) -> Self { - Self { - title: String::arbitrary(g), - body: Option::arbitrary(g), - icon: Option::arbitrary(g), - } - } - } - - #[cfg(not(notification_all))] - #[test] - fn request_notification_permission() { - assert_eq!( - super::Cmd::request_notification_permission(crate::test::mock_invoke_context()).unwrap(), - super::PERMISSION_DENIED - ) - } - - #[cfg(not(notification_all))] - #[test] - fn is_notification_permission_granted() { - assert_eq!( - super::Cmd::is_notification_permission_granted(crate::test::mock_invoke_context()).unwrap(), - Some(false) - ); - } - - #[tauri_macros::module_command_test(notification_all, "notification > all")] - #[quickcheck_macros::quickcheck] - fn notification(_options: NotificationOptions) {} -} diff --git a/core/tauri/src/endpoints/operating_system.rs b/core/tauri/src/endpoints/operating_system.rs deleted file mode 100644 index f5d373b5e88f..000000000000 --- a/core/tauri/src/endpoints/operating_system.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -use crate::Runtime; -use serde::Deserialize; -use std::path::PathBuf; -use tauri_macros::{module_command_handler, CommandModule}; - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - Platform, - Version, - OsType, - Arch, - Tempdir, -} - -impl Cmd { - #[module_command_handler(os_all, "os > all")] - fn platform(_context: InvokeContext) -> crate::Result<&'static str> { - Ok(os_platform()) - } - - #[module_command_handler(os_all, "os > all")] - fn version(_context: InvokeContext) -> crate::Result { - Ok(os_info::get().version().to_string()) - } - - #[module_command_handler(os_all, "os > all")] - fn os_type(_context: InvokeContext) -> crate::Result<&'static str> { - Ok(os_type()) - } - - #[module_command_handler(os_all, "os > all")] - fn arch(_context: InvokeContext) -> crate::Result<&'static str> { - Ok(std::env::consts::ARCH) - } - - #[module_command_handler(os_all, "os > all")] - fn tempdir(_context: InvokeContext) -> crate::Result { - Ok(std::env::temp_dir()) - } -} - -#[cfg(os_all)] -fn os_type() -> &'static str { - #[cfg(target_os = "linux")] - return "Linux"; - #[cfg(target_os = "windows")] - return "Windows_NT"; - #[cfg(target_os = "macos")] - return "Darwin"; -} -#[cfg(os_all)] -fn os_platform() -> &'static str { - match std::env::consts::OS { - "windows" => "win32", - "macos" => "darwin", - _ => std::env::consts::OS, - } -} - -#[cfg(test)] -mod tests { - #[tauri_macros::module_command_test(os_all, "os > all")] - #[quickcheck_macros::quickcheck] - fn platform() {} - - #[tauri_macros::module_command_test(os_all, "os > all")] - #[quickcheck_macros::quickcheck] - fn version() {} - - #[tauri_macros::module_command_test(os_all, "os > all")] - #[quickcheck_macros::quickcheck] - fn os_type() {} - - #[tauri_macros::module_command_test(os_all, "os > all")] - #[quickcheck_macros::quickcheck] - fn arch() {} - - #[tauri_macros::module_command_test(os_all, "os > all")] - #[quickcheck_macros::quickcheck] - fn tempdir() {} -} diff --git a/core/tauri/src/endpoints/path.rs b/core/tauri/src/endpoints/path.rs deleted file mode 100644 index 71f69ca8eeed..000000000000 --- a/core/tauri/src/endpoints/path.rs +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::{api::path::BaseDirectory, Runtime}; -#[cfg(path_all)] -use crate::{Env, Manager}; -use std::path::PathBuf; -#[cfg(path_all)] -use std::path::{Component, Path, MAIN_SEPARATOR}; - -use super::InvokeContext; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - ResolvePath { - path: String, - directory: Option, - }, - Resolve { - paths: Vec, - }, - Normalize { - path: String, - }, - Join { - paths: Vec, - }, - Dirname { - path: String, - }, - Extname { - path: String, - }, - Basename { - path: String, - ext: Option, - }, - IsAbsolute { - path: String, - }, -} - -impl Cmd { - #[module_command_handler(path_all, "path > all")] - fn resolve_path( - context: InvokeContext, - path: String, - directory: Option, - ) -> crate::Result { - crate::api::path::resolve_path( - &context.config, - &context.package_info, - context.window.state::().inner(), - path, - directory, - ) - .map_err(Into::into) - } - - #[module_command_handler(path_all, "path > all")] - fn resolve(_context: InvokeContext, paths: Vec) -> crate::Result { - // Start with current directory then start adding paths from the vector one by one using `PathBuf.push()` which - // will ensure that if an absolute path is encountered in the iteration, it will be used as the current full path. - // - // examples: - // 1. `vec!["."]` or `vec![]` will be equal to `std::env::current_dir()` - // 2. `vec!["/foo/bar", "/tmp/file", "baz"]` will be equal to `PathBuf::from("/tmp/file/baz")` - let mut path = std::env::current_dir()?; - for p in paths { - path.push(p); - } - Ok(normalize_path(&path)) - } - - #[module_command_handler(path_all, "path > all")] - fn normalize(_context: InvokeContext, path: String) -> crate::Result { - let mut p = normalize_path_no_absolute(Path::new(&path)) - .to_string_lossy() - .to_string(); - Ok( - // Node.js behavior is to return `".."` for `normalize("..")` - // and `"."` for `normalize("")` or `normalize(".")` - if p.is_empty() && path == ".." { - "..".into() - } else if p.is_empty() && path == "." { - ".".into() - } else { - // Add a trailing separator if the path passed to this functions had a trailing separator. That's how Node.js behaves. - if (path.ends_with('/') || path.ends_with('\\')) - && (!p.ends_with('/') || !p.ends_with('\\')) - { - p.push(MAIN_SEPARATOR); - } - p - }, - ) - } - - #[module_command_handler(path_all, "path > all")] - fn join(_context: InvokeContext, mut paths: Vec) -> crate::Result { - let path = PathBuf::from( - paths - .iter_mut() - .map(|p| { - // Add a `MAIN_SEPARATOR` if it doesn't already have one. - // Doing this to ensure that the vector elements are separated in - // the resulting string so path.components() can work correctly when called - // in `normalize_path_no_absolute()` later on. - if !p.ends_with('/') && !p.ends_with('\\') { - p.push(MAIN_SEPARATOR); - } - p.to_string() - }) - .collect::(), - ); - - let p = normalize_path_no_absolute(&path) - .to_string_lossy() - .to_string(); - Ok(if p.is_empty() { ".".into() } else { p }) - } - - #[module_command_handler(path_all, "path > all")] - fn dirname(_context: InvokeContext, path: String) -> crate::Result { - match Path::new(&path).parent() { - Some(p) => Ok(p.to_path_buf()), - None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path( - "Couldn't get the parent directory".into(), - ))), - } - } - - #[module_command_handler(path_all, "path > all")] - fn extname(_context: InvokeContext, path: String) -> crate::Result { - match Path::new(&path) - .extension() - .and_then(std::ffi::OsStr::to_str) - { - Some(p) => Ok(p.to_string()), - None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path( - "Couldn't get the extension of the file".into(), - ))), - } - } - - #[module_command_handler(path_all, "path > all")] - fn basename( - _context: InvokeContext, - path: String, - ext: Option, - ) -> crate::Result { - match Path::new(&path) - .file_name() - .and_then(std::ffi::OsStr::to_str) - { - Some(p) => Ok(if let Some(ext) = ext { - p.replace(ext.as_str(), "") - } else { - p.to_string() - }), - None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path( - "Couldn't get the basename".into(), - ))), - } - } - - #[module_command_handler(path_all, "path > all")] - fn is_absolute(_context: InvokeContext, path: String) -> crate::Result { - Ok(Path::new(&path).is_absolute()) - } -} - -/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util. -/// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106 -#[cfg(path_all)] -fn normalize_path(path: &Path) -> PathBuf { - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - PathBuf::from(c.as_os_str()) - } else { - PathBuf::new() - }; - - for component in components { - match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(component.as_os_str()); - } - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - ret.push(c); - } - } - } - ret -} - -/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util but -/// slightly modified to not resolve absolute paths. -/// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106 -#[cfg(path_all)] -fn normalize_path_no_absolute(path: &Path) -> PathBuf { - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - PathBuf::from(c.as_os_str()) - } else { - PathBuf::new() - }; - - for component in components { - match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(component.as_os_str()); - } - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - // Using PathBuf::push here will replace the whole path if an absolute path is encountered - // which is not the intended behavior, so instead of that, convert the current resolved path - // to a string and do simple string concatenation with the current component then convert it - // back to a PathBuf - let mut p = ret.to_string_lossy().to_string(); - // Only add a separator if it doesn't have one already or if current normalized path is empty, - // this ensures it won't have an unwanted leading separator - if !p.is_empty() && !p.ends_with('/') && !p.ends_with('\\') { - p.push(MAIN_SEPARATOR); - } - if let Some(c) = c.to_str() { - p.push_str(c); - } - ret = PathBuf::from(p); - } - } - } - ret -} - -#[cfg(test)] -mod tests { - use crate::api::path::BaseDirectory; - - #[tauri_macros::module_command_test(path_all, "path > all")] - #[quickcheck_macros::quickcheck] - fn resolve_path(_path: String, _directory: Option) {} - - #[tauri_macros::module_command_test(path_all, "path > all")] - #[quickcheck_macros::quickcheck] - fn resolve(_paths: Vec) {} - - #[tauri_macros::module_command_test(path_all, "path > all")] - #[quickcheck_macros::quickcheck] - fn normalize(_path: String) {} - - #[tauri_macros::module_command_test(path_all, "path > all")] - #[quickcheck_macros::quickcheck] - fn join(_paths: Vec) {} - - #[tauri_macros::module_command_test(path_all, "path > all")] - #[quickcheck_macros::quickcheck] - fn dirname(_path: String) {} - - #[tauri_macros::module_command_test(path_all, "path > all")] - #[quickcheck_macros::quickcheck] - fn extname(_path: String) {} - - #[tauri_macros::module_command_test(path_all, "path > all")] - #[quickcheck_macros::quickcheck] - fn basename(_path: String, _ext: Option) {} - - #[tauri_macros::module_command_test(path_all, "path > all")] - #[quickcheck_macros::quickcheck] - fn is_absolute(_path: String) {} -} diff --git a/core/tauri/src/endpoints/process.rs b/core/tauri/src/endpoints/process.rs deleted file mode 100644 index be0495460c77..000000000000 --- a/core/tauri/src/endpoints/process.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -#[cfg(process_relaunch)] -use crate::Manager; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// Relaunch application - Relaunch, - /// Close application with provided exit_code - #[serde(rename_all = "camelCase")] - Exit { exit_code: i32 }, -} - -impl Cmd { - #[module_command_handler(process_relaunch, "process > relaunch")] - fn relaunch(context: InvokeContext) -> crate::Result<()> { - crate::api::process::restart(&context.window.state()); - Ok(()) - } - - #[module_command_handler(process_exit, "process > exit")] - fn exit(_context: InvokeContext, exit_code: i32) -> crate::Result<()> { - // would be great if we can have a handler inside tauri - // who close all window and emit an event that user can catch - // if they want to process something before closing the app - std::process::exit(exit_code); - } -} - -#[cfg(test)] -mod tests { - #[tauri_macros::module_command_test(process_relaunch, "process > relaunch")] - #[quickcheck_macros::quickcheck] - fn relaunch() {} - - #[tauri_macros::module_command_test(process_exit, "process > exit")] - #[quickcheck_macros::quickcheck] - fn exit(_exit_code: i32) {} -} diff --git a/core/tauri/src/endpoints/shell.rs b/core/tauri/src/endpoints/shell.rs deleted file mode 100644 index e1c4082e20d1..000000000000 --- a/core/tauri/src/endpoints/shell.rs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::InvokeContext; -use crate::{api::ipc::CallbackFn, Runtime}; -#[cfg(shell_scope)] -use crate::{Manager, Scopes}; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -#[cfg(shell_scope)] -use crate::ExecuteArgs; -#[cfg(not(shell_scope))] -type ExecuteArgs = (); - -#[cfg(any(shell_execute, shell_sidecar))] -use std::sync::{Arc, Mutex}; -use std::{collections::HashMap, path::PathBuf}; - -type ChildId = u32; -#[cfg(any(shell_execute, shell_sidecar))] -type ChildStore = Arc>>; - -#[cfg(any(shell_execute, shell_sidecar))] -fn command_childs() -> &'static ChildStore { - use once_cell::sync::Lazy; - static STORE: Lazy = Lazy::new(Default::default); - &STORE -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -pub enum Buffer { - Text(String), - Raw(Vec), -} - -#[allow(clippy::unnecessary_wraps)] -fn default_env() -> Option> { - Some(HashMap::default()) -} - -#[allow(dead_code)] -#[derive(Debug, Clone, Default, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CommandOptions { - #[serde(default)] - sidecar: bool, - cwd: Option, - // by default we don't add any env variables to the spawned process - // but the env is an `Option` so when it's `None` we clear the env. - #[serde(default = "default_env")] - env: Option>, -} - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// The execute script API. - #[serde(rename_all = "camelCase")] - Execute { - program: String, - args: ExecuteArgs, - on_event_fn: CallbackFn, - #[serde(default)] - options: CommandOptions, - }, - StdinWrite { - pid: ChildId, - buffer: Buffer, - }, - KillChild { - pid: ChildId, - }, - Open { - path: String, - with: Option, - }, -} - -impl Cmd { - #[allow(unused_variables)] - fn execute( - context: InvokeContext, - program: String, - args: ExecuteArgs, - on_event_fn: CallbackFn, - options: CommandOptions, - ) -> crate::Result { - let mut command = if options.sidecar { - #[cfg(not(shell_sidecar))] - return Err(crate::Error::ApiNotAllowlisted( - "shell > sidecar".to_string(), - )); - #[cfg(shell_sidecar)] - { - let program = PathBuf::from(program); - let program_as_string = program.display().to_string(); - let program_no_ext_as_string = program.with_extension("").display().to_string(); - let is_configured = context - .config - .tauri - .bundle - .external_bin - .as_ref() - .map(|bins| { - bins - .iter() - .any(|b| b == &program_as_string || b == &program_no_ext_as_string) - }) - .unwrap_or_default(); - if is_configured { - context - .window - .state::() - .shell - .prepare(&program.to_string_lossy(), args, true) - .map_err(Box::new)? - } else { - return Err(crate::Error::SidecarNotAllowed(program)); - } - } - } else { - #[cfg(not(shell_execute))] - return Err(crate::Error::ApiNotAllowlisted( - "shell > execute".to_string(), - )); - #[cfg(shell_execute)] - match context - .window - .state::() - .shell - .prepare(&program, args, false) - { - Ok(cmd) => cmd, - Err(e) => { - #[cfg(debug_assertions)] - eprintln!("{}", e); - return Err(crate::Error::ProgramNotAllowed(PathBuf::from(program))); - } - } - }; - #[cfg(any(shell_execute, shell_sidecar))] - { - if let Some(cwd) = options.cwd { - command = command.current_dir(cwd); - } - if let Some(env) = options.env { - command = command.envs(env); - } else { - command = command.env_clear(); - } - let (mut rx, child) = command.spawn()?; - - let pid = child.pid(); - command_childs().lock().unwrap().insert(pid, child); - - crate::async_runtime::spawn(async move { - while let Some(event) = rx.recv().await { - if matches!(event, crate::api::process::CommandEvent::Terminated(_)) { - command_childs().lock().unwrap().remove(&pid); - } - let js = crate::api::ipc::format_callback(on_event_fn, &event) - .expect("unable to serialize CommandEvent"); - - let _ = context.window.eval(js.as_str()); - } - }); - - Ok(pid) - } - } - - #[cfg(any(shell_execute, shell_sidecar))] - fn stdin_write( - _context: InvokeContext, - pid: ChildId, - buffer: Buffer, - ) -> crate::Result<()> { - if let Some(child) = command_childs().lock().unwrap().get_mut(&pid) { - match buffer { - Buffer::Text(t) => child.write(t.as_bytes())?, - Buffer::Raw(r) => child.write(&r)?, - } - } - Ok(()) - } - - #[cfg(not(any(shell_execute, shell_sidecar)))] - fn stdin_write( - _context: InvokeContext, - _pid: ChildId, - _buffer: Buffer, - ) -> crate::Result<()> { - Err(crate::Error::ApiNotAllowlisted( - "shell > execute or shell > sidecar".into(), - )) - } - - #[cfg(any(shell_execute, shell_sidecar))] - fn kill_child(_context: InvokeContext, pid: ChildId) -> crate::Result<()> { - if let Some(child) = command_childs().lock().unwrap().remove(&pid) { - child.kill()?; - } - Ok(()) - } - - #[cfg(not(any(shell_execute, shell_sidecar)))] - fn kill_child(_context: InvokeContext, _pid: ChildId) -> crate::Result<()> { - Err(crate::Error::ApiNotAllowlisted( - "shell > execute or shell > sidecar".into(), - )) - } - - /// Open a (url) path with a default or specific browser opening program. - /// - /// See [`crate::api::shell::open`] for how it handles security-related measures. - #[module_command_handler(shell_open, "shell > open")] - fn open( - context: InvokeContext, - path: String, - with: Option, - ) -> crate::Result<()> { - use std::str::FromStr; - - with - .as_deref() - // only allow pre-determined programs to be specified - .map(crate::api::shell::Program::from_str) - .transpose() - .map_err(Into::into) - // validate and open path - .and_then(|with| { - crate::api::shell::open(&context.window.state::().shell, path, with) - .map_err(Into::into) - }) - } -} - -#[cfg(test)] -mod tests { - use super::{Buffer, ChildId, CommandOptions, ExecuteArgs}; - use crate::api::ipc::CallbackFn; - use quickcheck::{Arbitrary, Gen}; - - impl Arbitrary for CommandOptions { - fn arbitrary(g: &mut Gen) -> Self { - Self { - sidecar: false, - cwd: Option::arbitrary(g), - env: Option::arbitrary(g), - } - } - } - - impl Arbitrary for Buffer { - fn arbitrary(g: &mut Gen) -> Self { - Buffer::Text(String::arbitrary(g)) - } - } - - #[cfg(shell_scope)] - impl Arbitrary for ExecuteArgs { - fn arbitrary(_: &mut Gen) -> Self { - ExecuteArgs::None - } - } - - #[tauri_macros::module_command_test(shell_execute, "shell > execute")] - #[quickcheck_macros::quickcheck] - fn execute( - _program: String, - _args: ExecuteArgs, - _on_event_fn: CallbackFn, - _options: CommandOptions, - ) { - } - - #[tauri_macros::module_command_test(shell_execute, "shell > execute or shell > sidecar")] - #[quickcheck_macros::quickcheck] - fn stdin_write(_pid: ChildId, _buffer: Buffer) {} - - #[tauri_macros::module_command_test(shell_execute, "shell > execute or shell > sidecar")] - #[quickcheck_macros::quickcheck] - fn kill_child(_pid: ChildId) {} - - #[tauri_macros::module_command_test(shell_open, "shell > open")] - #[quickcheck_macros::quickcheck] - fn open(_path: String, _with: Option) {} -} diff --git a/core/tauri/src/endpoints/window.rs b/core/tauri/src/endpoints/window.rs deleted file mode 100644 index 1f33963e9d42..000000000000 --- a/core/tauri/src/endpoints/window.rs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::{InvokeContext, InvokeResponse}; -#[cfg(window_create)] -use crate::runtime::{webview::WindowBuilder, Dispatch}; -use crate::{ - runtime::{ - window::dpi::{Position, Size}, - Runtime, UserAttentionType, - }, - utils::config::WindowConfig, - Manager, -}; -use serde::Deserialize; -use tauri_macros::{module_command_handler, CommandModule}; - -use crate::runtime::Icon; -use std::path::PathBuf; - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum IconDto { - File(PathBuf), - Raw(Vec), -} - -impl From for Icon { - fn from(icon: IconDto) -> Self { - match icon { - IconDto::File(path) => Self::File(path), - IconDto::Raw(raw) => Self::Raw(raw), - } - } -} - -/// Window management API descriptor. -#[derive(Deserialize)] -#[serde(tag = "type", content = "payload", rename_all = "camelCase")] -pub enum WindowManagerCmd { - // Getters - ScaleFactor, - InnerPosition, - OuterPosition, - InnerSize, - OuterSize, - IsFullscreen, - IsMaximized, - IsDecorated, - IsResizable, - IsVisible, - CurrentMonitor, - PrimaryMonitor, - AvailableMonitors, - // Setters - Center, - RequestUserAttention(Option), - SetResizable(bool), - SetTitle(String), - Maximize, - Unmaximize, - ToggleMaximize, - Minimize, - Unminimize, - Show, - Hide, - Close, - SetDecorations(bool), - #[serde(rename_all = "camelCase")] - SetAlwaysOnTop(bool), - SetSize(Size), - SetMinSize(Option), - SetMaxSize(Option), - SetPosition(Position), - SetFullscreen(bool), - SetFocus, - SetIcon { - icon: IconDto, - }, - SetSkipTaskbar(bool), - StartDragging, - Print, - // internals - #[serde(rename = "__toggleMaximize")] - InternalToggleMaximize, -} - -impl WindowManagerCmd { - fn into_allowlist_error(self) -> crate::Error { - match self { - Self::Center => crate::Error::ApiNotAllowlisted("window > center".to_string()), - Self::RequestUserAttention(_) => { - crate::Error::ApiNotAllowlisted("window > requestUserAttention".to_string()) - } - Self::SetResizable(_) => crate::Error::ApiNotAllowlisted("window > setResizable".to_string()), - Self::SetTitle(_) => crate::Error::ApiNotAllowlisted("window > setTitle".to_string()), - Self::Maximize => crate::Error::ApiNotAllowlisted("window > maximize".to_string()), - Self::Unmaximize => crate::Error::ApiNotAllowlisted("window > unmaximize".to_string()), - Self::ToggleMaximize => { - crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string()) - } - Self::Minimize => crate::Error::ApiNotAllowlisted("window > minimize".to_string()), - Self::Unminimize => crate::Error::ApiNotAllowlisted("window > unminimize".to_string()), - Self::Show => crate::Error::ApiNotAllowlisted("window > show".to_string()), - Self::Hide => crate::Error::ApiNotAllowlisted("window > hide".to_string()), - Self::Close => crate::Error::ApiNotAllowlisted("window > close".to_string()), - Self::SetDecorations(_) => { - crate::Error::ApiNotAllowlisted("window > setDecorations".to_string()) - } - Self::SetAlwaysOnTop(_) => { - crate::Error::ApiNotAllowlisted("window > setAlwaysOnTop".to_string()) - } - Self::SetSize(_) => crate::Error::ApiNotAllowlisted("window > setSize".to_string()), - Self::SetMinSize(_) => crate::Error::ApiNotAllowlisted("window > setMinSize".to_string()), - Self::SetMaxSize(_) => crate::Error::ApiNotAllowlisted("window > setMaxSize".to_string()), - Self::SetPosition(_) => crate::Error::ApiNotAllowlisted("window > setPosition".to_string()), - Self::SetFullscreen(_) => { - crate::Error::ApiNotAllowlisted("window > setFullscreen".to_string()) - } - Self::SetIcon { .. } => crate::Error::ApiNotAllowlisted("window > setIcon".to_string()), - Self::SetSkipTaskbar(_) => { - crate::Error::ApiNotAllowlisted("window > setSkipTaskbar".to_string()) - } - Self::StartDragging => crate::Error::ApiNotAllowlisted("window > startDragging".to_string()), - Self::Print => crate::Error::ApiNotAllowlisted("window > print".to_string()), - Self::InternalToggleMaximize => { - crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string()) - } - _ => crate::Error::ApiNotAllowlisted("window > all".to_string()), - } - } -} - -/// The API descriptor. -#[derive(Deserialize, CommandModule)] -#[cmd(async)] -#[serde(tag = "cmd", content = "data", rename_all = "camelCase")] -pub enum Cmd { - CreateWebview { - options: Box, - }, - Manage { - label: Option, - cmd: WindowManagerCmd, - }, -} - -impl Cmd { - #[module_command_handler(window_create, "window > create")] - async fn create_webview( - context: InvokeContext, - options: Box, - ) -> crate::Result<()> { - let mut window = context.window; - let label = options.label.clone(); - let url = options.url.clone(); - - window.create_window(label, url, |_, webview_attributes| { - ( - <::WindowBuilder>::with_config(*options), - webview_attributes, - ) - })?; - - Ok(()) - } - - async fn manage( - context: InvokeContext, - label: Option, - cmd: WindowManagerCmd, - ) -> crate::Result { - let window = match label { - Some(l) if !l.is_empty() => context - .window - .get_window(&l) - .ok_or(crate::Error::WebviewNotFound)?, - _ => context.window, - }; - match cmd { - // Getters - WindowManagerCmd::ScaleFactor => return Ok(window.scale_factor()?.into()), - WindowManagerCmd::InnerPosition => return Ok(window.inner_position()?.into()), - WindowManagerCmd::OuterPosition => return Ok(window.outer_position()?.into()), - WindowManagerCmd::InnerSize => return Ok(window.inner_size()?.into()), - WindowManagerCmd::OuterSize => return Ok(window.outer_size()?.into()), - WindowManagerCmd::IsFullscreen => return Ok(window.is_fullscreen()?.into()), - WindowManagerCmd::IsMaximized => return Ok(window.is_maximized()?.into()), - WindowManagerCmd::IsDecorated => return Ok(window.is_decorated()?.into()), - WindowManagerCmd::IsResizable => return Ok(window.is_resizable()?.into()), - WindowManagerCmd::IsVisible => return Ok(window.is_visible()?.into()), - WindowManagerCmd::CurrentMonitor => return Ok(window.current_monitor()?.into()), - WindowManagerCmd::PrimaryMonitor => return Ok(window.primary_monitor()?.into()), - WindowManagerCmd::AvailableMonitors => return Ok(window.available_monitors()?.into()), - // Setters - #[cfg(window_center)] - WindowManagerCmd::Center => window.center()?, - #[cfg(window_request_user_attention)] - WindowManagerCmd::RequestUserAttention(request_type) => { - window.request_user_attention(request_type)? - } - #[cfg(window_set_resizable)] - WindowManagerCmd::SetResizable(resizable) => window.set_resizable(resizable)?, - #[cfg(window_set_title)] - WindowManagerCmd::SetTitle(title) => window.set_title(&title)?, - #[cfg(window_maximize)] - WindowManagerCmd::Maximize => window.maximize()?, - #[cfg(window_unmaximize)] - WindowManagerCmd::Unmaximize => window.unmaximize()?, - #[cfg(all(window_maximize, window_unmaximize))] - WindowManagerCmd::ToggleMaximize => match window.is_maximized()? { - true => window.unmaximize()?, - false => window.maximize()?, - }, - #[cfg(window_minimize)] - WindowManagerCmd::Minimize => window.minimize()?, - #[cfg(window_unminimize)] - WindowManagerCmd::Unminimize => window.unminimize()?, - #[cfg(window_show)] - WindowManagerCmd::Show => window.show()?, - #[cfg(window_hide)] - WindowManagerCmd::Hide => window.hide()?, - #[cfg(window_close)] - WindowManagerCmd::Close => window.close()?, - #[cfg(window_set_decorations)] - WindowManagerCmd::SetDecorations(decorations) => window.set_decorations(decorations)?, - #[cfg(window_set_always_on_top)] - WindowManagerCmd::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top)?, - #[cfg(window_set_size)] - WindowManagerCmd::SetSize(size) => window.set_size(size)?, - #[cfg(window_set_min_size)] - WindowManagerCmd::SetMinSize(size) => window.set_min_size(size)?, - #[cfg(window_set_max_size)] - WindowManagerCmd::SetMaxSize(size) => window.set_max_size(size)?, - #[cfg(window_set_position)] - WindowManagerCmd::SetPosition(position) => window.set_position(position)?, - #[cfg(window_set_fullscreen)] - WindowManagerCmd::SetFullscreen(fullscreen) => window.set_fullscreen(fullscreen)?, - #[cfg(window_set_focus)] - WindowManagerCmd::SetFocus => window.set_focus()?, - #[cfg(window_set_icon)] - WindowManagerCmd::SetIcon { icon } => window.set_icon(icon.into())?, - #[cfg(window_set_skip_taskbar)] - WindowManagerCmd::SetSkipTaskbar(skip) => window.set_skip_taskbar(skip)?, - #[cfg(window_start_dragging)] - WindowManagerCmd::StartDragging => window.start_dragging()?, - #[cfg(window_print)] - WindowManagerCmd::Print => window.print()?, - // internals - #[cfg(all(window_maximize, window_unmaximize))] - WindowManagerCmd::InternalToggleMaximize => { - if window.is_resizable()? { - match window.is_maximized()? { - true => window.unmaximize()?, - false => window.maximize()?, - } - } - } - #[allow(unreachable_patterns)] - _ => return Err(cmd.into_allowlist_error()), - } - #[allow(unreachable_code)] - Ok(().into()) - } -} diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index 8ee0c913d3af..03ad98f8900a 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -1,8 +1,31 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::path::PathBuf; +use std::fmt; + +/// A generic boxed error. +#[derive(Debug)] +pub struct SetupError(Box); + +impl From> for SetupError { + fn from(error: Box) -> Self { + Self(error) + } +} + +// safety: the setup error is only used on the main thread +// and we exit the process immediately. +unsafe impl Send for SetupError {} +unsafe impl Sync for SetupError {} + +impl fmt::Display for SetupError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for SetupError {} /// Runtime errors that can happen inside a Tauri application. #[derive(Debug, thiserror::Error)] @@ -11,95 +34,40 @@ pub enum Error { /// Runtime error. #[error("runtime error: {0}")] Runtime(#[from] tauri_runtime::Error), - /// Failed to create webview. - #[error("failed to create webview: {0}")] - CreateWebview(Box), - /// Failed to create window. - #[error("failed to create window")] - CreateWindow, /// Window label must be unique. #[error("a window with label `{0}` already exists")] WindowLabelAlreadyExists(String), - /// Can't access webview dispatcher because the webview was closed or not found. - #[error("webview not found: invalid label or it was closed")] - WebviewNotFound, - /// Failed to send message to webview. - #[error("failed to send message to the webview")] - FailedToSendMessage, /// Embedded asset not found. #[error("asset not found: {0}")] AssetNotFound(String), /// Failed to serialize/deserialize. #[error("JSON error: {0}")] - Json(serde_json::Error), - /// Unknown API type. - #[error("unknown API: {0:?}")] - UnknownApi(Option), + Json(#[from] serde_json::Error), /// Failed to execute tauri API. #[error("failed to execute API: {0}")] FailedToExecuteApi(#[from] crate::api::Error), /// IO error. #[error("{0}")] Io(#[from] std::io::Error), - /// Failed to decode base64. - #[cfg(feature = "updater")] - #[error("Failed to decode base64 string: {0}")] - Base64Decode(#[from] base64::DecodeError), /// Failed to load window icon. #[error("invalid icon: {0}")] - InvalidIcon(Box), - /// Client with specified ID not found. - #[error("http client dropped or not initialized")] - HttpClientNotInitialized, - /// API not enabled by Tauri. - #[error("{0}")] - ApiNotEnabled(String), - /// API not whitelisted on tauri.conf.json - #[error("'{0}' not on the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)")] - ApiNotAllowlisted(String), + InvalidIcon(std::io::Error), /// Invalid args when running a command. - #[error("invalid args for command `{0}`: {1}")] - InvalidArgs(&'static str, serde_json::Error), + #[error("invalid args `{1}` for command `{0}`: {2}")] + InvalidArgs(&'static str, &'static str, serde_json::Error), /// Encountered an error in the setup hook, #[error("error encountered during setup hook: {0}")] - Setup(Box), - /// Tauri updater error. - #[cfg(feature = "updater")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))] - #[error("Updater: {0}")] - TauriUpdater(#[from] crate::updater::Error), + Setup(SetupError), /// Error initializing plugin. #[error("failed to initialize plugin `{0}`: {1}")] PluginInitialization(String, String), - /// Encountered an error creating the app system tray, - #[error("error encountered during tray setup: {0}")] - SystemTray(Box), /// A part of the URL is malformed or invalid. This may occur when parsing and combining /// user-provided URLs and paths. #[error("invalid url: {0}")] InvalidUrl(url::ParseError), /// Task join error. #[error(transparent)] - JoinError(Box), - /// Path not allowed by the scope. - #[error("path not allowed on the configured scope: {0}")] - PathNotAllowed(PathBuf), - /// The user did not allow sending notifications. - #[error("sending notification was not allowed by the user")] - NotificationNotAllowed, - /// URL not allowed by the scope. - #[error("url not allowed on the configured scope: {0}")] - UrlNotAllowed(url::Url), - /// Sidecar not allowed by the configuration. - #[error("sidecar not configured under `tauri.conf.json > tauri > bundle > externalBin`: {0}")] - SidecarNotAllowed(PathBuf), - /// Sidecar was not found by the configuration. - #[cfg(shell_scope)] - #[error("sidecar configuration found, but unable to create a path to it: {0}")] - SidecarNotFound(#[from] Box), - /// Program not allowed by the scope. - #[error("program not allowed on the configured shell scope: {0}")] - ProgramNotAllowed(PathBuf), + JoinError(#[from] tokio::task::JoinError), /// An error happened inside the isolation pattern. #[cfg(feature = "isolation")] #[error("isolation pattern error: {0}")] @@ -107,14 +75,18 @@ pub enum Error { /// An invalid window URL was provided. Includes details about the error. #[error("invalid window url: {0}")] InvalidWindowUrl(&'static str), -} - -impl From for Error { - fn from(error: serde_json::Error) -> Self { - if error.to_string().contains("unknown variant") { - Self::UnknownApi(Some(error)) - } else { - Self::Json(error) - } - } + /// Invalid glob pattern. + #[error("invalid glob pattern: {0}")] + GlobPattern(#[from] glob::PatternError), + /// Error decoding PNG image. + #[cfg(feature = "icon-png")] + #[error("failed to decode PNG: {0}")] + PngDecode(#[from] png::DecodingError), + /// The Window's raw handle is invalid for the platform. + #[error("Unexpected `raw_window_handle` for the current platform")] + InvalidWindowHandle, + /// JNI error. + #[cfg(target_os = "android")] + #[error("jni error: {0}")] + Jni(#[from] jni::errors::Error), } diff --git a/core/tauri/src/event.rs b/core/tauri/src/event.rs deleted file mode 100644 index 82d125562933..000000000000 --- a/core/tauri/src/event.rs +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::{ - boxed::Box, - collections::HashMap, - fmt, - hash::Hash, - sync::{Arc, Mutex}, -}; -use uuid::Uuid; - -/// Checks if an event name is valid. -pub fn is_event_name_valid(event: &str) -> bool { - event - .chars() - .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_') -} - -pub fn assert_event_name_is_valid(event: &str) { - assert!( - is_event_name_valid(event), - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." - ); -} - -/// Represents an event handler. -#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct EventHandler(Uuid); - -impl fmt::Display for EventHandler { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -/// An event that was triggered. -#[derive(Debug, Clone)] -pub struct Event { - id: EventHandler, - data: Option, -} - -impl Event { - /// The [`EventHandler`] that was triggered. - pub fn id(&self) -> EventHandler { - self.id - } - - /// The event payload. - pub fn payload(&self) -> Option<&str> { - self.data.as_deref() - } -} - -/// What to do with the pending handler when resolving it? -enum Pending { - Unlisten(EventHandler), - Listen(EventHandler, String, Handler), - Trigger(String, Option, Option), -} - -/// Stored in [`Listeners`] to be called upon when the event that stored it is triggered. -struct Handler { - window: Option, - callback: Box, -} - -/// Holds event handlers and pending event handlers, along with the salts associating them. -struct InnerListeners { - handlers: Mutex>>, - pending: Mutex>, - function_name: Uuid, - listeners_object_name: Uuid, -} - -/// A self-contained event manager. -pub(crate) struct Listeners { - inner: Arc, -} - -impl Default for Listeners { - fn default() -> Self { - Self { - inner: Arc::new(InnerListeners { - handlers: Mutex::default(), - pending: Mutex::default(), - function_name: Uuid::new_v4(), - listeners_object_name: Uuid::new_v4(), - }), - } - } -} - -impl Clone for Listeners { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } -} - -impl Listeners { - /// Randomly generated function name to represent the JavaScript event function. - pub(crate) fn function_name(&self) -> String { - self.inner.function_name.to_string() - } - - /// Randomly generated listener object name to represent the JavaScript event listener object. - pub(crate) fn listeners_object_name(&self) -> String { - self.inner.listeners_object_name.to_string() - } - - /// Insert a pending event action to the queue. - fn insert_pending(&self, action: Pending) { - self - .inner - .pending - .lock() - .expect("poisoned pending event queue") - .push(action) - } - - /// Finish all pending event actions. - fn flush_pending(&self) { - let pending = { - let mut lock = self - .inner - .pending - .lock() - .expect("poisoned pending event queue"); - std::mem::take(&mut *lock) - }; - - for action in pending { - match action { - Pending::Unlisten(id) => self.unlisten(id), - Pending::Listen(id, event, handler) => self.listen_(id, event, handler), - Pending::Trigger(ref event, window, payload) => self.trigger(event, window, payload), - } - } - } - - fn listen_(&self, id: EventHandler, event: String, handler: Handler) { - match self.inner.handlers.try_lock() { - Err(_) => self.insert_pending(Pending::Listen(id, event, handler)), - Ok(mut lock) => { - lock.entry(event).or_default().insert(id, handler); - } - } - } - - /// Adds an event listener for JS events. - pub(crate) fn listen( - &self, - event: String, - window: Option, - handler: F, - ) -> EventHandler { - let id = EventHandler(Uuid::new_v4()); - let handler = Handler { - window, - callback: Box::new(handler), - }; - - self.listen_(id, event, handler); - - id - } - - /// Listen to a JS event and immediately unlisten. - pub(crate) fn once( - &self, - event: String, - window: Option, - handler: F, - ) -> EventHandler { - let self_ = self.clone(); - self.listen(event, window, move |event| { - self_.unlisten(event.id); - handler(event); - }) - } - - /// Removes an event listener. - pub(crate) fn unlisten(&self, handler_id: EventHandler) { - match self.inner.handlers.try_lock() { - Err(_) => self.insert_pending(Pending::Unlisten(handler_id)), - Ok(mut lock) => lock.values_mut().for_each(|handler| { - handler.remove(&handler_id); - }), - } - } - - /// Triggers the given global event with its payload. - pub(crate) fn trigger(&self, event: &str, window: Option, payload: Option) { - let mut maybe_pending = false; - match self.inner.handlers.try_lock() { - Err(_) => self.insert_pending(Pending::Trigger(event.to_owned(), window, payload)), - Ok(lock) => { - if let Some(handlers) = lock.get(event) { - for (&id, handler) in handlers { - if handler.window.is_none() || window == handler.window { - maybe_pending = true; - (handler.callback)(self::Event { - id, - data: payload.clone(), - }) - } - } - } - } - } - - if maybe_pending { - self.flush_pending(); - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use proptest::prelude::*; - - // dummy event handler function - fn event_fn(s: Event) { - println!("{:?}", s); - } - - proptest! { - #![proptest_config(ProptestConfig::with_cases(10000))] - - // check to see if listen() is properly passing keys into the LISTENERS map - #[test] - fn listeners_check_key(e in "[a-z]+") { - let listeners: Listeners = Default::default(); - // clone e as the key - let key = e.clone(); - // pass e and an dummy func into listen - listeners.listen(e, None, event_fn); - - // lock mutex - let l = listeners.inner.handlers.lock().unwrap(); - - // check if the generated key is in the map - assert!(l.contains_key(&key)); - } - - // check to see if listen inputs a handler function properly into the LISTENERS map. - #[test] - fn listeners_check_fn(e in "[a-z]+") { - let listeners: Listeners = Default::default(); - // clone e as the key - let key = e.clone(); - // pass e and an dummy func into listen - listeners.listen(e, None, event_fn); - - // lock mutex - let mut l = listeners.inner.handlers.lock().unwrap(); - - // check if l contains key - if l.contains_key(&key) { - // grab key if it exists - let handler = l.get_mut(&key); - // check to see if we get back a handler or not - match handler { - // pass on Some(handler) - Some(_) => {}, - // Fail on None - None => panic!("handler is None") - } - } - } - - // check to see if on_event properly grabs the stored function from listen. - #[test] - fn check_on_event(e in "[a-z]+", d in "[a-z]+") { - let listeners: Listeners = Default::default(); - // clone e as the key - let key = e.clone(); - // call listen with e and the event_fn dummy func - listeners.listen(e.clone(), None, event_fn); - // call on event with e and d. - listeners.trigger(&e, None, Some(d)); - - // lock the mutex - let l = listeners.inner.handlers.lock().unwrap(); - - // assert that the key is contained in the listeners map - assert!(l.contains_key(&key)); - } - } -} - -pub fn unlisten_js(listeners_object_name: String, event_id: u64) -> String { - format!( - " - for (var event in (window['{listeners}'] || {{}})) {{ - var listeners = (window['{listeners}'] || {{}})[event] - if (listeners) {{ - window['{listeners}'][event] = window['{listeners}'][event].filter(function (e) {{ return e.id !== {event_id} }}) - }} - }} - ", - listeners = listeners_object_name, - event_id = event_id, - ) -} - -pub fn listen_js( - listeners_object_name: String, - event: String, - event_id: u64, - window_label: Option, - handler: String, -) -> String { - format!( - "if (window['{listeners}'] === void 0) {{ - Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }}); - }} - if (window['{listeners}'][{event}] === void 0) {{ - Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }}); - }} - window['{listeners}'][{event}].push({{ - id: {event_id}, - windowLabel: {window_label}, - handler: {handler} - }}); - ", - listeners = listeners_object_name, - event = event, - event_id = event_id, - window_label = if let Some(l) = window_label { - crate::runtime::window::assert_label_is_valid(&l); - format!("'{}'", l) - } else { - "null".to_owned() - }, - handler = handler - ) -} diff --git a/core/tauri/src/event/commands.rs b/core/tauri/src/event/commands.rs new file mode 100644 index 000000000000..770911467e34 --- /dev/null +++ b/core/tauri/src/event/commands.rs @@ -0,0 +1,90 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{api::ipc::CallbackFn, command, Manager, Result, Runtime, Window}; +use serde::{Deserialize, Deserializer}; +use serde_json::Value as JsonValue; +use tauri_runtime::window::is_label_valid; + +use super::is_event_name_valid; + +pub struct EventId(String); + +impl<'de> Deserialize<'de> for EventId { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let event_id = String::deserialize(deserializer)?; + if is_event_name_valid(&event_id) { + Ok(EventId(event_id)) + } else { + Err(serde::de::Error::custom( + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.", + )) + } + } +} + +pub struct WindowLabel(String); + +impl<'de> Deserialize<'de> for WindowLabel { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let event_id = String::deserialize(deserializer)?; + if is_label_valid(&event_id) { + Ok(WindowLabel(event_id)) + } else { + Err(serde::de::Error::custom( + "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`.", + )) + } + } +} + +#[command(root = "crate")] +pub fn listen( + window: Window, + event: EventId, + window_label: Option, + handler: CallbackFn, +) -> Result { + window.listen_js(window_label.map(|l| l.0), event.0, handler) +} + +#[command(root = "crate")] +pub fn unlisten(window: Window, event: EventId, event_id: usize) -> Result<()> { + window.unlisten_js(event.0, event_id) +} + +#[command(root = "crate")] +pub fn emit( + window: Window, + event: EventId, + window_label: Option, + payload: Option, +) -> Result<()> { + // dispatch the event to Rust listeners + window.trigger( + &event.0, + payload.as_ref().and_then(|p| { + serde_json::to_string(&p) + .map_err(|e| { + #[cfg(debug_assertions)] + eprintln!("{e}"); + e + }) + .ok() + }), + ); + + // emit event to JS + if let Some(target) = window_label { + window.emit_to(&target.0, &event.0, payload) + } else { + window.emit_all(&event.0, payload) + } +} diff --git a/core/tauri/src/event/listener.rs b/core/tauri/src/event/listener.rs new file mode 100644 index 000000000000..db1263cf3da8 --- /dev/null +++ b/core/tauri/src/event/listener.rs @@ -0,0 +1,256 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{Event, EventHandler}; + +use std::{ + boxed::Box, + cell::Cell, + collections::HashMap, + sync::{Arc, Mutex}, +}; +use uuid::Uuid; + +/// What to do with the pending handler when resolving it? +enum Pending { + Unlisten(EventHandler), + Listen(EventHandler, String, Handler), + Trigger(String, Option, Option), +} + +/// Stored in [`Listeners`] to be called upon when the event that stored it is triggered. +struct Handler { + window: Option, + callback: Box, +} + +/// Holds event handlers and pending event handlers, along with the salts associating them. +struct InnerListeners { + handlers: Mutex>>, + pending: Mutex>, + function_name: Uuid, + listeners_object_name: Uuid, +} + +/// A self-contained event manager. +pub struct Listeners { + inner: Arc, +} + +impl Default for Listeners { + fn default() -> Self { + Self { + inner: Arc::new(InnerListeners { + handlers: Mutex::default(), + pending: Mutex::default(), + function_name: Uuid::new_v4(), + listeners_object_name: Uuid::new_v4(), + }), + } + } +} + +impl Clone for Listeners { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl Listeners { + /// Randomly generated function name to represent the JavaScript event function. + pub(crate) fn function_name(&self) -> String { + self.inner.function_name.to_string() + } + + /// Randomly generated listener object name to represent the JavaScript event listener object. + pub(crate) fn listeners_object_name(&self) -> String { + self.inner.listeners_object_name.to_string() + } + + /// Insert a pending event action to the queue. + fn insert_pending(&self, action: Pending) { + self + .inner + .pending + .lock() + .expect("poisoned pending event queue") + .push(action) + } + + /// Finish all pending event actions. + fn flush_pending(&self) { + let pending = { + let mut lock = self + .inner + .pending + .lock() + .expect("poisoned pending event queue"); + std::mem::take(&mut *lock) + }; + + for action in pending { + match action { + Pending::Unlisten(id) => self.unlisten(id), + Pending::Listen(id, event, handler) => self.listen_(id, event, handler), + Pending::Trigger(ref event, window, payload) => self.trigger(event, window, payload), + } + } + } + + fn listen_(&self, id: EventHandler, event: String, handler: Handler) { + match self.inner.handlers.try_lock() { + Err(_) => self.insert_pending(Pending::Listen(id, event, handler)), + Ok(mut lock) => { + lock.entry(event).or_default().insert(id, handler); + } + } + } + + /// Adds an event listener for JS events. + pub(crate) fn listen( + &self, + event: String, + window: Option, + handler: F, + ) -> EventHandler { + let id = EventHandler(Uuid::new_v4()); + let handler = Handler { + window, + callback: Box::new(handler), + }; + + self.listen_(id, event, handler); + + id + } + + /// Listen to a JS event and immediately unlisten. + pub(crate) fn once( + &self, + event: String, + window: Option, + handler: F, + ) -> EventHandler { + let self_ = self.clone(); + let handler = Cell::new(Some(handler)); + + self.listen(event, window, move |event| { + self_.unlisten(event.id); + let handler = handler + .take() + .expect("attempted to call handler more than once"); + handler(event) + }) + } + + /// Removes an event listener. + pub(crate) fn unlisten(&self, handler_id: EventHandler) { + match self.inner.handlers.try_lock() { + Err(_) => self.insert_pending(Pending::Unlisten(handler_id)), + Ok(mut lock) => lock.values_mut().for_each(|handler| { + handler.remove(&handler_id); + }), + } + } + + /// Triggers the given global event with its payload. + pub(crate) fn trigger(&self, event: &str, window: Option, payload: Option) { + let mut maybe_pending = false; + match self.inner.handlers.try_lock() { + Err(_) => self.insert_pending(Pending::Trigger(event.to_owned(), window, payload)), + Ok(lock) => { + if let Some(handlers) = lock.get(event) { + for (&id, handler) in handlers { + if handler.window.is_none() || window == handler.window { + maybe_pending = true; + (handler.callback)(self::Event { + id, + data: payload.clone(), + }) + } + } + } + } + } + + if maybe_pending { + self.flush_pending(); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use proptest::prelude::*; + + // dummy event handler function + fn event_fn(s: Event) { + println!("{s:?}"); + } + + proptest! { + #![proptest_config(ProptestConfig::with_cases(10000))] + + // check to see if listen() is properly passing keys into the LISTENERS map + #[test] + fn listeners_check_key(e in "[a-z]+") { + let listeners: Listeners = Default::default(); + // clone e as the key + let key = e.clone(); + // pass e and an dummy func into listen + listeners.listen(e, None, event_fn); + + // lock mutex + let l = listeners.inner.handlers.lock().unwrap(); + + // check if the generated key is in the map + assert!(l.contains_key(&key)); + } + + // check to see if listen inputs a handler function properly into the LISTENERS map. + #[test] + fn listeners_check_fn(e in "[a-z]+") { + let listeners: Listeners = Default::default(); + // clone e as the key + let key = e.clone(); + // pass e and an dummy func into listen + listeners.listen(e, None, event_fn); + + // lock mutex + let mut l = listeners.inner.handlers.lock().unwrap(); + + // check if l contains key + if l.contains_key(&key) { + // grab key if it exists + let handler = l.get_mut(&key); + // check to see if we get back a handler or not + match handler { + // pass on Some(handler) + Some(_) => {}, + // Fail on None + None => panic!("handler is None") + } + } + } + + // check to see if on_event properly grabs the stored function from listen. + #[test] + fn check_on_event(key in "[a-z]+", d in "[a-z]+") { + let listeners: Listeners = Default::default(); + // call listen with e and the event_fn dummy func + listeners.listen(key.clone(), None, event_fn); + // call on event with e and d. + listeners.trigger(&key, None, Some(d)); + + // lock the mutex + let l = listeners.inner.handlers.lock().unwrap(); + + // assert that the key is contained in the listeners map + assert!(l.contains_key(&key)); + } + } +} diff --git a/core/tauri/src/event/mod.rs b/core/tauri/src/event/mod.rs new file mode 100644 index 000000000000..ddc351d54867 --- /dev/null +++ b/core/tauri/src/event/mod.rs @@ -0,0 +1,120 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{fmt, hash::Hash}; +use uuid::Uuid; + +mod commands; +mod listener; +pub(crate) use listener::Listeners; + +use crate::{ + plugin::{Builder, TauriPlugin}, + Runtime, +}; + +/// Checks if an event name is valid. +pub fn is_event_name_valid(event: &str) -> bool { + event + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_') +} + +pub fn assert_event_name_is_valid(event: &str) { + assert!( + is_event_name_valid(event), + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." + ); +} + +/// Represents an event handler. +#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct EventHandler(Uuid); + +impl fmt::Display for EventHandler { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +/// An event that was triggered. +#[derive(Debug, Clone)] +pub struct Event { + id: EventHandler, + data: Option, +} + +impl Event { + /// The [`EventHandler`] that was triggered. + pub fn id(&self) -> EventHandler { + self.id + } + + /// The event payload. + pub fn payload(&self) -> Option<&str> { + self.data.as_deref() + } +} + +/// Initializes the event plugin. +pub(crate) fn init() -> TauriPlugin { + Builder::new("event") + .invoke_handler(crate::generate_handler![ + commands::listen, + commands::unlisten, + commands::emit, + ]) + .build() +} + +pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: usize) -> String { + format!( + " + (function () {{ + const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}'] + if (listeners) {{ + const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id}) + if (index > -1) {{ + window['{listeners_object_name}']['{event_name}'].splice(index, 1) + }} + }} + }})() + ", + ) +} + +pub fn listen_js( + listeners_object_name: String, + event: String, + event_id: usize, + window_label: Option, + handler: String, +) -> String { + format!( + " + (function () {{ + if (window['{listeners}'] === void 0) {{ + Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }}); + }} + if (window['{listeners}'][{event}] === void 0) {{ + Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }}); + }} + const eventListeners = window['{listeners}'][{event}] + const listener = {{ + id: {event_id}, + windowLabel: {window_label}, + handler: {handler} + }}; + eventListeners.push(listener); + }})() + ", + listeners = listeners_object_name, + window_label = if let Some(l) = window_label { + crate::runtime::window::assert_label_is_valid(&l); + format!("'{l}'") + } else { + "null".to_owned() + }, + ) +} diff --git a/core/tauri/src/hooks.rs b/core/tauri/src/hooks.rs index 8d7a5c93f192..4bbe77f0a9d6 100644 --- a/core/tauri/src/hooks.rs +++ b/core/tauri/src/hooks.rs @@ -1,12 +1,11 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use crate::{ api::ipc::{format_callback, format_callback_result, CallbackFn}, app::App, - runtime::Runtime, - StateManager, Window, + Runtime, StateManager, Window, }; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -17,10 +16,10 @@ use tauri_macros::default_runtime; /// A closure that is run when the Tauri application is setting up. pub type SetupHook = - Box) -> Result<(), Box> + Send>; + Box) -> Result<(), Box> + Send>; -/// A closure that is run everytime Tauri receives a message it doesn't explicitly handle. -pub type InvokeHandler = dyn Fn(Invoke) + Send + Sync + 'static; +/// A closure that is run every time Tauri receives a message it doesn't explicitly handle. +pub type InvokeHandler = dyn Fn(Invoke) -> bool + Send + Sync + 'static; /// A closure that is responsible for respond a JS message. pub type InvokeResponder = @@ -62,9 +61,6 @@ impl PageLoadPayload { pub struct InvokePayload { /// The invoke command. pub cmd: String, - #[serde(rename = "__tauriModule")] - #[doc(hidden)] - pub tauri_module: Option, /// The success callback. pub callback: CallbackFn, /// The error callback. @@ -95,6 +91,12 @@ impl InvokeError { pub fn from_serde_json(error: serde_json::Error) -> Self { Self(JsonValue::String(error.to_string())) } + + /// Create an [`InvokeError`] as a string of the [`anyhow::Error`] message. + #[inline(always)] + pub fn from_anyhow(error: anyhow::Error) -> Self { + Self(JsonValue::String(format!("{error:#}"))) + } } impl From for InvokeError { @@ -161,6 +163,16 @@ pub struct InvokeResolver { pub(crate) error: CallbackFn, } +impl Clone for InvokeResolver { + fn clone(&self) -> Self { + Self { + window: self.window.clone(), + callback: self.callback, + error: self.error, + } + } +} + impl InvokeResolver { pub(crate) fn new(window: Window, callback: CallbackFn, error: CallbackFn) -> Self { Self { @@ -187,7 +199,11 @@ impl InvokeResolver { F: Future> + Send + 'static, { crate::async_runtime::spawn(async move { - Self::return_result(self.window, task.await.into(), self.callback, self.error) + let response = match task.await { + Ok(ok) => InvokeResponse::Ok(ok), + Err(err) => InvokeResponse::Err(err), + }; + Self::return_result(self.window, response, self.callback, self.error) }); } @@ -283,6 +299,17 @@ pub struct InvokeMessage { pub(crate) payload: JsonValue, } +impl Clone for InvokeMessage { + fn clone(&self) -> Self { + Self { + window: self.window.clone(), + state: self.state.clone(), + command: self.command.clone(), + payload: self.payload.clone(), + } + } +} + impl InvokeMessage { /// Create an new [`InvokeMessage`] from a payload send to a window. pub(crate) fn new( diff --git a/core/tauri/src/ios.rs b/core/tauri/src/ios.rs new file mode 100644 index 000000000000..5b9af9ec3d89 --- /dev/null +++ b/core/tauri/src/ios.rs @@ -0,0 +1,176 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use cocoa::base::{id, nil, NO, YES}; +use objc::*; +use serde_json::Value as JsonValue; +use swift_rs::{swift, SRString, SwiftArg}; + +use std::{ + ffi::c_void, + os::raw::{c_char, c_int}, +}; + +type PluginMessageCallbackFn = unsafe extern "C" fn(c_int, c_int, *const c_char); +pub struct PluginMessageCallback(pub PluginMessageCallbackFn); + +impl<'a> SwiftArg<'a> for PluginMessageCallback { + type ArgType = PluginMessageCallbackFn; + + unsafe fn as_arg(&'a self) -> Self::ArgType { + self.0 + } +} + +swift!(pub fn post_ipc_message( + webview: *const c_void, + name: &SRString, + method: &SRString, + data: *const c_void, + callback: usize, + error: usize +)); +swift!(pub fn run_plugin_method( + id: i32, + name: &SRString, + method: &SRString, + data: *const c_void, + callback: PluginMessageCallback +)); +swift!(pub fn register_plugin( + name: &SRString, + plugin: *const c_void, + config: *const c_void, + webview: *const c_void +)); +swift!(pub fn on_webview_created(webview: *const c_void, controller: *const c_void)); + +pub fn json_to_dictionary(json: &JsonValue) -> id { + if let serde_json::Value::Object(map) = json { + unsafe { + let dictionary: id = msg_send![class!(NSMutableDictionary), alloc]; + let data: id = msg_send![dictionary, init]; + for (key, value) in map { + add_json_entry_to_dictionary(data, key, value); + } + data + } + } else { + nil + } +} + +const UTF8_ENCODING: usize = 4; + +struct NSString(id); + +impl NSString { + fn new(s: &str) -> Self { + // Safety: objc runtime calls are unsafe + NSString(unsafe { + let ns_string: id = msg_send![class!(NSString), alloc]; + let ns_string: id = msg_send![ns_string, + initWithBytes:s.as_ptr() + length:s.len() + encoding:UTF8_ENCODING]; + + // The thing is allocated in rust, the thing must be set to autorelease in rust to relinquish control + // or it can not be released correctly in OC runtime + let _: () = msg_send![ns_string, autorelease]; + + ns_string + }) + } +} + +unsafe fn add_json_value_to_array(array: id, value: &JsonValue) { + match value { + JsonValue::Null => { + let null: id = msg_send![class!(NSNull), null]; + let () = msg_send![array, addObject: null]; + } + JsonValue::Bool(val) => { + let value = if *val { YES } else { NO }; + let v: id = msg_send![class!(NSNumber), numberWithBool: value]; + let () = msg_send![array, addObject: v]; + } + JsonValue::Number(val) => { + let number: id = if let Some(v) = val.as_i64() { + msg_send![class!(NSNumber), numberWithInteger: v] + } else if let Some(v) = val.as_u64() { + msg_send![class!(NSNumber), numberWithUnsignedLongLong: v] + } else if let Some(v) = val.as_f64() { + msg_send![class!(NSNumber), numberWithDouble: v] + } else { + unreachable!() + }; + let () = msg_send![array, addObject: number]; + } + JsonValue::String(val) => { + let () = msg_send![array, addObject: NSString::new(&val)]; + } + JsonValue::Array(val) => { + let nsarray: id = msg_send![class!(NSMutableArray), alloc]; + let inner_array: id = msg_send![nsarray, init]; + for value in val { + add_json_value_to_array(inner_array, value); + } + let () = msg_send![array, addObject: inner_array]; + } + JsonValue::Object(val) => { + let dictionary: id = msg_send![class!(NSMutableDictionary), alloc]; + let data: id = msg_send![dictionary, init]; + for (key, value) in val { + add_json_entry_to_dictionary(data, key, value); + } + let () = msg_send![array, addObject: data]; + } + } +} + +unsafe fn add_json_entry_to_dictionary(data: id, key: &str, value: &JsonValue) { + let key = NSString::new(&key); + match value { + JsonValue::Null => { + let null: id = msg_send![class!(NSNull), null]; + let () = msg_send![data, setObject:null forKey: key]; + } + JsonValue::Bool(val) => { + let flag = if *val { YES } else { NO }; + let value: id = msg_send![class!(NSNumber), numberWithBool: flag]; + let () = msg_send![data, setObject:value forKey: key]; + } + JsonValue::Number(val) => { + let number: id = if let Some(v) = val.as_i64() { + msg_send![class!(NSNumber), numberWithInteger: v] + } else if let Some(v) = val.as_u64() { + msg_send![class!(NSNumber), numberWithUnsignedLongLong: v] + } else if let Some(v) = val.as_f64() { + msg_send![class!(NSNumber), numberWithDouble: v] + } else { + unreachable!() + }; + let () = msg_send![data, setObject:number forKey: key]; + } + JsonValue::String(val) => { + let () = msg_send![data, setObject:NSString::new(&val) forKey: key]; + } + JsonValue::Array(val) => { + let nsarray: id = msg_send![class!(NSMutableArray), alloc]; + let array: id = msg_send![nsarray, init]; + for value in val { + add_json_value_to_array(array, value); + } + let () = msg_send![data, setObject:array forKey: key]; + } + JsonValue::Object(val) => { + let dictionary: id = msg_send![class!(NSMutableDictionary), alloc]; + let inner_data: id = msg_send![dictionary, init]; + for (key, value) in val { + add_json_entry_to_dictionary(inner_data, key, value); + } + let () = msg_send![data, setObject:inner_data forKey: key]; + } + } +} diff --git a/core/tauri/src/jni_helpers.rs b/core/tauri/src/jni_helpers.rs new file mode 100644 index 000000000000..e6edb281077d --- /dev/null +++ b/core/tauri/src/jni_helpers.rs @@ -0,0 +1,87 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::Runtime; +use jni::{ + errors::Error as JniError, + objects::{JObject, JValue}, + JNIEnv, +}; +use serde_json::Value as JsonValue; +use tauri_runtime::RuntimeHandle; + +fn json_to_java<'a, R: Runtime>( + env: JNIEnv<'a>, + activity: JObject<'a>, + runtime_handle: &R::Handle, + json: &JsonValue, +) -> Result<(&'static str, JValue<'a>), JniError> { + let (class, v) = match json { + JsonValue::Null => ("Ljava/lang/Object;", JObject::null().into()), + JsonValue::Bool(val) => ("Z", (*val).into()), + JsonValue::Number(val) => { + if let Some(v) = val.as_i64() { + ("J", v.into()) + } else if let Some(v) = val.as_f64() { + ("D", v.into()) + } else { + ("Ljava/lang/Object;", JObject::null().into()) + } + } + JsonValue::String(val) => ( + "Ljava/lang/Object;", + JObject::from(env.new_string(&val)?).into(), + ), + JsonValue::Array(val) => { + let js_array_class = runtime_handle.find_class(env, activity, "app/tauri/plugin/JSArray")?; + let data = env.new_object(js_array_class, "()V", &[])?; + + for v in val { + let (signature, val) = json_to_java::(env, activity, runtime_handle, v)?; + env.call_method( + data, + "put", + format!("({signature})Lorg/json/JSONArray;"), + &[val], + )?; + } + + ("Ljava/lang/Object;", data.into()) + } + JsonValue::Object(val) => { + let js_object_class = + runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?; + let data = env.new_object(js_object_class, "()V", &[])?; + + for (key, value) in val { + let (signature, val) = json_to_java::(env, activity, runtime_handle, value)?; + env.call_method( + data, + "put", + format!("(Ljava/lang/String;{signature})Lapp/tauri/plugin/JSObject;"), + &[env.new_string(&key)?.into(), val], + )?; + } + + ("Ljava/lang/Object;", data.into()) + } + }; + Ok((class, v)) +} + +pub fn to_jsobject<'a, R: Runtime>( + env: JNIEnv<'a>, + activity: JObject<'a>, + runtime_handle: &R::Handle, + json: &JsonValue, +) -> Result, JniError> { + if let JsonValue::Object(_) = json { + json_to_java::(env, activity, runtime_handle, json).map(|(_class, data)| data) + } else { + // currently the Kotlin lib cannot handle nulls or raw values, it must be an object + let js_object_class = runtime_handle.find_class(env, activity, "app/tauri/plugin/JSObject")?; + let data = env.new_object(js_object_class, "()V", &[])?; + Ok(data.into()) + } +} diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 99e664036c37..43a5b5da82cf 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -11,162 +11,129 @@ //! The following are a list of [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section) that can be enabled or disabled: //! //! - **wry** *(enabled by default)*: Enables the [wry](https://github.com/tauri-apps/wry) runtime. Only disable it if you want a custom runtime. +//! - **test**: Enables the [`test`] module exposing unit test helpers. +//! - **dox**: Internal feature to generate Rust documentation without linking on Linux. +//! - **objc-exception**: Wrap each msg_send! in a @try/@catch and panics if an exception is caught, preventing Objective-C from unwinding into Rust. +//! - **linux-protocol-headers**: Enables headers support for custom protocol requests on Linux. Requires webkit2gtk v2.36 or above. //! - **isolation**: Enables the isolation pattern. Enabled by default if the `tauri > pattern > use` config option is set to `isolation` on the `tauri.conf.json` file. //! - **custom-protocol**: Feature managed by the Tauri CLI. When enabled, Tauri assumes a production environment instead of a development one. -//! - **updater**: Enables the application auto updater. Enabled by default if the `updater` config is defined on the `tauri.conf.json` file. //! - **devtools**: Enables the developer tools (Web inspector) and [`Window::open_devtools`]. Enabled by default on debug builds. //! On macOS it uses private APIs, so you can't enable it if your app will be published to the App Store. -//! - **http-api**: Enables the [`api::http`] module. -//! - **reqwest-client**: Uses `reqwest` as HTTP client on the `http` APIs. Improves performance, but increases the bundle size. -//! - **command**: Enables the [`api::process::Command`] APIs. -//! - **dialog**: Enables the [`api::dialog`] module. -//! - **notification**: Enables the [`api::notification`] module. -//! - **cli**: Enables usage of `clap` for CLI argument parsing. Enabled by default if the `cli` config is defined on the `tauri.conf.json` file. +//! - **native-tls**: Provides TLS support to connect over HTTPS. +//! - **native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL. +//! - **rustls-tls**: Provides TLS support to connect over HTTPS using rustls. +//! - **process-relaunch-dangerous-allow-symlink-macos**: Allows the [`process::current_binary`] function to allow symlinks on macOS (this is dangerous, see the Security section in the documentation website). //! - **system-tray**: Enables application system tray API. Enabled by default if the `systemTray` config is defined on the `tauri.conf.json` file. //! - **macos-private-api**: Enables features only available in **macOS**'s private APIs, currently the `transparent` window functionality and the `fullScreenEnabled` preference setting to `true`. Enabled by default if the `tauri > macosPrivateApi` config flag is set to `true` on the `tauri.conf.json` file. //! - **window-data-url**: Enables usage of data URLs on the webview. +//! - **compression** *(enabled by default): Enables asset compression. You should only disable this if you want faster compile times in release builds - it produces larger binaries. +//! - **config-json5**: Adds support to JSON5 format for `tauri.conf.json`. +//! - **config-toml**: Adds support to TOML format for the configuration `Tauri.toml`. +//! - **icon-ico**: Adds support to set `.ico` window icons. Enables [`Icon::File`] and [`Icon::Raw`] variants. +//! - **icon-png**: Adds support to set `.png` window icons. Enables [`Icon::File`] and [`Icon::Raw`] variants. //! //! ## Cargo allowlist features //! //! The following are a list of [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section) that enables commands for Tauri's API package. //! These features are automatically enabled by the Tauri CLI based on the `allowlist` configuration under `tauri.conf.json`. //! -//! - **api-all**: Enables all API endpoints. -//! -//! ### Clipboard allowlist -//! -//! - **clipboard-all**: Enables all [Clipboard APIs](https://tauri.studio/en/docs/api/js/modules/clipboard/). -//! - **clipboard-read-text**: Enables the [`readText` API](https://tauri.studio/en/docs/api/js/modules/clipboard/#readtext). -//! - **clipboard-write-text**: Enables the [`writeText` API](https://tauri.studio/en/docs/api/js/modules/clipboard/#writetext). -//! -//! ### Dialog allowlist -//! -//! - **dialog-all**: Enables all [Dialog APIs](https://tauri.studio/en/docs/api/js/modules/dialog). -//! - **dialog-ask**: Enables the [`ask` API](https://tauri.studio/en/docs/api/js/modules/dialog#ask). -//! - **dialog-confirm**: Enables the [`confirm` API](https://tauri.studio/en/docs/api/js/modules/dialog#confirm). -//! - **dialog-message**: Enables the [`message` API](https://tauri.studio/en/docs/api/js/modules/dialog#message). -//! - **dialog-open**: Enables the [`open` API](https://tauri.studio/en/docs/api/js/modules/dialog#open). -//! - **dialog-save**: Enables the [`save` API](https://tauri.studio/en/docs/api/js/modules/dialog#save). -//! -//! ### Filesystem allowlist -//! -//! - **fs-all**: Enables all [Filesystem APIs](https://tauri.studio/en/docs/api/js/modules/fs). -//! - **fs-copy-file**: Enables the [`copyFile` API](https://tauri.studio/en/docs/api/js/modules/fs#copyfile). -//! - **fs-create-dir**: Enables the [`createDir` API](https://tauri.studio/en/docs/api/js/modules/fs#createdir). -//! - **fs-read-dir**: Enables the [`readDir` API](https://tauri.studio/en/docs/api/js/modules/fs#readdir). -//! - **fs-read-file**: Enables the [`readTextFile` API](https://tauri.studio/en/docs/api/js/modules/fs#readtextfile) and the [`readBinaryFile` API](https://tauri.studio/en/docs/api/js/modules/fs#readbinaryfile). -//! - **fs-remove-dir**: Enables the [`removeDir` API](https://tauri.studio/en/docs/api/js/modules/fs#removedir). -//! - **fs-remove-file**: Enables the [`removeFile` API](https://tauri.studio/en/docs/api/js/modules/fs#removefile). -//! - **fs-rename-file**: Enables the [`renameFile` API](https://tauri.studio/en/docs/api/js/modules/fs#renamefile). -//! - **fs-write-file**: Enables the [`writeFile` API](https://tauri.studio/en/docs/api/js/modules/fs#writefile) and the [`writeBinaryFile` API](https://tauri.studio/en/docs/api/js/modules/fs#writebinaryfile). -//! -//! ### Global shortcut allowlist -//! -//! - **global-shortcut-all**: Enables all [GlobalShortcut APIs](https://tauri.studio/en/docs/api/js/modules/globalShortcut). -//! -//! ### HTTP allowlist -//! -//! - **http-all**: Enables all [HTTP APIs](https://tauri.studio/en/docs/api/js/modules/http). -//! - **http-request**: Enables the [`request` APIs](https://tauri.studio/en/docs/api/js/classes/http.client/). -//! -//! ### Notification allowlist -//! -//! - **notification-all**: Enables all [Notification APIs](https://tauri.studio/en/docs/api/js/modules/notification). -//! -//! ### OS allowlist -//! -//! - **os-all**: Enables all [OS APIs](https://tauri.studio/en/docs/api/js/modules/os). -//! -//! ### Path allowlist -//! -//! - **path-all**: Enables all [Path APIs](https://tauri.studio/en/docs/api/js/modules/path). -//! -//! ### Process allowlist -//! -//! - **process-all**: Enables all [Process APIs](https://tauri.studio/en/docs/api/js/modules/process). -//! - **process-exit**: Enables the [`exit` API](https://tauri.studio/en/docs/api/js/modules/process#exit). -//! - **process-relaunch**: Enables the [`relaunch` API](https://tauri.studio/en/docs/api/js/modules/process#relaunch). -//! //! ### Protocol allowlist //! -//! - **protocol-all**: Enables all Protocol APIs. //! - **protocol-asset**: Enables the `asset` custom protocol. -//! -//! ### Shell allowlist -//! -//! - **shell-all**: Enables all [Clipboard APIs](https://tauri.studio/en/docs/api/js/modules/shell). -//! - **shell-execute**: Enables [executing arbitrary programs](https://tauri.studio/en/docs/api/js/classes/shell.Command#constructor). -//! - **shell-sidecar**: Enables [executing a `sidecar` program](https://tauri.studio/en/docs/api/js/classes/shell.Command#sidecar). -//! - **shell-open**: Enables the [`open` API](https://tauri.studio/en/docs/api/js/modules/shell#open). -//! -//! ### Window allowlist -//! -//! - **window-all**: Enables all [Window APIs](https://tauri.studio/en/docs/api/js/modules/window). -//! - **window-create**: Enables the API used to [create new windows](https://tauri.studio/en/docs/api/js/classes/window.webviewwindow/). -//! - **window-center**: Enables the [`center` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#center). -//! - **window-request-user-attention**: Enables the [`requestUserAttention` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#requestuserattention). -//! - **window-set-resizable**: Enables the [`setResizable` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setresizable). -//! - **window-set-title**: Enables the [`setTitle` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#settitle). -//! - **window-maximize**: Enables the [`maximize` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#maximize). -//! - **window-unmaximize**: Enables the [`unmaximize` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#unmaximize). -//! - **window-minimize**: Enables the [`minimize` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#minimize). -//! - **window-unminimize**: Enables the [`unminimize` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#unminimize). -//! - **window-show**: Enables the [`show` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#show). -//! - **window-hide**: Enables the [`hide` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#hide). -//! - **window-close**: Enables the [`close` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#close). -//! - **window-set-decorations**: Enables the [`setDecorations` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setdecorations). -//! - **window-set-always-on-top**: Enables the [`setAlwaysOnTop` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setalwaysontop). -//! - **window-set-size**: Enables the [`setSize` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setsize). -//! - **window-set-min-size**: Enables the [`setMinSize` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setminsize). -//! - **window-set-max-size**: Enables the [`setMaxSize` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setmaxsize). -//! - **window-set-position**: Enables the [`setPosition` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setposition). -//! - **window-set-fullscreen**: Enables the [`setFullscreen` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setfullscreen). -//! - **window-set-focus**: Enables the [`setFocus` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setfocus). -//! - **window-set-icon**: Enables the [`setIcon` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#seticon). -//! - **window-set-skip-taskbar**: Enables the [`setSkipTaskbar` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#setskiptaskbar). -//! - **window-start-dragging**: Enables the [`startDragging` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#startdragging). -//! - **window-print**: Enables the [`print` API](https://tauri.studio/en/docs/api/js/classes/window.WebviewWindow#print). #![warn(missing_docs, rust_2018_idioms)] #![cfg_attr(doc_cfg, feature(doc_cfg))] +/// Setups the binding that initializes an iOS plugin. +#[cfg(target_os = "ios")] +#[macro_export] +macro_rules! ios_plugin_binding { + ($fn_name: ident) => { + tauri::swift_rs::swift!(fn $fn_name() -> *const ::std::ffi::c_void); + } +} +#[cfg(target_os = "ios")] +#[doc(hidden)] +pub use cocoa; #[cfg(target_os = "macos")] #[doc(hidden)] pub use embed_plist; /// The Tauri error enum. pub use error::Error; -#[cfg(shell_scope)] +#[cfg(target_os = "ios")] #[doc(hidden)] -pub use regex; +pub use swift_rs; +#[cfg(mobile)] +pub use tauri_macros::mobile_entry_point; pub use tauri_macros::{command, generate_handler}; pub mod api; pub(crate) mod app; +#[cfg(feature = "protocol-asset")] +pub(crate) mod asset_protocol; pub mod async_runtime; pub mod command; -/// The Tauri API endpoints. -mod endpoints; mod error; mod event; mod hooks; mod manager; mod pattern; pub mod plugin; +mod vibrancy; pub mod window; -pub use tauri_runtime as runtime; +use tauri_runtime as runtime; +#[cfg(target_os = "ios")] +mod ios; +#[cfg(target_os = "android")] +mod jni_helpers; +/// Path APIs. +pub mod path; +pub mod process; /// The allowlist scopes. pub mod scope; -pub mod settings; mod state; -#[cfg(feature = "updater")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))] -pub mod updater; pub use tauri_utils as utils; +/// A Tauri [`Runtime`] wrapper around wry. #[cfg(feature = "wry")] #[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))] -pub use tauri_runtime_wry::Wry; +pub type Wry = tauri_runtime_wry::Wry; + +#[cfg(all(feature = "wry", target_os = "android"))] +#[cfg_attr(doc_cfg, doc(cfg(all(feature = "wry", target_os = "android"))))] +#[doc(hidden)] +#[macro_export] +macro_rules! android_binding { + ($domain:ident, $package:ident, $main: ident, $wry: path) => { + ::tauri::wry::android_binding!($domain, $package, $main, $wry); + ::tauri::wry::application::android_fn!( + app_tauri, + plugin, + PluginManager, + handlePluginResponse, + [i32, JString, JString], + ); + + #[allow(non_snake_case)] + pub unsafe fn handlePluginResponse( + env: JNIEnv, + _: JClass, + id: i32, + success: JString, + error: JString, + ) { + ::tauri::handle_android_plugin_response(env, id, success, error); + } + }; +} + +#[cfg(all(feature = "wry", target_os = "android"))] +#[doc(hidden)] +pub use plugin::mobile::handle_android_plugin_response; +#[cfg(all(feature = "wry", target_os = "android"))] +#[doc(hidden)] +pub use tauri_runtime_wry::wry; /// `Result` pub type Result = std::result::Result; @@ -174,36 +141,38 @@ pub type Result = std::result::Result; /// A task to run on the main thread. pub type SyncTask = Box; -use crate::runtime::window::PendingWindow; use serde::Serialize; use std::{collections::HashMap, fmt, sync::Arc}; // Export types likely to be used by the application. pub use runtime::http; +#[cfg(feature = "wry")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))] +pub use tauri_runtime_wry::webview_version; + #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] pub use runtime::{menu::NativeImage, ActivationPolicy}; -#[cfg(feature = "system-tray")] +#[cfg(target_os = "macos")] +pub use self::utils::TitleBarStyle; +#[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] pub use { - self::app::tray::{SystemTrayEvent, SystemTrayHandle}, - self::runtime::{ - menu::{SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu}, - SystemTray, - }, + self::app::tray::{SystemTray, SystemTrayEvent, SystemTrayHandle, SystemTrayMenuItemHandle}, + self::runtime::menu::{SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu}, }; pub use { self::app::WindowMenuEvent, self::event::{Event, EventHandler}, - self::runtime::menu::{CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu}, + self::runtime::menu::{AboutMetadata, CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu}, self::window::menu::MenuEvent, }; pub use { self::app::{ - App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, PathResolver, - RunEvent, + App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, RunEvent, + WindowEvent, }, self::hooks::{ Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokePayload, InvokeResolver, @@ -211,23 +180,72 @@ pub use { }, self::manager::Asset, self::runtime::{ - webview::{WebviewAttributes, WindowBuilder}, + webview::WebviewAttributes, window::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size}, - WindowEvent, + CursorIcon, FileDropEvent, }, - ClipboardManager, GlobalShortcutManager, Icon, RunIteration, Runtime, UserAttentionType, + DeviceEventFilter, RunIteration, UserAttentionType, }, self::state::{State, StateManager}, self::utils::{ assets::Assets, config::{Config, WindowUrl}, - Env, PackageInfo, + Env, PackageInfo, Theme, }, - self::window::{Monitor, Window}, + self::window::{Monitor, Window, WindowBuilder}, scope::*, }; +/// The Tauri version. +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg(target_os = "ios")] +#[doc(hidden)] +pub fn log_stdout() { + use std::{ + ffi::CString, + fs::File, + io::{BufRead, BufReader}, + os::unix::prelude::*, + thread, + }; + + let mut logpipe: [RawFd; 2] = Default::default(); + unsafe { + libc::pipe(logpipe.as_mut_ptr()); + libc::dup2(logpipe[1], libc::STDOUT_FILENO); + libc::dup2(logpipe[1], libc::STDERR_FILENO); + } + thread::spawn(move || unsafe { + let file = File::from_raw_fd(logpipe[0]); + let mut reader = BufReader::new(file); + let mut buffer = String::new(); + loop { + buffer.clear(); + if let Ok(len) = reader.read_line(&mut buffer) { + if len == 0 { + break; + } else if let Ok(msg) = CString::new(buffer.clone()) + .map_err(|_| ()) + .and_then(|c| c.into_string().map_err(|_| ())) + { + log::info!("{}", msg); + } + } + } + }); +} + +/// The user event type. +#[derive(Debug, Clone)] +pub enum EventLoopMessage {} + +/// The webview runtime interface. A wrapper around [`runtime::Runtime`] with the proper user event type associated. +pub trait Runtime: runtime::Runtime {} + +impl> Runtime for W {} + /// Reads the config file at compile time and generates a [`Context`] based on its content. /// /// The default config file path is a `tauri.conf.json` file inside the Cargo manifest directory of @@ -243,7 +261,7 @@ pub use { /// /// This macro should not be called if you are using [`tauri-build`] to generate the context from /// inside your build script as it will just cause excess computations that will be discarded. Use -/// either the [`tauri-build] method or this macro - not both. +/// either the [`tauri-build`] method or this macro - not both. /// /// [`tauri-build`]: https://docs.rs/tauri-build pub use tauri_macros::generate_context; @@ -264,6 +282,92 @@ macro_rules! tauri_build_context { pub use pattern::Pattern; +/// A icon definition. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum Icon { + /// Icon from file path. + #[cfg(any(feature = "icon-ico", feature = "icon-png"))] + #[cfg_attr(doc_cfg, doc(cfg(any(feature = "icon-ico", feature = "icon-png"))))] + File(std::path::PathBuf), + /// Icon from raw RGBA bytes. Width and height is parsed at runtime. + #[cfg(any(feature = "icon-ico", feature = "icon-png"))] + #[cfg_attr(doc_cfg, doc(cfg(any(feature = "icon-ico", feature = "icon-png"))))] + Raw(Vec), + /// Icon from raw RGBA bytes. + Rgba { + /// RGBA bytes of the icon image. + rgba: Vec, + /// Icon width. + width: u32, + /// Icon height. + height: u32, + }, +} + +impl TryFrom for runtime::Icon { + type Error = Error; + + fn try_from(icon: Icon) -> Result { + #[allow(irrefutable_let_patterns)] + if let Icon::Rgba { + rgba, + width, + height, + } = icon + { + Ok(Self { + rgba, + width, + height, + }) + } else { + #[cfg(not(any(feature = "icon-ico", feature = "icon-png")))] + panic!("unexpected Icon variant"); + #[cfg(any(feature = "icon-ico", feature = "icon-png"))] + { + let bytes = match icon { + Icon::File(p) => std::fs::read(p)?, + Icon::Raw(r) => r, + Icon::Rgba { .. } => unreachable!(), + }; + let extension = infer::get(&bytes) + .expect("could not determine icon extension") + .extension(); + match extension { + #[cfg(feature = "icon-ico")] + "ico" => { + let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes))?; + let entry = &icon_dir.entries()[0]; + Ok(Self { + rgba: entry.decode()?.rgba_data().to_vec(), + width: entry.width(), + height: entry.height(), + }) + } + #[cfg(feature = "icon-png")] + "png" => { + let decoder = png::Decoder::new(std::io::Cursor::new(bytes)); + let mut reader = decoder.read_info()?; + let mut buffer = Vec::new(); + while let Ok(Some(row)) = reader.next_row() { + buffer.extend(row.data()); + } + Ok(Self { + rgba: buffer, + width: reader.info().width, + height: reader.info().height, + }) + } + _ => panic!( + "image `{extension}` extension not supported; please file a Tauri feature request. `png` or `ico` icons are supported with the `icon-png` and `icon-ico` feature flags" + ), + } + } + } + } +} + /// User supplied data required inside of a Tauri application. /// /// # Stability @@ -272,13 +376,13 @@ pub use pattern::Pattern; pub struct Context { pub(crate) config: Config, pub(crate) assets: Arc, - pub(crate) default_window_icon: Option>, + pub(crate) default_window_icon: Option, + pub(crate) app_icon: Option>, + #[cfg(desktop)] pub(crate) system_tray_icon: Option, pub(crate) package_info: PackageInfo, pub(crate) _info_plist: (), pub(crate) pattern: Pattern, - #[cfg(shell_scope)] - pub(crate) shell_scope: scope::ShellScopeConfig, } impl fmt::Debug for Context { @@ -286,11 +390,13 @@ impl fmt::Debug for Context { let mut d = f.debug_struct("Context"); d.field("config", &self.config) .field("default_window_icon", &self.default_window_icon) - .field("system_tray_icon", &self.system_tray_icon) + .field("app_icon", &self.app_icon) .field("package_info", &self.package_info) .field("pattern", &self.pattern); - #[cfg(shell_scope)] - d.field("shell_scope", &self.shell_scope); + + #[cfg(desktop)] + d.field("system_tray_icon", &self.system_tray_icon); + d.finish() } } @@ -322,23 +428,25 @@ impl Context { /// The default window icon Tauri should use when creating windows. #[inline(always)] - pub fn default_window_icon(&self) -> Option<&[u8]> { - self.default_window_icon.as_deref() + pub fn default_window_icon(&self) -> Option<&Icon> { + self.default_window_icon.as_ref() } /// A mutable reference to the default window icon Tauri should use when creating windows. #[inline(always)] - pub fn default_window_icon_mut(&mut self) -> &mut Option> { + pub fn default_window_icon_mut(&mut self) -> &mut Option { &mut self.default_window_icon } /// The icon to use on the system tray UI. + #[cfg(desktop)] #[inline(always)] pub fn system_tray_icon(&self) -> Option<&Icon> { self.system_tray_icon.as_ref() } /// A mutable reference to the icon to use on the system tray UI. + #[cfg(desktop)] #[inline(always)] pub fn system_tray_icon_mut(&mut self) -> &mut Option { &mut self.system_tray_icon @@ -362,61 +470,125 @@ impl Context { &self.pattern } - /// The scoped shell commands, where the `HashMap` key is the name each configuration. - #[cfg(shell_scope)] - #[inline(always)] - pub fn allowed_commands(&self) -> &scope::ShellScopeConfig { - &self.shell_scope - } - /// Create a new [`Context`] from the minimal required items. #[inline(always)] #[allow(clippy::too_many_arguments)] pub fn new( config: Config, assets: Arc, - default_window_icon: Option>, - system_tray_icon: Option, + default_window_icon: Option, + app_icon: Option>, package_info: PackageInfo, info_plist: (), pattern: Pattern, - #[cfg(shell_scope)] shell_scope: scope::ShellScopeConfig, ) -> Self { Self { config, assets, default_window_icon, - system_tray_icon, + app_icon, + #[cfg(desktop)] + system_tray_icon: None, package_info, _info_plist: info_plist, pattern, - #[cfg(shell_scope)] - shell_scope, } } + + /// Sets the app tray icon. + #[cfg(desktop)] + #[inline(always)] + pub fn set_system_tray_icon(&mut self, icon: Icon) { + self.system_tray_icon.replace(icon); + } + + /// Sets the app shell scope. + #[cfg(shell_scope)] + #[inline(always)] + pub fn set_shell_scope(&mut self, scope: scope::ShellScopeConfig) { + self.shell_scope = scope; + } } // TODO: expand these docs /// Manages a running application. pub trait Manager: sealed::ManagerBase { + /// The application handle associated with this manager. + fn app_handle(&self) -> AppHandle { + self.managed_app_handle() + } + /// The [`Config`] the manager was created with. fn config(&self) -> Arc { self.manager().config() } + /// The [`PackageInfo`] the manager was created with. + fn package_info(&self) -> &PackageInfo { + self.manager().package_info() + } + /// Emits a event to all windows. + /// + /// Only the webviews receives this event. + /// To trigger Rust listeners, use [`Self::trigger_global`], [`Window::trigger`] or [`Window::emit_and_trigger`]. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// #[tauri::command] + /// fn synchronize(app: tauri::AppHandle) { + /// // emits the synchronized event to all windows + /// app.emit_all("synchronized", ()); + /// } + /// ``` fn emit_all(&self, event: &str, payload: S) -> Result<()> { self.manager().emit_filter(event, None, payload, |_| true) } - /// Emits an event to a window with the specified label. + /// Emits an event to the window with the specified label. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// #[tauri::command] + /// fn download(app: tauri::AppHandle) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to the updater window + /// app.emit_to("updater", "download-progress", i); + /// } + /// } + /// ``` fn emit_to(&self, label: &str, event: &str, payload: S) -> Result<()> { self .manager() .emit_filter(event, None, payload, |w| label == w.label()) } - /// Listen to a global event. + /// Listen to a event triggered on any window ([`Window::trigger`] or [`Window::emit_and_trigger`]) or with [`Self::trigger_global`]. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// #[tauri::command] + /// fn synchronize(window: tauri::Window) { + /// // emits the synchronized event to all windows + /// window.emit_and_trigger("synchronized", ()); + /// } + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// app.listen_global("synchronized", |event| { + /// println!("app is in sync"); + /// }); + /// Ok(()) + /// }) + /// .invoke_handler(tauri::generate_handler![synchronize]); + /// ``` fn listen_global(&self, event: impl Into, handler: F) -> EventHandler where F: Fn(Event) + Send + 'static, @@ -425,19 +597,63 @@ pub trait Manager: sealed::ManagerBase { } /// Listen to a global event only once. + /// + /// See [`Self::listen_global`] for more information. fn once_global(&self, event: impl Into, handler: F) -> EventHandler where - F: Fn(Event) + Send + 'static, + F: FnOnce(Event) + Send + 'static, { self.manager().once(event.into(), None, handler) } - /// Trigger a global event. + /// Trigger a global event to Rust listeners. + /// To send the events to the webview, see [`Self::emit_all`] and [`Self::emit_to`]. + /// To trigger listeners registed on an specific window, see [`Window::trigger`]. + /// To trigger all listeners, see [`Window::emit_and_trigger`]. + /// + /// A global event does not have a source or target window attached. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// #[tauri::command] + /// fn download(app: tauri::AppHandle) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to all listeners registed in Rust + /// app.trigger_global("download-progress", Some(i.to_string())); + /// } + /// } + /// ``` fn trigger_global(&self, event: &str, data: Option) { self.manager().trigger(event, None, data) } /// Remove an event listener. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle(); + /// let handler = app.listen_global("ready", move |event| { + /// println!("app is ready"); + /// + /// // we no longer need to listen to the event + /// // we also could have used `app.once_global` instead + /// handle.unlisten(event.id()); + /// }); + /// + /// // stop listening to the event when you do not need it anymore + /// app.unlisten(handler); + /// + /// + /// Ok(()) + /// }); + /// ``` fn unlisten(&self, handler_id: EventHandler) { self.manager().unlisten(handler_id) } @@ -446,6 +662,10 @@ pub trait Manager: sealed::ManagerBase { fn get_window(&self, label: &str) -> Option> { self.manager().get_window(label) } + /// Fetch the focused window. Returns `None` if there is not any focused window. + fn get_focused_window(&self) -> Option> { + self.manager().get_focused_window() + } /// Fetch all managed windows. fn windows(&self) -> HashMap> { @@ -453,15 +673,107 @@ pub trait Manager: sealed::ManagerBase { } /// Add `state` to the state managed by the application. - /// See [`crate::Builder#manage`] for instructions. - fn manage(&self, state: T) + /// + /// This method can be called any number of times as long as each call + /// refers to a different `T`. + /// If a state for `T` is already managed, the function returns false and the value is ignored. + /// + /// Managed state can be retrieved by any command handler via the + /// [`State`](crate::State) guard. In particular, if a value of type `T` + /// is managed by Tauri, adding `State` to the list of arguments in a + /// command handler instructs Tauri to retrieve the managed value. + /// + /// # Panics + /// + /// Panics if state of type `T` is already being managed. + /// + /// # Mutability + /// + /// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability: + /// + /// ```rust,no_run + /// use std::{collections::HashMap, sync::Mutex}; + /// use tauri::State; + /// // here we use Mutex to achieve interior mutability + /// struct Storage { + /// store: Mutex>, + /// } + /// struct Connection; + /// struct DbConnection { + /// db: Mutex>, + /// } + /// + /// #[tauri::command] + /// fn connect(connection: State) { + /// // initialize the connection, mutating the state with interior mutability + /// *connection.db.lock().unwrap() = Some(Connection {}); + /// } + /// + /// #[tauri::command] + /// fn storage_insert(key: u64, value: String, storage: State) { + /// // mutate the storage behind the Mutex + /// storage.store.lock().unwrap().insert(key, value); + /// } + /// + /// tauri::Builder::default() + /// .manage(Storage { store: Default::default() }) + /// .manage(DbConnection { db: Default::default() }) + /// .invoke_handler(tauri::generate_handler![connect, storage_insert]) + /// // on an actual app, remove the string argument + /// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .expect("error while running tauri application"); + /// ``` + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::{Manager, State}; + /// + /// struct MyInt(isize); + /// struct MyString(String); + /// + /// #[tauri::command] + /// fn int_command(state: State) -> String { + /// format!("The stateful int is: {}", state.0) + /// } + /// + /// #[tauri::command] + /// fn string_command<'r>(state: State<'r, MyString>) { + /// println!("state: {}", state.inner().0); + /// } + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// app.manage(MyInt(0)); + /// app.manage(MyString("tauri".into())); + /// // `MyInt` is already managed, so `manage()` returns false + /// assert!(!app.manage(MyInt(1))); + /// // read the `MyInt` managed state with the turbofish syntax + /// let int = app.state::(); + /// assert_eq!(int.0, 0); + /// // read the `MyString` managed state with the `State` guard + /// let val: State = app.state(); + /// assert_eq!(val.0, "tauri"); + /// Ok(()) + /// }) + /// .invoke_handler(tauri::generate_handler![int_command, string_command]) + /// // on an actual app, remove the string argument + /// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .expect("error while running tauri application"); + /// ``` + fn manage(&self, state: T) -> bool where T: Send + Sync + 'static, { - self.manager().state().set(state); + self.manager().state().set(state) } - /// Gets the managed state for the type `T`. Panics if the type is not managed. + /// Retrieves the managed state for the type `T`. + /// + /// # Panics + /// + /// Panics if the state for the type `T` has not been previously [managed](Self::manage). + /// Use [try_state](Self::try_state) for a non-panicking version. fn state(&self) -> State<'_, T> where T: Send + Sync + 'static, @@ -474,7 +786,9 @@ pub trait Manager: sealed::ManagerBase { .expect("state() called before manage() for given type") } - /// Tries to get the managed state for the type `T`. Returns `None` if the type is not managed. + /// Attempts to retrieve the managed state for the type `T`. + /// + /// Returns `Some` if the state has previously been [managed](Self::manage). Otherwise returns `None`. fn try_state(&self) -> Option> where T: Send + Sync + 'static, @@ -487,28 +801,27 @@ pub trait Manager: sealed::ManagerBase { self.state::().inner().clone() } - /// Gets the scope for the filesystem APIs. - fn fs_scope(&self) -> FsScope { - self.state::().inner().fs.clone() + /// Gets the scope for the IPC. + fn ipc_scope(&self) -> IpcScope { + self.state::().inner().ipc.clone() } /// Gets the scope for the asset protocol. - #[cfg(protocol_asset)] + #[cfg(feature = "protocol-asset")] fn asset_protocol_scope(&self) -> FsScope { self.state::().inner().asset_protocol.clone() } - /// Gets the scope for the shell execute APIs. - #[cfg(shell_scope)] - fn shell_scope(&self) -> ShellScope { - self.state::().inner().shell.clone() + /// The path resolver. + fn path(&self) -> &crate::path::PathResolver { + self.state::>().inner() } } /// Prevent implementation details from leaking out of the [`Manager`] trait. pub(crate) mod sealed { + use super::Runtime; use crate::{app::AppHandle, manager::WindowManager}; - use tauri_runtime::{Runtime, RuntimeHandle}; /// A running [`Runtime`] or a dispatcher to it. pub enum RuntimeOrDispatch<'r, R: Runtime> { @@ -522,54 +835,61 @@ pub(crate) mod sealed { Dispatch(R::Dispatcher), } - #[derive(Clone, serde::Serialize)] - struct WindowCreatedEvent { - label: String, - } - /// Managed handle to the application runtime. pub trait ManagerBase { /// The manager behind the [`Managed`] item. fn manager(&self) -> &WindowManager; - fn runtime(&self) -> RuntimeOrDispatch<'_, R>; - fn app_handle(&self) -> AppHandle; - - /// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`]. - fn create_new_window( - &self, - pending: crate::PendingWindow, - ) -> crate::Result> { - use crate::runtime::Dispatch; - let labels = self.manager().labels().into_iter().collect::>(); - let pending = self - .manager() - .prepare_window(self.app_handle(), pending, &labels)?; - let window = match self.runtime() { - RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending), - RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending), - RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending), - } - .map(|window| self.manager().attach_window(self.app_handle(), window))?; - - self.manager().emit_filter( - "tauri://window-created", - None, - Some(WindowCreatedEvent { - label: window.label().into(), - }), - |w| w != &window, - )?; - - Ok(window) - } + fn managed_app_handle(&self) -> AppHandle; } } -/// Utilities for unit testing on Tauri applications. -#[cfg(test)] +#[cfg(any(test, feature = "test"))] +#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] pub mod test; +#[cfg(test)] +mod tests { + use cargo_toml::Manifest; + use once_cell::sync::OnceCell; + use std::{env::var, fs::read_to_string, path::PathBuf}; + + static MANIFEST: OnceCell = OnceCell::new(); + const CHECKED_FEATURES: &str = include_str!(concat!(env!("OUT_DIR"), "/checked_features")); + + fn get_manifest() -> &'static Manifest { + MANIFEST.get_or_init(|| { + let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap()); + Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest") + }) + } + + #[test] + fn features_are_documented() { + let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap()); + let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs"); + + for f in get_manifest().features.keys() { + if !(f.starts_with("__") || f == "default" || lib_code.contains(&format!("*{f}**"))) { + panic!("Feature {f} is not documented"); + } + } + } + + #[test] + fn aliased_features_exist() { + let checked_features = CHECKED_FEATURES.split(','); + let manifest = get_manifest(); + for checked_feature in checked_features { + if !manifest.features.iter().any(|(f, _)| f == checked_feature) { + panic!( + "Feature {checked_feature} was checked in the alias build step but it does not exist in core/tauri/Cargo.toml" + ); + } + } + } +} + #[cfg(test)] mod test_utils { use proptest::prelude::*; @@ -577,6 +897,13 @@ mod test_utils { pub fn assert_send() {} pub fn assert_sync() {} + #[allow(dead_code)] + pub fn assert_not_allowlist_error(res: anyhow::Result) { + if let Err(e) = res { + assert!(!e.to_string().contains("not on the allowlist")); + } + } + proptest! { #![proptest_config(ProptestConfig::with_cases(10000))] #[test] @@ -584,7 +911,7 @@ mod test_utils { fn check_spawn_task(task in "[a-z]+") { // create dummy task function let dummy_task = async move { - format!("{}-run-dummy-task", task); + format!("{task}-run-dummy-task"); }; // call spawn crate::async_runtime::spawn(dummy_task); diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 7283432d778a..416692f552b8 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -16,10 +16,12 @@ use serialize_to_javascript::{default_template, DefaultTemplate, Template}; use url::Url; use tauri_macros::default_runtime; +use tauri_utils::debug_eprintln; #[cfg(feature = "isolation")] use tauri_utils::pattern::isolation::RawIsolationPayload; use tauri_utils::{ assets::{AssetKey, CspHash}, + config::{Csp, CspDirectiveSources}, html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN}, }; @@ -27,7 +29,7 @@ use crate::app::{GlobalMenuEventListener, WindowMenuEvent}; use crate::hooks::IpcJavascript; #[cfg(feature = "isolation")] use crate::hooks::IsolationJavascript; -use crate::pattern::{format_real_schema, PatternJavascript}; +use crate::pattern::PatternJavascript; use crate::{ app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener}, event::{assert_event_name_is_valid, Event, EventHandler, Listeners}, @@ -38,20 +40,20 @@ use crate::{ MimeType, Request as HttpRequest, Response as HttpResponse, ResponseBuilder as HttpResponseBuilder, }, - webview::{FileDropEvent, FileDropHandler, WebviewIpcHandler, WindowBuilder}, - window::{dpi::PhysicalSize, DetachedWindow, PendingWindow, WindowEvent}, - Icon, Runtime, + webview::{WebviewIpcHandler, WindowBuilder}, + window::{dpi::PhysicalSize, DetachedWindow, FileDropEvent, PendingWindow}, }, utils::{ assets::Assets, config::{AppUrl, Config, WindowUrl}, PackageInfo, }, - Context, Invoke, Pattern, StateManager, Window, + Context, EventLoopMessage, Icon, Invoke, Manager, Pattern, Runtime, Scopes, StateManager, Window, + WindowEvent, }; #[cfg(any(target_os = "linux", target_os = "windows"))] -use crate::api::path::{resolve_path, BaseDirectory}; +use crate::path::BaseDirectory; use crate::{runtime::menu::Menu, MenuEvent}; @@ -62,13 +64,26 @@ const WINDOW_DESTROYED_EVENT: &str = "tauri://destroyed"; const WINDOW_FOCUS_EVENT: &str = "tauri://focus"; const WINDOW_BLUR_EVENT: &str = "tauri://blur"; const WINDOW_SCALE_FACTOR_CHANGED_EVENT: &str = "tauri://scale-change"; +const WINDOW_THEME_CHANGED: &str = "tauri://theme-changed"; +const WINDOW_FILE_DROP_EVENT: &str = "tauri://file-drop"; +const WINDOW_FILE_DROP_HOVER_EVENT: &str = "tauri://file-drop-hover"; +const WINDOW_FILE_DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled"; const MENU_EVENT: &str = "tauri://menu"; +pub(crate) const STRINGIFY_IPC_MESSAGE_FN: &str = + include_str!("../scripts/stringify-ipc-message-fn.js"); + +// we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address +// and we do not get a secure context without the custom protocol that proxies to the dev server +// additionally, we need the custom protocol to inject the initialization scripts on Android +// must also keep in sync with the `let mut response` assignment in prepare_uri_scheme_protocol +const PROXY_DEV_SERVER: bool = cfg!(all(dev, mobile)); + #[derive(Default)] /// Spaced and quoted Content-Security-Policy hash values. struct CspHashStrings { - script: String, - style: String, + script: Vec, + style: Vec, } /// Sets the CSP value to the asset HTML if needed (on Linux). @@ -77,63 +92,63 @@ fn set_csp( asset: &mut String, assets: Arc, asset_path: &AssetKey, - #[allow(unused_variables)] manager: &WindowManager, - mut csp: String, + manager: &WindowManager, + csp: Csp, ) -> String { + let mut csp = csp.into(); let hash_strings = assets .csp_hashes(asset_path) .fold(CspHashStrings::default(), |mut acc, hash| { match hash { CspHash::Script(hash) => { - acc.script.push(' '); - acc.script.push_str(hash); + acc.script.push(hash.into()); } CspHash::Style(hash) => { - acc.style.push(' '); - acc.style.push_str(hash); + acc.style.push(hash.into()); } _csp_hash => { - #[cfg(debug_assertions)] - eprintln!("Unknown CspHash variant encountered: {:?}", _csp_hash) + debug_eprintln!("Unknown CspHash variant encountered: {:?}", _csp_hash); } } acc }); - replace_csp_nonce( - asset, - SCRIPT_NONCE_TOKEN, - &mut csp, - "script-src", - hash_strings.script, - ); - - replace_csp_nonce( - asset, - STYLE_NONCE_TOKEN, - &mut csp, - "style-src", - hash_strings.style, - ); + let dangerous_disable_asset_csp_modification = &manager + .config() + .tauri + .security + .dangerous_disable_asset_csp_modification; + if dangerous_disable_asset_csp_modification.can_modify("script-src") { + replace_csp_nonce( + asset, + SCRIPT_NONCE_TOKEN, + &mut csp, + "script-src", + hash_strings.script, + ); + } + + if dangerous_disable_asset_csp_modification.can_modify("style-src") { + replace_csp_nonce( + asset, + STYLE_NONCE_TOKEN, + &mut csp, + "style-src", + hash_strings.style, + ); + } #[cfg(feature = "isolation")] if let Pattern::Isolation { schema, .. } = &manager.inner.pattern { - let default_src = format!("default-src {}", format_real_schema(schema)); - if csp.contains("default-src") { - csp = csp.replace("default-src", &default_src); - } else { - csp.push_str("; "); - csp.push_str(&default_src); - } + let default_src = csp + .entry("default-src".into()) + .or_insert_with(Default::default); + default_src.push(crate::pattern::format_real_schema(schema)); } - #[cfg(target_os = "linux")] - { - *asset = asset.replacen(tauri_utils::html::CSP_TOKEN, &csp, 1); - } - csp + Csp::DirectiveMap(csp).to_string() } // inspired by https://github.com/rust-lang/rust/blob/1be5c8f90912c446ecbdc405cbc4a89f9acd20fd/library/alloc/src/str.rs#L260-L297 @@ -156,9 +171,9 @@ fn replace_with_callback String>( fn replace_csp_nonce( asset: &mut String, token: &str, - csp: &mut String, - csp_attr: &str, - hashes: String, + csp: &mut HashMap, + directive: &str, + hashes: Vec, ) { let mut nonces = Vec::new(); *asset = replace_with_callback(asset, token, || { @@ -168,35 +183,25 @@ fn replace_csp_nonce( }); if !(nonces.is_empty() && hashes.is_empty()) { - let attr = format!( - "{} 'self'{}{}", - csp_attr, - if nonces.is_empty() { - "".into() - } else { - format!( - " {}", - nonces - .into_iter() - .map(|n| format!("'nonce-{}'", n)) - .collect::>() - .join(" ") - ) - }, - hashes - ); - if csp.contains(csp_attr) { - *csp = csp.replace(csp_attr, &attr); - } else { - csp.push_str("; "); - csp.push_str(&attr); + let nonce_sources = nonces + .into_iter() + .map(|n| format!("'nonce-{n}'")) + .collect::>(); + let sources = csp.entry(directive.into()).or_insert_with(Default::default); + let self_source = "'self'".to_string(); + if !sources.contains(&self_source) { + sources.push(self_source); } + sources.extend(nonce_sources); + sources.extend(hashes); } } #[default_runtime(crate::Wry, wry)] pub struct InnerWindowManager { windows: Mutex>>, + #[cfg(all(desktop, feature = "system-tray"))] + pub(crate) trays: Mutex>>, pub(crate) plugins: Mutex>, listeners: Listeners, pub(crate) state: Arc, @@ -209,10 +214,13 @@ pub struct InnerWindowManager { config: Arc, assets: Arc, - default_window_icon: Option>, + pub(crate) default_window_icon: Option, + pub(crate) app_icon: Option>, + #[cfg(desktop)] + pub(crate) tray_icon: Option, package_info: PackageInfo, - /// The webview protocols protocols available to all windows. + /// The webview protocols available to all windows. uri_scheme_protocols: HashMap>>, /// The menu set to all windows. menu: Option, @@ -225,20 +233,26 @@ pub struct InnerWindowManager { /// The script that initializes the invoke system. invoke_initialization_script: String, /// Application pattern. - pattern: Pattern, + pub(crate) pattern: Pattern, } impl fmt::Debug for InnerWindowManager { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("InnerWindowManager") - .field("plugins", &self.plugins) + let mut d = f.debug_struct("InnerWindowManager"); + + d.field("plugins", &self.plugins) .field("state", &self.state) .field("config", &self.config) .field("default_window_icon", &self.default_window_icon) + .field("app_icon", &self.app_icon) .field("package_info", &self.package_info) .field("menu", &self.menu) - .field("pattern", &self.pattern) - .finish() + .field("pattern", &self.pattern); + + #[cfg(desktop)] + d.field("tray_icon", &self.tray_icon); + + d.finish() } } @@ -299,6 +313,8 @@ impl WindowManager { Self { inner: Arc::new(InnerWindowManager { windows: Mutex::default(), + #[cfg(all(desktop, feature = "system-tray"))] + trays: Default::default(), plugins: Mutex::new(plugins), listeners: Listeners::default(), state: Arc::new(state), @@ -307,6 +323,9 @@ impl WindowManager { config: Arc::new(context.config), assets: context.assets, default_window_icon: context.default_window_icon, + app_icon: context.app_icon, + #[cfg(desktop)] + tray_icon: context.system_tray_icon, package_info: context.package_info, pattern: context.pattern, uri_scheme_protocols, @@ -342,7 +361,7 @@ impl WindowManager { /// /// * In dev mode, this will be based on the `devPath` configuration value. /// * Otherwise, this will be based on the `distDir` configuration value. - #[cfg(custom_protocol)] + #[cfg(not(dev))] fn base_path(&self) -> &AppUrl { &self.inner.config.build.dist_dir } @@ -355,28 +374,21 @@ impl WindowManager { /// Get the base URL to use for webview requests. /// /// In dev mode, this will be based on the `devPath` configuration value. - fn get_url(&self) -> Cow<'_, Url> { + pub(crate) fn get_url(&self) -> Cow<'_, Url> { match self.base_path() { AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url), - _ => Cow::Owned(Url::parse("tauri://localhost").unwrap()), + _ => self.protocol_url(), } } - /// Get the origin as it will be seen in the webview. - fn get_browser_origin(&self) -> Cow<'_, str> { - match self.base_path() { - AppUrl::Url(WindowUrl::External(url)) => { - let mut url = url.to_string(); - if url.ends_with('/') { - url.pop(); - } - Cow::Owned(url) - } - _ => Cow::Owned(format_real_schema("tauri")), - } + pub(crate) fn protocol_url(&self) -> Cow<'_, Url> { + #[cfg(any(window, target_os = "android"))] + return Cow::Owned(Url::parse("https://tauri.localhost").unwrap()); + #[cfg(not(any(window, target_os = "android")))] + Cow::Owned(Url::parse("tauri://localhost").unwrap()) } - fn csp(&self) -> Option { + fn csp(&self) -> Option { if cfg!(feature = "custom-protocol") { self.inner.config.tauri.security.csp.clone() } else { @@ -393,11 +405,11 @@ impl WindowManager { fn prepare_pending_window( &self, - mut pending: PendingWindow, + mut pending: PendingWindow, label: &str, window_labels: &[String], app_handle: AppHandle, - ) -> crate::Result> { + ) -> crate::Result> { let is_init_global = self.inner.config.build.with_global_tauri; let plugin_init = self .inner @@ -441,8 +453,7 @@ impl WindowManager { window_labels_array = serde_json::to_string(&window_labels)?, current_window_label = serde_json::to_string(&label)?, )) - .initialization_script(&self.initialization_script(&ipc_init.into_string(),&pattern_init.into_string(),&plugin_init, is_init_global)?) - ; + .initialization_script(&self.initialization_script(&ipc_init.into_string(),&pattern_init.into_string(),&plugin_init, is_init_global)?); #[cfg(feature = "isolation")] if let Pattern::Isolation { schema, .. } = self.pattern() { @@ -458,19 +469,6 @@ impl WindowManager { pending.webview_attributes = webview_attributes; - if !pending.window_builder.has_icon() { - if let Some(default_window_icon) = &self.inner.default_window_icon { - let icon = Icon::Raw(default_window_icon.clone()); - pending.window_builder = pending.window_builder.icon(icon)?; - } - } - - if pending.window_builder.get_menu().is_none() { - if let Some(menu) = &self.inner.menu { - pending = pending.set_menu(menu.clone()); - } - } - let mut registered_scheme_protocols = Vec::new(); for (uri_scheme, protocol) in &self.inner.uri_scheme_protocols { @@ -482,154 +480,41 @@ impl WindowManager { }); } + let window_url = Url::parse(&pending.url).unwrap(); + let window_origin = if window_url.scheme() == "data" { + "null".into() + } else if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" { + format!("https://{}.localhost", window_url.scheme()) + } else { + format!( + "{}://{}{}", + window_url.scheme(), + window_url.host().unwrap(), + window_url + .port() + .map(|p| format!(":{p}")) + .unwrap_or_default() + ) + }; + if !registered_scheme_protocols.contains(&"tauri".into()) { - pending.register_uri_scheme_protocol("tauri", self.prepare_uri_scheme_protocol()); + let web_resource_request_handler = pending.web_resource_request_handler.take(); + pending.register_uri_scheme_protocol( + "tauri", + self.prepare_uri_scheme_protocol(&window_origin, web_resource_request_handler), + ); registered_scheme_protocols.push("tauri".into()); } - #[cfg(protocol_asset)] + #[cfg(feature = "protocol-asset")] if !registered_scheme_protocols.contains(&"asset".into()) { - use tokio::io::{AsyncReadExt, AsyncSeekExt}; - use url::Position; let asset_scope = self.state().get::().asset_protocol.clone(); - let window_url = Url::parse(&pending.url).unwrap(); - let window_origin = - if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" { - format!("https://{}.localhost", window_url.scheme()) - } else { - format!( - "{}://{}{}", - window_url.scheme(), - window_url.host().unwrap(), - if let Some(port) = window_url.port() { - format!(":{}", port) - } else { - "".into() - } - ) - }; pending.register_uri_scheme_protocol("asset", move |request| { - let parsed_path = Url::parse(request.uri())?; - let filtered_path = &parsed_path[..Position::AfterPath]; - #[cfg(target_os = "windows")] - let path = filtered_path.replace("asset://localhost/", ""); - #[cfg(not(target_os = "windows"))] - let path = filtered_path.replace("asset://", ""); - let path = percent_encoding::percent_decode(path.as_bytes()) - .decode_utf8_lossy() - .to_string(); - - if !asset_scope.is_allowed(&path) { - #[cfg(debug_assertions)] - eprintln!("asset protocol not configured to allow the path: {}", path); - return HttpResponseBuilder::new().status(403).body(Vec::new()); - } - - let path_ = path.clone(); - - let mut response = - HttpResponseBuilder::new().header("Access-Control-Allow-Origin", &window_origin); - - // handle 206 (partial range) http request - if let Some(range) = request - .headers() - .get("range") - .and_then(|r| r.to_str().map(|r| r.to_string()).ok()) - { - let (headers, status_code, data) = crate::async_runtime::safe_block_on(async move { - let mut headers = HashMap::new(); - let mut buf = Vec::new(); - // open the file - let mut file = match tokio::fs::File::open(path_.clone()).await { - Ok(file) => file, - Err(e) => { - #[cfg(debug_assertions)] - eprintln!("Failed to open asset: {}", e); - return (headers, 404, buf); - } - }; - // Get the file size - let file_size = match file.metadata().await { - Ok(metadata) => metadata.len(), - Err(e) => { - #[cfg(debug_assertions)] - eprintln!("Failed to read asset metadata: {}", e); - return (headers, 404, buf); - } - }; - // parse the range - let range = match crate::runtime::http::HttpRange::parse(&range, file_size) { - Ok(r) => r, - Err(e) => { - #[cfg(debug_assertions)] - eprintln!("Failed to parse range: {:?}", e); - return (headers, 400, buf); - } - }; - - // FIXME: Support multiple ranges - // let support only 1 range for now - let status_code = if let Some(range) = range.first() { - let mut real_length = range.length; - // prevent max_length; - // specially on webview2 - if range.length > file_size / 3 { - // max size sent (400ko / request) - // as it's local file system we can afford to read more often - real_length = std::cmp::min(file_size - range.start, 1024 * 400); - } - - // last byte we are reading, the length of the range include the last byte - // who should be skipped on the header - let last_byte = range.start + real_length - 1; - - headers.insert("Connection", "Keep-Alive".into()); - headers.insert("Accept-Ranges", "bytes".into()); - headers.insert("Content-Length", real_length.to_string()); - headers.insert( - "Content-Range", - format!("bytes {}-{}/{}", range.start, last_byte, file_size), - ); - - if let Err(e) = file.seek(std::io::SeekFrom::Start(range.start)).await { - #[cfg(debug_assertions)] - eprintln!("Failed to seek file to {}: {}", range.start, e); - return (headers, 422, buf); - } - - if let Err(e) = file.take(real_length).read_to_end(&mut buf).await { - #[cfg(debug_assertions)] - eprintln!("Failed read file: {}", e); - return (headers, 422, buf); - } - // partial content - 206 - } else { - 200 - }; - - (headers, status_code, buf) - }); - - for (k, v) in headers { - response = response.header(k, v); - } - - let mime_type = MimeType::parse(&data, &path); - response.mimetype(&mime_type).status(status_code).body(data) - } else { - match crate::async_runtime::safe_block_on(async move { tokio::fs::read(path_).await }) { - Ok(data) => { - let mime_type = MimeType::parse(&data, &path); - response.mimetype(&mime_type).body(data) - } - Err(e) => { - #[cfg(debug_assertions)] - eprintln!("Failed to read file: {}", e); - response.status(404).body(Vec::new()) - } - } - } + crate::asset_protocol::asset_protocol_handler( + request, + asset_scope.clone(), + window_origin.clone(), + ) }); } @@ -643,7 +528,7 @@ impl WindowManager { { let assets = assets.clone(); let schema_ = schema.clone(); - let url_base = format!("{}://localhost", schema_); + let url_base = format!("{schema_}://localhost"); let aes_gcm_key = *crypto_keys.aes_gcm().raw(); pending.register_uri_scheme_protocol(schema, move |request| { @@ -653,6 +538,7 @@ impl WindowManager { let asset = String::from_utf8_lossy(asset.as_ref()); let template = tauri_utils::pattern::isolation::IsolationJavascriptRuntime { runtime_aes_gcm_key: &aes_gcm_key, + stringify_ipc_message_fn: STRINGIFY_IPC_MESSAGE_FN, }; match template.render(asset.as_ref(), &Default::default()) { Ok(asset) => HttpResponseBuilder::new() @@ -681,40 +567,40 @@ impl WindowManager { Ok(pending) } - fn prepare_ipc_handler(&self, app_handle: AppHandle) -> WebviewIpcHandler { + fn prepare_ipc_handler(&self) -> WebviewIpcHandler { let manager = self.clone(); Box::new(move |window, #[allow(unused_mut)] mut request| { - let window = Window::new(manager.clone(), window, app_handle.clone()); + if let Some(window) = manager.get_window(&window.label) { + #[cfg(feature = "isolation")] + if let Pattern::Isolation { crypto_keys, .. } = manager.pattern() { + match RawIsolationPayload::try_from(request.as_str()) + .and_then(|raw| crypto_keys.decrypt(raw)) + { + Ok(json) => request = json, + Err(e) => { + let error: crate::Error = e.into(); + let _ = window.eval(&format!( + r#"console.error({})"#, + JsonValue::String(error.to_string()) + )); + return; + } + } + } - #[cfg(feature = "isolation")] - if let Pattern::Isolation { crypto_keys, .. } = manager.pattern() { - match RawIsolationPayload::try_from(request.as_str()) - .and_then(|raw| crypto_keys.decrypt(raw)) - { - Ok(json) => request = json, + match serde_json::from_str::(&request) { + Ok(message) => { + let _ = window.on_message(message); + } Err(e) => { let error: crate::Error = e.into(); let _ = window.eval(&format!( r#"console.error({})"#, JsonValue::String(error.to_string()) )); - return; } } } - - match serde_json::from_str::(&request) { - Ok(message) => { - let _ = window.on_message(message); - } - Err(e) => { - let error: crate::Error = e.into(); - let _ = window.eval(&format!( - r#"console.error({})"#, - JsonValue::String(error.to_string()) - )); - } - } }) } @@ -733,21 +619,31 @@ impl WindowManager { // skip leading `/` path.chars().skip(1).collect::() }; - let is_html = path.ends_with(".html"); let mut asset_path = AssetKey::from(path.as_str()); let asset_response = assets .get(&path.as_str().into()) .or_else(|| { + eprintln!("Asset `{path}` not found; fallback to {path}.html"); + let fallback = format!("{}.html", path.as_str()).into(); + let asset = assets.get(&fallback); + asset_path = fallback; + asset + }) + .or_else(|| { + debug_eprintln!( + "Asset `{}` not found; fallback to {}/index.html", + path, + path + ); let fallback = format!("{}/index.html", path.as_str()).into(); let asset = assets.get(&fallback); asset_path = fallback; asset }) .or_else(|| { - #[cfg(debug_assertions)] - eprintln!("Asset `{}` not found; fallback to index.html", path); // TODO log::error! + debug_eprintln!("Asset `{}` not found; fallback to index.html", path); let fallback = AssetKey::from("index.html"); let asset = assets.get(&fallback); asset_path = fallback; @@ -757,6 +653,7 @@ impl WindowManager { .map(Cow::into_owned); let mut csp_header = None; + let is_html = asset_path.as_ref().ends_with(".html"); match asset_response { Ok(asset) => { @@ -784,8 +681,7 @@ impl WindowManager { }) } Err(e) => { - #[cfg(debug_assertions)] - eprintln!("{:?}", e); // TODO log::error! + debug_eprintln!("{:?}", e); // TODO log::error! Err(Box::new(e)) } } @@ -794,38 +690,130 @@ impl WindowManager { #[allow(clippy::type_complexity)] fn prepare_uri_scheme_protocol( &self, + window_origin: &str, + web_resource_request_handler: Option< + Box, + >, ) -> Box Result> + Send + Sync> { + #[cfg(all(dev, mobile))] + let url = { + let mut url = self.get_url().as_str().to_string(); + if url.ends_with('/') { + url.pop(); + } + url + }; + #[cfg(not(all(dev, mobile)))] let manager = self.clone(); + let window_origin = window_origin.to_string(); + + #[cfg(all(dev, mobile))] + #[derive(Clone)] + struct CachedResponse { + status: http::StatusCode, + headers: http::HeaderMap, + body: bytes::Bytes, + } + + #[cfg(all(dev, mobile))] + let response_cache = Arc::new(Mutex::new(HashMap::new())); + Box::new(move |request| { - let path = request - .uri() - .split(&['?', '#'][..]) + // use the entire URI as we are going to proxy the request + let path = if PROXY_DEV_SERVER { + request.uri() + } else { // ignore query string and fragment - .next() - .unwrap() - .to_string() - .replace("tauri://localhost", ""); - let asset = manager.get_asset(path)?; - let mut response = HttpResponseBuilder::new().mimetype(&asset.mime_type); - if let Some(csp) = asset.csp_header { - response = response.header("Content-Security-Policy", csp); - } - response.body(asset.bytes) - }) - } + request.uri().split(&['?', '#'][..]).next().unwrap() + }; - fn prepare_file_drop(&self, app_handle: AppHandle) -> FileDropHandler { - let manager = self.clone(); - Box::new(move |event, window| { - let window = Window::new(manager.clone(), window, app_handle.clone()); - let _ = match event { - FileDropEvent::Hovered(paths) => window.emit_and_trigger("tauri://file-drop-hover", paths), - FileDropEvent::Dropped(paths) => window.emit_and_trigger("tauri://file-drop", paths), - FileDropEvent::Cancelled => window.emit_and_trigger("tauri://file-drop-cancelled", ()), - _ => unimplemented!(), + let path = path + .strip_prefix("tauri://localhost") + .map(|p| p.to_string()) + // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows + // where `$P` is not `localhost/*` + .unwrap_or_else(|| "".to_string()); + + let mut builder = + HttpResponseBuilder::new().header("Access-Control-Allow-Origin", &window_origin); + + #[cfg(all(dev, mobile))] + let mut response = { + use reqwest::StatusCode; + let decoded_path = percent_encoding::percent_decode(path.as_bytes()) + .decode_utf8_lossy() + .to_string(); + let url = format!("{url}{decoded_path}"); + #[allow(unused_mut)] + let mut client_builder = reqwest::ClientBuilder::new(); + #[cfg(any(feature = "native-tls", feature = "rustls-tls"))] + { + client_builder = client_builder.danger_accept_invalid_certs(true); + } + let mut proxy_builder = client_builder + .build() + .unwrap() + .request(request.method().clone(), &url); + for (name, value) in request.headers() { + proxy_builder = proxy_builder.header(name, value); + } + match crate::async_runtime::block_on(proxy_builder.send()) { + Ok(r) => { + let mut response_cache_ = response_cache.lock().unwrap(); + let mut response = None; + if r.status() == StatusCode::NOT_MODIFIED { + response = response_cache_.get(&url); + } + let response = if let Some(r) = response { + r + } else { + let status = r.status(); + let headers = r.headers().clone(); + let body = crate::async_runtime::block_on(r.bytes())?; + let response = CachedResponse { + status, + headers, + body, + }; + response_cache_.insert(url.clone(), response); + response_cache_.get(&url).unwrap() + }; + for (name, value) in &response.headers { + builder = builder.header(name, value); + } + builder + .status(response.status) + .body(response.body.to_vec())? + } + Err(e) => { + debug_eprintln!("Failed to request {}: {}", url.as_str(), e); + return Err(Box::new(e)); + } + } + }; + + #[cfg(not(all(dev, mobile)))] + let mut response = { + let asset = manager.get_asset(path)?; + builder = builder.mimetype(&asset.mime_type); + if let Some(csp) = &asset.csp_header { + builder = builder.header("Content-Security-Policy", csp); + } + builder.body(asset.bytes)? }; - true + if let Some(handler) = &web_resource_request_handler { + handler(request, &mut response); + } + // if it's an HTML file, we need to set the CSP meta tag on Linux + #[cfg(all(not(dev), target_os = "linux"))] + if let Some(response_csp) = response.headers().get("Content-Security-Policy") { + let response_csp = String::from_utf8_lossy(response_csp.as_bytes()); + let html = String::from_utf8_lossy(response.body()); + let body = html.replacen(tauri_utils::html::CSP_TOKEN, &response_csp, 1); + *response.body_mut() = body.as_bytes().to_vec().into(); + } + Ok(response) }) } @@ -839,7 +827,6 @@ impl WindowManager { #[derive(Template)] #[default_template("../scripts/init.js")] struct InitJavascript<'a> { - origin: Cow<'a, str>, #[raw] pattern_script: &'a str, #[raw] @@ -860,7 +847,7 @@ impl WindowManager { } let bundle_script = if with_global_tauri { - include_str!("../scripts/bundle.js") + include_str!("../scripts/bundle.global.js") } else { "" }; @@ -872,7 +859,6 @@ impl WindowManager { }; InitJavascript { - origin: self.get_browser_origin(), pattern_script, ipc_script, bundle_script, @@ -897,7 +883,7 @@ impl WindowManager { } fn event_initialization_script(&self) -> String { - return format!( + format!( " Object.defineProperty(window, '{function}', {{ value: function (eventData) {{ @@ -905,7 +891,7 @@ impl WindowManager { for (let i = listeners.length - 1; i >= 0; i--) {{ const listener = listeners[i] - if (listener.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{ + if (listener.windowLabel === null || eventData.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{ eventData.id = listener.id listener.handler(eventData) }} @@ -915,7 +901,7 @@ impl WindowManager { ", function = self.event_emit_function_name(), listeners = self.event_listeners_object_name() - ); + ) } } @@ -931,7 +917,7 @@ mod test { let manager: WindowManager = WindowManager::with_handlers( context, PluginStore::default(), - Box::new(|_| ()), + Box::new(|_| false), Box::new(|_, _| ()), Default::default(), StateManager::new(), @@ -941,7 +927,16 @@ mod test { ); #[cfg(custom_protocol)] - assert_eq!(manager.get_url().to_string(), "tauri://localhost"); + { + assert_eq!( + manager.get_url().to_string(), + if cfg!(windows) { + "https://tauri.localhost/" + } else { + "tauri://localhost" + } + ); + } #[cfg(dev)] assert_eq!(manager.get_url().to_string(), "http://localhost:4000/"); @@ -949,8 +944,8 @@ mod test { } impl WindowManager { - pub fn run_invoke_handler(&self, invoke: Invoke) { - (self.inner.invoke_handler)(invoke); + pub fn run_invoke_handler(&self, invoke: Invoke) -> bool { + (self.inner.invoke_handler)(invoke) } pub fn run_on_page_load(&self, window: Window, payload: PageLoadPayload) { @@ -963,13 +958,13 @@ impl WindowManager { .on_page_load(window, payload); } - pub fn extend_api(&self, invoke: Invoke) { + pub fn extend_api(&self, plugin: &str, invoke: Invoke) -> bool { self .inner .plugins .lock() .expect("poisoned plugin store") - .extend_api(invoke); + .extend_api(plugin, invoke) } pub fn initialize_plugins(&self, app: &AppHandle) -> crate::Result<()> { @@ -984,31 +979,41 @@ impl WindowManager { pub fn prepare_window( &self, app_handle: AppHandle, - mut pending: PendingWindow, + mut pending: PendingWindow, window_labels: &[String], - ) -> crate::Result> { + ) -> crate::Result> { if self.windows_lock().contains_key(&pending.label) { return Err(crate::Error::WindowLabelAlreadyExists(pending.label)); } #[allow(unused_mut)] // mut url only for the data-url parsing - let (is_local, mut url) = match &pending.webview_attributes.url { + let mut url = match &pending.webview_attributes.url { WindowUrl::App(path) => { - let url = self.get_url(); - ( - true, - // ignore "index.html" just to simplify the url - if path.to_str() != Some("index.html") { - url - .join(&*path.to_string_lossy()) - .map_err(crate::Error::InvalidUrl) - // this will never fail - .unwrap() - } else { - url.into_owned() - }, - ) + let url = if PROXY_DEV_SERVER { + Cow::Owned(Url::parse("tauri://localhost").unwrap()) + } else { + self.get_url() + }; + // ignore "index.html" just to simplify the url + if path.to_str() != Some("index.html") { + url + .join(&path.to_string_lossy()) + .map_err(crate::Error::InvalidUrl) + // this will never fail + .unwrap() + } else { + url.into_owned() + } + } + WindowUrl::External(url) => { + let config_url = self.get_url(); + let is_local = config_url.make_relative(url).is_some(); + let mut url = url.clone(); + if is_local && PROXY_DEV_SERVER { + url.set_scheme("tauri").unwrap(); + url.set_host(Some("localhost")).unwrap(); + } + url } - WindowUrl::External(url) => (url.scheme() == "tauri", url.clone()), _ => unimplemented!(), }; @@ -1028,7 +1033,7 @@ impl WindowManager { // naive way to check if it's an html if html.contains('<') && html.contains('>') { let mut document = tauri_utils::html::parse(html); - tauri_utils::html::inject_csp(&mut document, &csp); + tauri_utils::html::inject_csp(&mut document, &csp.to_string()); url.set_path(&format!("text/html,{}", document.to_string())); } } @@ -1037,26 +1042,62 @@ impl WindowManager { pending.url = url.to_string(); - if is_local { - let label = pending.label.clone(); - pending = self.prepare_pending_window(pending, &label, window_labels, app_handle.clone())?; - pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle.clone())); + if !pending.window_builder.has_icon() { + if let Some(default_window_icon) = self.inner.default_window_icon.clone() { + pending.window_builder = pending + .window_builder + .icon(default_window_icon.try_into()?)?; + } + } + + if pending.window_builder.get_menu().is_none() { + if let Some(menu) = &self.inner.menu { + pending = pending.set_menu(menu.clone()); + } } - if pending.webview_attributes.file_drop_handler_enabled { - pending.file_drop_handler = Some(self.prepare_file_drop(app_handle)); + #[cfg(target_os = "android")] + { + pending = pending.on_webview_created(move |ctx| { + let plugin_manager = ctx + .env + .call_method( + ctx.activity, + "getPluginManager", + "()Lapp/tauri/plugin/PluginManager;", + &[], + )? + .l()?; + + // tell the manager the webview is ready + ctx.env.call_method( + plugin_manager, + "onWebViewCreated", + "(Landroid/webkit/WebView;)V", + &[ctx.webview.into()], + )?; + + Ok(()) + }); } + let label = pending.label.clone(); + pending = self.prepare_pending_window( + pending, + &label, + window_labels, + #[allow(clippy::redundant_clone)] + app_handle.clone(), + )?; + pending.ipc_handler = Some(self.prepare_ipc_handler()); + // in `Windows`, we need to force a data_directory // but we do respect user-specification #[cfg(any(target_os = "linux", target_os = "windows"))] if pending.webview_attributes.data_directory.is_none() { - let local_app_data = resolve_path( - &self.inner.config, - &self.inner.package_info, - self.inner.state.get::().inner(), + let local_app_data = app_handle.path().resolve( &self.inner.config.tauri.bundle.identifier, - Some(BaseDirectory::LocalData), + BaseDirectory::LocalData, ); if let Ok(user_data_dir) = local_app_data { pending.webview_attributes.data_directory = Some(user_data_dir); @@ -1070,10 +1111,34 @@ impl WindowManager { } } + #[cfg(feature = "isolation")] + let pattern = self.pattern().clone(); + let navigation_handler = pending.navigation_handler.take(); + pending.navigation_handler = Some(Box::new(move |url| { + // always allow navigation events for the isolation iframe and do not emit them for consumers + #[cfg(feature = "isolation")] + if let Pattern::Isolation { schema, .. } = &pattern { + if url.scheme() == schema + && url.domain() == Some(crate::pattern::ISOLATION_IFRAME_SRC_DOMAIN) + { + return true; + } + } + if let Some(handler) = &navigation_handler { + handler(url) + } else { + true + } + })); + Ok(pending) } - pub fn attach_window(&self, app_handle: AppHandle, window: DetachedWindow) -> Window { + pub fn attach_window( + &self, + app_handle: AppHandle, + window: DetachedWindow, + ) -> Window { let window = Window::new(self.clone(), window, app_handle); let window_ = window.clone(); @@ -1110,13 +1175,24 @@ impl WindowManager { } // let plugins know that a new window has been added to the manager - { - self - .inner + let manager = self.inner.clone(); + let window_ = window.clone(); + // run on main thread so the plugin store doesn't dead lock with the event loop handler in App + let _ = window.run_on_main_thread(move || { + manager .plugins .lock() .expect("poisoned plugin store") - .created(window.clone()); + .created(window_); + }); + + #[cfg(target_os = "ios")] + { + window + .with_webview(|w| { + unsafe { crate::ios::on_webview_created(w.inner() as _, w.view_controller() as _) }; + }) + .expect("failed to run on_webview_created hook"); } window @@ -1145,6 +1221,14 @@ impl WindowManager { .try_for_each(|window| window.emit_internal(event, source_window_label, payload.clone())) } + pub fn eval_script_all>(&self, script: S) -> crate::Result<()> { + let script = script.into(); + self + .windows_lock() + .values() + .try_for_each(|window| window.eval(&script)) + } + pub fn labels(&self) -> HashSet { self.windows_lock().keys().cloned().collect() } @@ -1176,7 +1260,7 @@ impl WindowManager { self.inner.listeners.listen(event, window, handler) } - pub fn once( + pub fn once( &self, event: String, window: Option, @@ -1198,39 +1282,72 @@ impl WindowManager { self.windows_lock().get(label).cloned() } + pub fn get_focused_window(&self) -> Option> { + self + .windows_lock() + .iter() + .find(|w| w.1.is_focused().unwrap_or(false)) + .map(|w| w.1.clone()) + } + pub fn windows(&self) -> HashMap> { self.windows_lock().clone() } } +/// Tray APIs +#[cfg(all(desktop, feature = "system-tray"))] +impl WindowManager { + pub fn get_tray(&self, id: &str) -> Option> { + self.inner.trays.lock().unwrap().get(id).cloned() + } + + pub fn trays(&self) -> HashMap> { + self.inner.trays.lock().unwrap().clone() + } + + pub fn attach_tray(&self, id: String, tray: crate::SystemTrayHandle) { + self.inner.trays.lock().unwrap().insert(id, tray); + } + + pub fn get_tray_by_runtime_id(&self, id: u16) -> Option<(String, crate::SystemTrayHandle)> { + let trays = self.inner.trays.lock().unwrap(); + let iter = trays.iter(); + for (tray_id, tray) in iter { + if tray.id == id { + return Some((tray_id.clone(), tray.clone())); + } + } + None + } +} + fn on_window_event( window: &Window, manager: &WindowManager, event: &WindowEvent, ) -> crate::Result<()> { match event { - WindowEvent::Resized(size) => window.emit_and_trigger(WINDOW_RESIZED_EVENT, size)?, - WindowEvent::Moved(position) => window.emit_and_trigger(WINDOW_MOVED_EVENT, position)?, - WindowEvent::CloseRequested { - label: _, - signal_tx, - } => { + WindowEvent::Resized(size) => window.emit(WINDOW_RESIZED_EVENT, size)?, + WindowEvent::Moved(position) => window.emit(WINDOW_MOVED_EVENT, position)?, + WindowEvent::CloseRequested { api } => { if window.has_js_listener(Some(window.label().into()), WINDOW_CLOSE_REQUESTED_EVENT) { - signal_tx.send(true).unwrap(); + api.prevent_close(); } - window.emit_and_trigger(WINDOW_CLOSE_REQUESTED_EVENT, ())?; + window.emit(WINDOW_CLOSE_REQUESTED_EVENT, ())?; } WindowEvent::Destroyed => { - window.emit_and_trigger(WINDOW_DESTROYED_EVENT, ())?; + window.emit(WINDOW_DESTROYED_EVENT, ())?; let label = window.label(); - for window in manager.inner.windows.lock().unwrap().values() { + let windows_map = manager.inner.windows.lock().unwrap(); + let windows = windows_map.values(); + for window in windows { window.eval(&format!( - r#"window.__TAURI_METADATA__.__windows = window.__TAURI_METADATA__.__windows.filter(w => w.label !== "{}");"#, - label + r#"(function () {{ const metadata = window.__TAURI_METADATA__; if (metadata != null) {{ metadata.__windows = window.__TAURI_METADATA__.__windows.filter(w => w.label !== "{label}"); }} }})()"#, ))?; } } - WindowEvent::Focused(focused) => window.emit_and_trigger( + WindowEvent::Focused(focused) => window.emit( if *focused { WINDOW_FOCUS_EVENT } else { @@ -1242,14 +1359,30 @@ fn on_window_event( scale_factor, new_inner_size, .. - } => window.emit_and_trigger( + } => window.emit( WINDOW_SCALE_FACTOR_CHANGED_EVENT, ScaleFactorChanged { scale_factor: *scale_factor, size: *new_inner_size, }, )?, - _ => unimplemented!(), + WindowEvent::FileDrop(event) => match event { + FileDropEvent::Hovered(paths) => window.emit(WINDOW_FILE_DROP_HOVER_EVENT, paths)?, + FileDropEvent::Dropped(paths) => { + let scopes = window.state::(); + for path in paths { + if path.is_file() { + let _ = scopes.allow_file(path); + } else { + let _ = scopes.allow_directory(path, false); + } + } + window.emit(WINDOW_FILE_DROP_EVENT, paths)? + } + FileDropEvent::Cancelled => window.emit(WINDOW_FILE_DROP_CANCELLED_EVENT, ())?, + _ => unimplemented!(), + }, + WindowEvent::ThemeChanged(theme) => window.emit(WINDOW_THEME_CHANGED, theme.to_string())?, } Ok(()) } @@ -1262,19 +1395,19 @@ struct ScaleFactorChanged { } fn on_menu_event(window: &Window, event: &MenuEvent) -> crate::Result<()> { - window.emit_and_trigger(MENU_EVENT, event.menu_item_id.clone()) + window.emit(MENU_EVENT, event.menu_item_id.clone()) } #[cfg(feature = "isolation")] -fn request_to_path(request: &tauri_runtime::http::Request, replace: &str) -> String { +fn request_to_path(request: &tauri_runtime::http::Request, base_url: &str) -> String { let mut path = request .uri() .split(&['?', '#'][..]) // ignore query string .next() .unwrap() - .to_string() - .replace(replace, ""); + .trim_start_matches(base_url) + .to_string(); if path.ends_with('/') { path.pop(); @@ -1300,6 +1433,7 @@ mod tests { #[test] fn string_replace_with_callback() { let mut tauri_index = 0; + #[allow(clippy::single_element_loop)] for (src, pattern, replacement, result) in [( "tauri is awesome, tauri is amazing", "tauri", diff --git a/core/tauri/src/path/android.rs b/core/tauri/src/path/android.rs new file mode 100644 index 000000000000..730e9f10ba67 --- /dev/null +++ b/core/tauri/src/path/android.rs @@ -0,0 +1,120 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::Result; +use crate::{plugin::PluginHandle, Runtime}; +use std::path::PathBuf; + +/// A helper class to access the mobile path APIs. +pub struct PathResolver(pub(crate) PluginHandle); + +#[derive(serde::Deserialize)] +struct PathResponse { + path: PathBuf, +} + +impl PathResolver { + fn call_resolve(&self, dir: &str) -> Result { + self + .0 + .run_mobile_plugin::(dir, ()) + .map(|r| r.path) + .map_err(Into::into) + } + + /// Returns the path to the user's audio directory. + pub fn audio_dir(&self) -> Result { + self.call_resolve("getAudioDir") + } + + /// Returns the path to the user's cache directory. + pub fn cache_dir(&self) -> Result { + self.call_resolve("getExternalCacheDir") + } + + /// Returns the path to the user's config directory. + pub fn config_dir(&self) -> Result { + self.call_resolve("getConfigDir") + } + + /// Returns the path to the user's data directory. + pub fn data_dir(&self) -> Result { + self.call_resolve("getDataDir") + } + + /// Returns the path to the user's local data directory. + pub fn local_data_dir(&self) -> Result { + self.call_resolve("getDataDir") + } + + /// Returns the path to the user's document directory. + pub fn document_dir(&self) -> Result { + self.call_resolve("getDocumentDir") + } + + /// Returns the path to the user's download directory. + pub fn download_dir(&self) -> Result { + self.call_resolve("getDownloadDir") + } + + /// Returns the path to the user's picture directory. + pub fn picture_dir(&self) -> Result { + self.call_resolve("getPictureDir") + } + + /// Returns the path to the user's public directory. + pub fn public_dir(&self) -> Result { + self.call_resolve("getPublicDir") + } + + /// Returns the path to the user's video dir + pub fn video_dir(&self) -> Result { + self.call_resolve("getVideoDir") + } + + /// Returns the path to the resource directory of this app. + pub fn resource_dir(&self) -> Result { + self.call_resolve("getResourcesDir") + } + + /// Returns the path to the suggested directory for your app's config files. + /// + /// Resolves to [`config_dir`]`/${bundle_identifier}`. + pub fn app_config_dir(&self) -> Result { + self.call_resolve("getConfigDir") + } + + /// Returns the path to the suggested directory for your app's data files. + /// + /// Resolves to [`data_dir`]`/${bundle_identifier}`. + pub fn app_data_dir(&self) -> Result { + self.call_resolve("getDataDir") + } + + /// Returns the path to the suggested directory for your app's local data files. + /// + /// Resolves to [`local_data_dir`]`/${bundle_identifier}`. + pub fn app_local_data_dir(&self) -> Result { + self.call_resolve("getDataDir") + } + + /// Returns the path to the suggested directory for your app's cache files. + /// + /// Resolves to [`cache_dir`]`/${bundle_identifier}`. + pub fn app_cache_dir(&self) -> Result { + self.call_resolve("getCacheDir") + } + + /// Returns the path to the suggested directory for your app's log files. + pub fn app_log_dir(&self) -> Result { + self + .call_resolve("getConfigDir") + .map(|dir| dir.join("logs")) + } + + /// A temporary directory. Resolves to [`std::env::temp_dir`]. + pub fn temp_dir(&self) -> Result { + Ok(std::env::temp_dir()) + } +} diff --git a/core/tauri/src/path/commands.rs b/core/tauri/src/path/commands.rs new file mode 100644 index 000000000000..8a343c5fec59 --- /dev/null +++ b/core/tauri/src/path/commands.rs @@ -0,0 +1,193 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR}; + +use super::{BaseDirectory, Error, PathResolver, Result}; +use crate::{command, AppHandle, Runtime, State}; + +/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util. +/// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106 +fn normalize_path(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +} + +/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util but +/// slightly modified to not resolve absolute paths. +/// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106 +fn normalize_path_no_absolute(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + // Using PathBuf::push here will replace the whole path if an absolute path is encountered + // which is not the intended behavior, so instead of that, convert the current resolved path + // to a string and do simple string concatenation with the current component then convert it + // back to a PathBuf + let mut p = ret.to_string_lossy().to_string(); + // Only add a separator if it doesn't have one already or if current normalized path is empty, + // this ensures it won't have an unwanted leading separator + if !p.is_empty() && !p.ends_with('/') && !p.ends_with('\\') { + p.push(MAIN_SEPARATOR); + } + if let Some(c) = c.to_str() { + p.push_str(c); + } + ret = PathBuf::from(p); + } + } + } + ret +} + +#[command(root = "crate")] +pub fn resolve_directory( + _app: AppHandle, + resolver: State<'_, PathResolver>, + directory: BaseDirectory, + path: Option, +) -> Result { + super::resolve_path(&resolver, directory, path) +} + +#[command(root = "crate")] +pub fn resolve(paths: Vec) -> Result { + // Start with current directory then start adding paths from the vector one by one using `PathBuf.push()` which + // will ensure that if an absolute path is encountered in the iteration, it will be used as the current full path. + // + // examples: + // 1. `vec!["."]` or `vec![]` will be equal to `std::env::current_dir()` + // 2. `vec!["/foo/bar", "/tmp/file", "baz"]` will be equal to `PathBuf::from("/tmp/file/baz")` + let mut path = std::env::current_dir().map_err(Error::CurrentDir)?; + for p in paths { + path.push(p); + } + Ok(normalize_path(&path)) +} + +#[command(root = "crate")] +pub fn normalize(path: String) -> String { + let mut p = normalize_path_no_absolute(Path::new(&path)) + .to_string_lossy() + .to_string(); + + // Node.js behavior is to return `".."` for `normalize("..")` + // and `"."` for `normalize("")` or `normalize(".")` + if p.is_empty() && path == ".." { + "..".into() + } else if p.is_empty() && path == "." { + ".".into() + } else { + // Add a trailing separator if the path passed to this functions had a trailing separator. That's how Node.js behaves. + if (path.ends_with('/') || path.ends_with('\\')) && (!p.ends_with('/') || !p.ends_with('\\')) { + p.push(MAIN_SEPARATOR); + } + p + } +} + +#[command(root = "crate")] +pub fn join(mut paths: Vec) -> String { + let path = PathBuf::from( + paths + .iter_mut() + .map(|p| { + // Add a `MAIN_SEPARATOR` if it doesn't already have one. + // Doing this to ensure that the vector elements are separated in + // the resulting string so path.components() can work correctly when called + // in `normalize_path_no_absolute()` later on. + if !p.ends_with('/') && !p.ends_with('\\') { + p.push(MAIN_SEPARATOR); + } + p.to_string() + }) + .collect::(), + ); + + let p = normalize_path_no_absolute(&path) + .to_string_lossy() + .to_string(); + if p.is_empty() { + ".".into() + } else { + p + } +} + +#[command(root = "crate")] +pub fn dirname(path: String) -> Result { + match Path::new(&path).parent() { + Some(p) => Ok(p.to_path_buf()), + None => Err(Error::NoParent), + } +} + +#[command(root = "crate")] +pub fn extname(path: String) -> Result { + match Path::new(&path) + .extension() + .and_then(std::ffi::OsStr::to_str) + { + Some(p) => Ok(p.to_string()), + None => Err(Error::NoExtension), + } +} + +#[command(root = "crate")] +pub fn basename(path: String, ext: Option) -> Result { + match Path::new(&path) + .file_name() + .and_then(std::ffi::OsStr::to_str) + { + Some(p) => Ok(if let Some(ext) = ext { + p.replace(ext.as_str(), "") + } else { + p.to_string() + }), + None => Err(Error::NoBasename), + } +} + +#[command(root = "crate")] +pub fn is_absolute(path: String) -> bool { + Path::new(&path).is_absolute() +} diff --git a/core/tauri/src/path/desktop.rs b/core/tauri/src/path/desktop.rs new file mode 100644 index 000000000000..15622b727963 --- /dev/null +++ b/core/tauri/src/path/desktop.rs @@ -0,0 +1,262 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{Error, Result}; +use crate::{AppHandle, Manager, Runtime}; +use std::path::PathBuf; + +/// A helper class to access the mobile camera APIs. +pub struct PathResolver(pub(crate) AppHandle); + +impl PathResolver { + /// Returns the path to the user's audio directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_MUSIC_DIR`. + /// - **macOS:** Resolves to `$HOME/Music`. + /// - **Windows:** Resolves to `{FOLDERID_Music}`. + pub fn audio_dir(&self) -> Result { + dirs_next::audio_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's cache directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to `$XDG_CACHE_HOME` or `$HOME/.cache`. + /// - **macOS:** Resolves to `$HOME/Library/Caches`. + /// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`. + pub fn cache_dir(&self) -> Result { + dirs_next::cache_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's config directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to `$XDG_CONFIG_HOME` or `$HOME/.config`. + /// - **macOS:** Resolves to `$HOME/Library/Application Support`. + /// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`. + pub fn config_dir(&self) -> Result { + dirs_next::config_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's data directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`. + /// - **macOS:** Resolves to `$HOME/Library/Application Support`. + /// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`. + pub fn data_dir(&self) -> Result { + dirs_next::data_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's local data directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`. + /// - **macOS:** Resolves to `$HOME/Library/Application Support`. + /// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`. + pub fn local_data_dir(&self) -> Result { + dirs_next::data_local_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's desktop directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DESKTOP_DIR`. + /// - **macOS:** Resolves to `$HOME/Desktop`. + /// - **Windows:** Resolves to `{FOLDERID_Desktop}`. + pub fn desktop_dir(&self) -> Result { + dirs_next::desktop_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's document directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOCUMENTS_DIR`. + /// - **macOS:** Resolves to `$HOME/Documents`. + /// - **Windows:** Resolves to `{FOLDERID_Documents}`. + pub fn document_dir(&self) -> Result { + dirs_next::document_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's download directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOWNLOAD_DIR`. + /// - **macOS:** Resolves to `$HOME/Downloads`. + /// - **Windows:** Resolves to `{FOLDERID_Downloads}`. + pub fn download_dir(&self) -> Result { + dirs_next::download_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's executable directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to `$XDG_BIN_HOME/../bin` or `$XDG_DATA_HOME/../bin` or `$HOME/.local/bin`. + /// - **macOS:** Not supported. + /// - **Windows:** Not supported. + pub fn executable_dir(&self) -> Result { + dirs_next::executable_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's font directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to `$XDG_DATA_HOME/fonts` or `$HOME/.local/share/fonts`. + /// - **macOS:** Resolves to `$HOME/Library/Fonts`. + /// - **Windows:** Not supported. + pub fn font_dir(&self) -> Result { + dirs_next::font_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's home directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to `$HOME`. + /// - **macOS:** Resolves to `$HOME`. + /// - **Windows:** Resolves to `{FOLDERID_Profile}`. + pub fn home_dir(&self) -> Result { + dirs_next::home_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's picture directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PICTURES_DIR`. + /// - **macOS:** Resolves to `$HOME/Pictures`. + /// - **Windows:** Resolves to `{FOLDERID_Pictures}`. + pub fn picture_dir(&self) -> Result { + dirs_next::picture_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's public directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PUBLICSHARE_DIR`. + /// - **macOS:** Resolves to `$HOME/Public`. + /// - **Windows:** Resolves to `{FOLDERID_Public}`. + pub fn public_dir(&self) -> Result { + dirs_next::public_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's runtime directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to `$XDG_RUNTIME_DIR`. + /// - **macOS:** Not supported. + /// - **Windows:** Not supported. + pub fn runtime_dir(&self) -> Result { + dirs_next::runtime_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's template directory. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_TEMPLATES_DIR`. + /// - **macOS:** Not supported. + /// - **Windows:** Resolves to `{FOLDERID_Templates}`. + pub fn template_dir(&self) -> Result { + dirs_next::template_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the user's video dir + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_VIDEOS_DIR`. + /// - **macOS:** Resolves to `$HOME/Movies`. + /// - **Windows:** Resolves to `{FOLDERID_Videos}`. + pub fn video_dir(&self) -> Result { + dirs_next::video_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the resource directory of this app. + pub fn resource_dir(&self) -> Result { + crate::utils::platform::resource_dir(self.0.package_info(), &self.0.env()) + .map_err(|_| Error::UnknownPath) + } + + /// Returns the path to the suggested directory for your app's config files. + /// + /// Resolves to [`config_dir`]`/${bundle_identifier}`. + pub fn app_config_dir(&self) -> Result { + dirs_next::config_dir() + .ok_or(Error::UnknownPath) + .map(|dir| dir.join(&self.0.config().tauri.bundle.identifier)) + } + + /// Returns the path to the suggested directory for your app's data files. + /// + /// Resolves to [`data_dir`]`/${bundle_identifier}`. + pub fn app_data_dir(&self) -> Result { + dirs_next::data_dir() + .ok_or(Error::UnknownPath) + .map(|dir| dir.join(&self.0.config().tauri.bundle.identifier)) + } + + /// Returns the path to the suggested directory for your app's local data files. + /// + /// Resolves to [`local_data_dir`]`/${bundle_identifier}`. + pub fn app_local_data_dir(&self) -> Result { + dirs_next::data_local_dir() + .ok_or(Error::UnknownPath) + .map(|dir| dir.join(&self.0.config().tauri.bundle.identifier)) + } + + /// Returns the path to the suggested directory for your app's cache files. + /// + /// Resolves to [`cache_dir`]`/${bundle_identifier}`. + pub fn app_cache_dir(&self) -> Result { + dirs_next::cache_dir() + .ok_or(Error::UnknownPath) + .map(|dir| dir.join(&self.0.config().tauri.bundle.identifier)) + } + + /// Returns the path to the suggested directory for your app's log files. + /// + /// ## Platform-specific + /// + /// - **Linux:** Resolves to [`data_local_dir`]`/${bundle_identifier}/logs`. + /// - **macOS:** Resolves to [`home_dir`]`/Library/Logs/${bundle_identifier}` + /// - **Windows:** Resolves to [`data_local_dir`]`/${bundle_identifier}/logs`. + pub fn app_log_dir(&self) -> Result { + #[cfg(target_os = "macos")] + let path = dirs_next::home_dir().ok_or(Error::UnknownPath).map(|dir| { + dir + .join("Library/Logs") + .join(&self.0.config().tauri.bundle.identifier) + }); + + #[cfg(not(target_os = "macos"))] + let path = dirs_next::data_local_dir() + .ok_or(Error::UnknownPath) + .map(|dir| { + dir + .join(&self.0.config().tauri.bundle.identifier) + .join("logs") + }); + + path + } + + /// A temporary directory. Resolves to [`std::env::temp_dir`]. + pub fn temp_dir(&self) -> Result { + Ok(std::env::temp_dir()) + } +} diff --git a/core/tauri/src/path/error.rs b/core/tauri/src/path/error.rs new file mode 100644 index 000000000000..d6a03646dfc6 --- /dev/null +++ b/core/tauri/src/path/error.rs @@ -0,0 +1,42 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +/// Path result. +pub type Result = std::result::Result; + +/// Path error. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Path does not have a parent. + #[error("path does not have a parent")] + NoParent, + /// Path does not have an extension. + #[error("path does not have an extension")] + NoExtension, + /// Path does not have a basename. + #[error("path does not have a basename")] + NoBasename, + /// Cannot resolve current directory. + #[error("failed to read current dir: {0}")] + CurrentDir(std::io::Error), + /// Unknown path. + #[cfg(not(target_os = "android"))] + #[error("unknown path")] + UnknownPath, + /// Failed to invoke mobile plugin. + #[cfg(target_os = "android")] + #[error(transparent)] + PluginInvoke(#[from] crate::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/core/tauri/src/path/init.js b/core/tauri/src/path/init.js new file mode 100644 index 000000000000..4de1c2d11187 --- /dev/null +++ b/core/tauri/src/path/init.js @@ -0,0 +1,10 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +if (!('path' in window.__TAURI__)) { + window.__TAURI__.path = {} +} + +window.__TAURI__.path.__sep = __TEMPLATE_sep__ +window.__TAURI__.path.__delimiter = __TEMPLATE_delimiter__ diff --git a/core/tauri/src/path/mod.rs b/core/tauri/src/path/mod.rs new file mode 100644 index 000000000000..ef6c2a7b9b0c --- /dev/null +++ b/core/tauri/src/path/mod.rs @@ -0,0 +1,398 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::{Component, Display, Path, PathBuf}; + +use crate::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +use serde::{de::Error as DeError, Deserialize, Deserializer}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use serialize_to_javascript::{default_template, DefaultTemplate, Template}; + +mod commands; +mod error; +pub use error::*; + +#[cfg(target_os = "android")] +mod android; +#[cfg(not(target_os = "android"))] +mod desktop; + +#[cfg(target_os = "android")] +pub use android::PathResolver; +#[cfg(not(target_os = "android"))] +pub use desktop::PathResolver; + +/// A wrapper for [`PathBuf`] that prevents path traversal. +#[derive(Clone, Debug)] +pub struct SafePathBuf(PathBuf); + +impl SafePathBuf { + /// Validates the path for directory traversal vulnerabilities and returns a new [`SafePathBuf`] instance if it is safe. + pub fn new(path: PathBuf) -> std::result::Result { + if path.components().any(|x| matches!(x, Component::ParentDir)) { + Err("cannot traverse directory, rewrite the path without the use of `../`") + } else { + Ok(Self(path)) + } + } + + /// Returns an object that implements [`std::fmt::Display`] for safely printing paths. + /// + /// See [`PathBuf#method.display`] for more information. + pub fn display(&self) -> Display<'_> { + self.0.display() + } +} + +impl AsRef for SafePathBuf { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +impl<'de> Deserialize<'de> for SafePathBuf { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let path = PathBuf::deserialize(deserializer)?; + SafePathBuf::new(path).map_err(DeError::custom) + } +} + +/// A base directory to be used in [`resolve_directory`]. +/// +/// The base directory is the optional root of a file system operation. +/// If informed by the API call, all paths will be relative to the path of the given directory. +/// +/// For more information, check the [`dirs_next` documentation](https://docs.rs/dirs_next/). +#[derive(Serialize_repr, Deserialize_repr, Clone, Copy, Debug)] +#[repr(u16)] +#[non_exhaustive] +pub enum BaseDirectory { + /// The Audio directory. + Audio = 1, + /// The Cache directory. + Cache, + /// The Config directory. + Config, + /// The Data directory. + Data, + /// The LocalData directory. + LocalData, + /// The Document directory. + Document, + /// The Download directory. + Download, + /// The Picture directory. + Picture, + /// The Public directory. + Public, + /// The Video directory. + Video, + /// The Resource directory. + Resource, + /// A temporary directory. Resolves to [`std::env::temp_dir`]. + Temp, + /// The default app config directory. + /// Resolves to [`BaseDirectory::Config`]`/{bundle_identifier}`. + AppConfig, + /// The default app data directory. + /// Resolves to [`BaseDirectory::Data`]`/{bundle_identifier}`. + AppData, + /// The default app local data directory. + /// Resolves to [`BaseDirectory::LocalData`]`/{bundle_identifier}`. + AppLocalData, + /// The default app cache directory. + /// Resolves to [`BaseDirectory::Cache`]`/{bundle_identifier}`. + AppCache, + /// The default app log directory. + /// Resolves to [`BaseDirectory::Home`]`/Library/Logs/{bundle_identifier}` on macOS + /// and [`BaseDirectory::Config`]`/{bundle_identifier}/logs` on linux and Windows. + AppLog, + + /// The Desktop directory. + #[cfg(not(target_os = "android"))] + Desktop, + /// The Executable directory. + #[cfg(not(target_os = "android"))] + Executable, + /// The Font directory. + #[cfg(not(target_os = "android"))] + Font, + /// The Home directory. + #[cfg(not(target_os = "android"))] + Home, + /// The Runtime directory. + #[cfg(not(target_os = "android"))] + Runtime, + /// The Template directory. + #[cfg(not(target_os = "android"))] + Template, +} + +impl BaseDirectory { + /// Gets the variable that represents this [`BaseDirectory`] for string paths. + pub fn variable(self) -> &'static str { + match self { + Self::Audio => "$AUDIO", + Self::Cache => "$CACHE", + Self::Config => "$CONFIG", + Self::Data => "$DATA", + Self::LocalData => "$LOCALDATA", + Self::Document => "$DOCUMENT", + Self::Download => "$DOWNLOAD", + Self::Picture => "$PICTURE", + Self::Public => "$PUBLIC", + Self::Video => "$VIDEO", + Self::Resource => "$RESOURCE", + Self::Temp => "$TEMP", + Self::AppConfig => "$APPCONFIG", + Self::AppData => "$APPDATA", + Self::AppLocalData => "$APPLOCALDATA", + Self::AppCache => "$APPCACHE", + Self::AppLog => "$APPLOG", + + #[cfg(not(target_os = "android"))] + Self::Desktop => "$DESKTOP", + #[cfg(not(target_os = "android"))] + Self::Executable => "$EXE", + #[cfg(not(target_os = "android"))] + Self::Font => "$FONT", + #[cfg(not(target_os = "android"))] + Self::Home => "$HOME", + #[cfg(not(target_os = "android"))] + Self::Runtime => "$RUNTIME", + #[cfg(not(target_os = "android"))] + Self::Template => "$TEMPLATE", + } + } + + /// Gets the [`BaseDirectory`] associated with the given variable, or [`None`] if the variable doesn't match any. + pub fn from_variable(variable: &str) -> Option { + let res = match variable { + "$AUDIO" => Self::Audio, + "$CACHE" => Self::Cache, + "$CONFIG" => Self::Config, + "$DATA" => Self::Data, + "$LOCALDATA" => Self::LocalData, + "$DOCUMENT" => Self::Document, + "$DOWNLOAD" => Self::Download, + + "$PICTURE" => Self::Picture, + "$PUBLIC" => Self::Public, + "$VIDEO" => Self::Video, + "$RESOURCE" => Self::Resource, + "$TEMP" => Self::Temp, + "$APPCONFIG" => Self::AppConfig, + "$APPDATA" => Self::AppData, + "$APPLOCALDATA" => Self::AppLocalData, + "$APPCACHE" => Self::AppCache, + "$APPLOG" => Self::AppLog, + + #[cfg(not(target_os = "android"))] + "$DESKTOP" => Self::Desktop, + #[cfg(not(target_os = "android"))] + "$EXE" => Self::Executable, + #[cfg(not(target_os = "android"))] + "$FONT" => Self::Font, + #[cfg(not(target_os = "android"))] + "$HOME" => Self::Home, + #[cfg(not(target_os = "android"))] + "$RUNTIME" => Self::Runtime, + #[cfg(not(target_os = "android"))] + "$TEMPLATE" => Self::Template, + + _ => return None, + }; + Some(res) + } +} + +impl PathResolver { + /// Resolves the path with the base directory. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::{path::BaseDirectory, Manager}; + /// tauri::Builder::default() + /// .setup(|app| { + /// let path = app.path().resolve("path/to/something", BaseDirectory::Config)?; + /// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/path/to/something"); + /// Ok(()) + /// }); + /// ``` + pub fn resolve>(&self, path: P, base_directory: BaseDirectory) -> Result { + resolve_path::(self, base_directory, Some(path.as_ref().to_path_buf())) + } + + /// Parse the given path, resolving a [`BaseDirectory`] variable if the path starts with one. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::Manager; + /// tauri::Builder::default() + /// .setup(|app| { + /// let path = app.path().parse("$HOME/.bashrc")?; + /// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.bashrc"); + /// Ok(()) + /// }); + /// ``` + pub fn parse>(&self, path: P) -> Result { + let mut p = PathBuf::new(); + let mut components = path.as_ref().components(); + match components.next() { + Some(Component::Normal(str)) => { + if let Some(base_directory) = BaseDirectory::from_variable(&str.to_string_lossy()) { + p.push(resolve_path::(self, base_directory, None)?); + } else { + p.push(str); + } + } + Some(component) => p.push(component), + None => (), + } + + for component in components { + if let Component::ParentDir = component { + continue; + } + p.push(component); + } + + Ok(p) + } +} + +fn resolve_path( + resolver: &PathResolver, + directory: BaseDirectory, + path: Option, +) -> Result { + let resolve_resource = matches!(directory, BaseDirectory::Resource); + let mut base_dir_path = match directory { + BaseDirectory::Audio => resolver.audio_dir(), + BaseDirectory::Cache => resolver.cache_dir(), + BaseDirectory::Config => resolver.config_dir(), + BaseDirectory::Data => resolver.data_dir(), + BaseDirectory::LocalData => resolver.local_data_dir(), + BaseDirectory::Document => resolver.document_dir(), + BaseDirectory::Download => resolver.download_dir(), + BaseDirectory::Picture => resolver.picture_dir(), + BaseDirectory::Public => resolver.public_dir(), + BaseDirectory::Video => resolver.video_dir(), + BaseDirectory::Resource => resolver.resource_dir(), + BaseDirectory::Temp => resolver.temp_dir(), + BaseDirectory::AppConfig => resolver.app_config_dir(), + BaseDirectory::AppData => resolver.app_data_dir(), + BaseDirectory::AppLocalData => resolver.app_local_data_dir(), + BaseDirectory::AppCache => resolver.app_cache_dir(), + BaseDirectory::AppLog => resolver.app_log_dir(), + #[cfg(not(target_os = "android"))] + BaseDirectory::Desktop => resolver.desktop_dir(), + #[cfg(not(target_os = "android"))] + BaseDirectory::Executable => resolver.executable_dir(), + #[cfg(not(target_os = "android"))] + BaseDirectory::Font => resolver.font_dir(), + #[cfg(not(target_os = "android"))] + BaseDirectory::Home => resolver.home_dir(), + #[cfg(not(target_os = "android"))] + BaseDirectory::Runtime => resolver.runtime_dir(), + #[cfg(not(target_os = "android"))] + BaseDirectory::Template => resolver.template_dir(), + }?; + + if let Some(path) = path { + // use the same path resolution mechanism as the bundler's resource injection algorithm + if resolve_resource { + let mut resource_path = PathBuf::new(); + for component in path.components() { + match component { + Component::Prefix(_) => {} + Component::RootDir => resource_path.push("_root_"), + Component::CurDir => {} + Component::ParentDir => resource_path.push("_up_"), + Component::Normal(p) => resource_path.push(p), + } + } + base_dir_path.push(resource_path); + } else { + base_dir_path.push(path); + } + } + + Ok(base_dir_path) +} + +#[derive(Template)] +#[default_template("./init.js")] +struct InitJavascript { + sep: &'static str, + delimiter: &'static str, +} + +/// Initializes the plugin. +pub(crate) fn init() -> TauriPlugin { + #[cfg(windows)] + let (sep, delimiter) = ("\\", ";"); + #[cfg(not(windows))] + let (sep, delimiter) = ("/", ":"); + + let init_js = InitJavascript { sep, delimiter } + .render_default(&Default::default()) + // this will never fail with the above sep and delimiter values + .unwrap(); + + Builder::new("path") + .invoke_handler(crate::generate_handler![ + commands::resolve_directory, + commands::resolve, + commands::normalize, + commands::join, + commands::dirname, + commands::extname, + commands::basename, + commands::is_absolute + ]) + .js_init_script(init_js.to_string()) + .setup(|app, _api| { + #[cfg(target_os = "android")] + { + let handle = _api.register_android_plugin("app.tauri", "PathPlugin")?; + app.manage(PathResolver(handle)); + } + + #[cfg(not(target_os = "android"))] + { + app.manage(PathResolver(app.clone())); + } + + Ok(()) + }) + .build() +} + +#[cfg(test)] +mod test { + use super::SafePathBuf; + use quickcheck::{Arbitrary, Gen}; + + use std::path::PathBuf; + + impl Arbitrary for SafePathBuf { + fn arbitrary(g: &mut Gen) -> Self { + Self(PathBuf::arbitrary(g)) + } + + fn shrink(&self) -> Box> { + Box::new(self.0.shrink().map(SafePathBuf)) + } + } +} diff --git a/core/tauri/src/pattern.rs b/core/tauri/src/pattern.rs index 19b3713033ea..2318e5972eaa 100644 --- a/core/tauri/src/pattern.rs +++ b/core/tauri/src/pattern.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -11,8 +11,11 @@ use serialize_to_javascript::{default_template, Template}; use tauri_utils::assets::{Assets, EmbeddedAssets}; +/// The domain of the isolation iframe source. +pub const ISOLATION_IFRAME_SRC_DOMAIN: &str = "localhost"; + /// An application pattern. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum Pattern { /// The brownfield pattern. Brownfield(PhantomData), @@ -35,6 +38,26 @@ pub enum Pattern { }, } +impl Clone for Pattern { + fn clone(&self) -> Self { + match self { + Self::Brownfield(a) => Self::Brownfield(*a), + #[cfg(feature = "isolation")] + Self::Isolation { + assets, + schema, + key, + crypto_keys, + } => Self::Isolation { + assets: assets.clone(), + schema: schema.clone(), + key: key.clone(), + crypto_keys: crypto_keys.clone(), + }, + } + } +} + /// The shape of the JavaScript Pattern config #[derive(Debug, Serialize)] #[serde(rename_all = "lowercase", tag = "pattern")] @@ -86,9 +109,9 @@ pub(crate) struct PatternJavascript { #[allow(dead_code)] pub(crate) fn format_real_schema(schema: &str) -> String { - if cfg!(windows) { - format!("https://{}.localhost", schema) + if cfg!(windows) || cfg!(target_os = "android") { + format!("https://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}") } else { - format!("{}://localhost", schema) + format!("{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}") } } diff --git a/core/tauri/src/plugin.rs b/core/tauri/src/plugin.rs index b4de66d5278d..c7a67913cec4 100644 --- a/core/tauri/src/plugin.rs +++ b/core/tauri/src/plugin.rs @@ -1,20 +1,25 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! The Tauri plugin extension to expand Tauri functionality. use crate::{ - runtime::Runtime, utils::config::PluginConfig, AppHandle, Invoke, InvokeHandler, OnPageLoad, - PageLoadPayload, RunEvent, Window, + utils::config::PluginConfig, AppHandle, Invoke, InvokeHandler, PageLoadPayload, RunEvent, + Runtime, Window, }; +use serde::de::DeserializeOwned; use serde_json::Value as JsonValue; use tauri_macros::default_runtime; -use std::{collections::HashMap, fmt}; +use std::{collections::HashMap, fmt, result::Result as StdResult, sync::Arc}; + +/// Mobile APIs. +#[cfg(mobile)] +pub mod mobile; /// The result type of Tauri plugin module. -pub type Result = std::result::Result>; +pub type Result = StdResult>; /// The plugin interface. pub trait Plugin: Send { @@ -27,11 +32,14 @@ pub trait Plugin: Send { Ok(()) } - /// The JS script to evaluate on webview initialization. - /// The script is wrapped into its own context with `(function () { /* your script here */ })();`, - /// so global variables must be assigned to `window` instead of implicity declared. + /// Add the provided JavaScript to a list of scripts that should be run after the global object has been created, + /// but before the HTML document has been parsed and before any other script included by the HTML document is run. + /// + /// Since it runs on all top-level document and child frame page navigations, + /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. /// - /// It's guaranteed that this script is executed before the page is loaded. + /// The script is wrapped into its own context with `(function () { /* your script here */ })();`, + /// so global variables must be assigned to `window` instead of implicitly declared. fn initialization_script(&self) -> Option { None } @@ -50,125 +58,422 @@ pub trait Plugin: Send { /// Extend commands to [`crate::Builder::invoke_handler`]. #[allow(unused_variables)] - fn extend_api(&mut self, invoke: Invoke) {} + fn extend_api(&mut self, invoke: Invoke) -> bool { + false + } +} + +type SetupHook = dyn FnOnce(&AppHandle, PluginApi) -> Result<()> + Send; +type OnWebviewReady = dyn FnMut(Window) + Send; +type OnEvent = dyn FnMut(&AppHandle, &RunEvent) + Send; +type OnPageLoad = dyn FnMut(Window, PageLoadPayload) + Send; +type OnDrop = dyn FnOnce(AppHandle) + Send; + +/// A handle to a plugin. +#[derive(Debug)] +#[allow(dead_code)] +pub struct PluginHandle { + name: &'static str, + handle: AppHandle, +} + +impl Clone for PluginHandle { + fn clone(&self) -> Self { + Self { + name: self.name, + handle: self.handle.clone(), + } + } +} + +impl PluginHandle { + /// Returns the application handle. + pub fn app(&self) -> &AppHandle { + &self.handle + } } -type SetupHook = dyn Fn(&AppHandle) -> Result<()> + Send + Sync; -type OnWebviewReady = dyn Fn(Window) + Send + Sync; -type OnEvent = dyn Fn(&AppHandle, &RunEvent) + Send + Sync; +/// Api exposed to the plugin setup hook. +#[derive(Clone)] +#[allow(dead_code)] +pub struct PluginApi { + handle: AppHandle, + name: &'static str, + raw_config: Arc, + config: C, +} + +impl PluginApi { + /// Returns the plugin configuration. + pub fn config(&self) -> &C { + &self.config + } + + /// Returns the application handle. + pub fn app(&self) -> &AppHandle { + &self.handle + } +} /// Builds a [`TauriPlugin`]. -pub struct Builder { +/// +/// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly. +/// +/// # Conventions +/// +/// When using the Builder Pattern it is encouraged to export a function called `init` that constructs and returns the plugin. +/// While plugin authors can provide every possible way to construct a plugin, +/// sticking to the `init` function convention helps users to quickly identify the correct function to call. +/// +/// ```rust +/// use tauri::{plugin::{Builder, TauriPlugin}, Runtime}; +/// +/// pub fn init() -> TauriPlugin { +/// Builder::new("example") +/// .build() +/// } +/// ``` +/// +/// When plugins expose more complex configuration options, it can be helpful to provide a Builder instead: +/// +/// ```rust +/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime}; +/// +/// pub struct Builder { +/// option_a: String, +/// option_b: String, +/// option_c: bool +/// } +/// +/// impl Default for Builder { +/// fn default() -> Self { +/// Self { +/// option_a: "foo".to_string(), +/// option_b: "bar".to_string(), +/// option_c: false +/// } +/// } +/// } +/// +/// impl Builder { +/// pub fn new() -> Self { +/// Default::default() +/// } +/// +/// pub fn option_a(mut self, option_a: String) -> Self { +/// self.option_a = option_a; +/// self +/// } +/// +/// pub fn option_b(mut self, option_b: String) -> Self { +/// self.option_b = option_b; +/// self +/// } +/// +/// pub fn option_c(mut self, option_c: bool) -> Self { +/// self.option_c = option_c; +/// self +/// } +/// +/// pub fn build(self) -> TauriPlugin { +/// PluginBuilder::new("example") +/// .setup(move |app_handle, api| { +/// // use the options here to do stuff +/// println!("a: {}, b: {}, c: {}", self.option_a, self.option_b, self.option_c); +/// +/// Ok(()) +/// }) +/// .build() +/// } +/// } +/// ``` +pub struct Builder { name: &'static str, invoke_handler: Box>, - setup: Box>, + setup: Option>>, js_init_script: Option, on_page_load: Box>, on_webview_ready: Box>, on_event: Box>, + on_drop: Option>>, } -impl Builder { +impl Builder { /// Creates a new Plugin builder. pub fn new(name: &'static str) -> Self { Self { name, - setup: Box::new(|_| Ok(())), + setup: None, js_init_script: None, - invoke_handler: Box::new(|_| ()), + invoke_handler: Box::new(|_| false), on_page_load: Box::new(|_, _| ()), on_webview_ready: Box::new(|_| ()), on_event: Box::new(|_, _| ()), + on_drop: None, } } /// Defines the JS message handler callback. + /// It is recommended you use the [tauri::generate_handler] to generate the input to this method, as the input type is not considered stable yet. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime}; + /// + /// #[tauri::command] + /// async fn foobar(app: tauri::AppHandle, window: tauri::Window) -> Result<(), String> { + /// println!("foobar"); + /// + /// Ok(()) + /// } + /// + /// fn init() -> TauriPlugin { + /// Builder::new("example") + /// .invoke_handler(tauri::generate_handler![foobar]) + /// .build() + /// } + /// + /// ``` + /// [tauri::generate_handler]: ../macro.generate_handler.html + #[must_use] pub fn invoke_handler(mut self, invoke_handler: F) -> Self where - F: Fn(Invoke) + Send + Sync + 'static, + F: Fn(Invoke) -> bool + Send + Sync + 'static, { self.invoke_handler = Box::new(invoke_handler); self } - /// The JS script to evaluate on webview initialization. + /// Sets the provided JavaScript to be run after the global object has been created, + /// but before the HTML document has been parsed and before any other script included by the HTML document is run. + /// + /// Since it runs on all top-level document and child frame page navigations, + /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. + /// /// The script is wrapped into its own context with `(function () { /* your script here */ })();`, - /// so global variables must be assigned to `window` instead of implicity declared. + /// so global variables must be assigned to `window` instead of implicitly declared. + /// + /// Note that calling this function multiple times overrides previous values. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime}; /// - /// It's guaranteed that this script is executed before the page is loaded. + /// const INIT_SCRIPT: &str = r#" + /// if (window.location.origin === 'https://tauri.app') { + /// console.log("hello world from js init script"); + /// + /// window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' }; + /// } + /// "#; + /// + /// fn init() -> TauriPlugin { + /// Builder::new("example") + /// .js_init_script(INIT_SCRIPT.to_string()) + /// .build() + /// } + /// ``` + #[must_use] pub fn js_init_script(mut self, js_init_script: String) -> Self { self.js_init_script = Some(js_init_script); self } - /// Define a closure that can setup plugin specific state. + /// Define a closure that runs when the plugin is registered. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime, Manager}; + /// use std::path::PathBuf; + /// + /// #[derive(Debug, Default)] + /// struct PluginState { + /// dir: Option + /// } + /// + /// fn init() -> TauriPlugin { + /// Builder::new("example") + /// .setup(|app, api| { + /// app.manage(PluginState::default()); + /// + /// Ok(()) + /// }) + /// .build() + /// } + /// ``` + #[must_use] pub fn setup(mut self, setup: F) -> Self where - F: Fn(&AppHandle) -> Result<()> + Send + Sync + 'static, + F: FnOnce(&AppHandle, PluginApi) -> Result<()> + Send + 'static, { - self.setup = Box::new(setup); + self.setup.replace(Box::new(setup)); self } /// Callback invoked when the webview performs a navigation to a page. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime}; + /// + /// fn init() -> TauriPlugin { + /// Builder::new("example") + /// .on_page_load(|window, payload| { + /// println!("Loaded URL {} in window {}", payload.url(), window.label()); + /// }) + /// .build() + /// } + /// ``` + #[must_use] pub fn on_page_load(mut self, on_page_load: F) -> Self where - F: Fn(Window, PageLoadPayload) + Send + Sync + 'static, + F: FnMut(Window, PageLoadPayload) + Send + 'static, { self.on_page_load = Box::new(on_page_load); self } /// Callback invoked when the webview is created. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime}; + /// + /// fn init() -> TauriPlugin { + /// Builder::new("example") + /// .on_webview_ready(|window| { + /// println!("created window {}", window.label()); + /// }) + /// .build() + /// } + /// ``` + #[must_use] pub fn on_webview_ready(mut self, on_webview_ready: F) -> Self where - F: Fn(Window) + Send + Sync + 'static, + F: FnMut(Window) + Send + 'static, { self.on_webview_ready = Box::new(on_webview_ready); self } /// Callback invoked when the event loop receives a new event. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{plugin::{Builder, TauriPlugin}, RunEvent, Runtime}; + /// + /// fn init() -> TauriPlugin { + /// Builder::new("example") + /// .on_event(|app_handle, event| { + /// match event { + /// RunEvent::ExitRequested { api, .. } => { + /// // Prevents the app from exiting. + /// // This will cause the core thread to continue running in the background even without any open windows. + /// api.prevent_exit(); + /// } + /// // Ignore all other cases. + /// _ => {} + /// } + /// }) + /// .build() + /// } + /// ``` + #[must_use] pub fn on_event(mut self, on_event: F) -> Self where - F: Fn(&AppHandle, &RunEvent) + Send + Sync + 'static, + F: FnMut(&AppHandle, &RunEvent) + Send + 'static, { self.on_event = Box::new(on_event); self } + /// Callback invoked when the plugin is dropped. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime}; + /// + /// fn init() -> TauriPlugin { + /// Builder::new("example") + /// .on_drop(|app| { + /// println!("plugin has been dropped and is no longer running"); + /// // you can run cleanup logic here + /// }) + /// .build() + /// } + /// ``` + #[must_use] + pub fn on_drop(mut self, on_drop: F) -> Self + where + F: FnOnce(AppHandle) + Send + 'static, + { + self.on_drop.replace(Box::new(on_drop)); + self + } + /// Builds the [TauriPlugin]. - pub fn build(self) -> TauriPlugin { + pub fn build(self) -> TauriPlugin { TauriPlugin { name: self.name, + app: None, invoke_handler: self.invoke_handler, setup: self.setup, js_init_script: self.js_init_script, on_page_load: self.on_page_load, on_webview_ready: self.on_webview_ready, on_event: self.on_event, + on_drop: self.on_drop, } } } -/// Plugin struct that is returned by the [`PluginBuilder`]. Should only be constructed through the builder. -pub struct TauriPlugin { +/// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder. +pub struct TauriPlugin { name: &'static str, + app: Option>, invoke_handler: Box>, - setup: Box>, + setup: Option>>, js_init_script: Option, on_page_load: Box>, on_webview_ready: Box>, on_event: Box>, + on_drop: Option>>, } -impl Plugin for TauriPlugin { +impl Drop for TauriPlugin { + fn drop(&mut self) { + if let (Some(on_drop), Some(app)) = (self.on_drop.take(), self.app.take()) { + on_drop(app); + } + } +} + +impl Plugin for TauriPlugin { fn name(&self) -> &'static str { self.name } - fn initialize(&mut self, app: &AppHandle, _: JsonValue) -> Result<()> { - (self.setup)(app) + fn initialize(&mut self, app: &AppHandle, config: JsonValue) -> Result<()> { + self.app.replace(app.clone()); + if let Some(s) = self.setup.take() { + (s)( + app, + PluginApi { + name: self.name, + handle: app.clone(), + raw_config: Arc::new(config.clone()), + config: serde_json::from_value(config)?, + }, + )?; + } + Ok(()) } fn initialization_script(&self) -> Option { @@ -187,7 +492,7 @@ impl Plugin for TauriPlugin { (self.on_event)(app, event) } - fn extend_api(&mut self, invoke: Invoke) { + fn extend_api(&mut self, invoke: Invoke) -> bool { (self.invoke_handler)(invoke) } } @@ -222,6 +527,11 @@ impl PluginStore { self.store.insert(plugin.name(), Box::new(plugin)).is_some() } + /// Removes the plugin with the given name from the store. + pub fn unregister(&mut self, plugin: &'static str) -> bool { + self.store.remove(plugin).is_some() + } + /// Initializes all plugins in the store. pub(crate) fn initialize( &mut self, @@ -245,7 +555,7 @@ impl PluginStore { .values() .filter_map(|p| p.initialization_script()) .fold(String::new(), |acc, script| { - format!("{}\n(function () {{ {} }})();", acc, script) + format!("{acc}\n(function () {{ {script} }})();") }) } @@ -273,22 +583,15 @@ impl PluginStore { .for_each(|plugin| plugin.on_event(app, event)) } - pub(crate) fn extend_api(&mut self, mut invoke: Invoke) { - let command = invoke.message.command.replace("plugin:", ""); - let mut tokens = command.split('|'); - // safe to unwrap: split always has a least one item - let target = tokens.next().unwrap(); - - if let Some(plugin) = self.store.get_mut(target) { - invoke.message.command = tokens - .next() - .map(|c| c.to_string()) - .unwrap_or_else(String::new); - plugin.extend_api(invoke); + /// Runs the plugin `extend_api` hook if it exists. Returns whether the invoke message was handled or not. + /// + /// The message is not handled when the plugin exists **and** the command does not. + pub(crate) fn extend_api(&mut self, plugin: &str, invoke: Invoke) -> bool { + if let Some(plugin) = self.store.get_mut(plugin) { + plugin.extend_api(invoke) } else { - invoke - .resolver - .reject(format!("plugin {} not found", target)); + invoke.resolver.reject(format!("plugin {plugin} not found")); + true } } } diff --git a/core/tauri/src/plugin/mobile.rs b/core/tauri/src/plugin/mobile.rs new file mode 100644 index 000000000000..614aaf9aac7e --- /dev/null +++ b/core/tauri/src/plugin/mobile.rs @@ -0,0 +1,410 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{PluginApi, PluginHandle}; + +use crate::Runtime; +#[cfg(target_os = "android")] +use crate::{ + runtime::RuntimeHandle, + sealed::{ManagerBase, RuntimeOrDispatch}, +}; + +use once_cell::sync::OnceCell; +use serde::de::DeserializeOwned; + +use std::{ + collections::HashMap, + fmt, + sync::{mpsc::channel, Mutex}, +}; + +type PendingPluginCallHandler = + Box) + Send + 'static>; + +static PENDING_PLUGIN_CALLS: OnceCell>> = + OnceCell::new(); + +/// Possible errors when invoking a plugin. +#[derive(Debug, thiserror::Error)] +pub enum PluginInvokeError { + /// Failed to reach platform webview handle. + #[error("the webview is unreachable")] + UnreachableWebview, + /// JNI error. + #[cfg(target_os = "android")] + #[error("jni error: {0}")] + Jni(#[from] jni::errors::Error), + /// Error returned from direct mobile plugin invoke. + #[error(transparent)] + InvokeRejected(#[from] ErrorResponse), + /// Failed to deserialize response. + #[error("failed to deserialize response: {0}")] + CannotDeserializeResponse(serde_json::Error), +} + +/// Glue between Rust and the Kotlin code that sends the plugin response back. +#[cfg(target_os = "android")] +pub fn handle_android_plugin_response( + env: jni::JNIEnv<'_>, + id: i32, + success: jni::objects::JString<'_>, + error: jni::objects::JString<'_>, +) { + let (payload, is_ok): (serde_json::Value, bool) = match ( + env + .is_same_object(success, jni::objects::JObject::default()) + .unwrap_or_default(), + env + .is_same_object(error, jni::objects::JObject::default()) + .unwrap_or_default(), + ) { + // both null + (true, true) => (serde_json::Value::Null, true), + // error null + (false, true) => ( + serde_json::from_str(env.get_string(success).unwrap().to_str().unwrap()).unwrap(), + true, + ), + // success null + (true, false) => ( + serde_json::from_str(env.get_string(error).unwrap().to_str().unwrap()).unwrap(), + false, + ), + // both are set - impossible in the Kotlin code + (false, false) => unreachable!(), + }; + + if let Some(handler) = PENDING_PLUGIN_CALLS + .get_or_init(Default::default) + .lock() + .unwrap() + .remove(&id) + { + handler(if is_ok { Ok(payload) } else { Err(payload) }); + } +} + +/// Error response from the Kotlin and Swift backends. +#[derive(Debug, thiserror::Error, Clone, serde::Deserialize)] +pub struct ErrorResponse { + /// Error code. + pub code: Option, + /// Error message. + pub message: Option, + /// Optional error data. + #[serde(flatten)] + pub data: T, +} + +impl fmt::Display for ErrorResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(code) = &self.code { + write!(f, "[{code}]")?; + if self.message.is_some() { + write!(f, " - ")?; + } + } + if let Some(message) = &self.message { + write!(f, "{message}")?; + } + Ok(()) + } +} + +impl PluginApi { + /// Registers an iOS plugin. + #[cfg(target_os = "ios")] + pub fn register_ios_plugin( + &self, + init_fn: unsafe fn() -> *const std::ffi::c_void, + ) -> Result, PluginInvokeError> { + if let Some(window) = self.handle.manager.windows().values().next() { + let (tx, rx) = channel(); + let name = self.name; + let config = self.raw_config.clone(); + window + .with_webview(move |w| { + unsafe { + crate::ios::register_plugin( + &name.into(), + init_fn(), + crate::ios::json_to_dictionary(&config) as _, + w.inner() as _, + ) + }; + tx.send(()).unwrap(); + }) + .map_err(|_| PluginInvokeError::UnreachableWebview)?; + rx.recv().unwrap(); + } else { + unsafe { + crate::ios::register_plugin( + &self.name.into(), + init_fn(), + crate::ios::json_to_dictionary(&self.raw_config) as _, + std::ptr::null(), + ) + }; + } + Ok(PluginHandle { + name: self.name, + handle: self.handle.clone(), + }) + } + + /// Registers an Android plugin. + #[cfg(target_os = "android")] + pub fn register_android_plugin( + &self, + plugin_identifier: &str, + class_name: &str, + ) -> Result, PluginInvokeError> { + use jni::{errors::Error as JniError, objects::JObject, JNIEnv}; + + fn initialize_plugin<'a, R: Runtime>( + env: JNIEnv<'a>, + activity: JObject<'a>, + webview: JObject<'a>, + runtime_handle: &R::Handle, + plugin_name: &'static str, + plugin_class: String, + plugin_config: &serde_json::Value, + ) -> Result<(), JniError> { + let plugin_manager = env + .call_method( + activity, + "getPluginManager", + "()Lapp/tauri/plugin/PluginManager;", + &[], + )? + .l()?; + + // instantiate plugin + let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?; + let plugin = env.new_object( + plugin_class, + "(Landroid/app/Activity;)V", + &[activity.into()], + )?; + + // load plugin + env.call_method( + plugin_manager, + "load", + "(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;Lapp/tauri/plugin/JSObject;)V", + &[ + webview.into(), + env.new_string(plugin_name)?.into(), + plugin.into(), + crate::jni_helpers::to_jsobject::(env, activity, runtime_handle, plugin_config)? + ], + )?; + + Ok(()) + } + + let plugin_class = format!("{}/{}", plugin_identifier.replace('.', "/"), class_name); + let plugin_name = self.name; + let plugin_config = self.raw_config.clone(); + let runtime_handle = self.handle.runtime_handle.clone(); + let (tx, rx) = channel(); + self + .handle + .runtime_handle + .run_on_android_context(move |env, activity, webview| { + let result = initialize_plugin::( + env, + activity, + webview, + &runtime_handle, + plugin_name, + plugin_class, + &plugin_config, + ); + tx.send(result).unwrap(); + }); + + rx.recv().unwrap()?; + + Ok(PluginHandle { + name: self.name, + handle: self.handle.clone(), + }) + } +} + +impl PluginHandle { + /// Executes the given mobile method. + pub fn run_mobile_plugin( + &self, + method: impl AsRef, + payload: impl serde::Serialize, + ) -> Result { + #[cfg(target_os = "ios")] + { + self.run_ios_plugin(method, payload).map_err(Into::into) + } + #[cfg(target_os = "android")] + { + self.run_android_plugin(method, payload).map_err(Into::into) + } + } + + // Executes the given iOS method. + #[cfg(target_os = "ios")] + fn run_ios_plugin( + &self, + method: impl AsRef, + payload: impl serde::Serialize, + ) -> Result { + use std::{ + ffi::CStr, + os::raw::{c_char, c_int}, + }; + + let id: i32 = rand::random(); + let (tx, rx) = channel(); + PENDING_PLUGIN_CALLS + .get_or_init(Default::default) + .lock() + .unwrap() + .insert( + id, + Box::new(move |arg| { + tx.send(arg).unwrap(); + }), + ); + + unsafe { + extern "C" fn plugin_method_response_handler( + id: c_int, + success: c_int, + payload: *const c_char, + ) { + let payload = unsafe { + assert!(!payload.is_null()); + CStr::from_ptr(payload) + }; + + if let Some(handler) = PENDING_PLUGIN_CALLS + .get_or_init(Default::default) + .lock() + .unwrap() + .remove(&id) + { + let payload = serde_json::from_str(payload.to_str().unwrap()).unwrap(); + handler(if success == 1 { + Ok(payload) + } else { + Err(payload) + }); + } + } + + crate::ios::run_plugin_method( + id, + &self.name.into(), + &method.as_ref().into(), + crate::ios::json_to_dictionary(&serde_json::to_value(payload).unwrap()) as _, + crate::ios::PluginMessageCallback(plugin_method_response_handler), + ); + } + + let response = rx.recv().unwrap(); + match response { + Ok(r) => serde_json::from_value(r).map_err(PluginInvokeError::CannotDeserializeResponse), + Err(r) => Err( + serde_json::from_value::(r) + .map(Into::into) + .map_err(PluginInvokeError::CannotDeserializeResponse)?, + ), + } + } + + // Executes the given Android method. + #[cfg(target_os = "android")] + fn run_android_plugin( + &self, + method: impl AsRef, + payload: impl serde::Serialize, + ) -> Result { + use jni::{errors::Error as JniError, objects::JObject, JNIEnv}; + + fn run( + id: i32, + plugin: &'static str, + method: String, + payload: &serde_json::Value, + runtime_handle: &R::Handle, + env: JNIEnv<'_>, + activity: JObject<'_>, + ) -> Result<(), JniError> { + let data = crate::jni_helpers::to_jsobject::(env, activity, runtime_handle, payload)?; + let plugin_manager = env + .call_method( + activity, + "getPluginManager", + "()Lapp/tauri/plugin/PluginManager;", + &[], + )? + .l()?; + + env.call_method( + plugin_manager, + "runCommand", + "(ILjava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;)V", + &[ + id.into(), + env.new_string(plugin)?.into(), + env.new_string(&method)?.into(), + data, + ], + )?; + + Ok(()) + } + + let handle = match self.handle.runtime() { + RuntimeOrDispatch::Runtime(r) => r.handle(), + RuntimeOrDispatch::RuntimeHandle(h) => h, + _ => unreachable!(), + }; + + let id: i32 = rand::random(); + let plugin_name = self.name; + let method = method.as_ref().to_string(); + let payload = serde_json::to_value(payload).unwrap(); + let handle_ = handle.clone(); + + let (tx, rx) = channel(); + let tx_ = tx.clone(); + PENDING_PLUGIN_CALLS + .get_or_init(Default::default) + .lock() + .unwrap() + .insert( + id, + Box::new(move |arg| { + tx.send(Ok(arg)).unwrap(); + }), + ); + + handle.run_on_android_context(move |env, activity, _webview| { + if let Err(e) = run::(id, plugin_name, method, &payload, &handle_, env, activity) { + tx_.send(Err(e)).unwrap(); + } + }); + + let response = rx.recv().unwrap()?; + match response { + Ok(r) => serde_json::from_value(r).map_err(PluginInvokeError::CannotDeserializeResponse), + Err(r) => Err( + serde_json::from_value::(r) + .map(Into::into) + .map_err(PluginInvokeError::CannotDeserializeResponse)?, + ), + } + } +} diff --git a/core/tauri/src/process.rs b/core/tauri/src/process.rs new file mode 100644 index 000000000000..7eef25939acf --- /dev/null +++ b/core/tauri/src/process.rs @@ -0,0 +1,85 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Types and functions related to child processes management. + +use crate::Env; + +use std::path::PathBuf; + +/// Finds the current running binary's path. +/// +/// With exception to any following platform-specific behavior, the path is cached as soon as +/// possible, and then used repeatedly instead of querying for a new path every time this function +/// is called. +/// +/// # Platform-specific behavior +/// +/// ## Linux +/// +/// On Linux, this function will **attempt** to detect if it's currently running from a +/// valid [AppImage] and use that path instead. +/// +/// ## macOS +/// +/// On `macOS`, this function will return an error if the original path contained any symlinks +/// due to less protection on macOS regarding symlinks. This behavior can be disabled by setting the +/// `process-relaunch-dangerous-allow-symlink-macos` feature, although it is *highly discouraged*. +/// +/// # Security +/// +/// See [`tauri_utils::platform::current_exe`] for possible security implications. +/// +/// # Examples +/// +/// ```rust,no_run +/// use tauri::{process::current_binary, Env, Manager}; +/// let current_binary_path = current_binary(&Env::default()).unwrap(); +/// +/// tauri::Builder::default() +/// .setup(|app| { +/// let current_binary_path = current_binary(&app.env())?; +/// Ok(()) +/// }); +/// ``` +/// +/// [AppImage]: https://appimage.org/ +pub fn current_binary(_env: &Env) -> std::io::Result { + // if we are running from an AppImage, we ONLY want the set AppImage path + #[cfg(target_os = "linux")] + if let Some(app_image_path) = &_env.appimage { + return Ok(PathBuf::from(app_image_path)); + } + + tauri_utils::platform::current_exe() +} + +/// Restarts the currently running binary. +/// +/// See [`current_binary`] for platform specific behavior, and +/// [`tauri_utils::platform::current_exe`] for possible security implications. +/// +/// # Examples +/// +/// ```rust,no_run +/// use tauri::{process::restart, Env, Manager}; +/// +/// tauri::Builder::default() +/// .setup(|app| { +/// restart(&app.env()); +/// Ok(()) +/// }); +/// ``` +pub fn restart(env: &Env) { + use std::process::{exit, Command}; + + if let Ok(path) = current_binary(env) { + Command::new(path) + .args(&env.args) + .spawn() + .expect("application failed to start"); + } + + exit(0); +} diff --git a/core/tauri/src/scope/fs.rs b/core/tauri/src/scope/fs.rs index 9c9dffcb5323..754720e7d435 100644 --- a/core/tauri/src/scope/fs.rs +++ b/core/tauri/src/scope/fs.rs @@ -1,30 +1,57 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::{fmt, path::Path}; - -use glob::Pattern; -use tauri_utils::{ - config::{Config, FsAllowlistScope}, - Env, PackageInfo, +use std::{ + collections::{HashMap, HashSet}, + fmt, + path::{Path, PathBuf, MAIN_SEPARATOR}, + sync::{Arc, Mutex}, }; -use crate::api::path::parse as parse_path; +pub use glob::Pattern; +use tauri_utils::config::FsScope; +use uuid::Uuid; + +/// Scope change event. +#[derive(Debug, Clone)] +pub enum Event { + /// A path has been allowed. + PathAllowed(PathBuf), + /// A path has been forbidden. + PathForbidden(PathBuf), +} + +type EventListener = Box; /// Scope for filesystem access. #[derive(Clone)] pub struct Scope { - allow_patterns: Vec, + allowed_patterns: Arc>>, + forbidden_patterns: Arc>>, + event_listeners: Arc>>, + match_options: glob::MatchOptions, } impl fmt::Debug for Scope { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Scope") .field( - "allow_patterns", + "allowed_patterns", + &self + .allowed_patterns + .lock() + .unwrap() + .iter() + .map(|p| p.as_str()) + .collect::>(), + ) + .field( + "forbidden_patterns", &self - .allow_patterns + .forbidden_patterns + .lock() + .unwrap() .iter() .map(|p| p.as_str()) .collect::>(), @@ -33,27 +60,163 @@ impl fmt::Debug for Scope { } } +fn push_pattern, F: Fn(&str) -> Result>( + list: &mut HashSet, + pattern: P, + f: F, +) -> crate::Result<()> { + let path: PathBuf = pattern.as_ref().components().collect(); + list.insert(f(&path.to_string_lossy())?); + #[cfg(windows)] + { + if let Ok(p) = std::fs::canonicalize(&path) { + list.insert(f(&p.to_string_lossy())?); + } else { + list.insert(f(&format!("\\\\?\\{}", path.display()))?); + } + } + Ok(()) +} + impl Scope { /// Creates a new scope from a `FsAllowlistScope` configuration. - pub fn for_fs_api( - config: &Config, - package_info: &PackageInfo, - env: &Env, - scope: &FsAllowlistScope, - ) -> Self { - let mut allow_patterns = Vec::new(); - for path in &scope.0 { - if let Ok(path) = parse_path(config, package_info, env, path) { - allow_patterns.push(Pattern::new(&path.to_string_lossy()).expect("invalid glob pattern")); - #[cfg(windows)] - { - allow_patterns.push( - Pattern::new(&format!("\\\\?\\{}", path.display())).expect("invalid glob pattern"), - ); + #[allow(unused)] + pub(crate) fn for_fs_api>( + manager: &M, + scope: &tauri_utils::config::FsScope, + ) -> crate::Result { + let mut allowed_patterns = HashSet::new(); + for path in scope.allowed_paths() { + if let Ok(path) = manager.path().parse(path) { + push_pattern(&mut allowed_patterns, path, Pattern::new)?; + } + } + + let mut forbidden_patterns = HashSet::new(); + if let Some(forbidden_paths) = scope.forbidden_paths() { + for path in forbidden_paths { + if let Ok(path) = manager.path().parse(path) { + push_pattern(&mut forbidden_patterns, path, Pattern::new)?; } } } - Self { allow_patterns } + + let require_literal_leading_dot = match scope { + FsScope::Scope { + require_literal_leading_dot: Some(require), + .. + } => *require, + // dotfiles are not supposed to be exposed by default on unix + #[cfg(unix)] + _ => false, + #[cfg(windows)] + _ => true, + }; + + Ok(Self { + allowed_patterns: Arc::new(Mutex::new(allowed_patterns)), + forbidden_patterns: Arc::new(Mutex::new(forbidden_patterns)), + event_listeners: Default::default(), + match_options: 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() + }, + }) + } + + /// The list of allowed patterns. + pub fn allowed_patterns(&self) -> HashSet { + self.allowed_patterns.lock().unwrap().clone() + } + + /// The list of forbidden patterns. + pub fn forbidden_patterns(&self) -> HashSet { + self.forbidden_patterns.lock().unwrap().clone() + } + + /// Listen to an event on this scope. + pub fn listen(&self, f: F) -> Uuid { + let id = Uuid::new_v4(); + self.event_listeners.lock().unwrap().insert(id, Box::new(f)); + id + } + + fn trigger(&self, event: Event) { + let listeners = self.event_listeners.lock().unwrap(); + let handlers = listeners.values(); + for listener in handlers { + listener(&event); + } + } + + /// 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>(&self, path: P, recursive: bool) -> crate::Result<()> { + let path = path.as_ref(); + { + let mut list = self.allowed_patterns.lock().unwrap(); + + // allow the directory to be read + push_pattern(&mut list, path, escaped_pattern)?; + // allow its files and subdirectories to be read + push_pattern(&mut list, path, |p| { + escaped_pattern_with(p, if recursive { "**" } else { "*" }) + })?; + } + self.trigger(Event::PathAllowed(path.to_path_buf())); + Ok(()) + } + + /// 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>(&self, path: P) -> crate::Result<()> { + let path = path.as_ref(); + push_pattern( + &mut self.allowed_patterns.lock().unwrap(), + path, + escaped_pattern, + )?; + self.trigger(Event::PathAllowed(path.to_path_buf())); + Ok(()) + } + + /// 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>(&self, path: P, recursive: bool) -> crate::Result<()> { + let path = path.as_ref(); + { + let mut list = self.forbidden_patterns.lock().unwrap(); + + // allow the directory to be read + push_pattern(&mut list, path, escaped_pattern)?; + // allow its files and subdirectories to be read + push_pattern(&mut list, path, |p| { + escaped_pattern_with(p, if recursive { "**" } else { "*" }) + })?; + } + self.trigger(Event::PathForbidden(path.to_path_buf())); + Ok(()) + } + + /// 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>(&self, path: P) -> crate::Result<()> { + let path = path.as_ref(); + push_pattern( + &mut self.forbidden_patterns.lock().unwrap(), + path, + escaped_pattern, + )?; + self.trigger(Event::PathForbidden(path.to_path_buf())); + Ok(()) } /// Determines if the given path is allowed on this scope. @@ -66,10 +229,160 @@ impl Scope { }; if let Ok(path) = path { - let allowed = self.allow_patterns.iter().any(|p| p.matches_path(&path)); - allowed + let path: PathBuf = path.components().collect(); + let forbidden = self + .forbidden_patterns + .lock() + .unwrap() + .iter() + .any(|p| p.matches_path_with(&path, self.match_options)); + + if forbidden { + false + } else { + let allowed = self + .allowed_patterns + .lock() + .unwrap() + .iter() + .any(|p| p.matches_path_with(&path, self.match_options)); + allowed + } } else { false } } } + +fn escaped_pattern(p: &str) -> Result { + Pattern::new(&glob::Pattern::escape(p)) +} + +fn escaped_pattern_with(p: &str, append: &str) -> Result { + Pattern::new(&format!( + "{}{}{append}", + glob::Pattern::escape(p), + MAIN_SEPARATOR + )) +} + +#[cfg(test)] +mod tests { + use super::Scope; + + fn new_scope() -> Scope { + Scope { + allowed_patterns: Default::default(), + forbidden_patterns: Default::default(), + event_listeners: Default::default(), + match_options: 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, + // dotfiles are not supposed to be exposed by default on unix + #[cfg(unix)] + require_literal_leading_dot: false, + #[cfg(windows)] + require_literal_leading_dot: true, + ..Default::default() + }, + } + } + + #[test] + fn path_is_escaped() { + let scope = new_scope(); + #[cfg(unix)] + { + scope.allow_directory("/home/tauri/**", false).unwrap(); + assert!(scope.is_allowed("/home/tauri/**")); + assert!(scope.is_allowed("/home/tauri/**/file")); + assert!(!scope.is_allowed("/home/tauri/anyfile")); + } + #[cfg(windows)] + { + scope.allow_directory("C:\\home\\tauri\\**", false).unwrap(); + assert!(scope.is_allowed("C:\\home\\tauri\\**")); + assert!(scope.is_allowed("C:\\home\\tauri\\**\\file")); + assert!(!scope.is_allowed("C:\\home\\tauri\\anyfile")); + } + + let scope = new_scope(); + #[cfg(unix)] + { + scope.allow_file("/home/tauri/**").unwrap(); + assert!(scope.is_allowed("/home/tauri/**")); + assert!(!scope.is_allowed("/home/tauri/**/file")); + assert!(!scope.is_allowed("/home/tauri/anyfile")); + } + #[cfg(windows)] + { + scope.allow_file("C:\\home\\tauri\\**").unwrap(); + assert!(scope.is_allowed("C:\\home\\tauri\\**")); + assert!(!scope.is_allowed("C:\\home\\tauri\\**\\file")); + assert!(!scope.is_allowed("C:\\home\\tauri\\anyfile")); + } + + let scope = new_scope(); + #[cfg(unix)] + { + scope.allow_directory("/home/tauri", true).unwrap(); + scope.forbid_directory("/home/tauri/**", false).unwrap(); + assert!(!scope.is_allowed("/home/tauri/**")); + assert!(!scope.is_allowed("/home/tauri/**/file")); + assert!(scope.is_allowed("/home/tauri/**/inner/file")); + assert!(scope.is_allowed("/home/tauri/inner/folder/anyfile")); + assert!(scope.is_allowed("/home/tauri/anyfile")); + } + #[cfg(windows)] + { + scope.allow_directory("C:\\home\\tauri", true).unwrap(); + scope + .forbid_directory("C:\\home\\tauri\\**", false) + .unwrap(); + assert!(!scope.is_allowed("C:\\home\\tauri\\**")); + assert!(!scope.is_allowed("C:\\home\\tauri\\**\\file")); + assert!(scope.is_allowed("C:\\home\\tauri\\**\\inner\\file")); + assert!(scope.is_allowed("C:\\home\\tauri\\inner\\folder\\anyfile")); + assert!(scope.is_allowed("C:\\home\\tauri\\anyfile")); + } + + let scope = new_scope(); + #[cfg(unix)] + { + scope.allow_directory("/home/tauri", true).unwrap(); + scope.forbid_file("/home/tauri/**").unwrap(); + assert!(!scope.is_allowed("/home/tauri/**")); + assert!(scope.is_allowed("/home/tauri/**/file")); + assert!(scope.is_allowed("/home/tauri/**/inner/file")); + assert!(scope.is_allowed("/home/tauri/anyfile")); + } + #[cfg(windows)] + { + scope.allow_directory("C:\\home\\tauri", true).unwrap(); + scope.forbid_file("C:\\home\\tauri\\**").unwrap(); + assert!(!scope.is_allowed("C:\\home\\tauri\\**")); + assert!(scope.is_allowed("C:\\home\\tauri\\**\\file")); + assert!(scope.is_allowed("C:\\home\\tauri\\**\\inner\\file")); + assert!(scope.is_allowed("C:\\home\\tauri\\anyfile")); + } + + let scope = new_scope(); + #[cfg(unix)] + { + scope.allow_directory("/home/tauri", false).unwrap(); + assert!(scope.is_allowed("/home/tauri/**")); + assert!(!scope.is_allowed("/home/tauri/**/file")); + assert!(!scope.is_allowed("/home/tauri/**/inner/file")); + assert!(scope.is_allowed("/home/tauri/anyfile")); + } + #[cfg(windows)] + { + scope.allow_directory("C:\\home\\tauri", false).unwrap(); + assert!(scope.is_allowed("C:\\home\\tauri\\**")); + assert!(!scope.is_allowed("C:\\home\\tauri\\**\\file")); + assert!(!scope.is_allowed("C:\\home\\tauri\\**\\inner\\file")); + assert!(scope.is_allowed("C:\\home\\tauri\\anyfile")); + } + } +} diff --git a/core/tauri/src/scope/http.rs b/core/tauri/src/scope/http.rs deleted file mode 100644 index a17849e4e2e5..000000000000 --- a/core/tauri/src/scope/http.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use tauri_utils::config::HttpAllowlistScope; -use url::Url; - -/// Scope for filesystem access. -#[derive(Debug, Clone)] -pub struct Scope { - allowed_urls: Vec, -} - -impl Scope { - /// Creates a new scope from the allowlist's `http` scope configuration. - pub fn for_http_api(scope: &HttpAllowlistScope) -> Self { - Self { - allowed_urls: scope.0.clone(), - } - } - - /// Determines if the given URL is allowed on this scope. - pub fn is_allowed(&self, url: &Url) -> bool { - self.allowed_urls.iter().any(|allowed| { - let origin_matches = allowed.scheme() == url.scheme() - && allowed.host() == url.host() - && allowed.port() == url.port(); - let allowed_path_pattern = glob::Pattern::new(allowed.path()) - .unwrap_or_else(|_| panic!("invalid glob pattern on URL `{}` path", allowed)); - origin_matches && allowed_path_pattern.matches(url.path()) - }) - } -} - -#[cfg(test)] -mod tests { - use tauri_utils::config::HttpAllowlistScope; - - #[test] - fn is_allowed() { - // plain URL - let scope = super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://localhost:8080" - .parse() - .unwrap()])); - assert!(scope.is_allowed(&"http://localhost:8080".parse().unwrap())); - assert!(scope.is_allowed(&"http://localhost:8080/".parse().unwrap())); - - assert!(!scope.is_allowed(&"http://localhost:8080/file".parse().unwrap())); - assert!(!scope.is_allowed(&"http://localhost:8080/path/to/asset.png".parse().unwrap())); - assert!(!scope.is_allowed(&"https://localhost:8080".parse().unwrap())); - assert!(!scope.is_allowed(&"http://localhost:8081".parse().unwrap())); - assert!(!scope.is_allowed(&"http://local:8080".parse().unwrap())); - - // URL with fixed path - let scope = - super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://localhost:8080/file.png" - .parse() - .unwrap()])); - - assert!(scope.is_allowed(&"http://localhost:8080/file.png".parse().unwrap())); - - assert!(!scope.is_allowed(&"http://localhost:8080".parse().unwrap())); - assert!(!scope.is_allowed(&"http://localhost:8080/file".parse().unwrap())); - assert!(!scope.is_allowed(&"http://localhost:8080/file.png/other.jpg".parse().unwrap())); - - // URL with glob pattern - let scope = - super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://localhost:8080/*.png" - .parse() - .unwrap()])); - - assert!(scope.is_allowed(&"http://localhost:8080/file.png".parse().unwrap())); - assert!(scope.is_allowed(&"http://localhost:8080/assets/file.png".parse().unwrap())); - - assert!(!scope.is_allowed(&"http://localhost:8080/file.jpeg".parse().unwrap())); - } -} diff --git a/core/tauri/src/scope/ipc.rs b/core/tauri/src/scope/ipc.rs new file mode 100644 index 000000000000..5a0f6db27eff --- /dev/null +++ b/core/tauri/src/scope/ipc.rs @@ -0,0 +1,357 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::sync::{Arc, Mutex}; + +use crate::{Config, Runtime, Window}; +use url::Url; + +/// IPC access configuration for a remote domain. +#[derive(Debug, Clone)] +pub struct RemoteDomainAccessScope { + scheme: Option, + domain: String, + windows: Vec, + plugins: Vec, +} + +impl RemoteDomainAccessScope { + /// Creates a new access scope. + pub fn new(domain: impl Into) -> Self { + Self { + scheme: None, + domain: domain.into(), + windows: Vec::new(), + plugins: Vec::new(), + } + } + + /// Sets the scheme of the URL to allow in this scope. By default, all schemes with the given domain are allowed. + pub fn allow_on_scheme(mut self, scheme: impl Into) -> Self { + self.scheme.replace(scheme.into()); + self + } + + /// Adds the given window label to the list of windows that uses this scope. + pub fn add_window(mut self, window: impl Into) -> Self { + self.windows.push(window.into()); + self + } + + /// Adds the given plugin to the allowed plugin list. + pub fn add_plugin(mut self, plugin: impl Into) -> Self { + self.plugins.push(plugin.into()); + self + } + + /// Adds the given list of plugins to the allowed plugin list. + pub fn add_plugins(mut self, plugins: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.plugins.extend(plugins.into_iter().map(Into::into)); + self + } + + /// The domain of the URLs that can access this scope. + pub fn domain(&self) -> &str { + &self.domain + } + + /// The list of window labels that can access this scope. + pub fn windows(&self) -> &Vec { + &self.windows + } + + /// The list of plugins enabled by this scope. + pub fn plugins(&self) -> &Vec { + &self.plugins + } +} + +pub(crate) struct RemoteAccessError { + pub matches_window: bool, + pub matches_domain: bool, +} + +/// IPC scope. +#[derive(Clone)] +pub struct Scope { + remote_access: Arc>>, +} + +impl Scope { + pub(crate) fn new(config: &Config) -> Self { + #[allow(unused_mut)] + let mut remote_access: Vec = config + .tauri + .security + .dangerous_remote_domain_ipc_access + .clone() + .into_iter() + .map(|s| RemoteDomainAccessScope { + scheme: s.scheme, + domain: s.domain, + windows: s.windows, + plugins: s.plugins, + }) + .collect(); + + Self { + remote_access: Arc::new(Mutex::new(remote_access)), + } + } + + /// Adds the given configuration for remote access. + /// + /// # Examples + /// + /// ``` + /// use tauri::{Manager, scope::ipc::RemoteDomainAccessScope}; + /// tauri::Builder::default() + /// .setup(|app| { + /// app.ipc_scope().configure_remote_access( + /// RemoteDomainAccessScope::new("tauri.app") + /// .add_window("main") + /// .add_plugins(["path", "event"]) + /// ); + /// Ok(()) + /// }); + /// ``` + pub fn configure_remote_access(&self, access: RemoteDomainAccessScope) { + self.remote_access.lock().unwrap().push(access); + } + + pub(crate) fn remote_access_for( + &self, + window: &Window, + url: &Url, + ) -> Result { + let mut scope = None; + let mut found_scope_for_window = false; + let mut found_scope_for_domain = false; + let label = window.label().to_string(); + + for s in &*self.remote_access.lock().unwrap() { + #[allow(unused_mut)] + let mut matches_window = s.windows.contains(&label); + + let matches_scheme = s + .scheme + .as_ref() + .map(|scheme| scheme == url.scheme()) + .unwrap_or(true); + + let matches_domain = + matches_scheme && url.domain().map(|d| d == s.domain).unwrap_or_default(); + found_scope_for_window = found_scope_for_window || matches_window; + found_scope_for_domain = found_scope_for_domain || matches_domain; + if matches_window && matches_domain && scope.is_none() { + scope.replace(s.clone()); + } + } + + if let Some(s) = scope { + Ok(s) + } else { + Err(RemoteAccessError { + matches_window: found_scope_for_window, + matches_domain: found_scope_for_domain, + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::RemoteDomainAccessScope; + use crate::{ + api::ipc::CallbackFn, + test::{assert_ipc_response, mock_app, MockRuntime}, + App, InvokePayload, Manager, Window, WindowBuilder, + }; + + const PLUGIN_NAME: &str = "test"; + + fn test_context(scopes: Vec) -> (App, Window) { + let app = mock_app(); + let window = WindowBuilder::new(&app, "main", Default::default()) + .build() + .unwrap(); + + for scope in scopes { + app.ipc_scope().configure_remote_access(scope); + } + + (app, window) + } + + fn path_is_absolute_payload() -> InvokePayload { + let callback = CallbackFn(0); + let error = CallbackFn(1); + + let mut payload = serde_json::Map::new(); + payload.insert( + "path".into(), + serde_json::Value::String(std::env::current_dir().unwrap().display().to_string()), + ); + + InvokePayload { + cmd: "plugin:path|is_absolute".into(), + callback, + error, + inner: serde_json::Value::Object(payload), + } + } + + fn plugin_test_payload() -> InvokePayload { + let callback = CallbackFn(0); + let error = CallbackFn(1); + + InvokePayload { + cmd: format!("plugin:{PLUGIN_NAME}|doSomething"), + callback, + error, + inner: Default::default(), + } + } + + #[test] + fn scope_not_defined() { + let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("app.tauri.app") + .add_window("other") + .add_plugin("path")]); + + window.navigate("https://tauri.app".parse().unwrap()); + assert_ipc_response( + &window, + path_is_absolute_payload(), + Err(&crate::window::ipc_scope_not_found_error_message( + "main", + "https://tauri.app/", + )), + ); + } + + #[test] + fn scope_not_defined_for_window() { + let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") + .add_window("second") + .add_plugin("path")]); + + window.navigate("https://tauri.app".parse().unwrap()); + assert_ipc_response( + &window, + path_is_absolute_payload(), + Err(&crate::window::ipc_scope_window_error_message("main")), + ); + } + + #[test] + fn scope_not_defined_for_url() { + let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("github.com") + .add_window("main") + .add_plugin("path")]); + + window.navigate("https://tauri.app".parse().unwrap()); + assert_ipc_response( + &window, + path_is_absolute_payload(), + Err(&crate::window::ipc_scope_domain_error_message( + "https://tauri.app/", + )), + ); + } + + #[test] + fn subdomain_is_not_allowed() { + let (_app, mut window) = test_context(vec![ + RemoteDomainAccessScope::new("tauri.app") + .add_window("main") + .add_plugin("path"), + RemoteDomainAccessScope::new("sub.tauri.app") + .add_window("main") + .add_plugin("path"), + ]); + + window.navigate("https://tauri.app".parse().unwrap()); + assert_ipc_response(&window, path_is_absolute_payload(), Ok(true)); + + window.navigate("https://blog.tauri.app".parse().unwrap()); + assert_ipc_response( + &window, + path_is_absolute_payload(), + Err(&crate::window::ipc_scope_domain_error_message( + "https://blog.tauri.app/", + )), + ); + + window.navigate("https://sub.tauri.app".parse().unwrap()); + assert_ipc_response(&window, path_is_absolute_payload(), Ok(true)); + + window.window.label = "test".into(); + window.navigate("https://dev.tauri.app".parse().unwrap()); + assert_ipc_response( + &window, + path_is_absolute_payload(), + Err(&crate::window::ipc_scope_not_found_error_message( + "test", + "https://dev.tauri.app/", + )), + ); + } + + #[test] + fn subpath_is_allowed() { + let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") + .add_window("main") + .add_plugin("path")]); + + window.navigate("https://tauri.app/inner/path".parse().unwrap()); + assert_ipc_response(&window, path_is_absolute_payload(), Ok(true)); + } + + #[test] + fn tauri_api_not_allowed() { + let (_app, mut window) = test_context(vec![ + RemoteDomainAccessScope::new("tauri.app").add_window("main") + ]); + + window.navigate("https://tauri.app".parse().unwrap()); + assert_ipc_response( + &window, + path_is_absolute_payload(), + Err(crate::window::IPC_SCOPE_DOES_NOT_ALLOW), + ); + } + + #[test] + fn plugin_allowed() { + let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") + .add_window("main") + .add_plugin(PLUGIN_NAME)]); + + window.navigate("https://tauri.app".parse().unwrap()); + assert_ipc_response( + &window, + plugin_test_payload(), + Err(&format!("plugin {PLUGIN_NAME} not found")), + ); + } + + #[test] + fn plugin_not_allowed() { + let (_app, mut window) = test_context(vec![ + RemoteDomainAccessScope::new("tauri.app").add_window("main") + ]); + + window.navigate("https://tauri.app".parse().unwrap()); + assert_ipc_response( + &window, + plugin_test_payload(), + Err(crate::window::IPC_SCOPE_DOES_NOT_ALLOW), + ); + } +} diff --git a/core/tauri/src/scope/mod.rs b/core/tauri/src/scope/mod.rs index 65b9511b6fa4..3bb818d384b0 100644 --- a/core/tauri/src/scope/mod.rs +++ b/core/tauri/src/scope/mod.rs @@ -1,27 +1,44 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT mod fs; -mod http; -#[cfg(shell_scope)] -mod shell; +/// IPC scope. +pub mod ipc; -pub use self::http::Scope as HttpScope; -pub use fs::Scope as FsScope; -#[cfg(shell_scope)] -pub use shell::{ - ExecuteArgs, Scope as ShellScope, ScopeAllowedArg as ShellScopeAllowedArg, - ScopeAllowedCommand as ShellScopeAllowedCommand, ScopeConfig as ShellScopeConfig, - ScopeError as ShellScopeError, -}; +pub use self::ipc::Scope as IpcScope; +pub use fs::{Event as FsScopeEvent, Pattern as GlobPattern, Scope as FsScope}; +use std::path::Path; -pub(crate) struct Scopes { - pub fs: FsScope, - #[cfg(protocol_asset)] - pub asset_protocol: FsScope, - #[cfg(http_request)] - pub http: HttpScope, - #[cfg(shell_scope)] - pub shell: ShellScope, +/// Managed state for all the core scopes in a tauri application. +pub struct Scopes { + pub(crate) ipc: IpcScope, + #[cfg(feature = "protocol-asset")] + pub(crate) asset_protocol: FsScope, +} + +impl Scopes { + /// Allows a directory on the scopes. + #[allow(unused)] + pub fn allow_directory>(&self, path: P, recursive: bool) -> crate::Result<()> { + #[cfg(feature = "protocol-asset")] + self.asset_protocol.allow_directory(path, recursive)?; + Ok(()) + } + + /// Allows a file on the scopes. + #[allow(unused)] + pub fn allow_file>(&self, path: P) -> crate::Result<()> { + #[cfg(feature = "protocol-asset")] + self.asset_protocol.allow_file(path)?; + Ok(()) + } + + /// Forbids a file on the scopes. + #[allow(unused)] + pub fn forbid_file>(&self, path: P) -> crate::Result<()> { + #[cfg(feature = "protocol-asset")] + self.asset_protocol.forbid_file(path)?; + Ok(()) + } } diff --git a/core/tauri/src/scope/shell.rs b/core/tauri/src/scope/shell.rs deleted file mode 100644 index c61848f06c20..000000000000 --- a/core/tauri/src/scope/shell.rs +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#[cfg(any(shell_execute, shell_sidecar))] -use crate::api::process::Command; -#[cfg(feature = "shell-open-api")] -use crate::api::shell::Program; - -use regex::Regex; - -use std::collections::HashMap; - -/// Allowed representation of `Execute` command arguments. -#[derive(Debug, Clone, serde::Deserialize)] -#[serde(untagged, deny_unknown_fields)] -#[non_exhaustive] -pub enum ExecuteArgs { - /// No arguments - None, - - /// A single string argument - Single(String), - - /// Multiple string arguments - List(Vec), -} - -impl ExecuteArgs { - /// Whether the argument list is empty or not. - pub fn is_empty(&self) -> bool { - match self { - Self::None => true, - Self::Single(s) if s.is_empty() => true, - Self::List(l) => l.is_empty(), - _ => false, - } - } -} - -impl From<()> for ExecuteArgs { - fn from(_: ()) -> Self { - Self::None - } -} - -impl From for ExecuteArgs { - fn from(string: String) -> Self { - Self::Single(string) - } -} - -impl From> for ExecuteArgs { - fn from(vec: Vec) -> Self { - Self::List(vec) - } -} - -/// Shell scope configuration. -#[derive(Debug, Clone)] -pub struct ScopeConfig { - /// The validation regex that `shell > open` paths must match against. - pub open: Option, - - /// All allowed commands, using their unique command name as the keys. - pub scopes: HashMap, -} - -/// A configured scoped shell command. -#[derive(Debug, Clone)] -pub struct ScopeAllowedCommand { - /// The shell command to be called. - pub command: std::path::PathBuf, - - /// The arguments the command is allowed to be called with. - pub args: Option>, - - /// If this command is a sidecar command. - pub sidecar: bool, -} - -/// A configured argument to a scoped shell command. -#[derive(Debug, Clone)] -pub enum ScopeAllowedArg { - /// A non-configurable argument. - Fixed(String), - - /// An argument with a value to be evaluated at runtime, must pass a regex validation. - Var { - /// The validation that the variable value must pass in order to be called. - validator: Regex, - }, -} - -impl ScopeAllowedArg { - /// If the argument is fixed. - pub fn is_fixed(&self) -> bool { - matches!(self, Self::Fixed(_)) - } - - /// If the argument is a variable value. - pub fn is_var(&self) -> bool { - matches!(self, Self::Var { .. }) - } -} - -/// Scope for filesystem access. -#[derive(Clone)] -pub struct Scope(ScopeConfig); - -/// All errors that can happen while validating a scoped command. -#[derive(Debug, thiserror::Error)] -pub enum ScopeError { - /// At least one argument did not pass input validation. - #[cfg(any(shell_execute, shell_sidecar))] - #[cfg_attr( - doc_cfg, - doc(cfg(any(feature = "shell-execute", feature = "shell-sidecar"))) - )] - #[error("The scoped command was called with the improper sidecar flag set")] - BadSidecarFlag, - - /// The sidecar program validated but failed to find the sidecar path. - /// - /// Note: This can be called on `shell-execute` feature too due to [`Scope::prepare`] checking if - /// it's a sidecar from the config. - #[cfg(any(shell_execute, shell_sidecar))] - #[cfg_attr( - doc_cfg, - doc(cfg(any(feature = "shell-execute", feature = "shell-sidecar"))) - )] - #[error( - "The scoped sidecar command was validated, but failed to create the path to the command: {0}" - )] - Sidecar(crate::Error), - - /// The named command was not found in the scoped config. - #[error("Scoped command {0} not found")] - #[cfg(any(shell_execute, shell_sidecar))] - #[cfg_attr( - doc_cfg, - doc(cfg(any(feature = "shell-execute", feature = "shell-sidecar"))) - )] - NotFound(String), - - /// A command variable has no value set in the arguments. - #[error( - "Scoped command argument at position {0} must match regex validation {1} but it was not found" - )] - #[cfg(any(shell_execute, shell_sidecar))] - #[cfg_attr( - doc_cfg, - doc(cfg(any(feature = "shell-execute", feature = "shell-sidecar"))) - )] - MissingVar(usize, String), - - /// At least one argument did not pass input validation. - #[cfg(shell_scope)] - #[cfg_attr( - doc_cfg, - doc(cfg(any(feature = "shell-execute", feature = "shell-open"))) - )] - #[error("Scoped command argument at position {index} was found, but failed regex validation {validation}")] - Validation { - /// Index of the variable. - index: usize, - - /// Regex that the variable value failed to match. - validation: String, - }, - - /// The format of the passed input does not match the expected shape. - /// - /// This can happen from passing a string or array of strings to a command that is expecting - /// named variables, and vice-versa. - #[cfg(any(shell_execute, shell_sidecar))] - #[cfg_attr( - doc_cfg, - doc(cfg(any(feature = "shell-execute", feature = "shell-sidecar"))) - )] - #[error("Scoped command {0} received arguments in an unexpected format")] - InvalidInput(String), - - /// A generic IO error that occurs while executing specified shell commands. - #[cfg(shell_scope)] - #[cfg_attr( - doc_cfg, - doc(cfg(any(feature = "shell-execute", feature = "shell-sidecar"))) - )] - #[error("Scoped shell IO error: {0}")] - Io(#[from] std::io::Error), -} - -impl Scope { - /// Creates a new shell scope. - pub fn new(scope: ScopeConfig) -> Self { - Self(scope) - } - - /// Validates argument inputs and creates a Tauri [`Command`]. - #[cfg(any(shell_execute, shell_sidecar))] - pub fn prepare( - &self, - command_name: &str, - args: ExecuteArgs, - sidecar: bool, - ) -> Result { - let command = match self.0.scopes.get(command_name) { - Some(command) => command, - None => return Err(ScopeError::NotFound(command_name.into())), - }; - - if command.sidecar != sidecar { - return Err(ScopeError::BadSidecarFlag); - } - - let args = match (&command.args, args) { - (None, ExecuteArgs::None) => Ok(vec![]), - (None, ExecuteArgs::List(list)) => Ok(list), - (None, ExecuteArgs::Single(string)) => Ok(vec![string]), - (Some(list), ExecuteArgs::List(args)) => list - .iter() - .enumerate() - .map(|(i, arg)| match arg { - ScopeAllowedArg::Fixed(fixed) => Ok(fixed.to_string()), - ScopeAllowedArg::Var { validator } => { - let value = args - .get(i) - .ok_or_else(|| ScopeError::MissingVar(i, validator.to_string()))? - .to_string(); - if validator.is_match(&value) { - Ok(value) - } else { - Err(ScopeError::Validation { - index: i, - validation: validator.to_string(), - }) - } - } - }) - .collect(), - (Some(list), arg) if arg.is_empty() && list.iter().all(ScopeAllowedArg::is_fixed) => list - .iter() - .map(|arg| match arg { - ScopeAllowedArg::Fixed(fixed) => Ok(fixed.to_string()), - _ => unreachable!(), - }) - .collect(), - (Some(list), _) if list.is_empty() => Err(ScopeError::InvalidInput(command_name.into())), - (Some(_), _) => Err(ScopeError::InvalidInput(command_name.into())), - }?; - - let command_s = command.command.to_string_lossy(); - let command = if command.sidecar { - Command::new_sidecar(command_s).map_err(ScopeError::Sidecar)? - } else { - Command::new(command_s) - }; - - Ok(command.args(args)) - } - - /// Open a path in the default (or specified) browser. - /// - /// The path is validated against the `tauri > allowlist > shell > open` validation regex, which - /// defaults to `^https?://`. - #[cfg(feature = "shell-open-api")] - pub fn open(&self, path: &str, with: Option) -> Result<(), ScopeError> { - // ensure we pass validation if the configuration has one - if let Some(regex) = &self.0.open { - if !regex.is_match(path) { - return Err(ScopeError::Validation { - index: 0, - validation: regex.as_str().into(), - }); - } - } - - // The prevention of argument escaping is handled by the usage of std::process::Command::arg by - // the `open` dependency. This behavior should be re-confirmed during upgrades of `open`. - match with.map(Program::name) { - Some(program) => ::open::with(&path, program), - None => ::open::that(&path), - } - .map_err(Into::into) - } -} diff --git a/core/tauri/src/settings.rs b/core/tauri/src/settings.rs deleted file mode 100644 index f61b15e71aa5..000000000000 --- a/core/tauri/src/settings.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! The Tauri-specific settings for your application. -//! -//! This only contains notification permission status for now, but is able to expand in the future. - -use crate::{ - api::{ - file::read_binary, - path::{resolve_path, BaseDirectory}, - }, - Config, Env, PackageInfo, -}; -use serde::{Deserialize, Serialize}; -use std::{ - fs::File, - io::Write, - path::{Path, PathBuf}, -}; - -/// The Tauri Settings. -#[derive(Debug, Clone, Default, Deserialize, Serialize)] -#[non_exhaustive] -pub struct Settings { - /// Whether the user allows notifications or not. - #[cfg(notification_all)] - pub allow_notification: Option, -} - -/// Gets the path to the settings file. -fn get_settings_path( - config: &Config, - package_info: &PackageInfo, - env: &Env, -) -> crate::api::Result { - resolve_path( - config, - package_info, - env, - ".tauri-settings", - Some(BaseDirectory::App), - ) -} - -/// Write the settings to the file system. -#[allow(dead_code)] -pub(crate) fn write_settings( - config: &Config, - package_info: &PackageInfo, - env: &Env, - settings: Settings, -) -> crate::Result<()> { - let settings_path = get_settings_path(config, package_info, env)?; - let settings_folder = Path::new(&settings_path).parent().unwrap(); - if !settings_folder.exists() { - std::fs::create_dir(settings_folder)?; - } - File::create(settings_path) - .map_err(Into::into) - .and_then(|mut f| { - f.write_all(&bincode::serialize(&settings).map_err(crate::api::Error::Bincode)?) - .map_err(Into::into) - }) -} - -/// Reads the settings from the file system. -pub fn read_settings(config: &Config, package_info: &PackageInfo, env: &Env) -> Settings { - if let Ok(settings_path) = get_settings_path(config, package_info, env) { - if settings_path.exists() { - read_binary(settings_path) - .and_then(|settings| bincode::deserialize(&settings).map_err(Into::into)) - .unwrap_or_default() - } else { - Settings::default() - } - } else { - Settings::default() - } -} diff --git a/core/tauri/src/state.rs b/core/tauri/src/state.rs index 84d9894108a8..b13f0c0f3f4a 100644 --- a/core/tauri/src/state.rs +++ b/core/tauri/src/state.rs @@ -1,15 +1,16 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use crate::{ command::{CommandArg, CommandItem}, - runtime::Runtime, - InvokeError, + InvokeError, Runtime, }; use state::Container; /// A guard for a state value. +/// +/// See [`Manager::manage`](`crate::Manager::manage`) for usage examples. pub struct State<'r, T: Send + Sync + 'static>(&'r T); impl<'r, T: Send + Sync + 'static> State<'r, T> { @@ -37,12 +38,18 @@ impl Clone for State<'_, T> { } } +impl<'r, T: Send + Sync + std::fmt::Debug> std::fmt::Debug for State<'r, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("State").field(&self.0).finish() + } +} + impl<'r, 'de: 'r, T: Send + Sync + 'static, R: Runtime> CommandArg<'de, R> for State<'r, T> { /// Grabs the [`State`] from the [`CommandItem`]. This will never fail. fn from_command(command: CommandItem<'de, R>) -> Result { Ok(command.message.state_ref().try_get().unwrap_or_else(|| { panic!( - "state not managed for field `{}` on command `{}`. You muse call `.manage()` before using this command", + "state not managed for field `{}` on command `{}`. You must call `.manage()` before using this command", command.key, command.name ) })) @@ -64,6 +71,7 @@ impl StateManager { /// Gets the state associated with the specified type. pub fn get(&self) -> State<'_, T> { + self.0.get::(); State( self .0 diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index a2e284926603..f007373256df 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -1,47 +1,93 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT #![allow(dead_code)] +#![allow(missing_docs)] use tauri_runtime::{ - menu::{Menu, MenuUpdate, SystemTrayMenu, TrayHandle}, + menu::{Menu, MenuUpdate}, monitor::Monitor, webview::{WindowBuilder, WindowBuilderBase}, window::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - DetachedWindow, MenuEvent, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, MenuEvent, PendingWindow, WindowEvent, }, - ClipboardManager, Dispatch, GlobalShortcutManager, Icon, Result, RunEvent, Runtime, - RuntimeHandle, UserAttentionType, + DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, + RunEvent, Runtime, RuntimeHandle, UserAttentionType, UserEvent, }; -#[cfg(feature = "system-tray")] -use tauri_runtime::{SystemTray, SystemTrayEvent}; -use tauri_utils::config::WindowConfig; +#[cfg(all(desktop, feature = "system-tray"))] +use tauri_runtime::{ + menu::{SystemTrayMenu, TrayHandle}, + SystemTray, SystemTrayEvent, TrayId, +}; +#[cfg(target_os = "macos")] +use tauri_utils::TitleBarStyle; +use tauri_utils::{config::WindowConfig, Theme}; use uuid::Uuid; #[cfg(windows)] use windows::Win32::Foundation::HWND; use std::{ + cell::RefCell, collections::HashMap, fmt, - sync::{Arc, Mutex}, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::{channel, sync_channel, Receiver, SyncSender}, + Arc, Mutex, + }, }; type ShortcutMap = HashMap>; +type WindowId = usize; + +enum Message { + Task(Box), + CloseWindow(WindowId), +} + +struct Window; #[derive(Clone)] pub struct RuntimeContext { + is_running: Arc, + windows: Arc>>, shortcuts: Arc>, - clipboard: Arc>>, + run_tx: SyncSender, +} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for RuntimeContext {} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for RuntimeContext {} + +impl RuntimeContext { + fn send_message(&self, message: Message) -> Result<()> { + if self.is_running.load(Ordering::Relaxed) { + self + .run_tx + .send(message) + .map_err(|_| Error::FailedToSendMessage) + } else { + match message { + Message::Task(task) => task(), + Message::CloseWindow(id) => { + self.windows.borrow_mut().remove(&id); + } + } + Ok(()) + } + } } impl fmt::Debug for RuntimeContext { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RuntimeContext") - .field("clipboard", &self.clipboard) - .finish() + f.debug_struct("RuntimeContext").finish() } } @@ -50,91 +96,107 @@ pub struct MockRuntimeHandle { context: RuntimeContext, } -impl RuntimeHandle for MockRuntimeHandle { +impl RuntimeHandle for MockRuntimeHandle { type Runtime = MockRuntime; + + fn create_proxy(&self) -> EventProxy { + EventProxy {} + } + /// Create a new webview window. fn create_window( &self, - pending: PendingWindow, - ) -> Result> { + pending: PendingWindow, + ) -> Result> { + let id = rand::random(); + self.context.windows.borrow_mut().insert(id, Window); Ok(DetachedWindow { label: pending.label, dispatcher: MockDispatcher { + id, context: self.context.clone(), + last_evaluated_script: Default::default(), + url: pending.url, }, menu_ids: Default::default(), - js_event_listeners: Default::default(), }) } /// Run a task on the main thread. fn run_on_main_thread(&self, f: F) -> Result<()> { - unimplemented!() + self.context.send_message(Message::Task(Box::new(f))) } - #[cfg(all(windows, feature = "system-tray"))] - #[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))] - fn remove_system_tray(&self) -> Result<()> { - Ok(()) + #[cfg(all(desktop, feature = "system-tray"))] + #[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "system-tray"))))] + fn system_tray( + &self, + system_tray: SystemTray, + ) -> Result<>::TrayHandler> { + Ok(MockTrayHandler { + context: self.context.clone(), + }) } -} -#[derive(Debug, Clone)] -pub struct MockDispatcher { - context: RuntimeContext, -} - -#[derive(Debug, Clone)] -pub struct MockGlobalShortcutManager { - context: RuntimeContext, -} - -impl GlobalShortcutManager for MockGlobalShortcutManager { - fn is_registered(&self, accelerator: &str) -> Result { - Ok( - self - .context - .shortcuts - .lock() - .unwrap() - .contains_key(accelerator), - ) + fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { + #[cfg(target_os = "linux")] + return raw_window_handle::RawDisplayHandle::Xlib(raw_window_handle::XlibDisplayHandle::empty()); + #[cfg(target_os = "macos")] + return raw_window_handle::RawDisplayHandle::AppKit( + raw_window_handle::AppKitDisplayHandle::empty(), + ); + #[cfg(windows)] + return raw_window_handle::RawDisplayHandle::Windows( + raw_window_handle::WindowsDisplayHandle::empty(), + ); + #[cfg(not(any(target_os = "linux", target_os = "macos", windows)))] + return unimplemented!(); } - fn register(&mut self, accelerator: &str, handler: F) -> Result<()> { - self - .context - .shortcuts - .lock() - .unwrap() - .insert(accelerator.into(), Box::new(handler)); + /// Shows the application, but does not automatically focus it. + #[cfg(target_os = "macos")] + fn show(&self) -> Result<()> { Ok(()) } - fn unregister_all(&mut self) -> Result<()> { - *self.context.shortcuts.lock().unwrap() = Default::default(); + /// Hides the application. + #[cfg(target_os = "macos")] + fn hide(&self) -> Result<()> { Ok(()) } - fn unregister(&mut self, accelerator: &str) -> Result<()> { - self.context.shortcuts.lock().unwrap().remove(accelerator); - Ok(()) + #[cfg(target_os = "android")] + fn find_class<'a>( + &'a self, + env: jni::JNIEnv<'a>, + activity: jni::objects::JObject<'a>, + name: impl Into, + ) -> std::result::Result, jni::errors::Error> { + todo!() + } + + #[cfg(target_os = "android")] + fn run_on_android_context(&self, f: F) + where + F: FnOnce(jni::JNIEnv<'_>, jni::objects::JObject<'_>, jni::objects::JObject<'_>) + + Send + + 'static, + { + todo!() } } #[derive(Debug, Clone)] -pub struct MockClipboardManager { +pub struct MockDispatcher { + id: WindowId, context: RuntimeContext, + url: String, + last_evaluated_script: Arc>>, } -impl ClipboardManager for MockClipboardManager { - fn write_text>(&mut self, text: T) -> Result<()> { - self.context.clipboard.lock().unwrap().replace(text.into()); - Ok(()) - } - - fn read_text(&self) -> Result> { - Ok(self.context.clipboard.lock().unwrap().clone()) +impl MockDispatcher { + pub fn last_evaluated_script(&self) -> Option { + self.last_evaluated_script.lock().unwrap().clone() } } @@ -180,6 +242,18 @@ impl WindowBuilder for MockWindowBuilder { self } + fn maximizable(self, resizable: bool) -> Self { + self + } + + fn minimizable(self, resizable: bool) -> Self { + self + } + + fn closable(self, resizable: bool) -> Self { + self + } + fn title>(self, title: S) -> Self { self } @@ -188,7 +262,7 @@ impl WindowBuilder for MockWindowBuilder { self } - fn focus(self) -> Self { + fn focused(self, focused: bool) -> Self { self } @@ -217,6 +291,10 @@ impl WindowBuilder for MockWindowBuilder { self } + fn content_protected(self, protected: bool) -> Self { + self + } + fn icon(self, icon: Icon) -> Result { Ok(self) } @@ -225,16 +303,44 @@ impl WindowBuilder for MockWindowBuilder { self } + fn shadow(self, enable: bool) -> Self { + self + } + #[cfg(windows)] fn parent_window(self, parent: HWND) -> Self { self } + #[cfg(target_os = "macos")] + fn parent_window(self, parent: *mut std::ffi::c_void) -> Self { + self + } + #[cfg(windows)] fn owner_window(self, owner: HWND) -> Self { self } + #[cfg(target_os = "macos")] + fn title_bar_style(self, style: TitleBarStyle) -> Self { + self + } + + #[cfg(target_os = "macos")] + fn hidden_title(self, transparent: bool) -> Self { + self + } + + #[cfg(target_os = "macos")] + fn tabbing_identifier(self, identifier: &str) -> Self { + self + } + + fn theme(self, theme: Option) -> Self { + self + } + fn has_icon(&self) -> bool { false } @@ -244,13 +350,13 @@ impl WindowBuilder for MockWindowBuilder { } } -impl Dispatch for MockDispatcher { +impl Dispatch for MockDispatcher { type Runtime = MockRuntime; type WindowBuilder = MockWindowBuilder; fn run_on_main_thread(&self, f: F) -> Result<()> { - Ok(()) + self.context.send_message(Message::Task(Box::new(f))) } fn on_window_event(&self, f: F) -> Uuid { @@ -261,9 +367,25 @@ impl Dispatch for MockDispatcher { Uuid::new_v4() } + fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { + Ok(()) + } + #[cfg(any(debug_assertions, feature = "devtools"))] fn open_devtools(&self) {} + #[cfg(any(debug_assertions, feature = "devtools"))] + fn close_devtools(&self) {} + + #[cfg(any(debug_assertions, feature = "devtools"))] + fn is_devtools_open(&self) -> Result { + Ok(false) + } + + fn url(&self) -> Result { + self.url.parse().map_err(|_| Error::FailedToReceiveMessage) + } + fn scale_factor(&self) -> Result { Ok(1.0) } @@ -294,10 +416,18 @@ impl Dispatch for MockDispatcher { Ok(false) } + fn is_minimized(&self) -> Result { + Ok(false) + } + fn is_maximized(&self) -> Result { Ok(false) } + fn is_focused(&self) -> Result { + Ok(false) + } + fn is_decorated(&self) -> Result { Ok(false) } @@ -306,10 +436,26 @@ impl Dispatch for MockDispatcher { Ok(false) } + fn is_maximizable(&self) -> Result { + Ok(true) + } + + fn is_minimizable(&self) -> Result { + Ok(true) + } + + fn is_closable(&self) -> Result { + Ok(true) + } + fn is_visible(&self) -> Result { Ok(true) } + fn title(&self) -> Result { + Ok(String::new()) + } + fn is_menu_visible(&self) -> Result { Ok(true) } @@ -326,14 +472,8 @@ impl Dispatch for MockDispatcher { Ok(Vec::new()) } - #[cfg(windows)] - fn hwnd(&self) -> Result { - unimplemented!() - } - - #[cfg(target_os = "macos")] - fn ns_window(&self) -> Result<*mut std::ffi::c_void> { - unimplemented!() + fn theme(&self) -> Result { + Ok(Theme::Light) } #[cfg(any( @@ -347,6 +487,23 @@ impl Dispatch for MockDispatcher { unimplemented!() } + fn raw_window_handle(&self) -> Result { + #[cfg(target_os = "linux")] + return Ok(raw_window_handle::RawWindowHandle::Xlib( + raw_window_handle::XlibWindowHandle::empty(), + )); + #[cfg(target_os = "macos")] + return Ok(raw_window_handle::RawWindowHandle::AppKit( + raw_window_handle::AppKitWindowHandle::empty(), + )); + #[cfg(windows)] + return Ok(raw_window_handle::RawWindowHandle::Win32( + raw_window_handle::Win32WindowHandle::empty(), + )); + #[cfg(not(any(target_os = "linux", target_os = "macos", windows)))] + return unimplemented!(); + } + fn center(&self) -> Result<()> { Ok(()) } @@ -361,15 +518,38 @@ impl Dispatch for MockDispatcher { fn create_window( &mut self, - pending: PendingWindow, - ) -> Result> { - unimplemented!() + pending: PendingWindow, + ) -> Result> { + let id = rand::random(); + self.context.windows.borrow_mut().insert(id, Window); + Ok(DetachedWindow { + label: pending.label, + dispatcher: MockDispatcher { + id, + context: self.context.clone(), + last_evaluated_script: Default::default(), + url: pending.url, + }, + menu_ids: Default::default(), + }) } fn set_resizable(&self, resizable: bool) -> Result<()> { Ok(()) } + fn set_maximizable(&self, maximizable: bool) -> Result<()> { + Ok(()) + } + + fn set_minimizable(&self, minimizable: bool) -> Result<()> { + Ok(()) + } + + fn set_closable(&self, closable: bool) -> Result<()> { + Ok(()) + } + fn set_title>(&self, title: S) -> Result<()> { Ok(()) } @@ -407,6 +587,7 @@ impl Dispatch for MockDispatcher { } fn close(&self) -> Result<()> { + self.context.send_message(Message::CloseWindow(self.id))?; Ok(()) } @@ -414,10 +595,18 @@ impl Dispatch for MockDispatcher { Ok(()) } + fn set_shadow(&self, shadow: bool) -> Result<()> { + Ok(()) + } + fn set_always_on_top(&self, always_on_top: bool) -> Result<()> { Ok(()) } + fn set_content_protected(&self, protected: bool) -> Result<()> { + Ok(()) + } + fn set_size(&self, size: Size) -> Result<()> { Ok(()) } @@ -450,11 +639,36 @@ impl Dispatch for MockDispatcher { Ok(()) } + fn set_cursor_grab(&self, grab: bool) -> Result<()> { + Ok(()) + } + + fn set_cursor_visible(&self, visible: bool) -> Result<()> { + Ok(()) + } + + fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()> { + Ok(()) + } + + fn set_cursor_position>(&self, position: Pos) -> Result<()> { + Ok(()) + } + + fn set_ignore_cursor_events(&self, ignore: bool) -> Result<()> { + Ok(()) + } + fn start_dragging(&self) -> Result<()> { Ok(()) } fn eval_script>(&self, script: S) -> Result<()> { + self + .last_evaluated_script + .lock() + .unwrap() + .replace(script.into()); Ok(()) } @@ -463,11 +677,13 @@ impl Dispatch for MockDispatcher { } } +#[cfg(all(desktop, feature = "system-tray"))] #[derive(Debug, Clone)] pub struct MockTrayHandler { context: RuntimeContext, } +#[cfg(all(desktop, feature = "system-tray"))] impl TrayHandle for MockTrayHandler { fn set_icon(&self, icon: Icon) -> Result<()> { Ok(()) @@ -482,45 +698,67 @@ impl TrayHandle for MockTrayHandler { fn set_icon_as_template(&self, is_template: bool) -> Result<()> { Ok(()) } + + #[cfg(target_os = "macos")] + fn set_title(&self, title: &str) -> tauri_runtime::Result<()> { + Ok(()) + } + + fn set_tooltip(&self, tooltip: &str) -> Result<()> { + Ok(()) + } + + fn destroy(&self) -> Result<()> { + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct EventProxy {} + +impl EventLoopProxy for EventProxy { + fn send_event(&self, event: T) -> Result<()> { + Ok(()) + } } +#[derive(Debug)] pub struct MockRuntime { + is_running: Arc, pub context: RuntimeContext, - global_shortcut_manager: MockGlobalShortcutManager, - clipboard_manager: MockClipboardManager, - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] tray_handler: MockTrayHandler, + run_rx: Receiver, } impl MockRuntime { fn init() -> Self { + let is_running = Arc::new(AtomicBool::new(false)); + let (tx, rx) = sync_channel(1); let context = RuntimeContext { + is_running: is_running.clone(), + windows: Default::default(), shortcuts: Default::default(), - clipboard: Default::default(), + run_tx: tx, }; Self { - global_shortcut_manager: MockGlobalShortcutManager { - context: context.clone(), - }, - clipboard_manager: MockClipboardManager { - context: context.clone(), - }, - #[cfg(feature = "system-tray")] + is_running, + #[cfg(all(desktop, feature = "system-tray"))] tray_handler: MockTrayHandler { context: context.clone(), }, context, + run_rx: rx, } } } -impl Runtime for MockRuntime { +impl Runtime for MockRuntime { type Dispatcher = MockDispatcher; type Handle = MockRuntimeHandle; - type GlobalShortcutManager = MockGlobalShortcutManager; - type ClipboardManager = MockClipboardManager; - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] type TrayHandler = MockTrayHandler; + type EventLoopProxy = EventProxy; fn new() -> Result { Ok(Self::init()) @@ -531,57 +769,104 @@ impl Runtime for MockRuntime { Ok(Self::init()) } + fn create_proxy(&self) -> EventProxy { + EventProxy {} + } + fn handle(&self) -> Self::Handle { MockRuntimeHandle { context: self.context.clone(), } } - fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager { - self.global_shortcut_manager.clone() - } - - fn clipboard_manager(&self) -> Self::ClipboardManager { - self.clipboard_manager.clone() - } - - fn create_window(&self, pending: PendingWindow) -> Result> { + fn create_window(&self, pending: PendingWindow) -> Result> { + let id = rand::random(); + self.context.windows.borrow_mut().insert(id, Window); Ok(DetachedWindow { label: pending.label, dispatcher: MockDispatcher { + id, context: self.context.clone(), + last_evaluated_script: Default::default(), + url: pending.url, }, menu_ids: Default::default(), - js_event_listeners: Default::default(), }) } - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] fn system_tray(&self, system_tray: SystemTray) -> Result { Ok(self.tray_handler.clone()) } - #[cfg(feature = "system-tray")] + #[cfg(all(desktop, feature = "system-tray"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))] - fn on_system_tray_event(&mut self, f: F) -> Uuid { - Uuid::new_v4() - } + fn on_system_tray_event(&mut self, f: F) {} #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] fn set_activation_policy(&mut self, activation_policy: tauri_runtime::ActivationPolicy) {} - fn run_iteration( + #[cfg(target_os = "macos")] + #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + fn show(&self) {} + + #[cfg(target_os = "macos")] + #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + fn hide(&self) {} + + fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {} + + #[cfg(any( + target_os = "macos", + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn run_iteration) + 'static>( &mut self, callback: F, ) -> tauri_runtime::RunIteration { Default::default() } - fn run(self, callback: F) { + fn run) + 'static>(self, mut callback: F) { + self.is_running.store(true, Ordering::Relaxed); + callback(RunEvent::Ready); + loop { + if let Ok(m) = self.run_rx.try_recv() { + match m { + Message::Task(p) => p(), + Message::CloseWindow(id) => { + let removed = self.context.windows.borrow_mut().remove(&id).is_some(); + if removed { + let is_empty = self.context.windows.borrow().is_empty(); + if is_empty { + let (tx, rx) = channel(); + callback(RunEvent::ExitRequested { tx }); + + let recv = rx.try_recv(); + let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); + + if !should_prevent { + break; + } + } + } + } + } + } + + callback(RunEvent::MainEventsCleared); + std::thread::sleep(std::time::Duration::from_secs(1)); } + + callback(RunEvent::Exit); } } diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 7f95994bf1ab..3884733d63ab 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -1,24 +1,100 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +//! Utilities for unit testing on Tauri applications. +//! +//! # Stability +//! +//! This module is unstable. +//! +//! # Examples +//! +//! ```rust +//! #[tauri::command] +//! fn my_cmd() {} +//! +//! fn create_app(mut builder: tauri::Builder) -> tauri::App { +//! builder +//! .setup(|app| { +//! // do something +//! Ok(()) +//! }) +//! .invoke_handler(tauri::generate_handler![my_cmd]) +//! // remove the string argument on your app +//! .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) +//! .expect("failed to build app") +//! } +//! +//! fn main() { +//! let app = create_app(tauri::Builder::default()); +//! // app.run(|_handle, _event| {}); +//! } +//! +//! //#[cfg(test)] +//! mod tests { +//! use tauri::Manager; +//! //#[cfg(test)] +//! fn something() { +//! let app = super::create_app(tauri::test::mock_builder()); +//! let window = app.get_window("main").unwrap(); +//! // do something with the app and window +//! // in this case we'll run the my_cmd command with no arguments +//! tauri::test::assert_ipc_response( +//! &window, +//! tauri::InvokePayload { +//! cmd: "my_cmd".into(), +//! callback: tauri::api::ipc::CallbackFn(0), +//! error: tauri::api::ipc::CallbackFn(1), +//! inner: serde_json::Value::Null, +//! }, +//! Ok(()) +//! ); +//! } +//! } +//! ``` + #![allow(unused_variables)] mod mock_runtime; pub use mock_runtime::*; +use serde::Serialize; +use serde_json::Value as JsonValue; -#[cfg(shell_scope)] -use std::collections::HashMap; -use std::{borrow::Cow, sync::Arc}; +use std::{ + borrow::Cow, + collections::HashMap, + fmt::Debug, + hash::{Hash, Hasher}, + sync::{ + mpsc::{channel, Sender}, + Arc, Mutex, + }, +}; -#[cfg(shell_scope)] -use crate::ShellScopeConfig; -use crate::{Manager, Pattern}; +use crate::hooks::window_invoke_responder; +use crate::{api::ipc::CallbackFn, App, Builder, Context, InvokePayload, Manager, Pattern, Window}; use tauri_utils::{ assets::{AssetKey, Assets, CspHash}, - config::{CliConfig, Config, PatternKind, TauriConfig}, + config::{Config, PatternKind, TauriConfig}, }; +#[derive(Eq, PartialEq)] +struct IpcKey { + callback: CallbackFn, + error: CallbackFn, +} + +impl Hash for IpcKey { + fn hash(&self, state: &mut H) { + self.callback.0.hash(state); + self.error.0.hash(state); + } +} + +struct Ipc(Mutex>>>); + +/// An empty [`Assets`] implementation. pub struct NoopAsset { csp_hashes: Vec>, } @@ -33,31 +109,24 @@ impl Assets for NoopAsset { } } +/// Creates a new empty [`Assets`] implementation. pub fn noop_assets() -> NoopAsset { NoopAsset { csp_hashes: Default::default(), } } +/// Creates a new [`crate::Context`] for testing. pub fn mock_context(assets: A) -> crate::Context { - crate::Context { + Context { config: Config { + schema: None, package: Default::default(), tauri: TauriConfig { pattern: PatternKind::Brownfield, - windows: vec![Default::default()], - cli: Some(CliConfig { - description: None, - long_description: None, - before_help: None, - after_help: None, - args: None, - subcommands: None, - }), + windows: Vec::new(), bundle: Default::default(), - allowlist: Default::default(), security: Default::default(), - updater: Default::default(), system_tray: None, macos_private_api: false, }, @@ -66,34 +135,148 @@ pub fn mock_context(assets: A) -> crate::Context { }, assets: Arc::new(assets), default_window_icon: None, + app_icon: None, + #[cfg(desktop)] system_tray_icon: None, package_info: crate::PackageInfo { name: "test".into(), - version: "0.1.0".into(), + version: "0.1.0".parse().unwrap(), authors: "Tauri", description: "Tauri test", + crate_name: "test", }, _info_plist: (), pattern: Pattern::Brownfield(std::marker::PhantomData), - #[cfg(shell_scope)] - shell_scope: ShellScopeConfig { - open: None, - scopes: HashMap::new(), - }, } } -pub fn mock_app() -> crate::App { - crate::Builder::::new() - .build(mock_context(noop_assets())) - .unwrap() +/// Creates a new [`Builder`] using the [`MockRuntime`]. +/// +/// To use a dummy [`Context`], see [`mock_app`]. +/// +/// # Examples +/// +/// ```rust +/// #[cfg(test)] +/// fn do_something() { +/// let app = tauri::test::mock_builder() +/// // remove the string argument to use your app's config file +/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) +/// .unwrap(); +/// } +/// ``` +pub fn mock_builder() -> Builder { + let mut builder = Builder::::new().manage(Ipc(Default::default())); + + builder.invoke_responder = Arc::new(|window, response, callback, error| { + let window_ = window.clone(); + let ipc = window_.state::(); + let mut ipc_ = ipc.0.lock().unwrap(); + if let Some(tx) = ipc_.remove(&IpcKey { callback, error }) { + tx.send(response.into_result()).unwrap(); + } else { + window_invoke_responder(window, response, callback, error) + } + }); + + builder } -pub(crate) fn mock_invoke_context() -> crate::endpoints::InvokeContext { - let app = mock_app(); - crate::endpoints::InvokeContext { - window: app.get_window("main").unwrap(), - config: app.config(), - package_info: app.package_info().clone(), +/// Creates a new [`App`] for testing using the [`mock_context`] with a [`noop_assets`]. +pub fn mock_app() -> App { + mock_builder().build(mock_context(noop_assets())).unwrap() +} + +/// Executes the given IPC message and assert the response matches the expected value. +/// +/// # Examples +/// +/// ```rust +/// #[tauri::command] +/// fn ping() -> &'static str { +/// "pong" +/// } +/// +/// fn create_app(mut builder: tauri::Builder) -> tauri::App { +/// builder +/// .invoke_handler(tauri::generate_handler![ping]) +/// // remove the string argument on your app +/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) +/// .expect("failed to build app") +/// } +/// +/// fn main() { +/// let app = create_app(tauri::Builder::default()); +/// // app.run(|_handle, _event| {});} +/// } +/// +/// //#[cfg(test)] +/// mod tests { +/// use tauri::Manager; +/// +/// //#[cfg(test)] +/// fn something() { +/// let app = super::create_app(tauri::test::mock_builder()); +/// let window = app.get_window("main").unwrap(); +/// +/// // run the `ping` command and assert it returns `pong` +/// tauri::test::assert_ipc_response( +/// &window, +/// tauri::InvokePayload { +/// cmd: "ping".into(), +/// callback: tauri::api::ipc::CallbackFn(0), +/// error: tauri::api::ipc::CallbackFn(1), +/// inner: serde_json::Value::Null, +/// }, +/// // the expected response is a success with the "pong" payload +/// // we could also use Err("error message") here to ensure the command failed +/// Ok("pong") +/// ); +/// } +/// } +/// ``` +pub fn assert_ipc_response( + window: &Window, + payload: InvokePayload, + expected: Result, +) { + let callback = payload.callback; + let error = payload.error; + let ipc = window.state::(); + let (tx, rx) = channel(); + ipc.0.lock().unwrap().insert(IpcKey { callback, error }, tx); + window.clone().on_message(payload).unwrap(); + + assert_eq!( + rx.recv().unwrap(), + expected + .map(|e| serde_json::to_value(e).unwrap()) + .map_err(|e| serde_json::to_value(e).unwrap()) + ); +} + +#[cfg(test)] +mod tests { + use crate::WindowBuilder; + use std::time::Duration; + + use super::mock_app; + + #[test] + fn run_app() { + let app = mock_app(); + + let w = WindowBuilder::new(&app, "main", Default::default()) + .build() + .unwrap(); + + std::thread::spawn(move || { + std::thread::sleep(Duration::from_secs(1)); + w.close().unwrap(); + }); + + app.run(|_app, event| { + println!("{:?}", event); + }); } } diff --git a/core/tauri/src/updater/core.rs b/core/tauri/src/updater/core.rs deleted file mode 100644 index 69c7da8ef0fb..000000000000 --- a/core/tauri/src/updater/core.rs +++ /dev/null @@ -1,1188 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::error::{Error, Result}; -use crate::api::{ - file::{ArchiveFormat, Extract, Move}, - http::{ClientBuilder, HttpRequestBuilder}, - version, -}; -use base64::decode; -use http::StatusCode; -use minisign_verify::{PublicKey, Signature}; -use tauri_utils::{platform::current_exe, Env}; - -use std::{ - collections::HashMap, - env, - io::{Cursor, Read, Seek}, - path::{Path, PathBuf}, - str::from_utf8, -}; - -#[cfg(not(target_os = "macos"))] -use std::ffi::OsStr; - -#[cfg(not(target_os = "windows"))] -use crate::api::file::Compression; - -#[cfg(target_os = "windows")] -use std::{ - fs::read_dir, - process::{exit, Command}, -}; - -#[derive(Debug)] -pub struct RemoteRelease { - /// Version to install - pub version: String, - /// Release date - pub date: String, - /// Download URL for current platform - pub download_url: String, - /// Update short description - pub body: Option, - /// Optional signature for the current platform - pub signature: Option, - #[cfg(target_os = "windows")] - /// Optional: Windows only try to use elevated task - pub with_elevated_task: bool, -} - -impl RemoteRelease { - // Read JSON and confirm this is a valid Schema - fn from_release(release: &serde_json::Value, target: &str) -> Result { - // Version or name is required for static and dynamic JSON - // if `version` is not announced, we fallback to `name` (can be the tag name example v1.0.0) - let version = match release.get("version") { - Some(version) => version - .as_str() - .ok_or_else(|| { - Error::RemoteMetadata("Unable to extract `version` from remote server".into()) - })? - .trim_start_matches('v') - .to_string(), - None => release - .get("name") - .ok_or_else(|| Error::RemoteMetadata("Release missing `name` and `version`".into()))? - .as_str() - .ok_or_else(|| { - Error::RemoteMetadata("Unable to extract `name` from remote server`".into()) - })? - .trim_start_matches('v') - .to_string(), - }; - - // pub_date is required default is: `N/A` if not provided by the remote JSON - let date = release - .get("pub_date") - .and_then(|v| v.as_str()) - .unwrap_or("N/A") - .to_string(); - - // body is optional to build our update - let body = release - .get("notes") - .map(|notes| notes.as_str().unwrap_or("").to_string()); - - // signature is optional to build our update - let mut signature = release - .get("signature") - .map(|signature| signature.as_str().unwrap_or("").to_string()); - - let download_url; - #[cfg(target_os = "windows")] - let with_elevated_task; - - match release.get("platforms") { - // - // Did we have a platforms field? - // If we did, that mean it's a static JSON. - // The main difference with STATIC and DYNAMIC is static announce ALL platforms - // and dynamic announce only the current platform. - // - // This could be used if you do NOT want an update server and use - // a GIST, S3 or any static JSON file to announce your updates. - // - // Notes: - // Dynamic help to reduce bandwidth usage or to intelligently update your clients - // based on the request you give. The server can remotely drive behaviors like - // rolling back or phased rollouts. - // - Some(platforms) => { - // make sure we have our target available - if let Some(current_target_data) = platforms.get(target) { - // use provided signature if available - signature = current_target_data - .get("signature") - .map(|found_signature| found_signature.as_str().unwrap_or("").to_string()); - // Download URL is required - download_url = current_target_data - .get("url") - .ok_or_else(|| Error::RemoteMetadata("Release missing `url`".into()))? - .as_str() - .ok_or_else(|| { - Error::RemoteMetadata("Unable to extract `url` from remote server`".into()) - })? - .to_string(); - #[cfg(target_os = "windows")] - { - with_elevated_task = current_target_data - .get("with_elevated_task") - .and_then(|v| v.as_bool()) - .unwrap_or_default(); - } - } else { - // make sure we have an available platform from the static - return Err(Error::RemoteMetadata("Platform not available".into())); - } - } - // We don't have the `platforms` field announced, let's assume our - // download URL is at the root of the JSON. - None => { - download_url = release - .get("url") - .ok_or_else(|| Error::RemoteMetadata("Release missing `url`".into()))? - .as_str() - .ok_or_else(|| { - Error::RemoteMetadata("Unable to extract `url` from remote server`".into()) - })? - .to_string(); - #[cfg(target_os = "windows")] - { - with_elevated_task = match release.get("with_elevated_task") { - Some(with_elevated_task) => with_elevated_task.as_bool().unwrap_or(false), - None => false, - }; - } - } - } - // Return our formatted release - Ok(RemoteRelease { - version, - date, - download_url, - body, - signature, - #[cfg(target_os = "windows")] - with_elevated_task, - }) - } -} - -#[derive(Debug)] -pub struct UpdateBuilder<'a> { - /// Environment information. - pub env: Env, - /// Current version we are running to compare with announced version - pub current_version: &'a str, - /// The URLs to checks updates. We suggest at least one fallback on a different domain. - pub urls: Vec, - /// The platform the updater will check and install the update. Default is from `get_updater_target` - pub target: Option, - /// The current executable path. Default is automatically extracted. - pub executable_path: Option, -} - -// Create new updater instance and return an Update -impl<'a> UpdateBuilder<'a> { - pub fn new(env: Env) -> Self { - UpdateBuilder { - env, - urls: Vec::new(), - target: None, - executable_path: None, - current_version: env!("CARGO_PKG_VERSION"), - } - } - - #[allow(dead_code)] - pub fn url(mut self, url: String) -> Self { - self.urls.push(url); - self - } - - /// Add multiple URLS at once inside a Vec for future reference - pub fn urls(mut self, urls: &[String]) -> Self { - let mut formatted_vec: Vec = Vec::new(); - for url in urls { - formatted_vec.push(url.to_owned()); - } - self.urls = formatted_vec; - self - } - - /// Set the current app version, used to compare against the latest available version. - /// The `cargo_crate_version!` macro can be used to pull the version from your `Cargo.toml` - pub fn current_version(mut self, ver: &'a str) -> Self { - self.current_version = ver; - self - } - - /// Set the target (os) - /// win32, win64, darwin and linux are currently supported - #[allow(dead_code)] - pub fn target(mut self, target: &str) -> Self { - self.target = Some(target.to_owned()); - self - } - - /// Set the executable path - #[allow(dead_code)] - pub fn executable_path>(mut self, executable_path: A) -> Self { - self.executable_path = Some(PathBuf::from(executable_path.as_ref())); - self - } - - pub async fn build(self) -> Result { - let mut remote_release: Option = None; - - // make sure we have at least one url - if self.urls.is_empty() { - return Err(Error::Builder( - "Unable to check update, `url` is required.".into(), - )); - }; - - // set current version if not set - let current_version = self.current_version; - - // If no executable path provided, we use current_exe from tauri_utils - let executable_path = self.executable_path.unwrap_or(current_exe()?); - - // Did the target is provided by the config? - // Should be: linux, darwin, win32 or win64 - let target = self - .target - .or_else(get_updater_target) - .ok_or(Error::UnsupportedPlatform)?; - - // Get the extract_path from the provided executable_path - let extract_path = extract_path_from_executable(&self.env, &executable_path); - - // Set SSL certs for linux if they aren't available. - // We do not require to recheck in the download_and_install as we use - // ENV variables, we can expect them to be set for the second call. - #[cfg(target_os = "linux")] - { - if env::var_os("SSL_CERT_FILE").is_none() { - env::set_var("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt"); - } - if env::var_os("SSL_CERT_DIR").is_none() { - env::set_var("SSL_CERT_DIR", "/etc/ssl/certs"); - } - } - - // Allow fallback if more than 1 urls is provided - let mut last_error: Option = None; - for url in &self.urls { - // replace {{current_version}} and {{target}} in the provided URL - // this is usefull if we need to query example - // https://releases.myapp.com/update/{{target}}/{{current_version}} - // will be transleted into -> - // https://releases.myapp.com/update/darwin/1.0.0 - // The main objective is if the update URL is defined via the Cargo.toml - // the URL will be generated dynamicly - let fixed_link = str::replace( - &str::replace(url, "{{current_version}}", current_version), - "{{target}}", - &target, - ); - - // we want JSON only - let mut headers = HashMap::new(); - headers.insert("Accept".into(), "application/json".into()); - - let resp = ClientBuilder::new() - .build()? - .send( - HttpRequestBuilder::new("GET", &fixed_link)? - .headers(headers) - // wait 20sec for the firewall - .timeout(20), - ) - .await; - - // If we got a success, we stop the loop - // and we set our remote_release variable - if let Ok(res) = resp { - let res = res.read().await?; - // got status code 2XX - if StatusCode::from_u16(res.status) - .map_err(|e| Error::Builder(e.to_string()))? - .is_success() - { - // if we got 204 - if StatusCode::NO_CONTENT.as_u16() == res.status { - // return with `UpToDate` error - // we should catch on the client - return Err(Error::UpToDate); - }; - // Convert the remote result to our local struct - let built_release = RemoteRelease::from_release(&res.data, &target); - // make sure all went well and the remote data is compatible - // with what we need locally - match built_release { - Ok(release) => { - last_error = None; - remote_release = Some(release); - break; - } - Err(err) => last_error = Some(err), - } - } // if status code is not 2XX we keep loopin' our urls - } - } - - // Last error is cleaned on success -- shouldn't be triggered if - // we have a successful call - if let Some(error) = last_error { - return Err(Error::Network(error.to_string())); - } - - // Extracted remote metadata - let final_release = remote_release.ok_or_else(|| { - Error::RemoteMetadata("Unable to extract update metadata from the remote server.".into()) - })?; - - // did the announced version is greated than our current one? - let should_update = - version::is_greater(current_version, &final_release.version).unwrap_or(false); - - // create our new updater - Ok(Update { - env: self.env, - target, - extract_path, - should_update, - version: final_release.version, - date: final_release.date, - current_version: self.current_version.to_owned(), - download_url: final_release.download_url, - body: final_release.body, - signature: final_release.signature, - #[cfg(target_os = "windows")] - with_elevated_task: final_release.with_elevated_task, - }) - } -} - -pub fn builder<'a>(env: Env) -> UpdateBuilder<'a> { - UpdateBuilder::new(env) -} - -#[derive(Debug, Clone)] -pub struct Update { - /// Environment information. - pub env: Env, - /// Update description - pub body: Option, - /// Should we update or not - pub should_update: bool, - /// Version announced - pub version: String, - /// Running version - pub current_version: String, - /// Update publish date - pub date: String, - /// Target - #[allow(dead_code)] - target: String, - /// Extract path - extract_path: PathBuf, - /// Download URL announced - download_url: String, - /// Signature announced - signature: Option, - #[cfg(target_os = "windows")] - /// Optional: Windows only try to use elevated task - /// Default to false - with_elevated_task: bool, -} - -impl Update { - // Download and install our update - // @todo(lemarier): Split into download and install (two step) but need to be thread safe - pub async fn download_and_install(&self, pub_key: String) -> Result { - // download url for selected release - let url = self.download_url.as_str(); - // extract path - let extract_path = &self.extract_path; - - // make sure we can install the update on linux - // We fail here because later we can add more linux support - // actually if we use APPIMAGE, our extract path should already - // be set with our APPIMAGE env variable, we don't need to do - // anythin with it yet - #[cfg(target_os = "linux")] - if self.env.appimage.is_none() { - return Err(Error::UnsupportedPlatform); - } - - // set our headers - let mut headers = HashMap::new(); - headers.insert("Accept".into(), "application/octet-stream".into()); - headers.insert("User-Agent".into(), "tauri/updater".into()); - - // Create our request - let resp = ClientBuilder::new() - .build()? - .send( - HttpRequestBuilder::new("GET", url)? - .headers(headers) - // wait 20sec for the firewall - .timeout(20), - ) - .await? - .bytes() - .await?; - - // make sure it's success - if !StatusCode::from_u16(resp.status) - .map_err(|e| Error::Network(e.to_string()))? - .is_success() - { - return Err(Error::Network(format!( - "Download request failed with status: {}", - resp.status - ))); - } - - // create memory buffer from our archive (Seek + Read) - let mut archive_buffer = Cursor::new(resp.data); - - // We need an announced signature by the server - // if there is no signature, bail out. - if let Some(signature) = &self.signature { - // we make sure the archive is valid and signed with the private key linked with the publickey - verify_signature(&mut archive_buffer, signature, &pub_key)?; - } else { - // We have a public key inside our source file, but not announced by the server, - // we assume this update is NOT valid. - return Err(Error::MissingUpdaterSignature); - } - - // we copy the files depending of the operating system - // we run the setup, appimage re-install or overwrite the - // macos .app - #[cfg(target_os = "windows")] - copy_files_and_run(archive_buffer, extract_path, self.with_elevated_task)?; - #[cfg(not(target_os = "windows"))] - copy_files_and_run(archive_buffer, extract_path)?; - // We are done! - Ok(()) - } -} - -// Linux (AppImage) - -// ### Expected structure: -// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler -// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage -// └── ... - -// We should have an AppImage already installed to be able to copy and install -// the extract_path is the current AppImage path -// tmp_dir is where our new AppImage is found -#[cfg(target_os = "linux")] -fn copy_files_and_run(archive_buffer: R, extract_path: &Path) -> Result { - let tmp_dir = tempfile::Builder::new() - .prefix("tauri_current_app") - .tempdir()?; - - let tmp_app_image = &tmp_dir.path().join("current_app.AppImage"); - - // create a backup of our current app image - Move::from_source(extract_path).to_dest(tmp_app_image)?; - - // extract the buffer to the tmp_dir - // we extract our signed archive into our final directory without any temp file - let mut extractor = - Extract::from_cursor(archive_buffer, ArchiveFormat::Tar(Some(Compression::Gz))); - - for file in extractor.files()? { - if file.extension() == Some(OsStr::new("AppImage")) { - // if something went wrong during the extraction, we should restore previous app - if let Err(err) = extractor.extract_file(extract_path, &file) { - Move::from_source(tmp_app_image).to_dest(extract_path)?; - return Err(Error::Extract(err.to_string())); - } - // early finish we have everything we need here - return Ok(()); - } - } - - Ok(()) -} - -// Windows - -// ### Expected structure: -// ├── [AppName]_[version]_x64.msi.zip # ZIP generated by tauri-bundler -// │ └──[AppName]_[version]_x64.msi # Application MSI -// └── ... - -// ## MSI -// Update server can provide a MSI for Windows. (Generated with tauri-bundler from *Wix*) -// To replace current version of the application. In later version we'll offer -// incremental update to push specific binaries. - -// ## EXE -// Update server can provide a custom EXE (installer) who can run any task. -#[cfg(target_os = "windows")] -#[allow(clippy::unnecessary_wraps)] -fn copy_files_and_run( - archive_buffer: R, - _extract_path: &Path, - with_elevated_task: bool, -) -> Result { - // FIXME: We need to create a memory buffer with the MSI and then run it. - // (instead of extracting the MSI to a temp path) - // - // The tricky part is the MSI need to be exposed and spawned so the memory allocation - // shouldn't drop but we should be able to pass the reference so we can drop it once the installation - // is done, otherwise we have a huge memory leak. - - let tmp_dir = tempfile::Builder::new().tempdir()?.into_path(); - - // extract the buffer to the tmp_dir - // we extract our signed archive into our final directory without any temp file - let mut extractor = Extract::from_cursor(archive_buffer, ArchiveFormat::Zip); - - // extract the msi - extractor.extract_into(&tmp_dir)?; - - let paths = read_dir(&tmp_dir)?; - // This consumes the TempDir without deleting directory on the filesystem, - // meaning that the directory will no longer be automatically deleted. - - for path in paths { - let found_path = path?.path(); - // we support 2 type of files exe & msi for now - // If it's an `exe` we expect an installer not a runtime. - if found_path.extension() == Some(OsStr::new("exe")) { - // Run the EXE - Command::new(found_path) - .spawn() - .expect("installer failed to start"); - - exit(0); - } else if found_path.extension() == Some(OsStr::new("msi")) { - if with_elevated_task { - if let Some(bin_name) = current_exe() - .ok() - .and_then(|pb| pb.file_name().map(|s| s.to_os_string())) - .and_then(|s| s.into_string().ok()) - { - let product_name = bin_name.replace(".exe", ""); - - // Check if there is a task that enables the updater to skip the UAC prompt - let update_task_name = format!("Update {} - Skip UAC", product_name); - if let Ok(status) = Command::new("schtasks") - .arg("/QUERY") - .arg("/TN") - .arg(update_task_name.clone()) - .status() - { - if status.success() { - // Rename the MSI to the match file name the Skip UAC task is expecting it to be - let temp_msi = tmp_dir.with_file_name(bin_name).with_extension("msi"); - Move::from_source(&found_path) - .to_dest(&temp_msi) - .expect("Unable to move update MSI"); - let exit_status = Command::new("schtasks") - .arg("/RUN") - .arg("/TN") - .arg(update_task_name) - .status() - .expect("failed to start updater task"); - - if exit_status.success() { - // Successfully launched task that skips the UAC prompt - exit(0); - } - } - // Failed to run update task. Following UAC Path - } - } - } - - // restart should be handled by WIX as we exit the process - Command::new("msiexec.exe") - .arg("/i") - .arg(found_path) - // quiet basic UI with prompt at the end - .arg("/qb+") - .spawn() - .expect("installer failed to start"); - - exit(0); - } - } - - Ok(()) -} - -// MacOS -// ### Expected structure: -// ├── [AppName]_[version]_x64.app.tar.gz # GZ generated by tauri-bundler -// │ └──[AppName].app # Main application -// │ └── Contents # Application contents... -// │ └── ... -// └── ... -#[cfg(target_os = "macos")] -fn copy_files_and_run(archive_buffer: R, extract_path: &Path) -> Result { - let mut extracted_files: Vec = Vec::new(); - - // extract the buffer to the tmp_dir - // we extract our signed archive into our final directory without any temp file - let mut extractor = - Extract::from_cursor(archive_buffer, ArchiveFormat::Tar(Some(Compression::Gz))); - // the first file in the tar.gz will always be - // /Contents - let all_files = extractor.files()?; - let tmp_dir = tempfile::Builder::new() - .prefix("tauri_current_app") - .tempdir()?; - - // create backup of our current app - Move::from_source(extract_path).to_dest(tmp_dir.path())?; - - // extract all the files - for file in all_files { - // skip the first folder (should be the app name) - let collected_path: PathBuf = file.iter().skip(1).collect(); - let extraction_path = extract_path.join(collected_path); - - // if something went wrong during the extraction, we should restore previous app - if let Err(err) = extractor.extract_file(&extraction_path, &file) { - for file in extracted_files { - // delete all the files we extracted - if file.is_dir() { - std::fs::remove_dir(file)?; - } else { - std::fs::remove_file(file)?; - } - } - Move::from_source(tmp_dir.path()).to_dest(extract_path)?; - return Err(Error::Extract(err.to_string())); - } - - extracted_files.push(extraction_path); - } - - Ok(()) -} - -/// Returns a target os -/// We do not use a helper function like the target_triple -/// from tauri-utils because this function return `None` if -/// the updater do not support the platform. -/// -/// Available target: `linux, darwin, win32, win64` -pub fn get_updater_target() -> Option { - if cfg!(target_os = "linux") { - Some("linux".into()) - } else if cfg!(target_os = "macos") { - Some("darwin".into()) - } else if cfg!(target_os = "windows") { - if cfg!(target_pointer_width = "32") { - Some("win32".into()) - } else { - Some("win64".into()) - } - } else { - None - } -} - -/// Get the extract_path from the provided executable_path -#[allow(unused_variables)] -pub fn extract_path_from_executable(env: &Env, executable_path: &Path) -> PathBuf { - // Return the path of the current executable by default - // Example C:\Program Files\My App\ - let extract_path = executable_path - .parent() - .map(PathBuf::from) - .expect("Can't determine extract path"); - - // MacOS example binary is in /Applications/TestApp.app/Contents/MacOS/myApp - // We need to get /Applications/.app - // todo(lemarier): Need a better way here - // Maybe we could search for <*.app> to get the right path - #[cfg(target_os = "macos")] - if extract_path - .display() - .to_string() - .contains("Contents/MacOS") - { - return extract_path - .parent() - .map(PathBuf::from) - .expect("Unable to find the extract path") - .parent() - .map(PathBuf::from) - .expect("Unable to find the extract path"); - } - - // We should use APPIMAGE exposed env variable - // This is where our APPIMAGE should sit and should be replaced - #[cfg(target_os = "linux")] - if let Some(app_image_path) = &env.appimage { - return PathBuf::from(app_image_path); - } - - extract_path -} - -// Convert base64 to string and prevent failing -fn base64_to_string(base64_string: &str) -> Result { - let decoded_string = &decode(base64_string)?; - let result = from_utf8(decoded_string)?.to_string(); - Ok(result) -} - -// Validate signature -// need to be public because its been used -// by our tests in the bundler -// -// NOTE: The buffer position is not reset. -pub fn verify_signature( - archive_reader: &mut R, - release_signature: &str, - pub_key: &str, -) -> Result -where - R: Read, -{ - // we need to convert the pub key - let pub_key_decoded = base64_to_string(pub_key)?; - let public_key = PublicKey::decode(&pub_key_decoded)?; - let signature_base64_decoded = base64_to_string(release_signature)?; - let signature = Signature::decode(&signature_base64_decoded)?; - - // read all bytes until EOF in the buffer - let mut data = Vec::new(); - archive_reader.read_to_end(&mut data)?; - - // Validate signature or bail out - public_key.verify(&data, &signature, true)?; - Ok(true) -} - -#[cfg(test)] -mod test { - use super::*; - #[cfg(target_os = "macos")] - use std::fs::File; - - macro_rules! block { - ($e:expr) => { - tokio_test::block_on($e) - }; - } - - fn generate_sample_raw_json() -> String { - r#"{ - "version": "v2.0.0", - "notes": "Test version !", - "pub_date": "2020-06-22T19:25:57Z", - "platforms": { - "darwin": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJZVGdpKzJmRWZ0SkRvWS9TdFpqTU9xcm1mUmJSSG5OWVlwSklrWkN1SFpWbmh4SDlBcTU3SXpjbm0xMmRjRkphbkpVeGhGcTdrdzlrWGpGVWZQSWdzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1MDU3CWZpbGU6L1VzZXJzL3J1bm5lci9ydW5uZXJzLzIuMjYzLjAvd29yay90YXVyaS90YXVyaS90YXVyaS9leGFtcGxlcy9jb21tdW5pY2F0aW9uL3NyYy10YXVyaS90YXJnZXQvZGVidWcvYnVuZGxlL29zeC9hcHAuYXBwLnRhci5negp4ZHFlUkJTVnpGUXdDdEhydTE5TGgvRlVPeVhjTnM5RHdmaGx3c0ZPWjZXWnFwVDRNWEFSbUJTZ1ZkU1IwckJGdmlwSzJPd00zZEZFN2hJOFUvL1FDZz09Cg==", - "url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.app.tar.gz" - }, - "linux": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOWZSM29hTFNmUEdXMHRoOC81WDFFVVFRaXdWOUdXUUdwT0NlMldqdXkyaWVieXpoUmdZeXBJaXRqSm1YVmczNXdRL1Brc0tHb1NOTzhrL1hadFcxdmdnPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE3MzQzCWZpbGU6L2hvbWUvcnVubmVyL3dvcmsvdGF1cmkvdGF1cmkvdGF1cmkvZXhhbXBsZXMvY29tbXVuaWNhdGlvbi9zcmMtdGF1cmkvdGFyZ2V0L2RlYnVnL2J1bmRsZS9hcHBpbWFnZS9hcHAuQXBwSW1hZ2UudGFyLmd6CmRUTUM2bWxnbEtTbUhOZGtERUtaZnpUMG5qbVo5TGhtZWE1SFNWMk5OOENaVEZHcnAvVW0zc1A2ajJEbWZUbU0yalRHT0FYYjJNVTVHOHdTQlYwQkF3PT0K", - "url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.AppImage.tar.gz" - }, - "win64": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K", - "url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.x64.msi.zip" - } - } - }"#.into() - } - - fn generate_sample_platform_json( - version: &str, - public_signature: &str, - download_url: &str, - ) -> String { - format!( - r#" - {{ - "name": "v{}", - "notes": "This is the latest version! Once updated you shouldn't see this prompt.", - "pub_date": "2020-06-25T14:14:19Z", - "signature": "{}", - "url": "{}" - }} - "#, - version, public_signature, download_url - ) - } - - fn generate_sample_with_elevated_task_platform_json( - version: &str, - public_signature: &str, - download_url: &str, - with_elevated_task: bool, - ) -> String { - format!( - r#" - {{ - "name": "v{}", - "notes": "This is the latest version! Once updated you shouldn't see this prompt.", - "pub_date": "2020-06-25T14:14:19Z", - "signature": "{}", - "url": "{}", - "with_elevated_task": "{}" - }} - "#, - version, public_signature, download_url, with_elevated_task - ) - } - - fn generate_sample_bad_json() -> String { - r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "date": "2020-02-20T15:41:00Z", - "download_link": "https://github.com/lemarier/tauri-test/releases/download/v0.0.1/update3.tar.gz" - }"#.into() - } - - #[test] - fn simple_http_updater() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_raw_json()) - .create(); - - let check_update = block!(builder(Default::default()) - .current_version("0.0.0") - .url(mockito::server_url()) - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - } - - #[test] - fn simple_http_updater_raw_json() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_raw_json()) - .create(); - - let check_update = block!(builder(Default::default()) - .current_version("0.0.0") - .url(mockito::server_url()) - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - } - - #[test] - fn simple_http_updater_raw_json_win64() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_raw_json()) - .create(); - - let check_update = block!(builder(Default::default()) - .current_version("0.0.0") - .target("win64") - .url(mockito::server_url()) - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - assert_eq!(updater.version, "2.0.0"); - assert_eq!(updater.signature, Some("dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K".into())); - assert_eq!( - updater.download_url, - "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.x64.msi.zip" - ); - } - - #[test] - fn simple_http_updater_raw_json_uptodate() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_raw_json()) - .create(); - - let check_update = block!(builder(Default::default()) - .current_version("10.0.0") - .url(mockito::server_url()) - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check update"); - - assert!(!updater.should_update); - } - - #[test] - fn simple_http_updater_without_version() { - let _m = mockito::mock("GET", "/darwin/1.0.0") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_platform_json( - "2.0.0", - "SampleTauriKey", - "https://tauri.studio", - )) - .create(); - - let check_update = block!(builder(Default::default()) - .current_version("1.0.0") - .url(format!( - "{}/darwin/{{{{current_version}}}}", - mockito::server_url() - )) - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - } - - #[test] - fn simple_http_updater_with_elevated_task() { - let _m = mockito::mock("GET", "/win64/1.0.0") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_with_elevated_task_platform_json( - "2.0.0", - "SampleTauriKey", - "https://tauri.studio", - true, - )) - .create(); - - let check_update = block!(builder(Default::default()) - .current_version("1.0.0") - .url(format!( - "{}/win64/{{{{current_version}}}}", - mockito::server_url() - )) - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - } - - #[test] - fn http_updater_uptodate() { - let _m = mockito::mock("GET", "/darwin/10.0.0") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_platform_json( - "2.0.0", - "SampleTauriKey", - "https://tauri.studio", - )) - .create(); - - let check_update = block!(builder(Default::default()) - .current_version("10.0.0") - .url(format!( - "{}/darwin/{{{{current_version}}}}", - mockito::server_url() - )) - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check update"); - - assert!(!updater.should_update); - } - - #[test] - fn http_updater_fallback_urls() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_raw_json()) - .create(); - - let check_update = block!(builder(Default::default()) - .url("http://badurl.www.tld/1".into()) - .url(mockito::server_url()) - .current_version("0.0.1") - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check remote update"); - - assert!(updater.should_update); - } - - #[test] - fn http_updater_fallback_urls_withs_array() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_raw_json()) - .create(); - - let check_update = block!(builder(Default::default()) - .urls(&["http://badurl.www.tld/1".into(), mockito::server_url(),]) - .current_version("0.0.1") - .build()); - - assert!(check_update.is_ok()); - let updater = check_update.expect("Can't check remote update"); - - assert!(updater.should_update); - } - - #[test] - fn http_updater_missing_remote_data() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_bad_json()) - .create(); - - let check_update = block!(builder(Default::default()) - .url(mockito::server_url()) - .current_version("0.0.1") - .build()); - - assert!(check_update.is_err()); - } - - // run complete process on mac only for now as we don't have - // server (api) that we can use to test - #[test] - #[cfg(target_os = "macos")] - fn http_updater_complete_process() { - #[cfg(target_os = "macos")] - let archive_file = "archive.macos.tar.gz"; - #[cfg(target_os = "linux")] - let archive_file = "archive.linux.tar.gz"; - #[cfg(target_os = "windows")] - let archive_file = "archive.windows.zip"; - - let good_archive_url = format!("{}/{}", mockito::server_url(), archive_file); - - let mut signature_file = File::open(format!( - "./test/updater/fixture/archives/{}.sig", - archive_file - )) - .expect("Unable to open signature"); - let mut signature = String::new(); - signature_file - .read_to_string(&mut signature) - .expect("Unable to read signature as string"); - - let mut pubkey_file = File::open("./test/updater/fixture/good_signature/update.key.pub") - .expect("Unable to open pubkey"); - let mut pubkey = String::new(); - pubkey_file - .read_to_string(&mut pubkey) - .expect("Unable to read signature as string"); - - // add sample file - let _m = mockito::mock("GET", format!("/{}", archive_file).as_str()) - .with_status(200) - .with_header("content-type", "application/octet-stream") - .with_body_from_file(format!("./test/updater/fixture/archives/{}", archive_file)) - .create(); - - // sample mock for update file - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_platform_json( - "2.0.1", - signature.as_ref(), - good_archive_url.as_ref(), - )) - .create(); - - // Build a tmpdir so we can test our extraction inside - // We dont want to overwrite our current executable or the directory - // Otherwise tests are failing... - let executable_path = current_exe().expect("Can't extract executable path"); - let parent_path = executable_path - .parent() - .expect("Can't find the parent path"); - - let tmp_dir = tempfile::Builder::new() - .prefix("tauri_updater_test") - .tempdir_in(parent_path); - - assert!(tmp_dir.is_ok()); - let tmp_dir_unwrap = tmp_dir.expect("Can't find tmp_dir"); - let tmp_dir_path = tmp_dir_unwrap.path(); - - #[cfg(target_os = "linux")] - let my_executable = &tmp_dir_path.join("updater-example_0.1.0_amd64.AppImage"); - #[cfg(target_os = "macos")] - let my_executable = &tmp_dir_path.join("my_app"); - #[cfg(target_os = "windows")] - let my_executable = &tmp_dir_path.join("my_app.exe"); - - // configure the updater - let check_update = block!(builder(Default::default()) - .url(mockito::server_url()) - // It should represent the executable path, that's why we add my_app.exe in our - // test path -- in production you shouldn't have to provide it - .executable_path(my_executable) - // make sure we force an update - .current_version("1.0.0") - .build()); - - #[cfg(target_os = "linux")] - { - env::set_var("APPIMAGE", my_executable); - } - - // make sure the process worked - assert!(check_update.is_ok()); - - // unwrap our results - let updater = check_update.expect("Can't check remote update"); - - // make sure we need to update - assert!(updater.should_update); - // make sure we can read announced version - assert_eq!(updater.version, "2.0.1"); - - // download, install and validate signature - let install_process = block!(updater.download_and_install(pubkey)); - assert!(install_process.is_ok()); - - // make sure the extraction went well (it should have skipped the main app.app folder) - // as we can't extract in /Applications directly - #[cfg(target_os = "macos")] - let bin_file = tmp_dir_path.join("Contents").join("MacOS").join("app"); - #[cfg(target_os = "linux")] - // linux should extract at same place as the executable path - let bin_file = my_executable; - #[cfg(target_os = "windows")] - let bin_file = tmp_dir_path.join("with").join("long").join("path.json"); - - assert!(bin_file.exists()); - } -} diff --git a/core/tauri/src/updater/error.rs b/core/tauri/src/updater/error.rs deleted file mode 100644 index f1c6d964f17e..000000000000 --- a/core/tauri/src/updater/error.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use thiserror::Error; - -/// All errors that can occur while running the updater. -#[derive(Debug, Error)] -#[non_exhaustive] -pub enum Error { - /// IO Errors. - #[error("`{0}`")] - Io(#[from] std::io::Error), - /// Semver Errors. - #[error("Unable to compare version: {0}")] - Semver(#[from] semver::Error), - /// JSON (Serde) Errors. - #[error("JSON error: {0}")] - SerdeJson(#[from] serde_json::Error), - /// Minisign is used for signature validation. - #[error("Verify signature error: {0}")] - Minisign(#[from] minisign_verify::Error), - /// Error with Minisign base64 decoding. - #[error("Signature decoding error: {0}")] - Base64(#[from] base64::DecodeError), - /// UTF8 Errors in signature. - #[error("Signature encoding error: {0}")] - Utf8(#[from] std::str::Utf8Error), - /// Tauri utils, mainly extract and file move. - #[error("Tauri API error: {0}")] - TauriApi(#[from] crate::api::Error), - /// Network error. - #[error("Network error: {0}")] - Network(String), - /// Metadata (JSON) error. - #[error("Remote JSON error: {0}")] - RemoteMetadata(String), - /// Error building updater. - #[error("Unable to prepare the updater: {0}")] - Builder(String), - /// Error building updater. - #[error("Unable to extract the new version: {0}")] - Extract(String), - /// Updater is not supported for current operating system or platform. - #[error("Unsuported operating system or platform")] - UnsupportedPlatform, - /// Public key found in `tauri.conf.json` but no signature announced remotely. - #[error("Signature not available, skipping update")] - MissingUpdaterSignature, - /// Triggered when there is NO error and the two versions are equals. - /// On client side, it's important to catch this error. - #[error("No updates available")] - UpToDate, -} - -pub type Result = std::result::Result; diff --git a/core/tauri/src/updater/mod.rs b/core/tauri/src/updater/mod.rs deleted file mode 100644 index aea8f65f5d87..000000000000 --- a/core/tauri/src/updater/mod.rs +++ /dev/null @@ -1,576 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! The Tauri updater. -//! -//! The updater is focused on making Tauri's application updates **as safe and transparent as updates to a website**. -//! -//! Instead of publishing a feed of versions from which your app must select, Tauri updates to the version your server tells it to. This allows you to intelligently update your clients based on the request you give to Tauri. -//! -//! The server can remotely drive behaviors like rolling back or phased rollouts. -//! -//! The update JSON Tauri requests should be dynamically generated based on criteria in the request, and whether an update is required. -//! -//! Tauri's installer is also designed to be fault-tolerant, and ensure that any updates installed are valid and safe. -//! -//! # Configuration -//! -//! Once you have your Tauri project ready, you need to configure the updater. -//! -//! Add this in tauri.conf.json -//! ```json -//! "updater": { -//! "active": true, -//! "endpoints": [ -//! "https://releases.myapp.com/{target}}/{current_version}}" -//! ], -//! "dialog": true, -//! "pubkey": "" -//! } -//! ``` -//! -//! The required keys are "active" and "endpoints", others are optional. -//! -//! "active" must be a boolean. By default, it's set to false. -//! -//! "endpoints" must be an array. The string `{{target}}` and `{{current_version}}` are automatically replaced in the URL allowing you determine [server-side](#update-server-json-format) if an update is available. If multiple endpoints are specified, the updater will fallback if a server is not responding within the pre-defined timeout. -//! -//! "dialog" if present must be a boolean. By default, it's set to true. If enabled, [events](#events) are turned-off as the updater will handle everything. If you need the custom events, you MUST turn off the built-in dialog. -//! -//! "pubkey" if present must be a valid public-key generated with Tauri cli. See [Signing updates](#signing-updates). -//! -//! ## Update Requests -//! -//! Tauri is indifferent to the request the client application provides for update checking. -//! -//! `Accept: application/json` is added to the request headers because Tauri is responsible for parsing the response. -//! -//! For the requirements imposed on the responses and the body format of an update, response see [Server Support](#server-support). -//! -//! Your update request must *at least* include a version identifier so that the server can determine whether an update for this specific version is required. -//! -//! It may also include other identifying criteria such as operating system version, to allow the server to deliver as fine-grained an update as you would like. -//! -//! How you include the version identifier or other criteria is specific to the server that you are requesting updates from. A common approach is to use query parameters, [Configuration](#configuration) shows an example of this. -//! -//! ## Built-in dialog -//! -//! By default, updater uses a built-in dialog API from Tauri. -//! -//! ![New Update](https://i.imgur.com/UMilB5A.png) -//! -//! The dialog release notes is represented by the update `note` provided by the [server](#server-support). -//! -//! If the user accepts, the download and install are initialized. The user will be then prompted to restart the application. -//! -//! ## Javascript API -//! -//! **Attention, you need to _disable built-in dialog_ in your [tauri configuration](#configuration), otherwise, events aren't emitted and the javascript API will NOT work.** -//! -//! -//! ```javascript -//! import { checkUpdate, installUpdate } from "@tauri-apps/api/updater"; -//! -//! try { -//! const {shouldUpdate, manifest} = await checkUpdate(); -//! -//! if (shouldUpdate) { -//! // display dialog -//! await installUpdate(); -//! // install complete, ask to restart -//! } -//! } catch(error) { -//! console.log(error); -//! } -//! ``` -//! -//! ## Events -//! -//! **Attention, you need to _disable built-in dialog_ in your [tauri configuration](#configuration), otherwise, events aren't emitted.** -//! -//! To know when an update is ready to be installed, you can subscribe to these events: -//! -//! ### Initialize updater and check if a new version is available -//! -//! #### If a new version is available, the event `tauri://update-available` is emitted. -//! -//! Event : `tauri://update` -//! -//! ### Rust -//! ```ignore -//! dispatcher.emit("tauri://update", None); -//! ``` -//! -//! ### Javascript -//! ```js -//! import { emit } from "@tauri-apps/api/event"; -//! emit("tauri://update"); -//! ``` -//! -//! ### Listen New Update Available -//! -//! Event : `tauri://update-available` -//! -//! Emitted data: -//! ```text -//! version Version announced by the server -//! date Date announced by the server -//! body Note announced by the server -//! ``` -//! -//! ### Rust -//! ```ignore -//! dispatcher.listen("tauri://update-available", move |msg| { -//! println("New version available: {:?}", msg); -//! }) -//! ``` -//! -//! ### Javascript -//! ```js -//! import { listen } from "@tauri-apps/api/event"; -//! listen("tauri://update-available", function (res) { -//! console.log("New version available: ", res); -//! }); -//! ``` -//! -//! ### Emit Install and Download -//! -//! You need to emit this event to initialize the download and listen to the [install progress](#listen-install-progress). -//! -//! Event : `tauri://update-install` -//! -//! ### Rust -//! ```ignore -//! dispatcher.emit("tauri://update-install", None); -//! ``` -//! -//! ### Javascript -//! ```js -//! import { emit } from "@tauri-apps/api/event"; -//! emit("tauri://update-install"); -//! ``` -//! -//! ### Listen Install Progress -//! -//! Event : `tauri://update-status` -//! -//! Emitted data: -//! ```text -//! status [ERROR/PENDING/DONE] -//! error String/null -//! ``` -//! -//! PENDING is emitted when the download is started and DONE when the install is complete. You can then ask to restart the application. -//! -//! ERROR is emitted when there is an error with the updater. We suggest to listen to this event even if the dialog is enabled. -//! -//! ### Rust -//! ```ignore -//! dispatcher.listen("tauri://update-status", move |msg| { -//! println("New status: {:?}", msg); -//! }) -//! ``` -//! -//! ### Javascript -//! ```js -//! import { listen } from "@tauri-apps/api/event"; -//! listen("tauri://update-status", function (res) { -//! console.log("New status: ", res); -//! }); -//! ``` -//! -//! # Server Support -//! -//! Your server should determine whether an update is required based on the [Update Request](#update-requests) your client issues. -//! -//! If an update is required your server should respond with a status code of [200 OK](http://tools.ietf.org/html/rfc2616#section-10.2.1) and include the [update JSON](#update-server-json-format) in the body. To save redundantly downloading the same version multiple times your server must not inform the client to update. -//! -//! If no update is required your server must respond with a status code of [204 No Content](http://tools.ietf.org/html/rfc2616#section-10.2.5). -//! -//! ## Update Server JSON Format -//! -//! When an update is available, Tauri expects the following schema in response to the update request provided: -//! -//! ```json -//! { -//! "url": "https://mycompany.example.com/myapp/releases/myrelease.tar.gz", -//! "version": "0.0.1", -//! "notes": "Theses are some release notes", -//! "pub_date": "2020-09-18T12:29:53+01:00", -//! "signature": "" -//! } -//! ``` -//! -//! The only required keys are "url" and "version", the others are optional. -//! -//! "pub_date" if present must be formatted according to ISO 8601. -//! -//! "signature" if present must be a valid signature generated with Tauri cli. See [Signing updates](#signing-updates). -//! -//! ## Update File JSON Format -//! -//! The alternate update technique uses a plain JSON file meaning you can store your update metadata on S3, gist, or another static file store. Tauri will check against the name/version field and if the version is smaller than the current one and the platform is available, the update will be triggered. The format of this file is detailed below: -//! -//! ```json -//! { -//! "name":"v1.0.0", -//! "notes":"Test version", -//! "pub_date":"2020-06-22T19:25:57Z", -//! "platforms": { -//! "darwin": { -//! "signature":"", -//! "url":"https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.app.tar.gz" -//! }, -//! "linux": { -//! "signature":"", -//! "url":"https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.AppImage.tar.gz" -//! }, -//! "win64": { -//! "signature":"", -//! "url":"https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.x64.msi.zip" -//! } -//! } -//! } -//! ``` -//! -//! -//! # Bundler (Artifacts) -//! -//! The Tauri bundler will automatically generate update artifacts if the updater is enabled in `tauri.conf.json` -//! -//! If the bundler can locate your private and pubkey, your update artifacts will be automatically signed. -//! -//! The signature can be found in the `sig` file. The signature can be uploaded to GitHub safely or made public as long as your private key is secure. -//! -//! You can see how it's [bundled with the CI](https://github.com/tauri-apps/tauri/blob/feature/new_updater/.github/workflows/artifacts-updater.yml#L44) and a [sample tauri.conf.json](https://github.com/tauri-apps/tauri/blob/feature/new_updater/examples/updater/src-tauri/tauri.conf.json#L52) -//! -//! ## macOS -//! -//! On MACOS we create a .tar.gz from the whole application. (.app) -//! -//! ```text -//! target/release/bundle -//! └── osx -//! └── app.app -//! └── app.app.tar.gz (update bundle) -//! └── app.app.tar.gz.sig (if signature enabled) -//! ``` -//! -//! ## Windows -//! -//! On Windows we create a .zip from the MSI, when downloaded and validated, we run the MSI install. -//! -//! ```text -//! target/release -//! └── app.x64.msi -//! └── app.x64.msi.zip (update bundle) -//! └── app.x64.msi.zip.sig (if signature enabled) -//! ``` -//! -//! ## Linux -//! -//! On Linux, we create a .tar.gz from the AppImage. -//! -//! ```text -//! target/release/bundle -//! └── appimage -//! └── app.AppImage -//! └── app.AppImage.tar.gz (update bundle) -//! └── app.AppImage.tar.gz.sig (if signature enabled) -//! ``` -//! -//! # Signing updates -//! -//! We offer a built-in signature to ensure your update is safe to be installed. -//! -//! To sign your updates, you need two things. -//! -//! The *Public-key* (pubkey) should be added inside your `tauri.conf.json` to validate the update archive before installing. -//! -//! The *Private key* (privkey) is used to sign your update and should NEVER be shared with anyone. Also, if you lost this key, you'll NOT be able to publish a new update to the current user base (if pubkey is set in tauri.conf.json). It's important to save it at a safe place and you can always access it. -//! -//! To generate your keys you need to use the Tauri cli. -//! -//! ```bash -//! tauri signer sign -g -w ~/.tauri/myapp.key -//! ``` -//! -//! You have multiple options available -//! ```bash -//! Tauri updates signer. -//! -//! USAGE: -//! tauri signer sign [FLAGS] [OPTIONS] -//! -//! FLAGS: -//! --force Overwrite private key even if it exists on the specified path -//! -g, --generate Generate keypair to sign files -//! -h, --help Prints help information -//! --no-password Set empty password for your private key -//! -V, --version Prints version information -//! -//! OPTIONS: -//! -p, --password Set private key password when signing -//! -k, --private-key Load the private key from a string -//! -f, --private-key-path Load the private key from a file -//! --sign-file Sign the specified file -//! -w, --write-keys Write private key to a file -//! ``` -//! -//! *** -//! -//! Environment variables used to sign with `tauri-bundler`: -//! If they are set, and `tauri.conf.json` expose the public key, the bundler will automatically generate and sign the updater artifacts. -//! -//! `TAURI_PRIVATE_KEY` Path or String of your private key -//! -//! `TAURI_KEY_PASSWORD` Your private key password (optional) - -mod core; -mod error; - -pub use self::error::Error; - -use crate::{ - api::{dialog::blocking::ask, process::restart}, - runtime::Runtime, - utils::config::UpdaterConfig, - Env, Manager, Window, -}; - -/// Check for new updates -pub const EVENT_CHECK_UPDATE: &str = "tauri://update"; -/// New update available -pub const EVENT_UPDATE_AVAILABLE: &str = "tauri://update-available"; -/// Used to initialize an update *should run check-update first (once you received the update available event)* -pub const EVENT_INSTALL_UPDATE: &str = "tauri://update-install"; -/// Send updater status or error even if dialog is enabled, you should -/// always listen for this event. It'll send you the install progress -/// and any error triggered during update check and install -pub const EVENT_STATUS_UPDATE: &str = "tauri://update-status"; -/// this is the status emitted when the download start -pub const EVENT_STATUS_PENDING: &str = "PENDING"; -/// When you got this status, something went wrong -/// you can find the error message inside the `error` field. -pub const EVENT_STATUS_ERROR: &str = "ERROR"; -/// When you receive this status, you should ask the user to restart -pub const EVENT_STATUS_SUCCESS: &str = "DONE"; -/// When you receive this status, this is because the application is running last version -pub const EVENT_STATUS_UPTODATE: &str = "UPTODATE"; - -#[derive(Clone, serde::Serialize)] -struct StatusEvent { - status: String, - error: Option, -} - -#[derive(Clone, serde::Serialize)] -struct UpdateManifest { - version: String, - date: String, - body: String, -} - -/// Check if there is any new update with builtin dialog. -pub(crate) async fn check_update_with_dialog( - updater_config: UpdaterConfig, - package_info: crate::PackageInfo, - window: Window, -) { - if let Some(endpoints) = updater_config.endpoints.clone() { - let endpoints = endpoints - .iter() - .map(|e| e.to_string()) - .collect::>(); - let env = window.state::().inner().clone(); - // check updates - match self::core::builder(env) - .urls(&endpoints[..]) - .current_version(&package_info.version) - .build() - .await - { - Ok(updater) => { - let pubkey = updater_config.pubkey.clone(); - - // if dialog enabled only - if updater.should_update && updater_config.dialog { - let body = updater.body.clone().unwrap_or_else(|| String::from("")); - let window_ = window.clone(); - let dialog = prompt_for_install( - window_, - &updater.clone(), - &package_info.name, - &body.clone(), - pubkey, - ) - .await; - - if dialog.is_err() { - send_status_update( - window.clone(), - EVENT_STATUS_ERROR, - Some(dialog.err().unwrap().to_string()), - ); - } - } - } - Err(e) => { - send_status_update(window.clone(), EVENT_STATUS_ERROR, Some(e.to_string())); - } - } - } -} - -/// Experimental listener -/// This function should be run on the main thread once. -pub(crate) fn listener( - updater_config: UpdaterConfig, - package_info: crate::PackageInfo, - window: &Window, -) { - let isolated_window = window.clone(); - - // Wait to receive the event `"tauri://update"` - window.listen(EVENT_CHECK_UPDATE, move |_msg| { - let window = isolated_window.clone(); - let package_info = package_info.clone(); - - // prepare our endpoints - let endpoints = updater_config - .endpoints - .as_ref() - .expect("Something wrong with endpoints") - .iter() - .map(|e| e.to_string()) - .collect::>(); - - let pubkey = updater_config.pubkey.clone(); - - // check updates - crate::async_runtime::spawn(async move { - let window = window.clone(); - let window_isolation = window.clone(); - let pubkey = pubkey.clone(); - let env = window.state::().inner().clone(); - - match self::core::builder(env) - .urls(&endpoints[..]) - .current_version(&package_info.version) - .build() - .await - { - Ok(updater) => { - // send notification if we need to update - if updater.should_update { - let body = updater.body.clone().unwrap_or_else(|| String::from("")); - - // Emit `tauri://update-available` - let _ = window.emit_and_trigger( - EVENT_UPDATE_AVAILABLE, - UpdateManifest { - body, - date: updater.date.clone(), - version: updater.version.clone(), - }, - ); - - // Listen for `tauri://update-install` - window.once(EVENT_INSTALL_UPDATE, move |_msg| { - let window = window_isolation.clone(); - let updater = updater.clone(); - let pubkey = pubkey.clone(); - - // Start installation - crate::async_runtime::spawn(async move { - // emit {"status": "PENDING"} - send_status_update(window.clone(), EVENT_STATUS_PENDING, None); - - // Launch updater download process - // macOS we display the `Ready to restart dialog` asking to restart - // Windows is closing the current App and launch the downloaded MSI when ready (the process stop here) - // Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here) - let update_result = updater.clone().download_and_install(pubkey.clone()).await; - - if let Err(err) = update_result { - // emit {"status": "ERROR", "error": "The error message"} - send_status_update(window.clone(), EVENT_STATUS_ERROR, Some(err.to_string())); - } else { - // emit {"status": "DONE"} - send_status_update(window.clone(), EVENT_STATUS_SUCCESS, None); - } - }); - }); - } else { - send_status_update(window.clone(), EVENT_STATUS_UPTODATE, None); - } - } - Err(e) => { - send_status_update(window.clone(), EVENT_STATUS_ERROR, Some(e.to_string())); - } - } - }); - }); -} - -// Send a status update via `tauri://update-status` event. -fn send_status_update(window: Window, status: &str, error: Option) { - let _ = window.emit_and_trigger( - EVENT_STATUS_UPDATE, - StatusEvent { - error, - status: String::from(status), - }, - ); -} - -// Prompt a dialog asking if the user want to install the new version -// Maybe we should add an option to customize it in future versions. -async fn prompt_for_install( - window: Window, - updater: &self::core::Update, - app_name: &str, - body: &str, - pubkey: String, -) -> crate::Result<()> { - // remove single & double quote - let escaped_body = body.replace(&['\"', '\''][..], ""); - - // todo(lemarier): We should review this and make sure we have - // something more conventional. - let should_install = ask( - Some(&window), - format!(r#"A new version of {} is available! "#, app_name), - format!( - r#"{} {} is now available -- you have {}. - -Would you like to install it now? - -Release Notes: -{}"#, - app_name, updater.version, updater.current_version, escaped_body, - ), - ); - - if should_install { - // Launch updater download process - // macOS we display the `Ready to restart dialog` asking to restart - // Windows is closing the current App and launch the downloaded MSI when ready (the process stop here) - // Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here) - updater.download_and_install(pubkey.clone()).await?; - - // Ask user if we need to restart the application - let env = window.state::().inner().clone(); - let should_exit = ask( - Some(&window), - "Ready to Restart", - "The installation was successful, do you want to restart the application now?", - ); - if should_exit { - restart(&env); - } - } - - Ok(()) -} diff --git a/core/tauri/src/vibrancy/macos.rs b/core/tauri/src/vibrancy/macos.rs new file mode 100644 index 000000000000..2b660f161fea --- /dev/null +++ b/core/tauri/src/vibrancy/macos.rs @@ -0,0 +1,299 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(target_os = "macos")] +#![allow(deprecated)] + +use crate::utils::config::WindowEffectsConfig; +use crate::window::{Effect, EffectState}; +use cocoa::{ + appkit::{ + NSAppKitVersionNumber, NSAppKitVersionNumber10_10, NSAppKitVersionNumber10_11, + NSAutoresizingMaskOptions, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, + NSWindowOrderingMode, + }, + base::{id, nil, BOOL}, + foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize}, +}; +use objc::{class, msg_send, sel, sel_impl}; + +pub fn apply_effects(window: id, effects: WindowEffectsConfig) { + let WindowEffectsConfig { + effects, + radius, + state, + .. + } = effects; + let mut appearance: NSVisualEffectMaterial = if let Some(effect) = effects.into_iter().find(|e| { + matches!( + e, + Effect::AppearanceBased + | Effect::Light + | Effect::Dark + | Effect::MediumLight + | Effect::UltraDark + | Effect::Titlebar + | Effect::Selection + | Effect::Menu + | Effect::Popover + | Effect::Sidebar + | Effect::HeaderView + | Effect::Sheet + | Effect::WindowBackground + | Effect::HudWindow + | Effect::FullScreenUI + | Effect::Tooltip + | Effect::ContentBackground + | Effect::UnderWindowBackground + | Effect::UnderPageBackground + ) + }) { + effect.into() + } else { + return; + }; + + unsafe { + if NSAppKitVersionNumber < NSAppKitVersionNumber10_10 { + return; + } + + if !msg_send![class!(NSThread), isMainThread] { + return; + } + + if appearance as u32 > 4 && NSAppKitVersionNumber < NSAppKitVersionNumber10_11 { + appearance = NSVisualEffectMaterial::AppearanceBased; + } + + if appearance as u32 > 9 && NSAppKitVersionNumber < NSAppKitVersionNumber10_14 { + appearance = NSVisualEffectMaterial::AppearanceBased; + } + + let ns_view: id = window.contentView(); + let bounds = NSView::bounds(ns_view); + + let blurred_view = NSVisualEffectView::initWithFrame_(NSVisualEffectView::alloc(nil), bounds); + blurred_view.autorelease(); + + blurred_view.setMaterial_(appearance); + blurred_view.setCornerRadius_(radius.unwrap_or(0.0)); + blurred_view.setBlendingMode_(NSVisualEffectBlendingMode::BehindWindow); + blurred_view.setState_( + state + .map(Into::into) + .unwrap_or(NSVisualEffectState::FollowsWindowActiveState), + ); + NSVisualEffectView::setAutoresizingMask_( + blurred_view, + NSViewWidthSizable | NSViewHeightSizable, + ); + + let _: () = msg_send![ns_view, addSubview: blurred_view positioned: NSWindowOrderingMode::NSWindowBelow relativeTo: 0]; + } +} + +#[allow(non_upper_case_globals)] +const NSAppKitVersionNumber10_14: f64 = 1671.0; + +// https://developer.apple.com/documentation/appkit/nsvisualeffectview/blendingmode +#[allow(dead_code)] +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum NSVisualEffectBlendingMode { + BehindWindow = 0, + WithinWindow = 1, +} + +// macos 10.10+ +// https://developer.apple.com/documentation/appkit/nsvisualeffectview +#[allow(non_snake_case)] +trait NSVisualEffectView: Sized { + unsafe fn alloc(_: Self) -> id { + msg_send![class!(NSVisualEffectView), alloc] + } + + unsafe fn init(self) -> id; + unsafe fn initWithFrame_(self, frameRect: NSRect) -> id; + unsafe fn bounds(self) -> NSRect; + unsafe fn frame(self) -> NSRect; + unsafe fn setFrameSize(self, frameSize: NSSize); + unsafe fn setFrameOrigin(self, frameOrigin: NSPoint); + + unsafe fn superview(self) -> id; + unsafe fn removeFromSuperview(self); + unsafe fn setAutoresizingMask_(self, autoresizingMask: NSAutoresizingMaskOptions); + + // API_AVAILABLE(macos(10.12)); + unsafe fn isEmphasized(self) -> BOOL; + // API_AVAILABLE(macos(10.12)); + unsafe fn setEmphasized_(self, emphasized: BOOL); + + unsafe fn setMaterial_(self, material: NSVisualEffectMaterial); + unsafe fn setCornerRadius_(self, radius: f64); + unsafe fn setState_(self, state: NSVisualEffectState); + unsafe fn setBlendingMode_(self, mode: NSVisualEffectBlendingMode); +} + +#[allow(non_snake_case)] +impl NSVisualEffectView for id { + unsafe fn init(self) -> id { + msg_send![self, init] + } + + unsafe fn initWithFrame_(self, frameRect: NSRect) -> id { + msg_send![self, initWithFrame: frameRect] + } + + unsafe fn bounds(self) -> NSRect { + msg_send![self, bounds] + } + + unsafe fn frame(self) -> NSRect { + msg_send![self, frame] + } + + unsafe fn setFrameSize(self, frameSize: NSSize) { + msg_send![self, setFrameSize: frameSize] + } + + unsafe fn setFrameOrigin(self, frameOrigin: NSPoint) { + msg_send![self, setFrameOrigin: frameOrigin] + } + + unsafe fn superview(self) -> id { + msg_send![self, superview] + } + + unsafe fn removeFromSuperview(self) { + msg_send![self, removeFromSuperview] + } + + unsafe fn setAutoresizingMask_(self, autoresizingMask: NSAutoresizingMaskOptions) { + msg_send![self, setAutoresizingMask: autoresizingMask] + } + + // API_AVAILABLE(macos(10.12)); + unsafe fn isEmphasized(self) -> BOOL { + msg_send![self, isEmphasized] + } + + // API_AVAILABLE(macos(10.12)); + unsafe fn setEmphasized_(self, emphasized: BOOL) { + msg_send![self, setEmphasized: emphasized] + } + + unsafe fn setMaterial_(self, material: NSVisualEffectMaterial) { + msg_send![self, setMaterial: material] + } + + unsafe fn setCornerRadius_(self, radius: f64) { + msg_send![self, setCornerRadius: radius] + } + + unsafe fn setState_(self, state: NSVisualEffectState) { + msg_send![self, setState: state] + } + + unsafe fn setBlendingMode_(self, mode: NSVisualEffectBlendingMode) { + msg_send![self, setBlendingMode: mode] + } +} + +/// +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum NSVisualEffectMaterial { + #[deprecated = "Since macOS 10.14 a default material appropriate for the view's effectiveAppearance. You should instead choose an appropriate semantic material."] + AppearanceBased = 0, + #[deprecated = "Since macOS 10.14 use a semantic material instead."] + Light = 1, + #[deprecated = "Since macOS 10.14 use a semantic material instead."] + Dark = 2, + #[deprecated = "Since macOS 10.14 use a semantic material instead."] + MediumLight = 8, + #[deprecated = "Since macOS 10.14 use a semantic material instead."] + UltraDark = 9, + + /// macOS 10.10+ + Titlebar = 3, + /// macOS 10.10+ + Selection = 4, + + /// macOS 10.11+ + Menu = 5, + /// macOS 10.11+ + Popover = 6, + /// macOS 10.11+ + Sidebar = 7, + + /// macOS 10.14+ + HeaderView = 10, + /// macOS 10.14+ + Sheet = 11, + /// macOS 10.14+ + WindowBackground = 12, + /// macOS 10.14+ + HudWindow = 13, + /// macOS 10.14+ + FullScreenUI = 15, + /// macOS 10.14+ + Tooltip = 17, + /// macOS 10.14+ + ContentBackground = 18, + /// macOS 10.14+ + UnderWindowBackground = 21, + /// macOS 10.14+ + UnderPageBackground = 22, +} + +/// +#[allow(dead_code)] +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum NSVisualEffectState { + /// Make window vibrancy state follow the window's active state + FollowsWindowActiveState = 0, + /// Make window vibrancy state always active + Active = 1, + /// Make window vibrancy state always inactive + Inactive = 2, +} + +impl From for NSVisualEffectMaterial { + fn from(value: crate::window::Effect) -> Self { + match value { + Effect::AppearanceBased => NSVisualEffectMaterial::AppearanceBased, + Effect::Light => NSVisualEffectMaterial::Light, + Effect::Dark => NSVisualEffectMaterial::Dark, + Effect::MediumLight => NSVisualEffectMaterial::MediumLight, + Effect::UltraDark => NSVisualEffectMaterial::UltraDark, + Effect::Titlebar => NSVisualEffectMaterial::Titlebar, + Effect::Selection => NSVisualEffectMaterial::Selection, + Effect::Menu => NSVisualEffectMaterial::Menu, + Effect::Popover => NSVisualEffectMaterial::Popover, + Effect::Sidebar => NSVisualEffectMaterial::Sidebar, + Effect::HeaderView => NSVisualEffectMaterial::HeaderView, + Effect::Sheet => NSVisualEffectMaterial::Sheet, + Effect::WindowBackground => NSVisualEffectMaterial::WindowBackground, + Effect::HudWindow => NSVisualEffectMaterial::HudWindow, + Effect::FullScreenUI => NSVisualEffectMaterial::FullScreenUI, + Effect::Tooltip => NSVisualEffectMaterial::Tooltip, + Effect::ContentBackground => NSVisualEffectMaterial::ContentBackground, + Effect::UnderWindowBackground => NSVisualEffectMaterial::UnderWindowBackground, + Effect::UnderPageBackground => NSVisualEffectMaterial::UnderPageBackground, + Effect::Mica | Effect::Blur | Effect::Acrylic => unreachable!(), + } + } +} + +impl From for NSVisualEffectState { + fn from(value: crate::window::EffectState) -> Self { + match value { + EffectState::FollowsWindowActiveState => NSVisualEffectState::FollowsWindowActiveState, + EffectState::Active => NSVisualEffectState::Active, + EffectState::Inactive => NSVisualEffectState::Inactive, + } + } +} diff --git a/core/tauri/src/vibrancy/mod.rs b/core/tauri/src/vibrancy/mod.rs new file mode 100644 index 000000000000..2ede80e978e4 --- /dev/null +++ b/core/tauri/src/vibrancy/mod.rs @@ -0,0 +1,39 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![allow(unused)] + +use tauri_utils::config::WindowEffectsConfig; + +use crate::{Runtime, Window}; + +#[cfg(target_os = "macos")] +mod macos; +#[cfg(windows)] +mod windows; + +pub fn set_window_effects( + window: &Window, + effects: Option, +) -> crate::Result<()> { + if let Some(_effects) = effects { + #[cfg(windows)] + { + let hwnd = window.hwnd()?; + windows::apply_effects(hwnd, _effects); + } + #[cfg(target_os = "macos")] + { + let ns_window = window.ns_window()?; + macos::apply_effects(ns_window as _, _effects); + } + } else { + #[cfg(windows)] + { + let hwnd = window.hwnd()?; + windows::clear_effects(hwnd); + } + } + Ok(()) +} diff --git a/core/tauri/src/vibrancy/windows.rs b/core/tauri/src/vibrancy/windows.rs new file mode 100644 index 000000000000..bc3ac9926c87 --- /dev/null +++ b/core/tauri/src/vibrancy/windows.rs @@ -0,0 +1,249 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(windows)] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(clippy::upper_case_acronyms)] + +use std::ffi::c_void; + +use crate::utils::config::WindowEffectsConfig; +use crate::window::{Color, Effect}; +use tauri_utils::platform::{get_function_impl, is_windows_7, windows_version}; +use windows::Win32::Graphics::Dwm::{DwmSetWindowAttribute, DWMWINDOWATTRIBUTE}; +use windows::Win32::{ + Foundation::{BOOL, HWND}, + Graphics::{ + Dwm::{DwmEnableBlurBehindWindow, DWM_BB_ENABLE, DWM_BLURBEHIND}, + Gdi::HRGN, + }, +}; + +pub fn apply_effects(window: HWND, effects: WindowEffectsConfig) { + let WindowEffectsConfig { effects, color, .. } = effects; + let effect = if let Some(effect) = effects + .iter() + .find(|e| matches!(e, Effect::Mica | Effect::Acrylic | Effect::Blur)) + { + effect + } else { + return; + }; + + match effect { + Effect::Blur => apply_blur(window, color), + Effect::Acrylic => apply_acrylic(window, color), + Effect::Mica => apply_mica(window), + _ => unreachable!(), + } +} + +pub fn clear_effects(window: HWND) { + clear_blur(window); + clear_acrylic(window); + clear_mica(window); +} + +pub fn apply_blur(hwnd: HWND, color: Option) { + if is_windows_7() { + let bb = DWM_BLURBEHIND { + dwFlags: DWM_BB_ENABLE, + fEnable: true.into(), + hRgnBlur: HRGN::default(), + fTransitionOnMaximized: false.into(), + }; + let _ = unsafe { DwmEnableBlurBehindWindow(hwnd, &bb) }; + } else if is_swca_supported() { + unsafe { SetWindowCompositionAttribute(hwnd, ACCENT_STATE::ACCENT_ENABLE_BLURBEHIND, color) }; + } else { + return; + } +} + +fn clear_blur(hwnd: HWND) { + if is_windows_7() { + let bb = DWM_BLURBEHIND { + dwFlags: DWM_BB_ENABLE, + fEnable: false.into(), + hRgnBlur: HRGN::default(), + fTransitionOnMaximized: false.into(), + }; + let _ = unsafe { DwmEnableBlurBehindWindow(hwnd, &bb) }; + } else if is_swca_supported() { + unsafe { SetWindowCompositionAttribute(hwnd, ACCENT_STATE::ACCENT_DISABLED, None) }; + } else { + return; + } +} + +pub fn apply_acrylic(hwnd: HWND, color: Option) { + if is_backdroptype_supported() { + unsafe { + let _ = DwmSetWindowAttribute( + hwnd, + DWMWA_SYSTEMBACKDROP_TYPE, + &DWM_SYSTEMBACKDROP_TYPE::DWMSBT_TRANSIENTWINDOW as *const _ as _, + 4, + ); + } + } else if is_swca_supported() { + unsafe { + SetWindowCompositionAttribute(hwnd, ACCENT_STATE::ACCENT_ENABLE_ACRYLICBLURBEHIND, color); + } + } else { + return; + } +} + +pub fn clear_acrylic(hwnd: HWND) { + if is_backdroptype_supported() { + unsafe { + let _ = DwmSetWindowAttribute( + hwnd, + DWMWA_SYSTEMBACKDROP_TYPE, + &DWM_SYSTEMBACKDROP_TYPE::DWMSBT_DISABLE as *const _ as _, + 4, + ); + } + } else if is_swca_supported() { + unsafe { SetWindowCompositionAttribute(hwnd, ACCENT_STATE::ACCENT_DISABLED, None) }; + } else { + return; + } +} + +pub fn apply_mica(hwnd: HWND) { + if is_backdroptype_supported() { + unsafe { + let _ = DwmSetWindowAttribute( + hwnd, + DWMWA_SYSTEMBACKDROP_TYPE, + &DWM_SYSTEMBACKDROP_TYPE::DWMSBT_MAINWINDOW as *const _ as _, + 4, + ); + } + } else if is_undocumented_mica_supported() { + let _ = unsafe { DwmSetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &1 as *const _ as _, 4) }; + } else { + return; + } +} + +pub fn clear_mica(hwnd: HWND) { + if is_backdroptype_supported() { + unsafe { + let _ = DwmSetWindowAttribute( + hwnd, + DWMWA_SYSTEMBACKDROP_TYPE, + &DWM_SYSTEMBACKDROP_TYPE::DWMSBT_DISABLE as *const _ as _, + 4, + ); + } + } else if is_undocumented_mica_supported() { + let _ = unsafe { DwmSetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &0 as *const _ as _, 4) }; + } else { + return; + } +} + +const DWMWA_MICA_EFFECT: DWMWINDOWATTRIBUTE = DWMWINDOWATTRIBUTE(1029i32); +const DWMWA_SYSTEMBACKDROP_TYPE: DWMWINDOWATTRIBUTE = DWMWINDOWATTRIBUTE(38i32); + +#[repr(C)] +struct ACCENT_POLICY { + AccentState: u32, + AccentFlags: u32, + GradientColor: u32, + AnimationId: u32, +} + +type WINDOWCOMPOSITIONATTRIB = u32; + +#[repr(C)] +struct WINDOWCOMPOSITIONATTRIBDATA { + Attrib: WINDOWCOMPOSITIONATTRIB, + pvData: *mut c_void, + cbData: usize, +} + +#[derive(PartialEq)] +#[repr(C)] +enum ACCENT_STATE { + ACCENT_DISABLED = 0, + ACCENT_ENABLE_BLURBEHIND = 3, + ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, +} + +macro_rules! get_function { + ($lib:expr, $func:ident) => { + get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')) + .map(|f| unsafe { std::mem::transmute::(f) }) + }; +} + +unsafe fn SetWindowCompositionAttribute( + hwnd: HWND, + accent_state: ACCENT_STATE, + color: Option, +) { + type SetWindowCompositionAttribute = + unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL; + + if let Some(set_window_composition_attribute) = + get_function!("user32.dll", SetWindowCompositionAttribute) + { + let mut color = color.unwrap_or_default(); + + let is_acrylic = accent_state == ACCENT_STATE::ACCENT_ENABLE_ACRYLICBLURBEHIND; + if is_acrylic && color.3 == 0 { + // acrylic doesn't like to have 0 alpha + color.3 = 1; + } + + let mut policy = ACCENT_POLICY { + AccentState: accent_state as _, + AccentFlags: if is_acrylic { 0 } else { 2 }, + GradientColor: (color.0 as u32) + | (color.1 as u32) << 8 + | (color.2 as u32) << 16 + | (color.3 as u32) << 24, + AnimationId: 0, + }; + + let mut data = WINDOWCOMPOSITIONATTRIBDATA { + Attrib: 0x13, + pvData: &mut policy as *mut _ as _, + cbData: std::mem::size_of_val(&policy), + }; + + set_window_composition_attribute(hwnd, &mut data as *mut _ as _); + } +} + +#[allow(unused)] +#[repr(C)] +enum DWM_SYSTEMBACKDROP_TYPE { + DWMSBT_DISABLE = 1, // None + DWMSBT_MAINWINDOW = 2, // Mica + DWMSBT_TRANSIENTWINDOW = 3, // Acrylic + DWMSBT_TABBEDWINDOW = 4, // Tabbed +} + +fn is_swca_supported() -> bool { + is_at_least_build(17763) +} + +fn is_undocumented_mica_supported() -> bool { + is_at_least_build(22000) +} + +fn is_backdroptype_supported() -> bool { + is_at_least_build(22523) +} + +fn is_at_least_build(build: u32) -> bool { + let v = windows_version().unwrap_or_default(); + v.2 >= build +} diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index a625f4e0afbf..788cdba8d6da 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -7,37 +7,66 @@ pub(crate) mod menu; pub use menu::{MenuEvent, MenuHandle}; +pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; +use url::Url; +#[cfg(target_os = "macos")] +use crate::TitleBarStyle; use crate::{ + api::ipc::CallbackFn, app::AppHandle, command::{CommandArg, CommandItem}, event::{Event, EventHandler}, hooks::{InvokePayload, InvokeResponder}, manager::WindowManager, runtime::{ + http::{Request as HttpRequest, Response as HttpResponse}, monitor::Monitor as RuntimeMonitor, - webview::{WebviewAttributes, WindowBuilder}, + webview::{WebviewAttributes, WindowBuilder as _}, window::{ - dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent, + dpi::{PhysicalPosition, PhysicalSize}, + DetachedWindow, PendingWindow, }, - Dispatch, Icon, Runtime, UserAttentionType, + Dispatch, RuntimeHandle, }, sealed::ManagerBase, sealed::RuntimeOrDispatch, - utils::config::WindowUrl, - Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager, PageLoadPayload, + utils::config::{WindowConfig, WindowEffectsConfig, WindowUrl}, + EventLoopMessage, Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager, PageLoadPayload, + Runtime, Theme, WindowEvent, +}; +#[cfg(desktop)] +use crate::{ + runtime::{ + menu::Menu, + window::dpi::{Position, Size}, + UserAttentionType, + }, + CursorIcon, Icon, }; use serde::Serialize; +#[cfg(windows)] +use windows::Win32::Foundation::HWND; use tauri_macros::default_runtime; use std::{ + collections::{HashMap, HashSet}, + fmt, hash::{Hash, Hasher}, - sync::Arc, + path::PathBuf, + sync::{Arc, Mutex}, }; +pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; +pub(crate) type NavigationHandler = dyn Fn(Url) -> bool + Send; + +#[derive(Clone, Serialize)] +struct WindowCreatedEvent { + label: String, +} + /// Monitor descriptor. #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] @@ -82,471 +111,758 @@ impl Monitor { } } -// TODO: expand these docs since this is a pretty important type -/// A webview window managed by Tauri. -/// -/// This type also implements [`Manager`] which allows you to manage other windows attached to -/// the same application. +/// A builder for a webview window managed by Tauri. #[default_runtime(crate::Wry, wry)] -#[derive(Debug)] -pub struct Window { - /// The webview window created by the runtime. - window: DetachedWindow, - /// The manager to associate this webview window with. +pub struct WindowBuilder<'a, R: Runtime> { manager: WindowManager, - pub(crate) app_handle: AppHandle, + runtime: RuntimeOrDispatch<'a, R>, + app_handle: AppHandle, + label: String, + pub(crate) window_builder: >::WindowBuilder, + pub(crate) webview_attributes: WebviewAttributes, + web_resource_request_handler: Option>, + navigation_handler: Option>, } -#[cfg(any(windows, target_os = "macos"))] -#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))] -unsafe impl raw_window_handle::HasRawWindowHandle for Window { - #[cfg(windows)] - fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { - let mut handle = raw_window_handle::Win32Handle::empty(); - handle.hwnd = self.hwnd().expect("failed to get window `hwnd`"); - raw_window_handle::RawWindowHandle::Win32(handle) - } - - #[cfg(target_os = "macos")] - fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { - let mut handle = raw_window_handle::AppKitHandle::empty(); - handle.ns_window = self - .ns_window() - .expect("failed to get window's `ns_window`"); - raw_window_handle::RawWindowHandle::AppKit(handle) +impl<'a, R: Runtime> fmt::Debug for WindowBuilder<'a, R> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WindowBuilder") + .field("manager", &self.manager) + .field("app_handle", &self.app_handle) + .field("label", &self.label) + .field("window_builder", &self.window_builder) + .field("webview_attributes", &self.webview_attributes) + .finish() } } -impl Clone for Window { - fn clone(&self) -> Self { +impl<'a, R: Runtime> WindowBuilder<'a, R> { + /// Initializes a webview window builder with the given window label and URL to load on the webview. + /// + /// # Known issues + /// + /// On Windows, this function deadlocks when used in a synchronous command, see [the Webview2 issue]. + /// You should use `async` commands when creating windows. + /// + /// # Examples + /// + /// - Create a window in the setup hook: + /// + /// ``` + /// tauri::Builder::default() + /// .setup(|app| { + /// let window = tauri::WindowBuilder::new(app, "label", tauri::WindowUrl::App("index.html".into())) + /// .build()?; + /// Ok(()) + /// }); + /// ``` + /// + /// - Create a window in a separate thread: + /// + /// ``` + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle(); + /// std::thread::spawn(move || { + /// let window = tauri::WindowBuilder::new(&handle, "label", tauri::WindowUrl::App("index.html".into())) + /// .build() + /// .unwrap(); + /// }); + /// Ok(()) + /// }); + /// ``` + /// + /// - Create a window in a command: + /// + /// ``` + /// #[tauri::command] + /// async fn create_window(app: tauri::AppHandle) { + /// let window = tauri::WindowBuilder::new(&app, "label", tauri::WindowUrl::External("https://tauri.app/".parse().unwrap())) + /// .build() + /// .unwrap(); + /// } + /// ``` + /// + /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583 + pub fn new, L: Into>(manager: &'a M, label: L, url: WindowUrl) -> Self { + let runtime = manager.runtime(); + let app_handle = manager.app_handle(); Self { - window: self.window.clone(), - manager: self.manager.clone(), - app_handle: self.app_handle.clone(), + manager: manager.manager().clone(), + runtime, + app_handle, + label: label.into(), + window_builder: >::WindowBuilder::new(), + webview_attributes: WebviewAttributes::new(url), + web_resource_request_handler: None, + navigation_handler: None, } } -} -impl Hash for Window { - /// Only use the [`Window`]'s label to represent its hash. - fn hash(&self, state: &mut H) { - self.window.label.hash(state) + /// Initializes a webview window builder from a window config from tauri.conf.json. + /// Keep in mind that you can't create 2 windows with the same `label` so make sure + /// that the initial window was closed or change the label of the new `WindowBuilder`. + /// + /// # Known issues + /// + /// On Windows, this function deadlocks when used in a synchronous command, see [the Webview2 issue]. + /// You should use `async` commands when creating windows. + /// + /// # Examples + /// + /// - Create a window in a command: + /// + /// ``` + /// #[tauri::command] + /// async fn reopen_window(app: tauri::AppHandle) { + /// let window = tauri::WindowBuilder::from_config(&app, app.config().tauri.windows.get(0).unwrap().clone()) + /// .build() + /// .unwrap(); + /// } + /// ``` + /// + /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583 + pub fn from_config>(manager: &'a M, config: WindowConfig) -> Self { + let builder = Self { + manager: manager.manager().clone(), + runtime: manager.runtime(), + app_handle: manager.app_handle(), + label: config.label.clone(), + webview_attributes: WebviewAttributes::from(&config), + window_builder: >::WindowBuilder::with_config( + config, + ), + web_resource_request_handler: None, + navigation_handler: None, + }; + + builder + } + + /// Defines a closure to be executed when the webview makes an HTTP request for a web resource, allowing you to modify the response. + /// + /// Currently only implemented for the `tauri` URI protocol. + /// + /// **NOTE:** Currently this is **not** executed when using external URLs such as a development server, + /// but it might be implemented in the future. **Always** check the request URL. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::{ + /// utils::config::{Csp, CspDirectiveSources, WindowUrl}, + /// http::header::HeaderValue, + /// window::WindowBuilder, + /// }; + /// use std::collections::HashMap; + /// tauri::Builder::default() + /// .setup(|app| { + /// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into())) + /// .on_web_resource_request(|request, response| { + /// if request.uri().starts_with("tauri://") { + /// // if we have a CSP header, Tauri is loading an HTML file + /// // for this example, let's dynamically change the CSP + /// if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") { + /// // use the tauri helper to parse the CSP policy to a map + /// let mut csp_map: HashMap = Csp::Policy(csp.to_str().unwrap().to_string()).into(); + /// csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'"); + /// // use the tauri helper to get a CSP string from the map + /// let csp_string = Csp::from(csp_map).to_string(); + /// *csp = HeaderValue::from_str(&csp_string).unwrap(); + /// } + /// } + /// }) + /// .build()?; + /// Ok(()) + /// }); + /// ``` + pub fn on_web_resource_request( + mut self, + f: F, + ) -> Self { + self.web_resource_request_handler.replace(Box::new(f)); + self } -} -impl Eq for Window {} -impl PartialEq for Window { - /// Only use the [`Window`]'s label to compare equality. - fn eq(&self, other: &Self) -> bool { - self.window.label.eq(&other.window.label) + /// Defines a closure to be executed when the webview navigates to a URL. Returning `false` cancels the navigation. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::{ + /// utils::config::{Csp, CspDirectiveSources, WindowUrl}, + /// http::header::HeaderValue, + /// window::WindowBuilder, + /// }; + /// use std::collections::HashMap; + /// tauri::Builder::default() + /// .setup(|app| { + /// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into())) + /// .on_navigation(|url| { + /// // allow the production URL or localhost on dev + /// url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost")) + /// }) + /// .build()?; + /// Ok(()) + /// }); + /// ``` + pub fn on_navigation bool + Send + 'static>(mut self, f: F) -> Self { + self.navigation_handler.replace(Box::new(f)); + self + } + + /// Creates a new webview window. + pub fn build(mut self) -> crate::Result> { + let mut pending = PendingWindow::new( + self.window_builder.clone(), + self.webview_attributes.clone(), + self.label.clone(), + )?; + pending.navigation_handler = self.navigation_handler.take(); + pending.web_resource_request_handler = self.web_resource_request_handler.take(); + + let labels = self.manager.labels().into_iter().collect::>(); + let pending = self + .manager + .prepare_window(self.app_handle.clone(), pending, &labels)?; + let window_effects = pending.webview_attributes.window_effects.clone(); + let window = match &mut self.runtime { + RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending), + RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending), + RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending), + } + .map(|window| self.manager.attach_window(self.app_handle.clone(), window))?; + + if let Some(effects) = window_effects { + crate::vibrancy::set_window_effects(&window, Some(effects))?; + } + self.manager.eval_script_all(format!( + "window.__TAURI_METADATA__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }})", + window_labels_array = serde_json::to_string(&self.manager.labels())?, + ))?; + + self.manager.emit_filter( + "tauri://window-created", + None, + Some(WindowCreatedEvent { + label: window.label().into(), + }), + |w| w != &window, + )?; + + Ok(window) } } -impl Manager for Window {} -impl ManagerBase for Window { - fn manager(&self) -> &WindowManager { - &self.manager +/// Desktop APIs. +#[cfg(desktop)] +impl<'a, R: Runtime> WindowBuilder<'a, R> { + /// Sets the menu for the window. + #[must_use] + pub fn menu(mut self, menu: Menu) -> Self { + self.window_builder = self.window_builder.menu(menu); + self } - fn runtime(&self) -> RuntimeOrDispatch<'_, R> { - RuntimeOrDispatch::Dispatch(self.dispatcher()) + /// Show window in the center of the screen. + #[must_use] + pub fn center(mut self) -> Self { + self.window_builder = self.window_builder.center(); + self } - fn app_handle(&self) -> AppHandle { - self.app_handle.clone() + /// The initial position of the window's. + #[must_use] + pub fn position(mut self, x: f64, y: f64) -> Self { + self.window_builder = self.window_builder.position(x, y); + self } -} -impl<'de, R: Runtime> CommandArg<'de, R> for Window { - /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail. - fn from_command(command: CommandItem<'de, R>) -> Result { - Ok(command.message.window()) + /// Window size. + #[must_use] + pub fn inner_size(mut self, width: f64, height: f64) -> Self { + self.window_builder = self.window_builder.inner_size(width, height); + self } -} -impl Window { - /// Create a new window that is attached to the manager. - pub(crate) fn new( - manager: WindowManager, - window: DetachedWindow, - app_handle: AppHandle, - ) -> Self { - Self { - window, - manager, - app_handle, - } + /// Window min inner size. + #[must_use] + pub fn min_inner_size(mut self, min_width: f64, min_height: f64) -> Self { + self.window_builder = self.window_builder.min_inner_size(min_width, min_height); + self } - /// Creates a new webview window. - /// - /// Data URLs are only supported with the `window-data-url` feature flag. - pub fn create_window( - &mut self, - label: String, - url: WindowUrl, - setup: F, - ) -> crate::Result> - where - F: FnOnce( - ::WindowBuilder, - WebviewAttributes, - ) -> ( - ::WindowBuilder, - WebviewAttributes, - ), - { - let (window_builder, webview_attributes) = setup( - ::WindowBuilder::new(), - WebviewAttributes::new(url), - ); - self.create_new_window(PendingWindow::new( - window_builder, - webview_attributes, - label, - )) + /// Window max inner size. + #[must_use] + pub fn max_inner_size(mut self, max_width: f64, max_height: f64) -> Self { + self.window_builder = self.window_builder.max_inner_size(max_width, max_height); + self } - pub(crate) fn invoke_responder(&self) -> Arc> { - self.manager.invoke_responder() + /// Whether the window is resizable or not. + /// When resizable is set to false, native window's maximize button is automatically disabled. + #[must_use] + pub fn resizable(mut self, resizable: bool) -> Self { + self.window_builder = self.window_builder.resizable(resizable); + self } - /// The current window's dispatcher. - pub(crate) fn dispatcher(&self) -> R::Dispatcher { - self.window.dispatcher.clone() + /// Whether the window's native maximize button is enabled or not. + /// If resizable is set to false, this setting is ignored. + /// + /// ## Platform-specific + /// + /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. + /// - **Linux / iOS / Android:** Unsupported. + #[must_use] + pub fn maximizable(mut self, maximizable: bool) -> Self { + self.window_builder = self.window_builder.maximizable(maximizable); + self } - /// Runs the given closure on the main thread. - pub fn run_on_main_thread(&self, f: F) -> crate::Result<()> { + /// Whether the window's native minimize button is enabled or not. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + #[must_use] + pub fn minimizable(mut self, minimizable: bool) -> Self { + self.window_builder = self.window_builder.minimizable(minimizable); self - .window - .dispatcher - .run_on_main_thread(f) - .map_err(Into::into) } - /// How to handle this window receiving an [`InvokeMessage`]. - pub fn on_message(self, payload: InvokePayload) -> crate::Result<()> { - let manager = self.manager.clone(); - match payload.cmd.as_str() { - "__initialized" => { - let payload: PageLoadPayload = serde_json::from_value(payload.inner)?; - manager.run_on_page_load(self, payload); - } - _ => { - let message = InvokeMessage::new( - self.clone(), - manager.state(), - payload.cmd.to_string(), - payload.inner, - ); - let resolver = InvokeResolver::new(self, payload.callback, payload.error); - - let invoke = Invoke { message, resolver }; - if let Some(module) = &payload.tauri_module { - let module = module.to_string(); - crate::endpoints::handle(module, invoke, manager.config(), manager.package_info()); - } else if payload.cmd.starts_with("plugin:") { - manager.extend_api(invoke); - } else { - manager.run_invoke_handler(invoke); - } - } - } + /// Whether the window's native close button is enabled or not. + /// + /// ## Platform-specific + /// + /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. + /// Depending on the system, this function may not have any effect when called on a window that is already visible" + /// - **iOS / Android:** Unsupported. + #[must_use] + pub fn closable(mut self, closable: bool) -> Self { + self.window_builder = self.window_builder.closable(closable); + self + } - Ok(()) + /// The title of the window in the title bar. + #[must_use] + pub fn title>(mut self, title: S) -> Self { + self.window_builder = self.window_builder.title(title); + self } - /// The label of this window. - pub fn label(&self) -> &str { - &self.window.label + /// Whether to start the window in fullscreen or not. + #[must_use] + pub fn fullscreen(mut self, fullscreen: bool) -> Self { + self.window_builder = self.window_builder.fullscreen(fullscreen); + self } - /// Emits an event to both the JavaScript and the Rust listeners. - pub fn emit_and_trigger( - &self, - event: &str, - payload: S, - ) -> crate::Result<()> { - self.trigger(event, Some(serde_json::to_string(&payload)?)); - self.emit(event, payload) + /// Sets the window to be initially focused. + #[must_use] + #[deprecated( + since = "1.2.0", + note = "The window is automatically focused by default. This function Will be removed in 2.0.0. Use `focused` instead." + )] + pub fn focus(mut self) -> Self { + self.window_builder = self.window_builder.focused(true); + self } - pub(crate) fn emit_internal( - &self, - event: &str, - source_window_label: Option<&str>, - payload: S, - ) -> crate::Result<()> { - self.eval(&format!( - "window['{}']({{event: {}, windowLabel: {}, payload: {}}})", - self.manager.event_emit_function_name(), - serde_json::to_string(event)?, - serde_json::to_string(&source_window_label)?, - serde_json::to_value(payload)?, - ))?; - Ok(()) + /// Whether the window will be initially focused or not. + #[must_use] + pub fn focused(mut self, focused: bool) -> Self { + self.window_builder = self.window_builder.focused(focused); + self } - /// Emits an event to the JavaScript listeners on the current window. - /// - /// The event is only delivered to listeners that used the `WebviewWindow#listen` method on the @tauri-apps/api `window` module. - pub fn emit(&self, event: &str, payload: S) -> crate::Result<()> { + /// Whether the window should be maximized upon creation. + #[must_use] + pub fn maximized(mut self, maximized: bool) -> Self { + self.window_builder = self.window_builder.maximized(maximized); self - .manager - .emit_filter(event, Some(self.label()), payload, |w| { - w.has_js_listener(None, event) || w.has_js_listener(Some(self.label().into()), event) - })?; - Ok(()) } - /// Listen to an event on this window. + /// Whether the window should be immediately visible upon creation. + #[must_use] + pub fn visible(mut self, visible: bool) -> Self { + self.window_builder = self.window_builder.visible(visible); + self + } + + /// Forces a theme or uses the system settings if None was provided. /// - /// This listener only receives events that are triggered using the - /// [`trigger`](Window#method.trigger) and [`emit_and_trigger`](Window#method.emit_and_trigger) methods or - /// the `appWindow.emit` function from the @tauri-apps/api `window` module. - pub fn listen(&self, event: impl Into, handler: F) -> EventHandler - where - F: Fn(Event) + Send + 'static, - { - let label = self.window.label.clone(); - self.manager.listen(event.into(), Some(label), handler) + /// ## Platform-specific + /// + /// - **macOS**: Only supported on macOS 10.14+. + #[must_use] + pub fn theme(mut self, theme: Option) -> Self { + self.window_builder = self.window_builder.theme(theme); + self } - /// Unlisten to an event on this window. - pub fn unlisten(&self, handler_id: EventHandler) { - self.manager.unlisten(handler_id) + /// Whether the window should be transparent. If this is true, writing colors + /// with alpha values different than `1.0` will produce a transparent window. + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + #[cfg_attr( + doc_cfg, + doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api"))) + )] + #[must_use] + pub fn transparent(mut self, transparent: bool) -> Self { + self.window_builder = self.window_builder.transparent(transparent); + self } - /// Listen to an event on this window a single time. - pub fn once(&self, event: impl Into, handler: F) -> EventHandler - where - F: Fn(Event) + Send + 'static, - { - let label = self.window.label.clone(); - self.manager.once(event.into(), Some(label), handler) + /// Whether the window should have borders and bars. + #[must_use] + pub fn decorations(mut self, decorations: bool) -> Self { + self.window_builder = self.window_builder.decorations(decorations); + self } - /// Triggers an event to the Rust listeners on this window. - /// - /// The event is only delivered to listeners that used the [`listen`](Window#method.listen) method. - pub fn trigger(&self, event: &str, data: Option) { - let label = self.window.label.clone(); - self.manager.trigger(event, Some(label), data) + /// Whether the window should always be on top of other windows. + #[must_use] + pub fn always_on_top(mut self, always_on_top: bool) -> Self { + self.window_builder = self.window_builder.always_on_top(always_on_top); + self } - /// Evaluates JavaScript on this window. - pub fn eval(&self, js: &str) -> crate::Result<()> { - self.window.dispatcher.eval_script(js).map_err(Into::into) + /// Prevents the window contents from being captured by other apps. + #[must_use] + pub fn content_protected(mut self, protected: bool) -> Self { + self.window_builder = self.window_builder.content_protected(protected); + self } - /// Registers a window event listener. - pub fn on_window_event(&self, f: F) { - self.window.dispatcher.on_window_event(f); + /// Sets the window icon. + pub fn icon(mut self, icon: Icon) -> crate::Result { + self.window_builder = self.window_builder.icon(icon.try_into()?)?; + Ok(self) } - /// Registers a menu event listener. - pub fn on_menu_event(&self, f: F) -> uuid::Uuid { - let menu_ids = self.window.menu_ids.clone(); - self.window.dispatcher.on_menu_event(move |event| { - f(MenuEvent { - menu_item_id: menu_ids - .lock() - .unwrap() - .get(&event.menu_item_id) - .unwrap() - .clone(), - }) - }) + /// Sets whether or not the window icon should be hidden from the taskbar. + /// + /// ## Platform-specific + /// + /// - **macOS**: Unsupported. + #[must_use] + pub fn skip_taskbar(mut self, skip: bool) -> Self { + self.window_builder = self.window_builder.skip_taskbar(skip); + self } - pub(crate) fn register_js_listener(&self, window_label: Option, event: String, id: u64) { + /// Sets whether or not the window has shadow. + /// + /// ## Platform-specific + /// + /// - **Windows:** + /// - `false` has no effect on decorated window, shadows are always ON. + /// - `true` will make ndecorated window have a 1px white border, + /// and on Windows 11, it will have a rounded corners. + /// - **Linux:** Unsupported. + #[must_use] + pub fn shadow(mut self, enable: bool) -> Self { + self.window_builder = self.window_builder.shadow(enable); self - .window - .js_event_listeners - .lock() - .unwrap() - .entry(JsEventListenerKey { - window_label, - event, - }) - .or_insert_with(Default::default) - .insert(id); } - pub(crate) fn unregister_js_listener(&self, id: u64) { - let mut empty = None; - let mut js_listeners = self.window.js_event_listeners.lock().unwrap(); - for (key, ids) in js_listeners.iter_mut() { - if ids.contains(&id) { - ids.remove(&id); - if ids.is_empty() { - empty.replace(key.clone()); - } - break; - } - } + /// Sets a parent to the window to be created. + /// + /// A child window has the WS_CHILD style and is confined to the client area of its parent window. + /// + /// For more information, see + #[cfg(windows)] + #[must_use] + pub fn parent_window(mut self, parent: HWND) -> Self { + self.window_builder = self.window_builder.parent_window(parent); + self + } - if let Some(key) = empty { - js_listeners.remove(&key); - } + /// Sets a parent to the window to be created. + #[cfg(target_os = "macos")] + #[must_use] + pub fn parent_window(mut self, parent: *mut std::ffi::c_void) -> Self { + self.window_builder = self.window_builder.parent_window(parent); + self } - /// Whether this window registered a listener to an event from the given window and event name. - pub(crate) fn has_js_listener(&self, window_label: Option, event: &str) -> bool { + /// Set an owner to the window to be created. + /// + /// From MSDN: + /// - An owned window is always above its owner in the z-order. + /// - The system automatically destroys an owned window when its owner is destroyed. + /// - An owned window is hidden when its owner is minimized. + /// + /// For more information, see + #[cfg(windows)] + #[must_use] + pub fn owner_window(mut self, owner: HWND) -> Self { + self.window_builder = self.window_builder.owner_window(owner); self - .window - .js_event_listeners - .lock() - .unwrap() - .contains_key(&JsEventListenerKey { - window_label, - event: event.into(), - }) } - /// Opens the developer tools window (Web Inspector). - /// The devtools is only enabled on debug builds or with the `devtools` feature flag. + /// Sets the [`TitleBarStyle`]. + #[cfg(target_os = "macos")] + #[must_use] + pub fn title_bar_style(mut self, style: TitleBarStyle) -> Self { + self.window_builder = self.window_builder.title_bar_style(style); + self + } + + /// Hide the window title. + #[cfg(target_os = "macos")] + #[must_use] + pub fn hidden_title(mut self, hidden: bool) -> Self { + self.window_builder = self.window_builder.hidden_title(hidden); + self + } + + /// Defines the window [tabbing identifier] for macOS. /// - /// ## Platform-specific + /// Windows with matching tabbing identifiers will be grouped together. + /// If the tabbing identifier is not set, automatic tabbing will be disabled. /// - /// - **macOS**: This is a private API on macOS, - /// so you cannot use this if your application will be published on the App Store. + /// [tabbing identifier]: + #[cfg(target_os = "macos")] + #[must_use] + pub fn tabbing_identifier(mut self, identifier: &str) -> Self { + self.window_builder = self.window_builder.tabbing_identifier(identifier); + self + } + + /// Sets whether clicking an inactive window also clicks through to the webview. + #[must_use] + pub fn accept_first_mouse(mut self, accept: bool) -> Self { + self.webview_attributes.accept_first_mouse = accept; + self + } + + /// Sets window effects. /// - /// # Example + /// Requires the window to be transparent. /// - /// ```rust,no_run - /// use tauri::Manager; - /// tauri::Builder::default() - /// .setup(|app| { - /// #[cfg(debug_assertions)] - /// app.get_window("main").unwrap().open_devtools(); - /// Ok(()) - /// }); - /// ``` - #[cfg(any(debug_assertions, feature = "devtools"))] - #[cfg_attr(doc_cfg, doc(cfg(any(debug_assertions, feature = "devtools"))))] - pub fn open_devtools(&self) { - self.window.dispatcher.open_devtools(); + /// ## Platform-specific: + /// + /// - **Windows**: If using decorations or shadows, you may want to try this workaround https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891 + /// - **Linux**: Unsupported + pub fn effects(mut self, effects: WindowEffectsConfig) -> Self { + self.webview_attributes = self.webview_attributes.window_effects(effects); + self } +} - // Getters +/// Webview attributes. +impl<'a, R: Runtime> WindowBuilder<'a, R> { + /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created, + /// but before the HTML document has been parsed and before any other script included by the HTML document is run. + /// + /// Since it runs on all top-level document and child frame page navigations, + /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{WindowBuilder, Runtime}; + /// + /// const INIT_SCRIPT: &str = r#" + /// if (window.location.origin === 'https://tauri.app') { + /// console.log("hello world from js init script"); + /// + /// window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' }; + /// } + /// "#; + /// + /// fn main() { + /// tauri::Builder::default() + /// .setup(|app| { + /// let window = tauri::WindowBuilder::new(app, "label", tauri::WindowUrl::App("index.html".into())) + /// .initialization_script(INIT_SCRIPT) + /// .build()?; + /// Ok(()) + /// }); + /// } + /// ``` + #[must_use] + pub fn initialization_script(mut self, script: &str) -> Self { + self + .webview_attributes + .initialization_scripts + .push(script.to_string()); + self + } - /// Gets a handle to the window menu. - pub fn menu_handle(&self) -> MenuHandle { - MenuHandle { - ids: self.window.menu_ids.clone(), - dispatcher: self.dispatcher(), - } + /// Set the user agent for the webview + #[must_use] + pub fn user_agent(mut self, user_agent: &str) -> Self { + self.webview_attributes.user_agent = Some(user_agent.to_string()); + self } - /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. - pub fn scale_factor(&self) -> crate::Result { - self.window.dispatcher.scale_factor().map_err(Into::into) + /// Set additional arguments for the webview. + /// + /// ## Platform-specific + /// + /// - **macOS / Linux / Android / iOS**: Unsupported. + /// + /// ## Warning + /// + /// By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection` + /// so if you use this method, you also need to disable these components by yourself if you want. + #[must_use] + pub fn additional_browser_args(mut self, additional_args: &str) -> Self { + self.webview_attributes.additional_browser_args = Some(additional_args.to_string()); + self } - /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop. - pub fn inner_position(&self) -> crate::Result> { - self.window.dispatcher.inner_position().map_err(Into::into) + /// Data directory for the webview. + #[must_use] + pub fn data_directory(mut self, data_directory: PathBuf) -> Self { + self + .webview_attributes + .data_directory + .replace(data_directory); + self } - /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. - pub fn outer_position(&self) -> crate::Result> { - self.window.dispatcher.outer_position().map_err(Into::into) + /// Disables the file drop handler. This is required to use drag and drop APIs on the front end on Windows. + #[must_use] + pub fn disable_file_drop_handler(mut self) -> Self { + self.webview_attributes.file_drop_handler_enabled = false; + self } - /// Returns the physical size of the window's client area. + /// Enables clipboard access for the page rendered on **Linux** and **Windows**. /// - /// The client area is the content of the window, excluding the title bar and borders. - pub fn inner_size(&self) -> crate::Result> { - self.window.dispatcher.inner_size().map_err(Into::into) + /// **macOS** doesn't provide such method and is always enabled by default, + /// but you still need to add menu item accelerators to use shortcuts. + #[must_use] + pub fn enable_clipboard_access(mut self) -> Self { + self.webview_attributes.clipboard = true; + self } - /// Returns the physical size of the entire window. + /// Enable or disable incognito mode for the WebView.. /// - /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead. - pub fn outer_size(&self) -> crate::Result> { - self.window.dispatcher.outer_size().map_err(Into::into) + /// ## Platform-specific: + /// + /// **Android**: Unsupported. + #[must_use] + pub fn incognito(mut self, incognito: bool) -> Self { + self.webview_attributes.incognito = incognito; + self } +} - /// Gets the window's current fullscreen state. - pub fn is_fullscreen(&self) -> crate::Result { - self.window.dispatcher.is_fullscreen().map_err(Into::into) - } +/// Key for a JS event listener. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct JsEventListenerKey { + /// The associated window label. + pub window_label: Option, + /// The event name. + pub event: String, +} - /// Gets the window's current maximized state. - pub fn is_maximized(&self) -> crate::Result { - self.window.dispatcher.is_maximized().map_err(Into::into) +// TODO: expand these docs since this is a pretty important type +/// A webview window managed by Tauri. +/// +/// This type also implements [`Manager`] which allows you to manage other windows attached to +/// the same application. +#[default_runtime(crate::Wry, wry)] +#[derive(Debug)] +pub struct Window { + /// The webview window created by the runtime. + pub(crate) window: DetachedWindow, + /// The manager to associate this webview window with. + manager: WindowManager, + pub(crate) app_handle: AppHandle, + js_event_listeners: Arc>>>, + + #[cfg(test)] + pub(crate) current_url: url::Url, +} + +unsafe impl raw_window_handle::HasRawWindowHandle for Window { + fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { + self.window.dispatcher.raw_window_handle().unwrap() } +} - /// Gets the window’s current decoration state. - pub fn is_decorated(&self) -> crate::Result { - self.window.dispatcher.is_decorated().map_err(Into::into) +impl Clone for Window { + fn clone(&self) -> Self { + Self { + window: self.window.clone(), + manager: self.manager.clone(), + app_handle: self.app_handle.clone(), + js_event_listeners: self.js_event_listeners.clone(), + #[cfg(test)] + current_url: self.current_url.clone(), + } } +} - /// Gets the window’s current resizable state. - pub fn is_resizable(&self) -> crate::Result { - self.window.dispatcher.is_resizable().map_err(Into::into) +impl Hash for Window { + /// Only use the [`Window`]'s label to represent its hash. + fn hash(&self, state: &mut H) { + self.window.label.hash(state) } +} - /// Gets the window's current vibility state. - pub fn is_visible(&self) -> crate::Result { - self.window.dispatcher.is_visible().map_err(Into::into) +impl Eq for Window {} +impl PartialEq for Window { + /// Only use the [`Window`]'s label to compare equality. + fn eq(&self, other: &Self) -> bool { + self.window.label.eq(&other.window.label) } +} - /// Returns the monitor on which the window currently resides. - /// - /// Returns None if current monitor can't be detected. - pub fn current_monitor(&self) -> crate::Result> { +impl Manager for Window { + fn emit_to( + &self, + label: &str, + event: &str, + payload: S, + ) -> crate::Result<()> { self - .window - .dispatcher - .current_monitor() - .map(|m| m.map(Into::into)) - .map_err(Into::into) + .manager() + .emit_filter(event, Some(self.label()), payload, |w| label == w.label()) } - /// Returns the primary monitor of the system. - /// - /// Returns None if it can't identify any monitor as a primary one. - pub fn primary_monitor(&self) -> crate::Result> { + fn emit_all(&self, event: &str, payload: S) -> crate::Result<()> { self - .window - .dispatcher - .primary_monitor() - .map(|m| m.map(Into::into)) - .map_err(Into::into) + .manager() + .emit_filter(event, Some(self.label()), payload, |_| true) + } +} +impl ManagerBase for Window { + fn manager(&self) -> &WindowManager { + &self.manager } - /// Returns the list of all the monitors available on the system. - pub fn available_monitors(&self) -> crate::Result> { - self - .window - .dispatcher - .available_monitors() - .map(|m| m.into_iter().map(Into::into).collect()) - .map_err(Into::into) + fn runtime(&self) -> RuntimeOrDispatch<'_, R> { + RuntimeOrDispatch::Dispatch(self.dispatcher()) } - /// Returns the native handle that is used by this window. - #[cfg(target_os = "macos")] - pub fn ns_window(&self) -> crate::Result<*mut std::ffi::c_void> { - self.window.dispatcher.ns_window().map_err(Into::into) + fn managed_app_handle(&self) -> AppHandle { + self.app_handle.clone() } - /// Returns the native handle that is used by this window. - #[cfg(windows)] - pub fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void> { - self - .window - .dispatcher - .hwnd() - .map(|hwnd| hwnd.0 as *mut _) - .map_err(Into::into) +} + +impl<'de, R: Runtime> CommandArg<'de, R> for Window { + /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail. + fn from_command(command: CommandItem<'de, R>) -> Result { + Ok(command.message.window()) } +} - /// Returns the `ApplicatonWindow` from gtk crate that is used by this window. - /// - /// Note that this can only be used on the main thread. +/// The platform webview handle. Accessed with [`Window#method.with_webview`]; +#[cfg(feature = "wry")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))] +pub struct PlatformWebview(tauri_runtime_wry::Webview); + +#[cfg(feature = "wry")] +impl PlatformWebview { + /// Returns [`webkit2gtk::WebView`] handle. #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -554,193 +870,1372 @@ impl Window { target_os = "netbsd", target_os = "openbsd" ))] - pub fn gtk_window(&self) -> crate::Result { - self.window.dispatcher.gtk_window().map_err(Into::into) + #[cfg_attr( + doc_cfg, + doc(cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))) + )] + pub fn inner(&self) -> std::rc::Rc { + self.0.clone() + } + + /// Returns the WebView2 controller. + #[cfg(windows)] + #[cfg_attr(doc_cfg, doc(cfg(windows)))] + pub fn controller( + &self, + ) -> webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller { + self.0.controller.clone() } - // Setters + /// Returns the [WKWebView] handle. + /// + /// [WKWebView]: https://developer.apple.com/documentation/webkit/wkwebview + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg_attr(doc_cfg, doc(cfg(any(target_os = "macos", target_os = "ios"))))] + pub fn inner(&self) -> cocoa::base::id { + self.0.webview + } - /// Centers the window. - pub fn center(&self) -> crate::Result<()> { - self.window.dispatcher.center().map_err(Into::into) + /// Returns WKWebView [controller] handle. + /// + /// [controller]: https://developer.apple.com/documentation/webkit/wkusercontentcontroller + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg_attr(doc_cfg, doc(cfg(any(target_os = "macos", target_os = "ios"))))] + pub fn controller(&self) -> cocoa::base::id { + self.0.manager } - /// Requests user attention to the window, this has no effect if the application - /// is already focused. How requesting for user attention manifests is platform dependent, - /// see `UserAttentionType` for details. + /// Returns [NSWindow] associated with the WKWebView webview. /// - /// Providing `None` will unset the request for user attention. Unsetting the request for - /// user attention might not be done automatically by the WM when the window receives input. + /// [NSWindow]: https://developer.apple.com/documentation/appkit/nswindow + #[cfg(target_os = "macos")] + #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + pub fn ns_window(&self) -> cocoa::base::id { + self.0.ns_window + } + + /// Returns [UIViewController] used by the WKWebView webview NSWindow. /// - /// ## Platform-specific + /// [UIViewController]: https://developer.apple.com/documentation/uikit/uiviewcontroller + #[cfg(target_os = "ios")] + #[cfg_attr(doc_cfg, doc(cfg(target_os = "ios")))] + pub fn view_controller(&self) -> cocoa::base::id { + self.0.view_controller + } + + /// Returns handle for JNI execution. + #[cfg(target_os = "android")] + pub fn jni_handle(&self) -> tauri_runtime_wry::wry::webview::JniHandle { + self.0 + } +} + +/// Base window functions. +impl Window { + /// Create a new window that is attached to the manager. + pub(crate) fn new( + manager: WindowManager, + window: DetachedWindow, + app_handle: AppHandle, + ) -> Self { + Self { + window, + manager, + app_handle, + js_event_listeners: Default::default(), + #[cfg(test)] + current_url: "http://tauri.app".parse().unwrap(), + } + } + + /// Initializes a webview window builder with the given window label and URL to load on the webview. /// - /// - **macOS:** `None` has no effect. - /// - **Linux:** Urgency levels have the same effect. - pub fn request_user_attention( - &self, - request_type: Option, - ) -> crate::Result<()> { + /// Data URLs are only supported with the `window-data-url` feature flag. + pub fn builder<'a, M: Manager, L: Into>( + manager: &'a M, + label: L, + url: WindowUrl, + ) -> WindowBuilder<'a, R> { + WindowBuilder::<'a, R>::new(manager, label.into(), url) + } + + pub(crate) fn invoke_responder(&self) -> Arc> { + self.manager.invoke_responder() + } + + /// The current window's dispatcher. + pub(crate) fn dispatcher(&self) -> R::Dispatcher { + self.window.dispatcher.clone() + } + + /// Runs the given closure on the main thread. + pub fn run_on_main_thread(&self, f: F) -> crate::Result<()> { self .window .dispatcher - .request_user_attention(request_type) + .run_on_main_thread(f) .map_err(Into::into) } - /// Opens the dialog to prints the contents of the webview. - /// Currently only supported on macOS on `wry`. - /// `window.print()` works on all platforms. - pub fn print(&self) -> crate::Result<()> { - self.window.dispatcher.print().map_err(Into::into) + /// The label of this window. + pub fn label(&self) -> &str { + &self.window.label } - /// Determines if this window should be resizable. - pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> { + /// Registers a window event listener. + pub fn on_window_event(&self, f: F) { self .window .dispatcher - .set_resizable(resizable) - .map_err(Into::into) + .on_window_event(move |event| f(&event.clone().into())); } - /// Set this window's title. - pub fn set_title(&self, title: &str) -> crate::Result<()> { + /// Registers a menu event listener. + pub fn on_menu_event(&self, f: F) -> uuid::Uuid { + let menu_ids = self.window.menu_ids.clone(); + self.window.dispatcher.on_menu_event(move |event| { + let id = menu_ids + .lock() + .unwrap() + .get(&event.menu_item_id) + .unwrap() + .clone(); + f(MenuEvent { menu_item_id: id }) + }) + } + + /// Executes a closure, providing it with the webview handle that is specific to the current platform. + /// + /// The closure is executed on the main thread. + /// + /// # Examples + /// + /// ```rust,no_run + /// #[cfg(target_os = "macos")] + /// #[macro_use] + /// extern crate objc; + /// use tauri::Manager; + /// + /// fn main() { + /// tauri::Builder::default() + /// .setup(|app| { + /// let main_window = app.get_window("main").unwrap(); + /// main_window.with_webview(|webview| { + /// #[cfg(target_os = "linux")] + /// { + /// // see https://docs.rs/webkit2gtk/0.18.2/webkit2gtk/struct.WebView.html + /// // and https://docs.rs/webkit2gtk/0.18.2/webkit2gtk/trait.WebViewExt.html + /// use webkit2gtk::traits::WebViewExt; + /// webview.inner().set_zoom_level(4.); + /// } + /// + /// #[cfg(windows)] + /// unsafe { + /// // see https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html + /// webview.controller().SetZoomFactor(4.).unwrap(); + /// } + /// + /// #[cfg(target_os = "macos")] + /// unsafe { + /// let () = msg_send![webview.inner(), setPageZoom: 4.]; + /// let () = msg_send![webview.controller(), removeAllUserScripts]; + /// let bg_color: cocoa::base::id = msg_send![class!(NSColor), colorWithDeviceRed:0.5 green:0.2 blue:0.4 alpha:1.]; + /// let () = msg_send![webview.ns_window(), setBackgroundColor: bg_color]; + /// } + /// + /// #[cfg(target_os = "android")] + /// { + /// use jni::objects::JValue; + /// webview.jni_handle().exec(|env, _, webview| { + /// env.call_method(webview, "zoomBy", "(F)V", &[JValue::Float(4.)]).unwrap(); + /// }) + /// } + /// }); + /// Ok(()) + /// }); + /// } + /// ``` + #[cfg(all(feature = "wry"))] + #[cfg_attr(doc_cfg, doc(all(feature = "wry")))] + pub fn with_webview( + &self, + f: F, + ) -> crate::Result<()> { self .window .dispatcher - .set_title(title.to_string()) + .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap()))) .map_err(Into::into) } +} - /// Maximizes this window. - pub fn maximize(&self) -> crate::Result<()> { - self.window.dispatcher.maximize().map_err(Into::into) +/// Window getters. +impl Window { + /// Gets a handle to the window menu. + pub fn menu_handle(&self) -> MenuHandle { + MenuHandle { + ids: self.window.menu_ids.clone(), + dispatcher: self.dispatcher(), + } } - /// Un-maximizes this window. - pub fn unmaximize(&self) -> crate::Result<()> { - self.window.dispatcher.unmaximize().map_err(Into::into) + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. + pub fn scale_factor(&self) -> crate::Result { + self.window.dispatcher.scale_factor().map_err(Into::into) } - /// Minimizes this window. - pub fn minimize(&self) -> crate::Result<()> { - self.window.dispatcher.minimize().map_err(Into::into) + /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop. + pub fn inner_position(&self) -> crate::Result> { + self.window.dispatcher.inner_position().map_err(Into::into) } - /// Un-minimizes this window. - pub fn unminimize(&self) -> crate::Result<()> { - self.window.dispatcher.unminimize().map_err(Into::into) + /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. + pub fn outer_position(&self) -> crate::Result> { + self.window.dispatcher.outer_position().map_err(Into::into) } - /// Show this window. - pub fn show(&self) -> crate::Result<()> { - self.window.dispatcher.show().map_err(Into::into) + /// Returns the physical size of the window's client area. + /// + /// The client area is the content of the window, excluding the title bar and borders. + pub fn inner_size(&self) -> crate::Result> { + self.window.dispatcher.inner_size().map_err(Into::into) } - /// Hide this window. - pub fn hide(&self) -> crate::Result<()> { - self.window.dispatcher.hide().map_err(Into::into) + /// Returns the physical size of the entire window. + /// + /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead. + pub fn outer_size(&self) -> crate::Result> { + self.window.dispatcher.outer_size().map_err(Into::into) } - /// Closes this window. - /// # Panics + /// Gets the window's current fullscreen state. + pub fn is_fullscreen(&self) -> crate::Result { + self.window.dispatcher.is_fullscreen().map_err(Into::into) + } + + /// Gets the window's current minimized state. + pub fn is_minimized(&self) -> crate::Result { + self.window.dispatcher.is_minimized().map_err(Into::into) + } + + /// Gets the window's current maximized state. + pub fn is_maximized(&self) -> crate::Result { + self.window.dispatcher.is_maximized().map_err(Into::into) + } + + /// Gets the window's current focus state. + pub fn is_focused(&self) -> crate::Result { + self.window.dispatcher.is_focused().map_err(Into::into) + } + + /// Gets the window’s current decoration state. + pub fn is_decorated(&self) -> crate::Result { + self.window.dispatcher.is_decorated().map_err(Into::into) + } + + /// Gets the window’s current resizable state. + pub fn is_resizable(&self) -> crate::Result { + self.window.dispatcher.is_resizable().map_err(Into::into) + } + + /// Gets the window’s native maximize button state /// - /// - Panics if the event loop is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. - /// - Panics when called on the main thread, usually on the [`run`](crate::App#method.run) closure. + /// ## Platform-specific /// - /// You can spawn a task to use the API using [`crate::async_runtime::spawn`] or [`std::thread::spawn`] to prevent the panic. - pub fn close(&self) -> crate::Result<()> { - self.window.dispatcher.close().map_err(Into::into) + /// - **Linux / iOS / Android:** Unsupported. + pub fn is_maximizable(&self) -> crate::Result { + self.window.dispatcher.is_maximizable().map_err(Into::into) } - /// Determines if this window should be [decorated]. + /// Gets the window’s native minimize button state /// - /// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration - pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> { - self - .window - .dispatcher - .set_decorations(decorations) - .map_err(Into::into) + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + pub fn is_minimizable(&self) -> crate::Result { + self.window.dispatcher.is_minimizable().map_err(Into::into) } - /// Determines if this window should always be on top of other windows. - pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> { - self - .window - .dispatcher - .set_always_on_top(always_on_top) - .map_err(Into::into) + /// Gets the window’s native close button state + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + pub fn is_closable(&self) -> crate::Result { + self.window.dispatcher.is_closable().map_err(Into::into) } - /// Resizes this window. - pub fn set_size>(&self, size: S) -> crate::Result<()> { + /// Gets the window's current visibility state. + pub fn is_visible(&self) -> crate::Result { + self.window.dispatcher.is_visible().map_err(Into::into) + } + + /// Gets the window's current title. + pub fn title(&self) -> crate::Result { + self.window.dispatcher.title().map_err(Into::into) + } + + /// Returns the monitor on which the window currently resides. + /// + /// Returns None if current monitor can't be detected. + pub fn current_monitor(&self) -> crate::Result> { self .window .dispatcher - .set_size(size.into()) + .current_monitor() + .map(|m| m.map(Into::into)) .map_err(Into::into) } - /// Sets this window's minimum size. - pub fn set_min_size>(&self, size: Option) -> crate::Result<()> { + /// Returns the primary monitor of the system. + /// + /// Returns None if it can't identify any monitor as a primary one. + pub fn primary_monitor(&self) -> crate::Result> { self .window .dispatcher - .set_min_size(size.map(|s| s.into())) + .primary_monitor() + .map(|m| m.map(Into::into)) .map_err(Into::into) } - /// Sets this window's maximum size. - pub fn set_max_size>(&self, size: Option) -> crate::Result<()> { + /// Returns the list of all the monitors available on the system. + pub fn available_monitors(&self) -> crate::Result> { self .window .dispatcher - .set_max_size(size.map(|s| s.into())) + .available_monitors() + .map(|m| m.into_iter().map(Into::into).collect()) .map_err(Into::into) } - /// Sets this window's position. - pub fn set_position>(&self, position: Pos) -> crate::Result<()> { + /// Returns the native handle that is used by this window. + #[cfg(target_os = "macos")] + pub fn ns_window(&self) -> crate::Result<*mut std::ffi::c_void> { self .window .dispatcher - .set_position(position.into()) + .raw_window_handle() .map_err(Into::into) + .and_then(|handle| { + if let raw_window_handle::RawWindowHandle::AppKit(h) = handle { + Ok(h.ns_window) + } else { + Err(crate::Error::InvalidWindowHandle) + } + }) } - /// Determines if this window should be fullscreen. - pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> { + /// Returns the native handle that is used by this window. + #[cfg(windows)] + pub fn hwnd(&self) -> crate::Result { self .window .dispatcher - .set_fullscreen(fullscreen) + .raw_window_handle() .map_err(Into::into) + .and_then(|handle| { + if let raw_window_handle::RawWindowHandle::Win32(h) = handle { + Ok(HWND(h.hwnd as _)) + } else { + Err(crate::Error::InvalidWindowHandle) + } + }) } - /// Bring the window to front and focus. - pub fn set_focus(&self) -> crate::Result<()> { - self.window.dispatcher.set_focus().map_err(Into::into) + /// Returns the `ApplicationWindow` from gtk crate that is used by this window. + /// + /// Note that this can only be used on the main thread. + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub fn gtk_window(&self) -> crate::Result { + self.window.dispatcher.gtk_window().map_err(Into::into) } - /// Sets this window' icon. - pub fn set_icon(&self, icon: Icon) -> crate::Result<()> { - self.window.dispatcher.set_icon(icon).map_err(Into::into) + /// Returns the current window theme. + /// + /// ## Platform-specific + /// + /// - **macOS**: Only supported on macOS 10.14+. + pub fn theme(&self) -> crate::Result { + self.window.dispatcher.theme().map_err(Into::into) } +} - /// Whether to show the window icon in the task bar or not. - pub fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()> { - self - .window - .dispatcher - .set_skip_taskbar(skip) +/// Desktop window setters and actions. +#[cfg(desktop)] +impl Window { + /// Centers the window. + pub fn center(&self) -> crate::Result<()> { + self.window.dispatcher.center().map_err(Into::into) + } + + /// Requests user attention to the window, this has no effect if the application + /// is already focused. How requesting for user attention manifests is platform dependent, + /// see `UserAttentionType` for details. + /// + /// Providing `None` will unset the request for user attention. Unsetting the request for + /// user attention might not be done automatically by the WM when the window receives input. + /// + /// ## Platform-specific + /// + /// - **macOS:** `None` has no effect. + /// - **Linux:** Urgency levels have the same effect. + pub fn request_user_attention( + &self, + request_type: Option, + ) -> crate::Result<()> { + self + .window + .dispatcher + .request_user_attention(request_type) .map_err(Into::into) } - /// Starts dragging the window. - pub fn start_dragging(&self) -> crate::Result<()> { - self.window.dispatcher.start_dragging().map_err(Into::into) + /// Opens the dialog to prints the contents of the webview. + /// Currently only supported on macOS on `wry`. + /// `window.print()` works on all platforms. + pub fn print(&self) -> crate::Result<()> { + self.window.dispatcher.print().map_err(Into::into) + } + + /// Determines if this window should be resizable. + /// When resizable is set to false, native window's maximize button is automatically disabled. + pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_resizable(resizable) + .map_err(Into::into) + } + + /// Determines if this window's native maximize button should be enabled. + /// If resizable is set to false, this setting is ignored. + /// + /// ## Platform-specific + /// + /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. + /// - **Linux / iOS / Android:** Unsupported. + pub fn set_maximizable(&self, maximizable: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_maximizable(maximizable) + .map_err(Into::into) + } + + /// Determines if this window's native minize button should be enabled. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + pub fn set_minimizable(&self, minimizable: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_minimizable(minimizable) + .map_err(Into::into) + } + + /// Determines if this window's native close button should be enabled. + /// + /// ## Platform-specific + /// + /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. + /// Depending on the system, this function may not have any effect when called on a window that is already visible" + /// - **iOS / Android:** Unsupported. + pub fn set_closable(&self, closable: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_closable(closable) + .map_err(Into::into) + } + + /// Set this window's title. + pub fn set_title(&self, title: &str) -> crate::Result<()> { + self + .window + .dispatcher + .set_title(title.to_string()) + .map_err(Into::into) + } + + /// Maximizes this window. + pub fn maximize(&self) -> crate::Result<()> { + self.window.dispatcher.maximize().map_err(Into::into) + } + + /// Un-maximizes this window. + pub fn unmaximize(&self) -> crate::Result<()> { + self.window.dispatcher.unmaximize().map_err(Into::into) + } + + /// Minimizes this window. + pub fn minimize(&self) -> crate::Result<()> { + self.window.dispatcher.minimize().map_err(Into::into) + } + + /// Un-minimizes this window. + pub fn unminimize(&self) -> crate::Result<()> { + self.window.dispatcher.unminimize().map_err(Into::into) + } + + /// Show this window. + pub fn show(&self) -> crate::Result<()> { + self.window.dispatcher.show().map_err(Into::into) + } + + /// Hide this window. + pub fn hide(&self) -> crate::Result<()> { + self.window.dispatcher.hide().map_err(Into::into) + } + + /// Closes this window. + /// # Panics + /// + /// - Panics if the event loop is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// - Panics when called on the main thread, usually on the [`run`](crate::App#method.run) closure. + /// + /// You can spawn a task to use the API using [`crate::async_runtime::spawn`] or [`std::thread::spawn`] to prevent the panic. + pub fn close(&self) -> crate::Result<()> { + self.window.dispatcher.close().map_err(Into::into) + } + + /// Determines if this window should be [decorated]. + /// + /// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration + pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_decorations(decorations) + .map_err(Into::into) + } + + /// Determines if this window should have shadow. + /// + /// ## Platform-specific + /// + /// - **Windows:** + /// - `false` has no effect on decorated window, shadow are always ON. + /// - `true` will make ndecorated window have a 1px white border, + /// and on Windows 11, it will have a rounded corners. + /// - **Linux:** Unsupported. + pub fn set_shadow(&self, enable: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_shadow(enable) + .map_err(Into::into) + } + + /// Sets window effects, pass [`None`] to clear any effects applied if possible. + /// + /// Requires the window to be transparent. + /// + /// See [`EffectsBuilder`] for a convenient builder for [`WindowEffectsConfig`]. + /// + /// + /// ```rust,no_run + /// use tauri::{Manager, window::{Color, Effect, EffectState, EffectsBuilder}}; + /// tauri::Builder::default() + /// .setup(|app| { + /// let window = app.get_window("main").unwrap(); + /// window.set_effects( + /// EffectsBuilder::new() + /// .effect(Effect::Popover) + /// .state(EffectState::Active) + /// .radius(5.) + /// .color(Color(0, 0, 0, 255)) + /// .build(), + /// )?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Windows**: If using decorations or shadows, you may want to try this workaround https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891 + /// - **Linux**: Unsupported + pub fn set_effects>>(&self, effects: E) -> crate::Result<()> { + let effects = effects.into(); + let window = self.clone(); + self.run_on_main_thread(move || { + let _ = crate::vibrancy::set_window_effects(&window, effects); + }) + } + + /// Determines if this window should always be on top of other windows. + pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_always_on_top(always_on_top) + .map_err(Into::into) + } + + /// Prevents the window contents from being captured by other apps. + pub fn set_content_protected(&self, protected: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_content_protected(protected) + .map_err(Into::into) + } + + /// Resizes this window. + pub fn set_size>(&self, size: S) -> crate::Result<()> { + self + .window + .dispatcher + .set_size(size.into()) + .map_err(Into::into) + } + + /// Sets this window's minimum size. + pub fn set_min_size>(&self, size: Option) -> crate::Result<()> { + self + .window + .dispatcher + .set_min_size(size.map(|s| s.into())) + .map_err(Into::into) + } + + /// Sets this window's maximum size. + pub fn set_max_size>(&self, size: Option) -> crate::Result<()> { + self + .window + .dispatcher + .set_max_size(size.map(|s| s.into())) + .map_err(Into::into) + } + + /// Sets this window's position. + pub fn set_position>(&self, position: Pos) -> crate::Result<()> { + self + .window + .dispatcher + .set_position(position.into()) + .map_err(Into::into) + } + + /// Determines if this window should be fullscreen. + pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_fullscreen(fullscreen) + .map_err(Into::into) + } + + /// Bring the window to front and focus. + pub fn set_focus(&self) -> crate::Result<()> { + self.window.dispatcher.set_focus().map_err(Into::into) + } + + /// Sets this window' icon. + pub fn set_icon(&self, icon: Icon) -> crate::Result<()> { + self + .window + .dispatcher + .set_icon(icon.try_into()?) + .map_err(Into::into) + } + + /// Whether to hide the window icon from the taskbar or not. + /// + /// ## Platform-specific + /// + /// - **macOS:** Unsupported. + pub fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_skip_taskbar(skip) + .map_err(Into::into) + } + + /// Grabs the cursor, preventing it from leaving the window. + /// + /// There's no guarantee that the cursor will be hidden. You should + /// hide it by yourself if you want so. + /// + /// ## Platform-specific + /// + /// - **Linux:** Unsupported. + /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward. + pub fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_cursor_grab(grab) + .map_err(Into::into) } + + /// Modifies the cursor's visibility. + /// + /// If `false`, this will hide the cursor. If `true`, this will show the cursor. + /// + /// ## Platform-specific + /// + /// - **Windows:** The cursor is only hidden within the confines of the window. + /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is + /// outside of the window. + pub fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_cursor_visible(visible) + .map_err(Into::into) + } + + /// Modifies the cursor icon of the window. + pub fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> { + self + .window + .dispatcher + .set_cursor_icon(icon) + .map_err(Into::into) + } + + /// Changes the position of the cursor in window coordinates. + pub fn set_cursor_position>(&self, position: Pos) -> crate::Result<()> { + self + .window + .dispatcher + .set_cursor_position(position) + .map_err(Into::into) + } + + /// Ignores the window cursor events. + pub fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_ignore_cursor_events(ignore) + .map_err(Into::into) + } + + /// Starts dragging the window. + pub fn start_dragging(&self) -> crate::Result<()> { + self.window.dispatcher.start_dragging().map_err(Into::into) + } +} + +/// Webview APIs. +impl Window { + /// Returns the current url of the webview. + // TODO: in v2, change this type to Result + #[cfg(not(test))] + pub fn url(&self) -> Url { + self.window.dispatcher.url().unwrap() + } + + #[cfg(test)] + pub fn url(&self) -> Url { + self.current_url.clone() + } + + #[cfg(test)] + pub(crate) fn navigate(&mut self, url: Url) { + self.current_url = url; + } + + fn is_local_url(&self, current_url: &Url) -> bool { + self.manager.get_url().make_relative(current_url).is_some() || { + let protocol_url = self.manager.protocol_url(); + current_url.scheme() == protocol_url.scheme() && current_url.domain() == protocol_url.domain() + } + } + + /// Handles this window receiving an [`InvokeMessage`]. + pub fn on_message(self, payload: InvokePayload) -> crate::Result<()> { + let manager = self.manager.clone(); + let current_url = self.url(); + let is_local = self.is_local_url(¤t_url); + + let mut scope_not_found_error_message = + ipc_scope_not_found_error_message(&self.window.label, current_url.as_str()); + let scope = if is_local { + None + } else { + match self.ipc_scope().remote_access_for(&self, ¤t_url) { + Ok(scope) => Some(scope), + Err(e) => { + if e.matches_window { + scope_not_found_error_message = ipc_scope_domain_error_message(current_url.as_str()); + } else if e.matches_domain { + scope_not_found_error_message = ipc_scope_window_error_message(&self.window.label); + } + None + } + } + }; + match payload.cmd.as_str() { + "__initialized" => { + let payload: PageLoadPayload = serde_json::from_value(payload.inner)?; + manager.run_on_page_load(self, payload); + } + _ => { + let message = InvokeMessage::new( + self.clone(), + manager.state(), + payload.cmd.to_string(), + payload.inner, + ); + #[allow(clippy::redundant_clone)] + let resolver = InvokeResolver::new(self.clone(), payload.callback, payload.error); + + let mut invoke = Invoke { message, resolver }; + if !is_local && scope.is_none() { + invoke.resolver.reject(scope_not_found_error_message); + return Ok(()); + } + + if payload.cmd.starts_with("plugin:") { + if !is_local { + let command = invoke.message.command.replace("plugin:", ""); + let plugin_name = command.split('|').next().unwrap().to_string(); + if !scope + .map(|s| s.plugins().contains(&plugin_name)) + .unwrap_or(true) + { + invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); + return Ok(()); + } + } + let command = invoke.message.command.replace("plugin:", ""); + let mut tokens = command.split('|'); + // safe to unwrap: split always has a least one item + let plugin = tokens.next().unwrap(); + invoke.message.command = tokens + .next() + .map(|c| c.to_string()) + .unwrap_or_else(String::new); + + let command = invoke.message.command.clone(); + let resolver = invoke.resolver.clone(); + #[cfg(mobile)] + let message = invoke.message.clone(); + + #[allow(unused_mut)] + let mut handled = manager.extend_api(plugin, invoke); + + #[cfg(target_os = "ios")] + { + if !handled { + handled = true; + let plugin = plugin.to_string(); + let (callback, error) = (resolver.callback, resolver.error); + self.with_webview(move |webview| { + unsafe { + crate::ios::post_ipc_message( + webview.inner() as _, + &plugin.as_str().into(), + &heck::ToLowerCamelCase::to_lower_camel_case(message.command.as_str()) + .as_str() + .into(), + crate::ios::json_to_dictionary(&message.payload) as _, + callback.0, + error.0, + ) + }; + })?; + } + } + + #[cfg(target_os = "android")] + { + if !handled { + handled = true; + let resolver_ = resolver.clone(); + let runtime_handle = self.app_handle.runtime_handle.clone(); + let plugin = plugin.to_string(); + self.with_webview(move |webview| { + webview.jni_handle().exec(move |env, activity, webview| { + use jni::{ + errors::Error as JniError, + objects::JObject, + JNIEnv, + }; + + fn handle_message( + plugin: &str, + runtime_handle: &R::Handle, + message: InvokeMessage, + (callback, error): (CallbackFn, CallbackFn), + env: JNIEnv<'_>, + activity: JObject<'_>, + webview: JObject<'_>, + ) -> Result<(), JniError> { + let data = crate::jni_helpers::to_jsobject::(env, activity, runtime_handle, &message.payload)?; + let plugin_manager = env + .call_method( + activity, + "getPluginManager", + "()Lapp/tauri/plugin/PluginManager;", + &[], + )? + .l()?; + + env.call_method( + plugin_manager, + "postIpcMessage", + "(Landroid/webkit/WebView;Ljava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;JJ)V", + &[ + webview.into(), + env.new_string(plugin)?.into(), + env.new_string(&heck::ToLowerCamelCase::to_lower_camel_case(message.command.as_str()))?.into(), + data, + (callback.0 as i64).into(), + (error.0 as i64).into(), + ], + )?; + + Ok(()) + } + + if let Err(e) = handle_message( + &plugin, + &runtime_handle, + message, + (resolver_.callback, resolver_.error), + env, + activity, + webview, + ) { + resolver_.reject(format!("failed to reach Android layer: {e}")); + } + }); + })?; + } + } + + if !handled { + resolver.reject(format!("Command {command} not found")); + } + } else { + let command = invoke.message.command.clone(); + let resolver = invoke.resolver.clone(); + let handled = manager.run_invoke_handler(invoke); + if !handled { + resolver.reject(format!("Command {command} not found")); + } + } + } + } + + Ok(()) + } + + /// Evaluates JavaScript on this window. + pub fn eval(&self, js: &str) -> crate::Result<()> { + self.window.dispatcher.eval_script(js).map_err(Into::into) + } + + /// Register a JS event listener and return its identifier. + pub(crate) fn listen_js( + &self, + window_label: Option, + event: String, + handler: CallbackFn, + ) -> crate::Result { + let event_id = rand::random(); + + self.eval(&crate::event::listen_js( + self.manager().event_listeners_object_name(), + format!("'{}'", event), + event_id, + window_label.clone(), + format!("window['_{}']", handler.0), + ))?; + + self + .js_event_listeners + .lock() + .unwrap() + .entry(JsEventListenerKey { + window_label, + event, + }) + .or_insert_with(Default::default) + .insert(event_id); + + Ok(event_id) + } + + /// Unregister a JS event listener. + pub(crate) fn unlisten_js(&self, event: String, id: usize) -> crate::Result<()> { + self.eval(&crate::event::unlisten_js( + self.manager().event_listeners_object_name(), + event, + id, + ))?; + + let mut empty = None; + let mut js_listeners = self.js_event_listeners.lock().unwrap(); + let iter = js_listeners.iter_mut(); + for (key, ids) in iter { + if ids.contains(&id) { + ids.remove(&id); + if ids.is_empty() { + empty.replace(key.clone()); + } + break; + } + } + + if let Some(key) = empty { + js_listeners.remove(&key); + } + + Ok(()) + } + + /// Whether this window registered a listener to an event from the given window and event name. + pub(crate) fn has_js_listener(&self, window_label: Option, event: &str) -> bool { + self + .js_event_listeners + .lock() + .unwrap() + .contains_key(&JsEventListenerKey { + window_label, + event: event.into(), + }) + } + + /// Opens the developer tools window (Web Inspector). + /// The devtools is only enabled on debug builds or with the `devtools` feature flag. + /// + /// ## Platform-specific + /// + /// - **macOS:** Only supported on macOS 10.15+. + /// This is a private API on macOS, so you cannot use this if your application will be published on the App Store. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::Manager; + /// tauri::Builder::default() + /// .setup(|app| { + /// #[cfg(debug_assertions)] + /// app.get_window("main").unwrap().open_devtools(); + /// Ok(()) + /// }); + /// ``` + #[cfg(any(debug_assertions, feature = "devtools"))] + #[cfg_attr(doc_cfg, doc(cfg(any(debug_assertions, feature = "devtools"))))] + pub fn open_devtools(&self) { + self.window.dispatcher.open_devtools(); + } + + /// Closes the developer tools window (Web Inspector). + /// The devtools is only enabled on debug builds or with the `devtools` feature flag. + /// + /// ## Platform-specific + /// + /// - **macOS:** Only supported on macOS 10.15+. + /// This is a private API on macOS, so you cannot use this if your application will be published on the App Store. + /// - **Windows:** Unsupported. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::Manager; + /// tauri::Builder::default() + /// .setup(|app| { + /// #[cfg(debug_assertions)] + /// { + /// let window = app.get_window("main").unwrap(); + /// window.open_devtools(); + /// std::thread::spawn(move || { + /// std::thread::sleep(std::time::Duration::from_secs(10)); + /// window.close_devtools(); + /// }); + /// } + /// Ok(()) + /// }); + /// ``` + #[cfg(any(debug_assertions, feature = "devtools"))] + #[cfg_attr(doc_cfg, doc(cfg(any(debug_assertions, feature = "devtools"))))] + pub fn close_devtools(&self) { + self.window.dispatcher.close_devtools(); + } + + /// Checks if the developer tools window (Web Inspector) is opened. + /// The devtools is only enabled on debug builds or with the `devtools` feature flag. + /// + /// ## Platform-specific + /// + /// - **macOS:** Only supported on macOS 10.15+. + /// This is a private API on macOS, so you cannot use this if your application will be published on the App Store. + /// - **Windows:** Unsupported. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::Manager; + /// tauri::Builder::default() + /// .setup(|app| { + /// #[cfg(debug_assertions)] + /// { + /// let window = app.get_window("main").unwrap(); + /// if !window.is_devtools_open() { + /// window.open_devtools(); + /// } + /// } + /// Ok(()) + /// }); + /// ``` + #[cfg(any(debug_assertions, feature = "devtools"))] + #[cfg_attr(doc_cfg, doc(cfg(any(debug_assertions, feature = "devtools"))))] + pub fn is_devtools_open(&self) -> bool { + self + .window + .dispatcher + .is_devtools_open() + .unwrap_or_default() + } +} + +/// Event system APIs. +impl Window { + /// Emits an event to both the JavaScript and the Rust listeners. + /// + /// This API is a combination of [`Self::trigger`] and [`Self::emit`]. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// #[tauri::command] + /// fn download(window: tauri::Window) { + /// window.emit_and_trigger("download-started", ()); + /// + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to all listeners + /// window.emit_and_trigger("download-progress", i); + /// } + /// } + /// ``` + pub fn emit_and_trigger( + &self, + event: &str, + payload: S, + ) -> crate::Result<()> { + self.trigger(event, Some(serde_json::to_string(&payload)?)); + self.emit(event, payload) + } + + pub(crate) fn emit_internal( + &self, + event: &str, + source_window_label: Option<&str>, + payload: S, + ) -> crate::Result<()> { + self.eval(&format!( + "(function () {{ const fn = window['{}']; fn && fn({{event: {}, windowLabel: {}, payload: {}}}) }})()", + self.manager.event_emit_function_name(), + serde_json::to_string(event)?, + serde_json::to_string(&source_window_label)?, + serde_json::to_value(payload)?, + ))?; + Ok(()) + } + + /// Emits an event to the JavaScript listeners on the current window or globally. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// #[tauri::command] + /// fn download(window: tauri::Window) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to all listeners registed in the webview + /// window.emit("download-progress", i); + /// } + /// } + /// ``` + pub fn emit(&self, event: &str, payload: S) -> crate::Result<()> { + self + .manager + .emit_filter(event, Some(self.label()), payload, |w| { + w.has_js_listener(None, event) || w.has_js_listener(Some(self.label().into()), event) + })?; + Ok(()) + } + + /// Listen to an event on this window. + /// + /// This listener only receives events that are triggered using the + /// [`trigger`](Window#method.trigger) and [`emit_and_trigger`](Window#method.emit_and_trigger) methods or + /// the `appWindow.emit` function from the @tauri-apps/api `window` module. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let window = app.get_window("main").unwrap(); + /// window.listen("component-loaded", move |event| { + /// println!("window just loaded a component"); + /// }); + /// + /// Ok(()) + /// }); + /// ``` + pub fn listen(&self, event: impl Into, handler: F) -> EventHandler + where + F: Fn(Event) + Send + 'static, + { + let label = self.window.label.clone(); + self.manager.listen(event.into(), Some(label), handler) + } + + /// Unlisten to an event on this window. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let window = app.get_window("main").unwrap(); + /// let window_ = window.clone(); + /// let handler = window.listen("component-loaded", move |event| { + /// println!("window just loaded a component"); + /// + /// // we no longer need to listen to the event + /// // we also could have used `window.once` instead + /// window_.unlisten(event.id()); + /// }); + /// + /// // stop listening to the event when you do not need it anymore + /// window.unlisten(handler); + /// + /// + /// Ok(()) + /// }); + /// ``` + pub fn unlisten(&self, handler_id: EventHandler) { + self.manager.unlisten(handler_id) + } + + /// Listen to an event on this window a single time. + /// + /// See [`Self::listen`] for more information. + pub fn once(&self, event: impl Into, handler: F) -> EventHandler + where + F: FnOnce(Event) + Send + 'static, + { + let label = self.window.label.clone(); + self.manager.once(event.into(), Some(label), handler) + } + + /// Triggers an event to the Rust listeners on this window or global listeners. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// #[tauri::command] + /// fn download(window: tauri::Window) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to all listeners registed on `window` in Rust + /// window.trigger("download-progress", Some(i.to_string())); + /// } + /// } + /// ``` + pub fn trigger(&self, event: &str, data: Option) { + let label = self.window.label.clone(); + self.manager.trigger(event, Some(label), data) + } +} + +/// The [`WindowEffectsConfig`] object builder +#[derive(Default)] +pub struct EffectsBuilder(WindowEffectsConfig); +impl EffectsBuilder { + /// Create a new [`WindowEffectsConfig`] builder + pub fn new() -> Self { + Self(WindowEffectsConfig::default()) + } + + /// Adds effect to the [`WindowEffectsConfig`] `effects` field + pub fn effect(mut self, effect: Effect) -> Self { + self.0.effects.push(effect); + self + } + + /// Adds effects to the [`WindowEffectsConfig`] `effects` field + pub fn effects>(mut self, effects: I) -> Self { + self.0.effects.extend(effects); + self + } + + /// Clears the [`WindowEffectsConfig`] `effects` field + pub fn clear_effects(mut self) -> Self { + self.0.effects.clear(); + self + } + + /// Sets `state` field for the [`WindowEffectsConfig`] **macOS Only** + pub fn state(mut self, state: EffectState) -> Self { + self.0.state = Some(state); + self + } + /// Sets `radius` field fo the [`WindowEffectsConfig`] **macOS Only** + pub fn radius(mut self, radius: f64) -> Self { + self.0.radius = Some(radius); + self + } + /// Sets `color` field fo the [`WindowEffectsConfig`] **Windows Only** + pub fn color(mut self, color: Color) -> Self { + self.0.color = Some(color); + self + } + + /// Builds a [`WindowEffectsConfig`] + pub fn build(self) -> WindowEffectsConfig { + self.0 + } +} + +impl From for EffectsBuilder { + fn from(value: WindowEffectsConfig) -> Self { + Self(value) + } +} + +pub(crate) const IPC_SCOPE_DOES_NOT_ALLOW: &str = "Not allowed by the scope"; + +pub(crate) fn ipc_scope_not_found_error_message(label: &str, url: &str) -> String { + format!("Scope not defined for window `{label}` and URL `{url}`. See https://tauri.app/v1/api/config/#securityconfig.dangerousremotedomainipcaccess and https://docs.rs/tauri/1/tauri/scope/struct.IpcScope.html#method.configure_remote_access") +} + +pub(crate) fn ipc_scope_window_error_message(label: &str) -> String { + format!("Scope not defined for window `{}`. See https://tauri.app/v1/api/config/#securityconfig.dangerousremotedomainipcaccess and https://docs.rs/tauri/1/tauri/scope/struct.IpcScope.html#method.configure_remote_access", label) +} + +pub(crate) fn ipc_scope_domain_error_message(url: &str) -> String { + format!("Scope not defined for URL `{url}`. See https://tauri.app/v1/api/config/#securityconfig.dangerousremotedomainipcaccess and https://docs.rs/tauri/1/tauri/scope/struct.IpcScope.html#method.configure_remote_access") } #[cfg(test)] diff --git a/core/tauri/src/window/menu.rs b/core/tauri/src/window/menu.rs index b9b346aaac1a..6888822843a1 100644 --- a/core/tauri/src/window/menu.rs +++ b/core/tauri/src/window/menu.rs @@ -1,10 +1,13 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::runtime::{ - menu::{MenuHash, MenuId, MenuIdRef, MenuUpdate}, - Dispatch, Runtime, +use crate::{ + runtime::{ + menu::{MenuHash, MenuId, MenuIdRef, MenuUpdate}, + Dispatch, + }, + Runtime, }; use tauri_macros::default_runtime; @@ -64,7 +67,9 @@ impl Clone for MenuItemHandle { impl MenuHandle { /// Gets a handle to the menu item that has the specified `id`. pub fn get_item(&self, id: MenuIdRef<'_>) -> MenuItemHandle { - for (raw, item_id) in self.ids.lock().unwrap().iter() { + let ids = self.ids.lock().unwrap(); + let iter = ids.iter(); + for (raw, item_id) in iter { if item_id == id { return MenuItemHandle { id: *raw, @@ -75,6 +80,20 @@ impl MenuHandle { panic!("item id not found") } + /// Attempts to get a handle to the menu item that has the specified `id`, return an error if `id` is not found. + pub fn try_get_item(&self, id: MenuIdRef<'_>) -> Option> { + self + .ids + .lock() + .unwrap() + .iter() + .find(|i| i.1 == id) + .map(|i| MenuItemHandle { + id: *i.0, + dispatcher: self.dispatcher.clone(), + }) + } + /// Shows the menu. pub fn show(&self) -> crate::Result<()> { self.dispatcher.show_menu().map_err(Into::into) diff --git a/core/tauri/test/api/test_binary b/core/tauri/test/api/test_binary index d0e645887a58..35d42e808f0a 100644 Binary files a/core/tauri/test/api/test_binary and b/core/tauri/test/api/test_binary differ diff --git a/core/tauri/test/fixture/src-tauri/tauri.conf.json b/core/tauri/test/fixture/src-tauri/tauri.conf.json index f5ecf03b2dc1..147743e3c13c 100644 --- a/core/tauri/test/fixture/src-tauri/tauri.conf.json +++ b/core/tauri/test/fixture/src-tauri/tauri.conf.json @@ -1,4 +1,5 @@ { + "$schema": "../../../../../core/tauri-config-schema/schema.json", "build": { "distDir": "../dist", "devPath": "http://localhost:4000" @@ -8,9 +9,6 @@ "identifier": "studio.tauri.example", "active": true }, - "allowlist": { - "all": true - }, "windows": [ { "title": "Tauri App" @@ -18,9 +16,6 @@ ], "security": { "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'" - }, - "updater": { - "active": false } } } diff --git a/core/tauri/tests/restart.rs b/core/tauri/tests/restart.rs deleted file mode 100644 index 464659444b6e..000000000000 --- a/core/tauri/tests/restart.rs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::io; -use std::path::{Path, PathBuf}; -use std::process::Command; - -/// Helper for generic catch-all errors. -type Result = std::result::Result<(), Box>; - -/// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699- -#[cfg(windows)] -const ERROR_PRIVILEGE_NOT_HELD: i32 = 1314; - -/// Represents a successfully created symlink. -enum Symlink { - /// Path to the created symlink - Created(PathBuf), - - /// A symlink that failed due to missing permissions (Windows). - #[allow(dead_code)] - Privilege, -} - -/// Compile the nested restart binary so that we have access to it during the test. -/// -/// This is compiled inside of this test so that we always have a literal binary file we can use -/// for some filesystem related tests. Because of how integration tests work, the current working -/// directory should be the same as the manifest of the crate (not workspace) this integration test -/// is a part of. -fn compile_restart_test_binary() -> io::Result { - let project = PathBuf::from("tests").join("restart"); - - let mut cargo = Command::new("cargo"); - cargo.arg("build"); - cargo.arg("--manifest-path"); - cargo.arg(project.join("Cargo.toml")); - - // enable the dangerous macos flag on tauri if the test runner has the feature enabled - if cfg!(feature = "process-relaunch-dangerous-allow-symlink-macos") { - cargo.args([ - "--features", - "tauri/process-relaunch-dangerous-allow-symlink-macos", - ]); - } - - let status = cargo.status()?; - if !status.success() { - return Err(io::Error::new( - io::ErrorKind::Other, - "Unable to compile restart test cargo project inside restart integration test", - )); - } - - let profile = if cfg!(debug_assertions) { - "debug" - } else { - "release" - }; - - let bin = if cfg!(windows) { - "restart.exe" - } else { - "restart" - }; - - Ok(project.join("target").join(profile).join(bin)) -} - -/// Compile the test binary, run it, and compare it with expected output. -/// -/// Failing to create a symlink due to permissions issues is also a success -/// for the purpose of this runner. -fn symlink_runner(create_symlinks: impl Fn(&Path) -> io::Result) -> Result { - let compiled_binary = compile_restart_test_binary()?; - - // set up all the temporary file paths - let temp = tempfile::TempDir::new()?; - let bin = temp.path().canonicalize()?.join("restart.exe"); - - // copy the built restart test binary to our temporary directory - std::fs::copy(compiled_binary, &bin)?; - - if let Symlink::Created(link) = create_symlinks(&bin)? { - // run the command from the symlink, so that we can test if restart resolves it correctly - let mut cmd = Command::new(link); - - // add the restart parameter so that the invocation will call tauri::api::process::restart - cmd.arg("restart"); - - let output = cmd.output()?; - - // run `TempDir` destructors to prevent resource leaking if the assertion fails - drop(temp); - - if output.status.success() { - // gather the output into a string - let stdout = String::from_utf8(output.stdout)?; - - // we expect the output to be the bin path, twice - assert_eq!(stdout, format!("{bin}\n{bin}\n", bin = bin.display())); - } else if cfg!(all( - target_os = "macos", - not(feature = "process-relaunch-dangerous-allow-symlink-macos") - )) { - // we expect this to fail on macOS without the dangerous symlink flag set - let stderr = String::from_utf8(output.stderr)?; - - // make sure it's the error that we expect - assert!(stderr.contains( - "StartingBinary found current_exe() that contains a symlink on a non-allowed platform" - )); - } else { - // we didn't expect the program to fail in this configuration, just panic - panic!("restart integration test runner failed for unknown reason"); - } - } - - Ok(()) -} - -/// Cross-platform way to create a symlink -/// -/// Symlinks that failed to create due to permissions issues (like on Windows) -/// are also seen as successful for the purpose of this testing suite. -fn create_symlink(original: &Path, link: PathBuf) -> io::Result { - #[cfg(unix)] - return std::os::unix::fs::symlink(original, &link).map(|()| Symlink::Created(link)); - - #[cfg(windows)] - return match std::os::windows::fs::symlink_file(original, &link) { - Ok(()) => Ok(Symlink::Created(link)), - Err(e) => match e.raw_os_error() { - Some(ERROR_PRIVILEGE_NOT_HELD) => Ok(Symlink::Privilege), - _ => Err(e), - }, - }; -} - -#[test] -fn symlink() -> Result { - symlink_runner(|bin| { - let mut link = bin.to_owned(); - link.set_file_name("symlink"); - link.set_extension("exe"); - create_symlink(bin, link) - }) -} - -#[test] -fn nested_symlinks() -> Result { - symlink_runner(|bin| { - let mut link1 = bin.to_owned(); - link1.set_file_name("symlink1"); - link1.set_extension("exe"); - create_symlink(bin, link1.clone())?; - - let mut link2 = bin.to_owned(); - link2.set_file_name("symlink2"); - link2.set_extension("exe"); - create_symlink(&link1, link2) - }) -} diff --git a/core/tauri/tests/restart/.license_template b/core/tauri/tests/restart/.license_template deleted file mode 120000 index d1662c6c9b2d..000000000000 --- a/core/tauri/tests/restart/.license_template +++ /dev/null @@ -1 +0,0 @@ -../../../../.license_template \ No newline at end of file diff --git a/core/tauri/tests/restart/Cargo.lock b/core/tauri/tests/restart/Cargo.lock deleted file mode 100644 index 02943f2b78af..000000000000 --- a/core/tauri/tests/restart/Cargo.lock +++ /dev/null @@ -1,3216 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "anyhow" -version = "1.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "atk" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a83b21d2aa75e464db56225e1bda2dd5993311ba1095acaa8fa03d1ae67026ba" -dependencies = [ - "atk-sys", - "bitflags", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badcf670157c84bb8b1cf6b5f70b650fed78da2033c9eed84c4e49b11cbe83ea" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 3.2.0", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "blake3" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.1", - "rayon", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "memchr", -] - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "bzip2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "cairo-rs" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b5725979db0c586d98abad2193cdb612dd40ef95cd26bd99851bf93b3cb482" -dependencies = [ - "bitflags", - "cairo-sys-rs", - "glib", - "libc", - "thiserror", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b448b876970834fda82ba3aeaccadbd760206b75388fc5c1b02f1e343b697570" -dependencies = [ - "glib-sys", - "libc", - "system-deps 3.2.0", -] - -[[package]] -name = "cc" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cfb" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca453e8624711b2f0f4eb47076a318feda166252a827ee25d067b43de83dcba0" -dependencies = [ - "byteorder", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e" -dependencies = [ - "smallvec", -] - -[[package]] -name = "cfg-expr" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" -dependencies = [ - "smallvec", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "cocoa" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" -dependencies = [ - "bitflags", - "block", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "core-foundation" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" -dependencies = [ - "bitflags", - "core-foundation", - "foreign-types", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" -dependencies = [ - "cfg-if", - "lazy_static", -] - -[[package]] -name = "crypto-common" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" -dependencies = [ - "generic-array", -] - -[[package]] -name = "cssparser" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", - "smallvec", - "syn", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "ctor" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - -[[package]] -name = "darling" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -dependencies = [ - "darling_core 0.10.2", - "darling_macro 0.10.2", -] - -[[package]] -name = "darling" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" -dependencies = [ - "darling_core 0.13.1", - "darling_macro 0.13.1", -] - -[[package]] -name = "darling_core" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.9.3", - "syn", -] - -[[package]] -name = "darling_core" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" -dependencies = [ - "darling_core 0.10.2", - "quote", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" -dependencies = [ - "darling_core 0.13.1", - "quote", - "syn", -] - -[[package]] -name = "data-url" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" -dependencies = [ - "matches", -] - -[[package]] -name = "deflate" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" -dependencies = [ - "block-buffer 0.10.0", - "crypto-common", - "generic-array", - "subtle", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - -[[package]] -name = "dtoa-short" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" -dependencies = [ - "dtoa", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - -[[package]] -name = "field-offset" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" -dependencies = [ - "memoffset", - "rustc_version 0.3.3", -] - -[[package]] -name = "filetime" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "winapi", -] - -[[package]] -name = "flate2" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" -dependencies = [ - "cfg-if", - "crc32fast", - "libc", - "miniz_oxide 0.4.4", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" - -[[package]] -name = "futures-executor" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" - -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-macro" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" - -[[package]] -name = "futures-task" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" - -[[package]] -name = "futures-util" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d749dcfc00d8de0d7c3a289e04a04293eb5ba3d8a4e64d64911d481fa9933b" -dependencies = [ - "bitflags", - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534192cb8f01daeb8fab2c8d4baa8f9aae5b7a39130525779f5c2608e235b10f" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f097c0704201fbc8f69c1762dc58c6947c8bb188b8ed0bc7e65259f1894fe590" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 3.2.0", -] - -[[package]] -name = "gdk-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e091b3d3d6696949ac3b3fb3c62090e5bfd7bd6850bef5c3c5ea701de1b1f1e" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps 3.2.0", -] - -[[package]] -name = "gdkx11-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cefbc8ac7be19c9b51f54fbd7cef48b70495a4cb23a812e2137e75b484b29d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps 3.2.0", - "x11", -] - -[[package]] -name = "generator" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "winapi", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "gio" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711c3632b3ebd095578a9c091418d10fed492da9443f58ebc8f45efbeb215cb0" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-io", - "gio-sys", - "glib", - "libc", - "once_cell", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a41df66e57fcc287c4bcf74fc26b884f31901ea9792ec75607289b456f48fa" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 3.2.0", - "winapi", -] - -[[package]] -name = "glib" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c515f1e62bf151ef6635f528d05b02c11506de986e43b34a5c920ef0b3796a4" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "once_cell", - "smallvec", -] - -[[package]] -name = "glib-macros" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518" -dependencies = [ - "anyhow", - "heck", - "proc-macro-crate 1.1.0", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1d60554a212445e2a858e42a0e48cece1bd57b311a19a9468f70376cf554ae" -dependencies = [ - "libc", - "system-deps 3.2.0", -] - -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "globset" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "gobject-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa92cae29759dae34ab5921d73fff5ad54b3d794ab842c117e36cafc7994c3f5" -dependencies = [ - "glib-sys", - "libc", - "system-deps 3.2.0", -] - -[[package]] -name = "gtk" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb51122dd3317e9327ec1e4faa151d1fa0d95664cd8fb8dcfacf4d4d29ac70c" -dependencies = [ - "atk", - "bitflags", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "once_cell", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c14c8d3da0545785a7c5a120345b3abb534010fb8ae0f2ef3f47c027fba303e" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps 3.2.0", -] - -[[package]] -name = "gtk3-macros" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21de1da96dc117443fb03c2e270b2d34b7de98d0a79a19bbb689476173745b79" -dependencies = [ - "anyhow", - "heck", - "proc-macro-crate 1.1.0", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "html5ever" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" -dependencies = [ - "log", - "mac", - "markup5ever", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "http" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" -dependencies = [ - "bytes", - "fnv", - "itoa 1.0.1", -] - -[[package]] -name = "http-range" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee9694f83d9b7c09682fdb32213682939507884e5bcf227be9aff5d644b90dc" - -[[package]] -name = "ico" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804" -dependencies = [ - "byteorder", - "png 0.11.0", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "ignore" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" -dependencies = [ - "crossbeam-utils", - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local", - "walkdir", - "winapi-util", -] - -[[package]] -name = "infer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92b41dab759f9e8427c03f519c344a14655490b8db548dac1e57a75b3258391" -dependencies = [ - "cfb", -] - -[[package]] -name = "inflate" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4" -dependencies = [ - "adler32", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" - -[[package]] -name = "javascriptcore-rs" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e207780c1d1cd3c36056695e44010a19dd481574a2106cd2540edda4128a9794" -dependencies = [ - "bitflags", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2adf2de824b178d76c6017da59f4e7e95de49a766b584c59d47821a6c3dce9e2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - -[[package]] -name = "kuchiki" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" -dependencies = [ - "cssparser", - "html5ever", - "matches", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" - -[[package]] -name = "lock_api" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "loom" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "markup5ever" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" -dependencies = [ - "log", - "phf 0.8.0", - "phf_codegen", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "ndk" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c" -dependencies = [ - "bitflags", - "jni-sys", - "ndk-sys", - "num_enum", - "thiserror", -] - -[[package]] -name = "ndk-glue" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e9e94628f24e7a3cb5b96a2dc5683acd9230bf11991c2a1677b87695138420" -dependencies = [ - "lazy_static", - "libc", - "log", - "ndk", - "ndk-macro", - "ndk-sys", -] - -[[package]] -name = "ndk-macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" -dependencies = [ - "darling 0.10.2", - "proc-macro-crate 0.1.5", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ndk-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" - -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" -dependencies = [ - "proc-macro-crate 1.1.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "once_cell" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "pango" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "546fd59801e5ca735af82839007edd226fe7d3bb06433ec48072be4439c28581" -dependencies = [ - "bitflags", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2367099ca5e761546ba1d501955079f097caa186bb53ce0f718dca99ac1942fe" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 3.2.0", -] - -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pest" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.4", -] - -[[package]] -name = "phf_macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" - -[[package]] -name = "png" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925" -dependencies = [ - "bitflags", - "deflate 0.7.20", - "inflate", - "num-iter", -] - -[[package]] -name = "png" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate 0.8.6", - "miniz_oxide 0.3.7", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-crate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" -dependencies = [ - "thiserror", - "toml", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro2" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom 0.2.4", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28f55143d0548dad60bb4fbdc835a3d7ac6acc3324506450c5fdd6e42903a76" -dependencies = [ - "libc", - "raw-window-handle 0.4.2", -] - -[[package]] -name = "raw-window-handle" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7" -dependencies = [ - "cty", -] - -[[package]] -name = "rayon" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "lazy_static", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" -dependencies = [ - "getrandom 0.2.4", - "redox_syscall", -] - -[[package]] -name = "regex" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "restart" -version = "0.1.0" -dependencies = [ - "tauri", -] - -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.4", -] - -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "selectors" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" -dependencies = [ - "bitflags", - "cssparser", - "derive_more", - "fxhash", - "log", - "matches", - "phf 0.8.0", - "phf_codegen", - "precomputed-hash", - "servo_arc", - "smallvec", - "thin-slice", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - -[[package]] -name = "serde" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" -dependencies = [ - "itoa 1.0.1", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_with" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6056b4cb69b6e43e3a0f055def223380baecc99da683884f205bf347f7c4b3" -dependencies = [ - "rustversion", - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e" -dependencies = [ - "darling 0.13.1", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.0" -source = "git+https://github.com/chippers/serialize-to-javascript#38d5026f371bfba4f5197ed143a6667a09375fbd" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.0" -source = "git+https://github.com/chippers/serialize-to-javascript#38d5026f371bfba4f5197ed143a6667a09375fbd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "servo_arc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "siphasher" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" - -[[package]] -name = "slab" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" - -[[package]] -name = "smallvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" - -[[package]] -name = "soup2-sys" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f056675eda9a7417163e5f742bb119e8e1d385edd2ada8f7031a7230a3ec10a" -dependencies = [ - "bitflags", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "state" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" -dependencies = [ - "loom", -] - -[[package]] -name = "string_cache" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" -dependencies = [ - "lazy_static", - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.8.0", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strum" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" - -[[package]] -name = "strum_macros" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "sys-info" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "system-deps" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6" -dependencies = [ - "anyhow", - "cfg-expr 0.8.1", - "heck", - "itertools", - "pkg-config", - "strum", - "strum_macros", - "thiserror", - "toml", - "version-compare", -] - -[[package]] -name = "system-deps" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" -dependencies = [ - "cfg-expr 0.9.1", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.5.2" -source = "git+https://github.com/tauri-apps/tao?branch=next#7811284e464f983485977b06010b6d1db134ca4e" -dependencies = [ - "bitflags", - "cairo-rs", - "cc", - "cocoa", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "gdk", - "gdk-pixbuf", - "gdk-sys", - "gdkx11-sys", - "gio", - "glib", - "glib-sys", - "gtk", - "instant", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-glue", - "ndk-sys", - "objc", - "parking_lot", - "raw-window-handle 0.4.2", - "scopeguard", - "serde", - "tao-core-video-sys", - "unicode-segmentation", - "windows", - "windows_macros", - "x11-dl", -] - -[[package]] -name = "tao-core-video-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "objc", -] - -[[package]] -name = "tar" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "tauri" -version = "1.0.0-beta.8" -dependencies = [ - "bincode", - "cfg_aliases", - "data-url", - "dirs-next", - "either", - "embed_plist", - "flate2", - "futures", - "futures-lite", - "glib", - "glob", - "gtk", - "http", - "ignore", - "once_cell", - "percent-encoding", - "rand 0.8.4", - "raw-window-handle 0.3.4", - "regex", - "semver 1.0.4", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "state", - "tar", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "tempfile", - "thiserror", - "tokio", - "url", - "uuid", - "zip", -] - -[[package]] -name = "tauri-codegen" -version = "1.0.0-beta.4" -dependencies = [ - "base64", - "blake3", - "proc-macro2", - "quote", - "regex", - "serde", - "serde_json", - "sha2", - "tauri-utils", - "thiserror", - "uuid", - "walkdir", - "zstd", -] - -[[package]] -name = "tauri-macros" -version = "1.0.0-beta.5" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", - "tauri-codegen", -] - -[[package]] -name = "tauri-runtime" -version = "0.2.1" -dependencies = [ - "gtk", - "http", - "http-range", - "infer", - "serde", - "serde_json", - "tauri-utils", - "thiserror", - "uuid", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "0.2.1" -dependencies = [ - "gtk", - "ico", - "infer", - "png 0.16.8", - "tauri-runtime", - "tauri-utils", - "uuid", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "1.0.0-beta.3" -dependencies = [ - "base64", - "ctor", - "heck", - "html5ever", - "kuchiki", - "phf 0.10.1", - "proc-macro2", - "quote", - "serde", - "serde_json", - "serde_with", - "serialize-to-javascript", - "sha2", - "thiserror", - "url", - "zstd", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "tendril" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - -[[package]] -name = "thiserror" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" -dependencies = [ - "bytes", - "memchr", - "num_cpus", - "pin-project-lite", -] - -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", -] - -[[package]] -name = "tracing" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tracing-log" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22" -dependencies = [ - "ansi_term", - "lazy_static", - "matchers", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "ucd-trie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" - -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.4", -] - -[[package]] -name = "version-compare" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "webkit2gtk" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07654baccd8874fc7c99cc33c27052fb02804276102dff0f78f981669316e0e9" -dependencies = [ - "bitflags", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854a0cbf3570541bf13df70aac23826da7cd88f27a722b7b2043f32637373113" -dependencies = [ - "atk-sys", - "bitflags", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pango-sys", - "pkg-config", - "soup2-sys", - "system-deps 5.0.0", -] - -[[package]] -name = "webview2-com" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381febeeb86214fd262941a2b26322c33c38bf8b565b0ddfd4a7a8d4003053a9" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows_macros", -] - -[[package]] -name = "webview2-com-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515c6c82fcee93f6edaacc72c8e233dbe4ff3ca569dce1901dfc36c404a3e99" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "webview2-com-sys" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e3542bb16fe61e951f87c9146571344f1e773327498efa65704a4cff472662" -dependencies = [ - "regex", - "serde", - "serde_json", - "thiserror", - "windows", - "windows-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b749ebd2304aa012c5992d11a25d07b406bdbe5f79d371cb7a918ce501a19eb0" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows-bindgen" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944c545fcae9dd66488308f8b69aa3ba34f53714416ecfcdcbbfa4b6821e27c6" -dependencies = [ - "windows_quote", - "windows_reader", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca" - -[[package]] -name = "windows_gen" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30dff4d91d22520628bb94b66f2bb313cb16a09a515a32320a84a1b449bc94c0" -dependencies = [ - "windows_quote", - "windows_reader", -] - -[[package]] -name = "windows_i686_gnu" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8" - -[[package]] -name = "windows_i686_msvc" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6" - -[[package]] -name = "windows_macros" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ae44ab917e9005fe710d99d52d227ca0164b10a09be90649142cc3fab825d3" -dependencies = [ - "syn", - "windows_gen", - "windows_quote", - "windows_reader", -] - -[[package]] -name = "windows_quote" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71f02c51a77e6248c1206aaa920802c32d50a05205e229b118d7f3afd3036667" - -[[package]] -name = "windows_reader" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44e6df0da993cda589c5ac852272fbb2a0ead67a031a017dd3eac11528a2d72" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1" - -[[package]] -name = "wry" -version = "0.12.2" -source = "git+ssh://git@github.com/tauri-sec/wry?branch=next#3a0076eb06aa249d9ef32dac993e3683a769165e" -dependencies = [ - "cocoa", - "core-graphics", - "gdk", - "gio", - "glib", - "gtk", - "http", - "libc", - "log", - "objc", - "objc_id", - "once_cell", - "serde", - "serde_json", - "sys-info", - "tao", - "thiserror", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows_macros", -] - -[[package]] -name = "x11" -version = "2.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" -dependencies = [ - "lazy_static", - "libc", - "pkg-config", -] - -[[package]] -name = "xattr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" -dependencies = [ - "libc", -] - -[[package]] -name = "zip" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" -dependencies = [ - "byteorder", - "bzip2", - "crc32fast", - "flate2", - "thiserror", - "time", -] - -[[package]] -name = "zstd" -version = "0.9.2+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "4.1.3+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "1.6.2+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" -dependencies = [ - "cc", - "libc", -] diff --git a/core/tauri/tests/restart/Cargo.toml b/core/tauri/tests/restart/Cargo.toml deleted file mode 100644 index 2ab1786d69f4..000000000000 --- a/core/tauri/tests/restart/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[workspace] -[package] -name = "restart" -version = "0.1.0" -authors = [ "Tauri Programme within The Commons Conservancy" ] -license = "Apache-2.0 OR MIT" -edition = "2021" - -[dependencies.tauri] -path = "../.." diff --git a/core/tauri/tests/restart/LICENSE.spdx b/core/tauri/tests/restart/LICENSE.spdx deleted file mode 120000 index cc563bd76897..000000000000 --- a/core/tauri/tests/restart/LICENSE.spdx +++ /dev/null @@ -1 +0,0 @@ -../../../../LICENSE.spdx \ No newline at end of file diff --git a/core/tauri/tests/restart/LICENSE_APACHE-2.0 b/core/tauri/tests/restart/LICENSE_APACHE-2.0 deleted file mode 120000 index 1f7a8da6ec1b..000000000000 --- a/core/tauri/tests/restart/LICENSE_APACHE-2.0 +++ /dev/null @@ -1 +0,0 @@ -../../../../LICENSE_APACHE-2.0 \ No newline at end of file diff --git a/core/tauri/tests/restart/LICENSE_MIT b/core/tauri/tests/restart/LICENSE_MIT deleted file mode 120000 index dd01abfbba9d..000000000000 --- a/core/tauri/tests/restart/LICENSE_MIT +++ /dev/null @@ -1 +0,0 @@ -../../../../LICENSE_MIT \ No newline at end of file diff --git a/core/tauri/tests/restart/src/main.rs b/core/tauri/tests/restart/src/main.rs deleted file mode 100644 index 79fe1bf2f4d0..000000000000 --- a/core/tauri/tests/restart/src/main.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -fn main() { - let mut argv = std::env::args(); - let argc = argv.len(); - if argc == 0 || argc > 2 { - panic!("restart test binary expect either no arguments or `restart`.") - } - - println!( - "{}", - tauri::api::process::current_binary(&Default::default()) - .expect("tauri::api::process::current_binary could not resolve") - .display() - ); - - match argv.nth(1).as_deref() { - Some("restart") => tauri::api::process::restart(&Default::default()), - Some(invalid) => panic!("only argument `restart` is allowed, {} is invalid", invalid), - None => {} - }; -} diff --git a/core/tests/restart/Cargo.toml b/core/tests/restart/Cargo.toml new file mode 100644 index 000000000000..1d7036e98661 --- /dev/null +++ b/core/tests/restart/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "restart" +version = "0.1.0" +authors = [ "Tauri Programme within The Commons Conservancy" ] +license = "Apache-2.0 OR MIT" +edition = "2021" + +[dependencies.tauri] +path = "../../tauri" + +[dev-dependencies] +tempfile = "3" diff --git a/core/tests/restart/LICENSE.spdx b/core/tests/restart/LICENSE.spdx new file mode 120000 index 000000000000..e3b1b803726f --- /dev/null +++ b/core/tests/restart/LICENSE.spdx @@ -0,0 +1 @@ +../../../LICENSE.spdx \ No newline at end of file diff --git a/examples/isolation/src-tauri/LICENSE_APACHE-2.0 b/core/tests/restart/LICENSE_APACHE-2.0 similarity index 100% rename from examples/isolation/src-tauri/LICENSE_APACHE-2.0 rename to core/tests/restart/LICENSE_APACHE-2.0 diff --git a/examples/isolation/src-tauri/LICENSE_MIT b/core/tests/restart/LICENSE_MIT similarity index 100% rename from examples/isolation/src-tauri/LICENSE_MIT rename to core/tests/restart/LICENSE_MIT diff --git a/core/tests/restart/build.rs b/core/tests/restart/build.rs new file mode 100644 index 000000000000..e6c5244c75c7 --- /dev/null +++ b/core/tests/restart/build.rs @@ -0,0 +1,5 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +fn main() {} diff --git a/core/tests/restart/src/main.rs b/core/tests/restart/src/main.rs new file mode 100644 index 000000000000..6984ab20c5d3 --- /dev/null +++ b/core/tests/restart/src/main.rs @@ -0,0 +1,30 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::Env; + +fn main() { + let mut argv = std::env::args(); + let argc = argv.len(); + if argc == 0 || argc > 2 { + panic!("restart test binary expect either no arguments or `restart`.") + } + + println!( + "{}", + tauri::process::current_binary(&Default::default()) + .expect("tauri::process::current_binary could not resolve") + .display() + ); + + match argv.nth(1).as_deref() { + Some("restart") => { + let mut env = Env::default(); + env.args.clear(); + tauri::process::restart(&env) + } + Some(invalid) => panic!("only argument `restart` is allowed, {invalid} is invalid"), + None => {} + }; +} diff --git a/core/tests/restart/tests/restart.rs b/core/tests/restart/tests/restart.rs new file mode 100644 index 000000000000..a174f537a977 --- /dev/null +++ b/core/tests/restart/tests/restart.rs @@ -0,0 +1,125 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::io; +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// Helper for generic catch-all errors. +type Result = std::result::Result<(), Box>; + +/// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699- +#[cfg(windows)] +const ERROR_PRIVILEGE_NOT_HELD: i32 = 1314; + +/// Represents a successfully created symlink. +enum Symlink { + /// Path to the created symlink + Created(PathBuf), + + /// A symlink that failed due to missing permissions (Windows). + #[allow(dead_code)] + Privilege, +} + +/// Compile the test binary, run it, and compare it with expected output. +/// +/// Failing to create a symlink due to permissions issues is also a success +/// for the purpose of this runner. +fn symlink_runner(create_symlinks: impl Fn(&Path) -> io::Result) -> Result { + let mut compiled_binary = PathBuf::from(env!("OUT_DIR")).join("../../../restart"); + if cfg!(windows) { + compiled_binary.set_extension("exe"); + } + println!("{compiled_binary:?}"); + + // set up all the temporary file paths + let temp = tempfile::TempDir::new()?; + let bin = temp.path().canonicalize()?.join("restart.exe"); + + // copy the built restart test binary to our temporary directory + std::fs::copy(compiled_binary, &bin)?; + + if let Symlink::Created(link) = create_symlinks(&bin)? { + // run the command from the symlink, so that we can test if restart resolves it correctly + let mut cmd = Command::new(link); + + // add the restart parameter so that the invocation will call tauri::process::restart + cmd.arg("restart"); + + let output = cmd.output()?; + + // run `TempDir` destructors to prevent resource leaking if the assertion fails + drop(temp); + + if output.status.success() { + // gather the output into a string + let stdout = String::from_utf8(output.stdout)?; + + // we expect the output to be the bin path, twice + assert_eq!(stdout, format!("{bin}\n{bin}\n", bin = bin.display())); + } else if cfg!(all( + target_os = "macos", + not(feature = "process-relaunch-dangerous-allow-symlink-macos") + )) { + // we expect this to fail on macOS without the dangerous symlink flag set + let stderr = String::from_utf8(output.stderr)?; + + // make sure it's the error that we expect + assert!(stderr.contains( + "StartingBinary found current_exe() that contains a symlink on a non-allowed platform" + )); + } else { + // we didn't expect the program to fail in this configuration, just panic + panic!("restart integration test runner failed for unknown reason"); + } + } + + Ok(()) +} + +/// Cross-platform way to create a symlink +/// +/// Symlinks that failed to create due to permissions issues (like on Windows) +/// are also seen as successful for the purpose of this testing suite. +fn create_symlink(original: &Path, link: PathBuf) -> io::Result { + #[cfg(unix)] + return std::os::unix::fs::symlink(original, &link).map(|()| Symlink::Created(link)); + + #[cfg(windows)] + return match std::os::windows::fs::symlink_file(original, &link) { + Ok(()) => Ok(Symlink::Created(link)), + Err(e) => match e.raw_os_error() { + Some(ERROR_PRIVILEGE_NOT_HELD) => Ok(Symlink::Privilege), + _ => Err(e), + }, + }; +} + +/// Only use 1 test to prevent cargo from waiting on itself. +/// +/// While not ideal, this is fine because they use the same solution for both cases. +#[test] +fn restart_symlinks() -> Result { + // single symlink + symlink_runner(|bin| { + let mut link = bin.to_owned(); + link.set_file_name("symlink"); + link.set_extension("exe"); + create_symlink(bin, link) + })?; + + // nested symlinks + symlink_runner(|bin| { + let mut link1 = bin.to_owned(); + link1.set_file_name("symlink1"); + link1.set_extension("exe"); + create_symlink(bin, link1.clone())?; + + let mut link2 = bin.to_owned(); + link2.set_file_name("symlink2"); + link2.set_extension("exe"); + create_symlink(&link1, link2) + }) +} diff --git a/dependabot.yml b/dependabot.yml new file mode 100644 index 000000000000..fb2177220ae0 --- /dev/null +++ b/dependabot.yml @@ -0,0 +1,107 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +version: 2 +updates: + # NPM packages + - package-ecosystem: 'npm' + directory: '/tooling/api' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'npm' + directory: '/tooling/cli/node' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + + # Tooling + - package-ecosystem: 'cargo' + directory: '/tooling/cli' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'cargo' + directory: '/tooling/bundler' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'cargo' + directory: '/tooling/webdriver' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + + # Core + - package-ecosystem: 'cargo' + directory: '/core/tauri' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'cargo' + directory: '/core/tauri-build' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'cargo' + directory: '/core/tauri-codegen' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'cargo' + directory: '/core/tauri-macros' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'cargo' + directory: '/core/tauri-runtime' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'cargo' + directory: '/core/tauri-runtime-wry' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 + - package-ecosystem: 'cargo' + directory: '/core/tauri-utils' + schedule: + internal: 'daily' + labels: + - 'type: chore' + # disable version updates + open-pull-requests-limit: 0 diff --git a/examples/.icons/128x128.png b/examples/.icons/128x128.png index 6be5e50e9b9a..77e7d2338e9d 100644 Binary files a/examples/.icons/128x128.png and b/examples/.icons/128x128.png differ diff --git a/examples/.icons/128x128@2x.png b/examples/.icons/128x128@2x.png index e81becee571e..0f7976f1a004 100644 Binary files a/examples/.icons/128x128@2x.png and b/examples/.icons/128x128@2x.png differ diff --git a/examples/.icons/32x32.png b/examples/.icons/32x32.png index a437dd51741e..98fda06fcb02 100644 Binary files a/examples/.icons/32x32.png and b/examples/.icons/32x32.png differ diff --git a/examples/.icons/Square107x107Logo.png b/examples/.icons/Square107x107Logo.png index 0ca4f2719883..f35d84ff1334 100644 Binary files a/examples/.icons/Square107x107Logo.png and b/examples/.icons/Square107x107Logo.png differ diff --git a/examples/.icons/Square142x142Logo.png b/examples/.icons/Square142x142Logo.png index b81f820394d1..1823bb2696be 100644 Binary files a/examples/.icons/Square142x142Logo.png and b/examples/.icons/Square142x142Logo.png differ diff --git a/examples/.icons/Square150x150Logo.png b/examples/.icons/Square150x150Logo.png index 624c7bfba049..dc2b22cea563 100644 Binary files a/examples/.icons/Square150x150Logo.png and b/examples/.icons/Square150x150Logo.png differ diff --git a/examples/.icons/Square284x284Logo.png b/examples/.icons/Square284x284Logo.png index c021d2ba7661..0ed3984c5fb4 100644 Binary files a/examples/.icons/Square284x284Logo.png and b/examples/.icons/Square284x284Logo.png differ diff --git a/examples/.icons/Square30x30Logo.png b/examples/.icons/Square30x30Logo.png index 621970023096..60bf0eadf75c 100644 Binary files a/examples/.icons/Square30x30Logo.png and b/examples/.icons/Square30x30Logo.png differ diff --git a/examples/.icons/Square310x310Logo.png b/examples/.icons/Square310x310Logo.png index f9bc04839491..c8ca0ad132e0 100644 Binary files a/examples/.icons/Square310x310Logo.png and b/examples/.icons/Square310x310Logo.png differ diff --git a/examples/.icons/Square44x44Logo.png b/examples/.icons/Square44x44Logo.png index d5fbfb2ab4e5..8756459b63d6 100644 Binary files a/examples/.icons/Square44x44Logo.png and b/examples/.icons/Square44x44Logo.png differ diff --git a/examples/.icons/Square71x71Logo.png b/examples/.icons/Square71x71Logo.png index 63440d798493..2c8023cc8244 100644 Binary files a/examples/.icons/Square71x71Logo.png and b/examples/.icons/Square71x71Logo.png differ diff --git a/examples/.icons/Square89x89Logo.png b/examples/.icons/Square89x89Logo.png index f3f705af2f2e..2c5e6034ff53 100644 Binary files a/examples/.icons/Square89x89Logo.png and b/examples/.icons/Square89x89Logo.png differ diff --git a/examples/.icons/StoreLogo.png b/examples/.icons/StoreLogo.png index 455638826192..17d142c0a432 100644 Binary files a/examples/.icons/StoreLogo.png and b/examples/.icons/StoreLogo.png differ diff --git a/examples/.icons/icon.icns b/examples/.icons/icon.icns index 8254645a797e..5594104c8149 100644 Binary files a/examples/.icons/icon.icns and b/examples/.icons/icon.icns differ diff --git a/examples/.icons/icon.ico b/examples/.icons/icon.ico index b3636e4b22ba..06c23c82fea6 100644 Binary files a/examples/.icons/icon.ico and b/examples/.icons/icon.ico differ diff --git a/examples/.icons/icon.png b/examples/.icons/icon.png index e1cd2619e0b5..d1756ce45d62 100644 Binary files a/examples/.icons/icon.png and b/examples/.icons/icon.png differ diff --git a/examples/api/.gitignore b/examples/api/.gitignore index 7c9b894420ed..452256283945 100644 --- a/examples/api/.gitignore +++ b/examples/api/.gitignore @@ -1,3 +1,5 @@ /node_modules/ /.vscode/ .DS_Store +gen/ +.cargo diff --git a/examples/api/.setup-cross.sh b/examples/api/.setup-cross.sh new file mode 100644 index 000000000000..22d5ee139bee --- /dev/null +++ b/examples/api/.setup-cross.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +export ICONS_VOLUME="$(realpath ../.icons)" +export DIST_VOLUME="$(realpath dist)" +export ISOLATION_VOLUME="$(realpath isolation-dist)" diff --git a/examples/api/.taurignore b/examples/api/.taurignore new file mode 100644 index 000000000000..848a0f2aa2db --- /dev/null +++ b/examples/api/.taurignore @@ -0,0 +1,3 @@ +src-tauri/locales/ +src-tauri/Cross.toml +src-tauri/.gitignore diff --git a/examples/api/dist/assets/index.css b/examples/api/dist/assets/index.css index bb235641fc94..1148ece6d18e 100644 --- a/examples/api/dist/assets/index.css +++ b/examples/api/dist/assets/index.css @@ -1 +1 @@ -#dialog-filter.svelte-1eg58yg{width:260px}#request-body.svelte-1xfmj7b{width:100%;margin-right:10px;font-size:12px}form.svelte-1tppwwz.svelte-1tppwwz{margin-top:24px}.flex-row.svelte-1tppwwz.svelte-1tppwwz{flex-direction:row}.grow.svelte-1tppwwz.svelte-1tppwwz{flex-grow:1}.window-controls.svelte-1tppwwz input.svelte-1tppwwz{width:50px}.window-property.svelte-1tppwwz.svelte-1tppwwz{margin-top:12px}.window-property.svelte-1tppwwz span.svelte-1tppwwz{font-size:.8rem}.env-vars.svelte-1g38c1n{width:300px}.container.svelte-1fam3xt{margin:1em}.view-container.svelte-1fam3xt{width:15em;margin-left:.5em}#response.svelte-1fam3xt{white-space:pre-line} +*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:var(--un-empty,/*!*/ /*!*/);--un-pan-y:var(--un-empty,/*!*/ /*!*/);--un-pinch-zoom:var(--un-empty,/*!*/ /*!*/);--un-scroll-snap-strictness:proximity;--un-ordinal:var(--un-empty,/*!*/ /*!*/);--un-slashed-zero:var(--un-empty,/*!*/ /*!*/);--un-numeric-figure:var(--un-empty,/*!*/ /*!*/);--un-numeric-spacing:var(--un-empty,/*!*/ /*!*/);--un-numeric-fraction:var(--un-empty,/*!*/ /*!*/);--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 #0000;--un-ring-shadow:0 0 #0000;--un-shadow-inset:var(--un-empty,/*!*/ /*!*/);--un-shadow:0 0 #0000;--un-ring-inset:var(--un-empty,/*!*/ /*!*/);--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur:var(--un-empty,/*!*/ /*!*/);--un-brightness:var(--un-empty,/*!*/ /*!*/);--un-contrast:var(--un-empty,/*!*/ /*!*/);--un-drop-shadow:var(--un-empty,/*!*/ /*!*/);--un-grayscale:var(--un-empty,/*!*/ /*!*/);--un-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-invert:var(--un-empty,/*!*/ /*!*/);--un-saturate:var(--un-empty,/*!*/ /*!*/);--un-sepia:var(--un-empty,/*!*/ /*!*/);--un-backdrop-blur:var(--un-empty,/*!*/ /*!*/);--un-backdrop-brightness:var(--un-empty,/*!*/ /*!*/);--un-backdrop-contrast:var(--un-empty,/*!*/ /*!*/);--un-backdrop-grayscale:var(--un-empty,/*!*/ /*!*/);--un-backdrop-hue-rotate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-invert:var(--un-empty,/*!*/ /*!*/);--un-backdrop-opacity:var(--un-empty,/*!*/ /*!*/);--un-backdrop-saturate:var(--un-empty,/*!*/ /*!*/);--un-backdrop-sepia:var(--un-empty,/*!*/ /*!*/);}@font-face { font-family: 'Fira Code'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/firacode/v21/uU9eCBsR6Z2vfE9aq3bL0fxyUs4tcw4W_D1sFVc.ttf) format('truetype');}@font-face { font-family: 'Fira Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/firamono/v14/N0bX2SlFPv1weGeLZDtQIQ.ttf) format('truetype');}@font-face { font-family: 'Fira Mono'; font-style: normal; font-weight: 700; font-display: swap; src: url(https://fonts.gstatic.com/s/firamono/v14/N0bS2SlFPv1weGeLZDtondv3mQ.ttf) format('truetype');}@font-face { font-family: 'Rubik'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/rubik/v26/iJWZBXyIfDnIV5PNhY1KTN7Z-Yh-B4i1UA.ttf) format('truetype');}.i-codicon-clear-all{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m10 12.6l.7.7l1.6-1.6l1.6 1.6l.8-.7L13 11l1.7-1.6l-.8-.8l-1.6 1.7l-1.6-1.7l-.7.8l1.6 1.6l-1.6 1.6zM1 4h14V3H1v1zm0 3h14V6H1v1zm8 2.5V9H1v1h8v-.5zM9 13v-1H1v1h8z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-close{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' fill-rule='evenodd' d='m8 8.707l3.646 3.647l.708-.707L8.707 8l3.647-3.646l-.707-.708L8 7.293L4.354 3.646l-.707.708L7.293 8l-3.646 3.646l.707.708L8 8.707z' clip-rule='evenodd'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-link-external{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M1.5 1H6v1H2v12h12v-4h1v4.5l-.5.5h-13l-.5-.5v-13l.5-.5z'/%3E%3Cpath d='M15 1.5V8h-1V2.707L7.243 9.465l-.707-.708L13.293 2H8V1h6.5l.5.5z'/%3E%3C/g%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-menu{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 5H0V4h16v1zm0 8H0v-1h16v1zm0-4.008H0V8h16v.992z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-codicon-radio-tower{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M2.998 5.58a5.55 5.55 0 0 1 1.62-3.88l-.71-.7a6.45 6.45 0 0 0 0 9.16l.71-.7a5.55 5.55 0 0 1-1.62-3.88zm1.06 0a4.42 4.42 0 0 0 1.32 3.17l.71-.71a3.27 3.27 0 0 1-.76-1.12a3.45 3.45 0 0 1 0-2.67a3.22 3.22 0 0 1 .76-1.13l-.71-.71a4.46 4.46 0 0 0-1.32 3.17zm7.65 3.21l-.71-.71c.33-.32.59-.704.76-1.13a3.449 3.449 0 0 0 0-2.67a3.22 3.22 0 0 0-.76-1.13l.71-.7a4.468 4.468 0 0 1 0 6.34zM13.068 1l-.71.71a5.43 5.43 0 0 1 0 7.74l.71.71a6.45 6.45 0 0 0 0-9.16zM9.993 5.43a1.5 1.5 0 0 1-.245.98a2 2 0 0 1-.27.23l3.44 7.73l-.92.4l-.77-1.73h-5.54l-.77 1.73l-.92-.4l3.44-7.73a1.52 1.52 0 0 1-.33-1.63a1.55 1.55 0 0 1 .56-.68a1.5 1.5 0 0 1 2.325 1.1zm-1.595-.34a.52.52 0 0 0-.25.14a.52.52 0 0 0-.11.22a.48.48 0 0 0 0 .29c.04.09.102.17.18.23a.54.54 0 0 0 .28.08a.51.51 0 0 0 .5-.5a.54.54 0 0 0-.08-.28a.58.58 0 0 0-.23-.18a.48.48 0 0 0-.29 0zm.23 2.05h-.27l-.87 1.94h2l-.86-1.94zm2.2 4.94l-.89-2h-2.88l-.89 2h4.66z' clip-rule='evenodd'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-broadcast{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M128 88a40 40 0 1 0 40 40a40 40 0 0 0-40-40Zm0 64a24 24 0 1 1 24-24a24.1 24.1 0 0 1-24 24Zm-59-48.9a64.5 64.5 0 0 0 0 49.8a65.4 65.4 0 0 0 13.7 20.4a7.9 7.9 0 0 1 0 11.3a8 8 0 0 1-5.6 2.3a8.3 8.3 0 0 1-5.7-2.3a80 80 0 0 1-17.1-25.5a79.9 79.9 0 0 1 0-62.2a80 80 0 0 1 17.1-25.5a8 8 0 0 1 11.3 0a7.9 7.9 0 0 1 0 11.3A65.4 65.4 0 0 0 69 103.1Zm132.7 56a80 80 0 0 1-17.1 25.5a8.3 8.3 0 0 1-5.7 2.3a8 8 0 0 1-5.6-2.3a7.9 7.9 0 0 1 0-11.3a65.4 65.4 0 0 0 13.7-20.4a64.5 64.5 0 0 0 0-49.8a65.4 65.4 0 0 0-13.7-20.4a7.9 7.9 0 0 1 0-11.3a8 8 0 0 1 11.3 0a80 80 0 0 1 17.1 25.5a79.9 79.9 0 0 1 0 62.2ZM54.5 201.5a8.1 8.1 0 0 1 0 11.4a8.3 8.3 0 0 1-5.7 2.3a8.5 8.5 0 0 1-5.7-2.3a121.8 121.8 0 0 1-25.7-38.2a120.7 120.7 0 0 1 0-93.4a121.8 121.8 0 0 1 25.7-38.2a8.1 8.1 0 0 1 11.4 11.4A103.5 103.5 0 0 0 24 128a103.5 103.5 0 0 0 30.5 73.5ZM248 128a120.2 120.2 0 0 1-9.4 46.7a121.8 121.8 0 0 1-25.7 38.2a8.5 8.5 0 0 1-5.7 2.3a8.3 8.3 0 0 1-5.7-2.3a8.1 8.1 0 0 1 0-11.4A103.5 103.5 0 0 0 232 128a103.5 103.5 0 0 0-30.5-73.5a8.1 8.1 0 1 1 11.4-11.4a121.8 121.8 0 0 1 25.7 38.2A120.2 120.2 0 0 1 248 128Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-hand-waving{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m220.2 104l-20-34.7a28.1 28.1 0 0 0-47.3-1.9l-17.3-30a28.1 28.1 0 0 0-38.3-10.3a29.4 29.4 0 0 0-9.9 9.6a27.9 27.9 0 0 0-11.5-6.2a27.2 27.2 0 0 0-21.2 2.8a27.9 27.9 0 0 0-10.3 38.2l3.4 5.8A28.5 28.5 0 0 0 36 81a28.1 28.1 0 0 0-10.2 38.2l42 72.8a88 88 0 1 0 152.4-88Zm-6.7 62.6a71.2 71.2 0 0 1-33.5 43.7A72.1 72.1 0 0 1 81.6 184l-42-72.8a12 12 0 0 1 20.8-12l22 38.1l.6.9v.2l.5.5l.2.2l.7.6h.1l.7.5h.3l.6.3h.2l.9.3h.1l.8.2h2.2l.9-.2h.3l.6-.2h.3l.9-.4a8.1 8.1 0 0 0 2.9-11l-22-38.1l-16-27.7a12 12 0 0 1-1.2-9.1a11.8 11.8 0 0 1 5.6-7.3a12 12 0 0 1 9.1-1.2a12.5 12.5 0 0 1 7.3 5.6l8 14h.1l26 45a7 7 0 0 0 1.5 1.9a8 8 0 0 0 12.3-9.9l-26-45a12 12 0 1 1 20.8-12l30 51.9l6.3 11a48.1 48.1 0 0 0-10.9 61a8 8 0 0 0 13.8-8a32 32 0 0 1 11.7-43.7l.7-.4l.5-.4h.1l.6-.6l.5-.5l.4-.5l.3-.6h.1l.2-.5v-.2a1.9 1.9 0 0 0 .2-.7h.1c0-.2.1-.4.1-.6s0-.2.1-.2v-2.1a6.4 6.4 0 0 0-.2-.7a1.9 1.9 0 0 0-.2-.7v-.2c0-.2-.1-.3-.2-.5l-.3-.7l-10-17.4a12 12 0 0 1 13.5-17.5a11.8 11.8 0 0 1 7.2 5.5l20 34.7a70.9 70.9 0 0 1 7.2 53.8Zm-125.8 78a8.2 8.2 0 0 1-6.6 3.4a8.6 8.6 0 0 1-4.6-1.4A117.9 117.9 0 0 1 41.1 208a8 8 0 1 1 13.8-8a102.6 102.6 0 0 0 30.8 33.4a8.1 8.1 0 0 1 2 11.2ZM168 31a8 8 0 0 1 8-8a60.2 60.2 0 0 1 52 30a7.9 7.9 0 0 1-3 10.9a7.1 7.1 0 0 1-4 1.1a8 8 0 0 1-6.9-4A44 44 0 0 0 176 39a8 8 0 0 1-8-8Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-moon{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M224.3 150.3a8.1 8.1 0 0 0-7.8-5.7l-2.2.4A84 84 0 0 1 111 41.6a5.7 5.7 0 0 0 .3-1.8a7.9 7.9 0 0 0-10.3-8.1a100 100 0 1 0 123.3 123.2a7.2 7.2 0 0 0 0-4.6ZM128 212A84 84 0 0 1 92.8 51.7a99.9 99.9 0 0 0 111.5 111.5A84.4 84.4 0 0 1 128 212Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.i-ph-sun{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 256 256' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M128 60a68 68 0 1 0 68 68a68.1 68.1 0 0 0-68-68Zm0 120a52 52 0 1 1 52-52a52 52 0 0 1-52 52Zm-8-144V16a8 8 0 0 1 16 0v20a8 8 0 0 1-16 0ZM43.1 54.5a8.1 8.1 0 1 1 11.4-11.4l14.1 14.2a8 8 0 0 1 0 11.3a8.1 8.1 0 0 1-11.3 0ZM36 136H16a8 8 0 0 1 0-16h20a8 8 0 0 1 0 16Zm32.6 51.4a8 8 0 0 1 0 11.3l-14.1 14.2a8.3 8.3 0 0 1-5.7 2.3a8.5 8.5 0 0 1-5.7-2.3a8.1 8.1 0 0 1 0-11.4l14.2-14.1a8 8 0 0 1 11.3 0ZM136 220v20a8 8 0 0 1-16 0v-20a8 8 0 0 1 16 0Zm76.9-18.5a8.1 8.1 0 0 1 0 11.4a8.5 8.5 0 0 1-5.7 2.3a8.3 8.3 0 0 1-5.7-2.3l-14.1-14.2a8 8 0 0 1 11.3-11.3ZM248 128a8 8 0 0 1-8 8h-20a8 8 0 0 1 0-16h20a8 8 0 0 1 8 8Zm-60.6-59.4a8 8 0 0 1 0-11.3l14.1-14.2a8.1 8.1 0 0 1 11.4 11.4l-14.2 14.1a8.1 8.1 0 0 1-11.3 0Z'/%3E%3C/svg%3E");mask:var(--un-icon) no-repeat;mask-size:100% 100%;-webkit-mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;background-color:currentColor;width:1em;height:1em;}.note-red{position:relative;display:inline-flex;align-items:center;border-left-width:4px;border-left-style:solid;--un-border-opacity:1;border-color:rgba(53,120,229,var(--un-border-opacity));border-radius:0.25rem;background-color:rgba(53,120,229,0.1);background-color:rgba(185,28,28,0.1);padding:0.5rem;text-decoration:none;}.nv{position:relative;display:flex;align-items:center;border-radius:0.25rem;padding:0.5rem;--un-text-opacity:1;color:rgba(194,197,202,var(--un-text-opacity));text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:125ms;}.nv_selected{position:relative;display:flex;align-items:center;border-left-width:4px;border-left-style:solid;border-radius:0.25rem;--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));padding:0.5rem;--un-text-opacity:1;color:rgba(194,197,202,var(--un-text-opacity));color:rgba(53,120,229,var(--un-text-opacity));text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:125ms;}.input{height:2.5rem;display:flex;align-items:center;border-radius:0.25rem;border-style:none;--un-bg-opacity:1;background-color:rgba(233,236,239,var(--un-bg-opacity));padding:0.5rem;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);outline:2px solid transparent;outline-offset:2px;}.btn{user-select:none;border-radius:0.25rem;border-style:none;--un-bg-opacity:1;background-color:rgba(53,120,229,var(--un-bg-opacity));padding:0.5rem;font-weight:400;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));color:rgba(255,255,255,var(--un-text-opacity));--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);outline:2px solid transparent;outline-offset:2px;}.nv_selected:hover,.nv:hover{border-left-width:4px;border-left-style:solid;--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));--un-text-opacity:1;color:rgba(53,120,229,var(--un-text-opacity));}.dark .note-red{--un-border-opacity:1;border-color:rgba(103,214,237,var(--un-border-opacity));background-color:rgba(103,214,237,0.1);background-color:rgba(185,28,28,0.1);}.btn:hover{--un-bg-opacity:1;background-color:rgba(45,102,195,var(--un-bg-opacity));}.dark .btn{--un-bg-opacity:1;background-color:rgba(103,214,237,var(--un-bg-opacity));font-weight:600;--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));}.dark .btn:hover{--un-bg-opacity:1;background-color:rgba(57,202,232,var(--un-bg-opacity));}.dark .input{--un-bg-opacity:1;background-color:rgba(36,37,38,var(--un-bg-opacity));--un-text-opacity:1;color:rgba(227,227,227,var(--un-text-opacity));}.dark .note-red::after,.note-red::after{--un-bg-opacity:1;background-color:rgba(185,28,28,var(--un-bg-opacity));}.btn:active{--un-bg-opacity:1;background-color:rgba(37,84,160,var(--un-bg-opacity));}.dark .btn:active{--un-bg-opacity:1;background-color:rgba(25,181,213,var(--un-bg-opacity));}.dark .nv_selected,.dark .nv_selected:hover,.dark .nv:hover{--un-text-opacity:1;color:rgba(103,214,237,var(--un-text-opacity));} ::-webkit-scrollbar-thumb { background-color: #3578E5; } .dark ::-webkit-scrollbar-thumb { background-color: #67d6ed; } code { font-size: 0.75rem; font-family: "Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; border-radius: 0.25rem; background-color: #d6d8da; } .code-block { font-family: "Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; font-size: 0.875rem; } .dark code { background-color: #282a2e; } .visible{visibility:visible;}.absolute{position:absolute;}.left-2{left:0.5rem;}.top-2{top:0.5rem;}.z-2000{z-index:2000;}.grid{display:grid;}.grid-rows-\[2fr_auto\]{grid-template-rows:2fr auto;}.grid-rows-\[2px_2rem_1fr\]{grid-template-rows:2px 2rem 1fr;}.grid-rows-\[auto_1fr\]{grid-template-rows:auto 1fr;}.grid-rows-\[min-content_auto\]{grid-template-rows:min-content auto;}.mr-2{margin-right:0.5rem;}.display-none{display:none;}.children-h-10>*{height:2.5rem;}.h-15rem{height:15rem;}.h-2px{height:2px;}.h-8{height:2rem;}.h-85\%{height:85%;}.h-screen{height:100vh;}.w-8{width:2rem;}.w-screen{width:100vw;}.flex{display:flex;}.flex-1{flex:1 1 0%;}.children-flex-none>*{flex:none;}.grow{flex-grow:1;}.flex-col{flex-direction:column;}@keyframes fade-in{from{opacity:0}to{opacity:1}}.animate-fade-in{animation:fade-in 1s linear 1;}.animate-duration-300ms{animation-duration:300ms;}.cursor-ns-resize{cursor:ns-resize;}.cursor-pointer{cursor:pointer;}.select-none{user-select:none;}.items-center{align-items:center;}.self-center{align-self:center;}.justify-center{justify-content:center;}.justify-between{justify-content:space-between;}.gap-1{grid-gap:0.25rem;gap:0.25rem;}.gap-2{grid-gap:0.5rem;gap:0.5rem;}.overflow-hidden{overflow:hidden;}.overflow-y-auto{overflow-y:auto;}.rd-1{border-radius:0.25rem;}.rd-8{border-radius:2rem;}.bg-accent{--un-bg-opacity:1;background-color:rgba(53,120,229,var(--un-bg-opacity));}.bg-black\/20{background-color:rgba(0,0,0,0.2);}.bg-darkPrimaryLighter{--un-bg-opacity:1;background-color:rgba(36,37,38,var(--un-bg-opacity));}.bg-primary{--un-bg-opacity:1;background-color:rgba(255,255,255,var(--un-bg-opacity));}.bg-white\/5{background-color:rgba(255,255,255,0.05);}.dark .dark\:bg-darkAccent{--un-bg-opacity:1;background-color:rgba(103,214,237,var(--un-bg-opacity));}.dark .dark\:bg-darkPrimary{--un-bg-opacity:1;background-color:rgba(27,27,29,var(--un-bg-opacity));}.dark .dark\:hover\:bg-darkHoverOverlay:hover{--un-bg-opacity:.05;background-color:hsla(0,0%,100%,var(--un-bg-opacity));}.hover\:bg-hoverOverlay:hover{--un-bg-opacity:.05;background-color:rgba(0,0,0,var(--un-bg-opacity));}.active\:bg-accentDark:active{--un-bg-opacity:1;background-color:rgba(48,108,206,var(--un-bg-opacity));}.active\:bg-hoverOverlay\/25:active{background-color:rgba(0,0,0,0.25);}.dark .dark\:active\:bg-darkAccentDark:active{--un-bg-opacity:1;background-color:rgba(73,206,233,var(--un-bg-opacity));}.dark .dark\:active\:bg-darkHoverOverlay\/25:active{background-color:hsla(0,0%,100%,0.25);}.p-1{padding:0.25rem;}.p-7{padding:1.75rem;}.px{padding-left:1rem;padding-right:1rem;}.px-2{padding-left:0.5rem;padding-right:0.5rem;}.px-5{padding-left:1.25rem;padding-right:1.25rem;}.children-pb-2>*{padding-bottom:0.5rem;}.children-pt8>*{padding-top:2rem;}.all\:font-mono *{font-family:"Fira Code","Fira Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;}.all\:text-xs *{font-size:0.75rem;line-height:1rem;}.font-semibold{font-weight:600;}.dark .dark\:text-darkAccent{--un-text-opacity:1;color:rgba(103,214,237,var(--un-text-opacity));}.dark .dark\:text-darkPrimaryText{--un-text-opacity:1;color:rgba(227,227,227,var(--un-text-opacity));}.text-accent{--un-text-opacity:1;color:rgba(53,120,229,var(--un-text-opacity));}.text-primaryText{--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));}.transition-colors-250{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:250ms;}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}@media (max-width: 639.9px){.lt-sm\:absolute{position:absolute;}.lt-sm\:z-1999{z-index:1999;}.lt-sm\:h-screen{height:100vh;}.lt-sm\:flex{display:flex;}.lt-sm\:shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}.lt-sm\:shadow-lg{--un-shadow:var(--un-shadow-inset) 0 10px 15px -3px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 4px 6px -4px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}.lt-sm\:transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}}*:not(h1,h2,h3,h4,h5,h6){margin:0;padding:0}*{box-sizing:border-box;font-family:Rubik,sans-serif}::-webkit-scrollbar{width:.25rem;height:3px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{border-radius:.25rem}code{padding:.05rem .25rem}code.code-block{padding:.5rem}#sidebar{width:18.75rem}@media screen and (max-width: 640px){#sidebar{--translate-x: -18.75rem;transform:translate(var(--translate-x))}} diff --git a/examples/api/dist/assets/index.js b/examples/api/dist/assets/index.js index 089c07d4e26f..0ee0c82e2d75 100644 --- a/examples/api/dist/assets/index.js +++ b/examples/api/dist/assets/index.js @@ -1,39 +1,9 @@ -var Io=Object.defineProperty,qo=Object.defineProperties;var No=Object.getOwnPropertyDescriptors;var Fi=Object.getOwnPropertySymbols;var Bo=Object.prototype.hasOwnProperty,Go=Object.prototype.propertyIsEnumerable;var Hi=(e,t,n)=>t in e?Io(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Ui=(e,t)=>{for(var n in t||(t={}))Bo.call(t,n)&&Hi(e,n,t[n]);if(Fi)for(var n of Fi(t))Go.call(t,n)&&Hi(e,n,t[n]);return e},Ii=(e,t)=>qo(e,No(t));import{S as K,i as X,s as Y,e as u,a as v,t as S,b as a,c as O,d as r,l as T,f as G,n as I,g as W,r as x,o as $e,h as On,j as E,p as Ke,k as de,m as Vo,q as qi,u as Xe,v as Ni,w as J,x as Bi,y as Gi,z as Vi,A as Ji,B as $i,C as Ki,D as Jo,E as Xi,F as Yi,G as $o,H as Ko,I as Xo}from"./vendor.js";const Yo=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))i(o);new MutationObserver(o=>{for(const l of o)if(l.type==="childList")for(const s of l.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&i(s)}).observe(document,{childList:!0,subtree:!0});function n(o){const l={};return o.integrity&&(l.integrity=o.integrity),o.referrerpolicy&&(l.referrerPolicy=o.referrerpolicy),o.crossorigin==="use-credentials"?l.credentials="include":o.crossorigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function i(o){if(o.ep)return;o.ep=!0;const l=n(o);fetch(o.href,l)}};Yo();/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */var Qi=function(e,t){return(Qi=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var o in i)Object.prototype.hasOwnProperty.call(i,o)&&(n[o]=i[o])})(e,t)};function Wn(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}Qi(e,t),e.prototype=t===null?Object.create(t):(n.prototype=t.prototype,new n)}var dt=function(){return(dt=Object.assign||function(e){for(var t,n=1,i=arguments.length;n0&&o[o.length-1])||f[0]!==6&&f[0]!==2)){s=0;continue}if(f[0]===3&&(!o||f[1]>o[0]&&f[1]{n(2,l=p)}),eo().then(p=>{n(0,i=p)}),io().then(p=>{n(1,o=p)});async function s(){await oo()}async function c(){await En()}return[i,o,l,s,c]}class tr extends K{constructor(t){super();X(this,t,xo,Zo,Y,{})}}function ro(){return _(this,void 0,void 0,function(){return g(this,function(e){return[2,z({__tauriModule:"Cli",message:{cmd:"cliMatches"}})]})})}Object.freeze({__proto__:null,getMatches:ro});function er(e){let t,n,i,o,l,s,c,p,h,f,M;return{c(){t=u("div"),n=S(`This binary can be run on the terminal and takes the following arguments: - `),i=u("ul"),i.innerHTML=`
  • --config PATH
  • -
  • --theme light|dark|system
  • -
  • --verbose
  • `,o=S(` - Additionally, it has a `),l=u("i"),l.textContent="update --background",s=S(` subcommand. - Note that the arguments are only parsed, not implemented. - `),c=u("br"),p=v(),h=u("button"),h.textContent="Get matches",a(h,"class","button"),a(h,"id","cli-matches")},m(m,d){O(m,t,d),r(t,n),r(t,i),r(t,o),r(t,l),r(t,s),r(t,c),r(t,p),r(t,h),f||(M=T(h,"click",e[0]),f=!0)},p:I,i:I,o:I,d(m){m&&W(t),f=!1,M()}}}function nr(e,t,n){let{onMessage:i}=t;function o(){ro().then(i).catch(i)}return e.$$set=l=>{"onMessage"in l&&n(1,i=l.onMessage)},[o,i]}class ir extends K{constructor(t){super();X(this,t,nr,er,Y,{onMessage:1})}}function lo(e){return _(this,void 0,void 0,function(){return g(this,function(t){return[2,z({__tauriModule:"Event",message:{cmd:"unlisten",eventId:e}})]})})}function so(e,t,n){return _(this,void 0,void 0,function(){return g(this,function(i){switch(i.label){case 0:return[4,z({__tauriModule:"Event",message:{cmd:"emit",event:e,windowLabel:t,payload:typeof n=="string"?n:JSON.stringify(n)}})];case 1:return i.sent(),[2]}})})}function Dn(e,t,n){return _(this,void 0,void 0,function(){var i=this;return g(this,function(o){return[2,z({__tauriModule:"Event",message:{cmd:"listen",event:e,windowLabel:t,handler:Wt(n)}}).then(function(l){return function(){return _(i,void 0,void 0,function(){return g(this,function(s){return[2,lo(l)]})})}})]})})}function uo(e,t,n){return _(this,void 0,void 0,function(){return g(this,function(i){return[2,Dn(e,t,function(o){n(o),lo(o.id).catch(function(){})})]})})}function pe(e,t){return _(this,void 0,void 0,function(){return g(this,function(n){return[2,Dn(e,null,t)]})})}function ao(e,t){return _(this,void 0,void 0,function(){return g(this,function(n){return[2,uo(e,null,t)]})})}function Ye(e,t){return _(this,void 0,void 0,function(){return g(this,function(n){return[2,so(e,void 0,t)]})})}Object.freeze({__proto__:null,listen:pe,once:ao,emit:Ye});function or(e){let t,n,i,o,l,s,c,p;return{c(){t=u("div"),n=u("button"),n.textContent="Call Log API",i=v(),o=u("button"),o.textContent="Call Request (async) API",l=v(),s=u("button"),s.textContent="Send event to Rust",a(n,"class","button"),a(n,"id","log"),a(o,"class","button"),a(o,"id","request"),a(s,"class","button"),a(s,"id","event")},m(h,f){O(h,t,f),r(t,n),r(t,i),r(t,o),r(t,l),r(t,s),c||(p=[T(n,"click",e[0]),T(o,"click",e[1]),T(s,"click",e[2])],c=!0)},p:I,i:I,o:I,d(h){h&&W(t),c=!1,x(p)}}}function rr(e,t,n){let{onMessage:i}=t,o;$e(async()=>{o=await pe("rust-event",i)}),On(()=>{o&&o()});function l(){fe("log_operation",{event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}function s(){fe("perform_request",{endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(i).catch(i)}function c(){Ye("js-event","this is the payload string")}return e.$$set=p=>{"onMessage"in p&&n(3,i=p.onMessage)},[l,s,c,i]}class lr extends K{constructor(t){super();X(this,t,rr,or,Y,{onMessage:3})}}function Rn(e){return e===void 0&&(e={}),_(this,void 0,void 0,function(){return g(this,function(t){return typeof e=="object"&&Object.freeze(e),[2,z({__tauriModule:"Dialog",message:{cmd:"openDialog",options:e}})]})})}function co(e){return e===void 0&&(e={}),_(this,void 0,void 0,function(){return g(this,function(t){return typeof e=="object"&&Object.freeze(e),[2,z({__tauriModule:"Dialog",message:{cmd:"saveDialog",options:e}})]})})}function sr(e){return _(this,void 0,void 0,function(){return g(this,function(t){return[2,z({__tauriModule:"Dialog",message:{cmd:"messageDialog",message:e}})]})})}function ur(e,t){return _(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"Dialog",message:{cmd:"askDialog",title:t,message:e}})]})})}function ar(e,t){return _(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"Dialog",message:{cmd:"confirmDialog",title:t,message:e}})]})})}Object.freeze({__proto__:null,open:Rn,save:co,message:sr,ask:ur,confirm:ar});var Bt;function cr(e,t){return t===void 0&&(t={}),_(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"Fs",message:{cmd:"readFile",path:e,options:t}}).then(function(i){return new TextDecoder().decode(new Uint8Array(i))})]})})}function jn(e,t){return t===void 0&&(t={}),_(this,void 0,void 0,function(){var n;return g(this,function(i){switch(i.label){case 0:return[4,z({__tauriModule:"Fs",message:{cmd:"readFile",path:e,options:t}})];case 1:return n=i.sent(),[2,Uint8Array.from(n)]}})})}function dr(e,t){return t===void 0&&(t={}),_(this,void 0,void 0,function(){return g(this,function(n){return typeof t=="object"&&Object.freeze(t),typeof e=="object"&&Object.freeze(e),[2,z({__tauriModule:"Fs",message:{cmd:"writeFile",path:e.path,contents:Array.from(new TextEncoder().encode(e.contents)),options:t}})]})})}function fr(e,t){return t===void 0&&(t={}),_(this,void 0,void 0,function(){return g(this,function(n){return typeof t=="object"&&Object.freeze(t),typeof e=="object"&&Object.freeze(e),[2,z({__tauriModule:"Fs",message:{cmd:"writeFile",path:e.path,contents:Array.from(e.contents),options:t}})]})})}function fo(e,t){return t===void 0&&(t={}),_(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"Fs",message:{cmd:"readDir",path:e,options:t}})]})})}function pr(e,t){return t===void 0&&(t={}),_(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"Fs",message:{cmd:"createDir",path:e,options:t}})]})})}function hr(e,t){return t===void 0&&(t={}),_(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"Fs",message:{cmd:"removeDir",path:e,options:t}})]})})}function mr(e,t,n){return n===void 0&&(n={}),_(this,void 0,void 0,function(){return g(this,function(i){return[2,z({__tauriModule:"Fs",message:{cmd:"copyFile",source:e,destination:t,options:n}})]})})}function vr(e,t){return t===void 0&&(t={}),_(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"Fs",message:{cmd:"removeFile",path:e,options:t}})]})})}function _r(e,t,n){return n===void 0&&(n={}),_(this,void 0,void 0,function(){return g(this,function(i){return[2,z({__tauriModule:"Fs",message:{cmd:"renameFile",oldPath:e,newPath:t,options:n}})]})})}(function(e){e[e.Audio=1]="Audio",e[e.Cache=2]="Cache",e[e.Config=3]="Config",e[e.Data=4]="Data",e[e.LocalData=5]="LocalData",e[e.Desktop=6]="Desktop",e[e.Document=7]="Document",e[e.Download=8]="Download",e[e.Executable=9]="Executable",e[e.Font=10]="Font",e[e.Home=11]="Home",e[e.Picture=12]="Picture",e[e.Public=13]="Public",e[e.Runtime=14]="Runtime",e[e.Template=15]="Template",e[e.Video=16]="Video",e[e.Resource=17]="Resource",e[e.App=18]="App",e[e.Log=19]="Log"})(Bt||(Bt={}));Object.freeze({__proto__:null,get BaseDirectory(){return Bt},get Dir(){return Bt},readTextFile:cr,readBinaryFile:jn,writeFile:dr,writeBinaryFile:fr,readDir:fo,createDir:pr,removeDir:hr,copyFile:mr,removeFile:vr,renameFile:_r});function gr(e){let t,n,i,o,l,s,c,p,h,f,M,m,d,b,k,w,P,C,j,F;return{c(){t=u("div"),n=u("input"),i=v(),o=u("input"),l=v(),s=u("div"),c=u("input"),p=v(),h=u("label"),h.textContent="Multiple",f=v(),M=u("div"),m=u("input"),d=v(),b=u("label"),b.textContent="Directory",k=v(),w=u("button"),w.textContent="Open dialog",P=v(),C=u("button"),C.textContent="Open save dialog",a(n,"id","dialog-default-path"),a(n,"placeholder","Default path"),a(o,"id","dialog-filter"),a(o,"placeholder","Extensions filter, comma-separated"),a(o,"class","svelte-1eg58yg"),a(c,"type","checkbox"),a(c,"id","dialog-multiple"),a(h,"for","dialog-multiple"),a(m,"type","checkbox"),a(m,"id","dialog-directory"),a(b,"for","dialog-directory"),a(w,"class","button"),a(w,"id","open-dialog"),a(C,"class","button"),a(C,"id","save-dialog")},m(H,A){O(H,t,A),r(t,n),E(n,e[0]),r(t,i),r(t,o),E(o,e[1]),r(t,l),r(t,s),r(s,c),c.checked=e[2],r(s,p),r(s,h),r(t,f),r(t,M),r(M,m),m.checked=e[3],r(M,d),r(M,b),r(t,k),r(t,w),r(t,P),r(t,C),j||(F=[T(n,"input",e[8]),T(o,"input",e[9]),T(c,"change",e[10]),T(m,"change",e[11]),T(w,"click",e[4]),T(C,"click",e[5])],j=!0)},p(H,[A]){A&1&&n.value!==H[0]&&E(n,H[0]),A&2&&o.value!==H[1]&&E(o,H[1]),A&4&&(c.checked=H[2]),A&8&&(m.checked=H[3])},i:I,o:I,d(H){H&&W(t),j=!1,x(F)}}}function br(e,t){var n=new Blob([e],{type:"application/octet-binary"}),i=new FileReader;i.onload=function(o){var l=o.target.result;t(l.substr(l.indexOf(",")+1))},i.readAsDataURL(n)}function wr(e,t,n){let{onMessage:i}=t,{insecureRenderHtml:o}=t,l=null,s=null,c=!1,p=!1;function h(){Rn({title:"My wonderful open dialog",defaultPath:l,filters:s?[{name:"Tauri Example",extensions:s.split(",").map(k=>k.trim())}]:[],multiple:c,directory:p}).then(function(k){if(Array.isArray(k))i(k);else{var w=k,P=w.match(/\S+\.\S+$/g);jn(w).then(function(C){P&&(w.includes(".png")||w.includes(".jpg"))?br(new Uint8Array(C),function(j){var F="data:image/png;base64,"+j;o('')}):i(k)}).catch(i(k))}}).catch(i)}function f(){co({title:"My wonderful save dialog",defaultPath:l,filters:s?[{name:"Tauri Example",extensions:s.split(",").map(k=>k.trim())}]:[]}).then(i).catch(i)}function M(){l=this.value,n(0,l)}function m(){s=this.value,n(1,s)}function d(){c=this.checked,n(2,c)}function b(){p=this.checked,n(3,p)}return e.$$set=k=>{"onMessage"in k&&n(6,i=k.onMessage),"insecureRenderHtml"in k&&n(7,o=k.insecureRenderHtml)},[l,s,c,p,h,f,i,o,M,m,d,b]}class yr extends K{constructor(t){super();X(this,t,wr,gr,Y,{onMessage:6,insecureRenderHtml:7})}}function po(e,t,n){const i=e.slice();return i[9]=t[n],i}function ho(e){let t,n=e[9][0]+"",i,o;return{c(){t=u("option"),i=S(n),t.__value=o=e[9][1],t.value=t.__value},m(l,s){O(l,t,s),r(t,i)},p:I,d(l){l&&W(t)}}}function Mr(e){let t,n,i,o,l,s,c,p,h,f,M,m,d,b=e[2],k=[];for(let w=0;wisNaN(parseInt(m))).map(m=>[m,Bt[m]]);function p(){const m=l.match(/\S+\.\S+$/g),d={dir:mo()};(m?jn(l,d):fo(l,d)).then(function(k){if(m)if(l.includes(".png")||l.includes(".jpg"))kr(new Uint8Array(k),function(w){const P="data:image/png;base64,"+w;o('')});else{const w=String.fromCharCode.apply(null,k);o(''),setTimeout(()=>{const P=document.getElementById("file-response");P.value=w,document.getElementById("file-save").addEventListener("click",function(){writeFile({file:l,contents:P.value},{dir:mo()}).catch(i)})})}else i(k)}).catch(i)}function h(){n(1,s.src=Zi(l),s)}function f(){l=this.value,n(0,l)}function M(m){Vo[m?"unshift":"push"](()=>{s=m,n(1,s)})}return e.$$set=m=>{"onMessage"in m&&n(5,i=m.onMessage),"insecureRenderHtml"in m&&n(6,o=m.insecureRenderHtml)},[l,s,c,p,h,i,o,f,M]}class Tr extends K{constructor(t){super();X(this,t,zr,Mr,Y,{onMessage:5,insecureRenderHtml:6})}}var he;(function(e){e[e.JSON=1]="JSON",e[e.Text=2]="Text",e[e.Binary=3]="Binary"})(he||(he={}));var Fn=function(){function e(t,n){this.type=t,this.payload=n}return e.form=function(t){var n={};for(var i in t){var o=t[i];n[i]=typeof o=="string"?o:Array.from(o)}return new e("Form",n)},e.json=function(t){return new e("Json",t)},e.text=function(t){return new e("Text",t)},e.bytes=function(t){return new e("Bytes",Array.from(t))},e}(),vo=function(e){this.url=e.url,this.status=e.status,this.ok=this.status>=200&&this.status<300,this.headers=e.headers,this.rawHeaders=e.rawHeaders,this.data=e.data},_o=function(){function e(t){this.id=t}return e.prototype.drop=function(){return _(this,void 0,void 0,function(){return g(this,function(t){return[2,z({__tauriModule:"Http",message:{cmd:"dropClient",client:this.id}})]})})},e.prototype.request=function(t){return _(this,void 0,void 0,function(){var n;return g(this,function(i){return(n=!t.responseType||t.responseType===he.JSON)&&(t.responseType=he.Text),[2,z({__tauriModule:"Http",message:{cmd:"httpRequest",client:this.id,options:t}}).then(function(o){var l=new vo(o);if(n){try{l.data=JSON.parse(l.data)}catch(s){if(l.ok&&l.data==="")l.data={};else if(l.ok)throw Error("Failed to parse response `".concat(l.data,"` as JSON: ").concat(s,";\n try setting the `responseType` option to `ResponseType.Text` or `ResponseType.Binary` if the API does not return a JSON response."))}return l}return l})]})})},e.prototype.get=function(t,n){return _(this,void 0,void 0,function(){return g(this,function(i){return[2,this.request(dt({method:"GET",url:t},n))]})})},e.prototype.post=function(t,n,i){return _(this,void 0,void 0,function(){return g(this,function(o){return[2,this.request(dt({method:"POST",url:t,body:n},i))]})})},e.prototype.put=function(t,n,i){return _(this,void 0,void 0,function(){return g(this,function(o){return[2,this.request(dt({method:"PUT",url:t,body:n},i))]})})},e.prototype.patch=function(t,n){return _(this,void 0,void 0,function(){return g(this,function(i){return[2,this.request(dt({method:"PATCH",url:t},n))]})})},e.prototype.delete=function(t,n){return _(this,void 0,void 0,function(){return g(this,function(i){return[2,this.request(dt({method:"DELETE",url:t},n))]})})},e}();function Hn(e){return _(this,void 0,void 0,function(){return g(this,function(t){return[2,z({__tauriModule:"Http",message:{cmd:"createClient",options:e}}).then(function(n){return new _o(n)})]})})}var Un=null;function Cr(e,t){var n;return _(this,void 0,void 0,function(){return g(this,function(i){switch(i.label){case 0:return Un!==null?[3,2]:[4,Hn()];case 1:Un=i.sent(),i.label=2;case 2:return[2,Un.request(dt({url:e,method:(n=t==null?void 0:t.method)!==null&&n!==void 0?n:"GET"},t))]}})})}Object.freeze({__proto__:null,getClient:Hn,fetch:Cr,Body:Fn,Client:_o,Response:vo,get ResponseType(){return he}});function Sr(e){let t,n,i,o,l,s,c,p,h,f,M,m,d,b,k,w,P;return{c(){t=u("form"),n=u("select"),i=u("option"),i.textContent="GET",o=u("option"),o.textContent="POST",l=u("option"),l.textContent="PUT",s=u("option"),s.textContent="PATCH",c=u("option"),c.textContent="DELETE",p=v(),h=u("input"),f=v(),M=u("br"),m=v(),d=u("textarea"),b=v(),k=u("button"),k.textContent="Make request",i.__value="GET",i.value=i.__value,o.__value="POST",o.value=o.__value,l.__value="PUT",l.value=l.__value,s.__value="PATCH",s.value=s.__value,c.__value="DELETE",c.value=c.__value,a(n,"class","button"),a(n,"id","request-method"),e[0]===void 0&&qi(()=>e[5].call(n)),a(h,"id","request-url"),a(h,"placeholder","Type the request URL..."),a(d,"id","request-body"),a(d,"placeholder","Request body"),a(d,"rows","5"),a(d,"class","svelte-1xfmj7b"),a(k,"class","button"),a(k,"id","make-request")},m(C,j){O(C,t,j),r(t,n),r(n,i),r(n,o),r(n,l),r(n,s),r(n,c),Xe(n,e[0]),r(t,p),r(t,h),E(h,e[1]),r(t,f),r(t,M),r(t,m),r(t,d),E(d,e[2]),r(t,b),r(t,k),w||(P=[T(n,"change",e[5]),T(h,"input",e[6]),T(d,"input",e[7]),T(t,"submit",Ke(e[3]))],w=!0)},p(C,[j]){j&1&&Xe(n,C[0]),j&2&&h.value!==C[1]&&E(h,C[1]),j&4&&E(d,C[2])},i:I,o:I,d(C){C&&W(t),w=!1,x(P)}}}function Pr(e,t,n){let i="GET",o="https://jsonplaceholder.typicode.com/todos/1",l="",{onMessage:s}=t;async function c(){const M=await Hn().catch(k=>{throw s(k),k}),b={url:o||""||"",method:i||"GET"||"GET"};l.startsWith("{")&&l.endsWith("}")||l.startsWith("[")&&l.endsWith("]")?b.body=Fn.json(JSON.parse(l)):l!==""&&(b.body=Fn.text(l)),M.request(b).then(s).catch(s)}function p(){i=Ni(this),n(0,i)}function h(){o=this.value,n(1,o)}function f(){l=this.value,n(2,l)}return e.$$set=M=>{"onMessage"in M&&n(4,s=M.onMessage)},[i,o,l,c,s,p,h,f]}class Or extends K{constructor(t){super();X(this,t,Pr,Sr,Y,{onMessage:4})}}function Wr(e){let t,n,i;return{c(){t=u("button"),t.textContent="Send test notification",a(t,"class","button"),a(t,"id","notification")},m(o,l){O(o,t,l),n||(i=T(t,"click",Ar),n=!0)},p:I,i:I,o:I,d(o){o&&W(t),n=!1,i()}}}function Ar(){new Notification("Notification title",{body:"This is the notification body"})}function Lr(e,t,n){let{onMessage:i}=t;return e.$$set=o=>{"onMessage"in o&&n(0,i=o.onMessage)},[i]}class Er extends K{constructor(t){super();X(this,t,Lr,Wr,Y,{onMessage:0})}}var me,Qe=function(e,t){this.type="Logical",this.width=e,this.height=t},Gt=function(){function e(t,n){this.type="Physical",this.width=t,this.height=n}return e.prototype.toLogical=function(t){return new Qe(this.width/t,this.height/t)},e}(),go=function(e,t){this.type="Logical",this.x=e,this.y=t},Vt=function(){function e(t,n){this.type="Physical",this.x=t,this.y=n}return e.prototype.toLogical=function(t){return new go(this.x/t,this.y/t)},e}();function Dr(){return new ve(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0})}function bo(){return window.__TAURI_METADATA__.__windows.map(function(e){return new ve(e.label,{skip:!0})})}(function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"})(me||(me={}));var wo=["tauri://created","tauri://error"],yo=function(){function e(t){this.label=t,this.listeners=Object.create(null)}return e.prototype.listen=function(t,n){return _(this,void 0,void 0,function(){var i=this;return g(this,function(o){return this._handleTauriEvent(t,n)?[2,Promise.resolve(function(){var l=i.listeners[t];l.splice(l.indexOf(n),1)})]:[2,Dn(t,this.label,n)]})})},e.prototype.once=function(t,n){return _(this,void 0,void 0,function(){var i=this;return g(this,function(o){return this._handleTauriEvent(t,n)?[2,Promise.resolve(function(){var l=i.listeners[t];l.splice(l.indexOf(n),1)})]:[2,uo(t,this.label,n)]})})},e.prototype.emit=function(t,n){return _(this,void 0,void 0,function(){var i,o;return g(this,function(l){if(wo.includes(t)){for(i=0,o=this.listeners[t]||[];ie[31].call(n)),a(s,"type","checkbox"),a(f,"type","checkbox"),a(d,"title","Unminimizes after 2 seconds"),a(k,"title","Unminimizes after 2 seconds"),a(P,"title","Visible again after 2 seconds"),a(F,"type","checkbox"),a(N,"type","checkbox"),a(B,"type","checkbox"),a(Q,"type","checkbox"),a(it,"type","number"),a(it,"min","0"),a(it,"class","svelte-1tppwwz"),a(ot,"type","number"),a(ot,"min","0"),a(ot,"class","svelte-1tppwwz"),a(ft,"class","flex col grow svelte-1tppwwz"),a(rt,"type","number"),a(rt,"min","400"),a(rt,"class","svelte-1tppwwz"),a(lt,"type","number"),a(lt,"min","400"),a(lt,"class","svelte-1tppwwz"),a(pt,"class","flex col grow svelte-1tppwwz"),a(ht,"type","number"),a(ht,"class","svelte-1tppwwz"),a(mt,"type","number"),a(mt,"class","svelte-1tppwwz"),a(q,"class","flex col grow svelte-1tppwwz"),a(at,"type","number"),a(at,"min","400"),a(at,"class","svelte-1tppwwz"),a(ct,"type","number"),a(ct,"min","400"),a(ct,"class","svelte-1tppwwz"),a(jt,"class","flex col grow svelte-1tppwwz"),a(et,"class","window-controls flex flex-row svelte-1tppwwz"),a(t,"class","flex col"),a($t,"class","svelte-1tppwwz"),a(Kt,"class","svelte-1tppwwz"),a(vt,"class","grow window-property svelte-1tppwwz"),a(Xt,"class","svelte-1tppwwz"),a(Yt,"class","svelte-1tppwwz"),a(_t,"class","grow window-property svelte-1tppwwz"),a(Ft,"class","flex"),a(Qt,"class","svelte-1tppwwz"),a(Zt,"class","svelte-1tppwwz"),a(gt,"class","grow window-property svelte-1tppwwz"),a(xt,"class","svelte-1tppwwz"),a(te,"class","svelte-1tppwwz"),a(bt,"class","grow window-property svelte-1tppwwz"),a(Ht,"class","flex"),a(ee,"class","svelte-1tppwwz"),a(ne,"class","svelte-1tppwwz"),a(wt,"class","grow window-property svelte-1tppwwz"),a(ie,"class","svelte-1tppwwz"),a(oe,"class","svelte-1tppwwz"),a(yt,"class","grow window-property svelte-1tppwwz"),a(Ut,"class","flex"),a(re,"class","svelte-1tppwwz"),a(le,"class","svelte-1tppwwz"),a(Mt,"class","grow window-property svelte-1tppwwz"),a(se,"class","svelte-1tppwwz"),a(ue,"class","svelte-1tppwwz"),a(kt,"class","grow window-property svelte-1tppwwz"),a(It,"class","flex"),a(St,"id","title"),a(ae,"class","button"),a(ae,"type","submit"),a(zt,"class","svelte-1tppwwz"),a(Pt,"id","url"),a(ce,"class","button"),a(ce,"id","open-url"),a(Tt,"class","svelte-1tppwwz"),a(Ot,"class","button"),a(Ot,"title","Minimizes the window, requests attention for 3s and then resets it"),a(qt,"class","button")},m(y,L){O(y,t,L),r(t,n);for(let V=0;V{typeof q=="string"&&o[i].setIcon(q)})}function ge(){const q=Math.random().toString().replace(".",""),Ct=new ve(q);n(1,o[q]=Ct,o),Ct.once("tauri://error",function(){l("Error creating new webview")})}function At(){o[i].innerSize().then(q=>{n(20,N=q),n(7,d=N.width),n(8,b=N.height)}),o[i].outerSize().then(q=>{n(21,tt=q)})}function be(){o[i].innerPosition().then(q=>{n(18,A=q)}),o[i].outerPosition().then(q=>{n(19,U=q),n(13,j=U.x),n(14,F=U.y)})}async function Jt(q){ut&&ut(),$&&$(),$=await q.listen("tauri://move",be),ut=await q.listen("tauri://resize",At)}async function et(){await o[i].minimize(),await o[i].requestUserAttention(me.Critical),await new Promise(q=>setTimeout(q,3e3)),await o[i].requestUserAttention(null)}function ft(){i=Ni(this),n(0,i),n(1,o)}function Lt(){c=this.checked,n(2,c)}function we(){p=this.checked,n(3,p)}const it=()=>o[i].center();function ye(){h=this.checked,n(16,h)}function Et(){f=this.checked,n(4,f)}function Me(){M=this.checked,n(5,M)}function ot(){m=this.checked,n(6,m)}function ke(){j=J(this.value),n(13,j)}function pt(){F=J(this.value),n(14,F)}function Dt(){d=J(this.value),n(7,d)}function ze(){b=J(this.value),n(8,b)}function rt(){k=J(this.value),n(9,k)}function Te(){w=J(this.value),n(10,w)}function Rt(){P=J(this.value),n(11,P)}function Ce(){C=J(this.value),n(12,C)}function lt(){B=this.value,n(22,B)}function Se(){s=this.value,n(15,s)}return e.$$set=q=>{"onMessage"in q&&n(30,l=q.onMessage)},e.$$.update=()=>{e.$$.dirty[0]&7&&o[i].setResizable(c),e.$$.dirty[0]&11&&(p?o[i].maximize():o[i].unmaximize()),e.$$.dirty[0]&19&&o[i].setDecorations(f),e.$$.dirty[0]&35&&o[i].setAlwaysOnTop(M),e.$$.dirty[0]&67&&o[i].setFullscreen(m),e.$$.dirty[0]&387&&o[i].setSize(new Gt(d,b)),e.$$.dirty[0]&1539&&(k&&w?o[i].setMinSize(new Qe(k,w)):o[i].setMinSize(null)),e.$$.dirty[0]&6147&&(P&&C?o[i].setMaxSize(new Qe(P,C)):o[i].setMaxSize(null)),e.$$.dirty[0]&24579&&o[i].setPosition(new Vt(j,F)),e.$$.dirty[0]&3&&o[i].scaleFactor().then(q=>n(17,H=q)),e.$$.dirty[0]&3&&Jt(o[i])},[i,o,c,p,f,M,m,d,b,k,w,P,C,j,F,s,h,H,A,U,N,tt,B,R,nt,D,Q,_e,ge,et,l,ft,Lt,we,it,ye,Et,Me,ot,ke,pt,Dt,ze,rt,Te,Rt,Ce,lt,Se]}class Ir extends K{constructor(t){super();X(this,t,Ur,Hr,Y,{onMessage:30},[-1,-1])}}function To(e,t){return _(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"GlobalShortcut",message:{cmd:"register",shortcut:e,handler:Wt(t)}})]})})}function qr(e,t){return _(this,void 0,void 0,function(){return g(this,function(n){return[2,z({__tauriModule:"GlobalShortcut",message:{cmd:"registerAll",shortcuts:e,handler:Wt(t)}})]})})}function Nr(e){return _(this,void 0,void 0,function(){return g(this,function(t){return[2,z({__tauriModule:"GlobalShortcut",message:{cmd:"isRegistered",shortcut:e}})]})})}function Co(e){return _(this,void 0,void 0,function(){return g(this,function(t){return[2,z({__tauriModule:"GlobalShortcut",message:{cmd:"unregister",shortcut:e}})]})})}function So(){return _(this,void 0,void 0,function(){return g(this,function(e){return[2,z({__tauriModule:"GlobalShortcut",message:{cmd:"unregisterAll"}})]})})}Object.freeze({__proto__:null,register:To,registerAll:qr,isRegistered:Nr,unregister:Co,unregisterAll:So});function Po(e,t,n){const i=e.slice();return i[9]=t[n],i}function Oo(e){let t,n=e[9]+"",i,o,l,s,c;function p(){return e[8](e[9])}return{c(){t=u("div"),i=S(n),o=v(),l=u("button"),l.textContent="Unregister",a(l,"type","button")},m(h,f){O(h,t,f),r(t,i),r(t,o),r(t,l),s||(c=T(l,"click",p),s=!0)},p(h,f){e=h,f&2&&n!==(n=e[9]+"")&&G(i,n)},d(h){h&&W(t),s=!1,c()}}}function Wo(e){let t,n,i;return{c(){t=u("button"),t.textContent="Unregister all",a(t,"type","button")},m(o,l){O(o,t,l),n||(i=T(t,"click",e[5]),n=!0)},p:I,d(o){o&&W(t),n=!1,i()}}}function Br(e){let t,n,i,o,l,s,c,p,h,f,M=e[1],m=[];for(let b=0;bn(1,i=m));let s="CmdOrControl+X";function c(){const m=s;To(m,()=>{o(`Shortcut ${m} triggered`)}).then(()=>{l.update(d=>[...d,m]),o(`Shortcut ${m} registered successfully`)}).catch(o)}function p(m){const d=m;Co(d).then(()=>{l.update(b=>b.filter(k=>k!==d)),o(`Shortcut ${d} unregistered`)}).catch(o)}function h(){So().then(()=>{l.update(()=>[]),o("Unregistered all shortcuts")}).catch(o)}function f(){s=this.value,n(0,s)}const M=m=>p(m);return e.$$set=m=>{"onMessage"in m&&n(6,o=m.onMessage)},[s,i,l,c,p,h,o,f,M]}class Vr extends K{constructor(t){super();X(this,t,Gr,Br,Y,{onMessage:6})}}function Ao(e){let t,n,i,o,l;return{c(){t=u("input"),n=v(),i=u("button"),i.textContent="Write",a(t,"placeholder","write to stdin"),a(i,"class","button")},m(s,c){O(s,t,c),E(t,e[3]),O(s,n,c),O(s,i,c),o||(l=[T(t,"input",e[10]),T(i,"click",e[7])],o=!0)},p(s,c){c&8&&t.value!==s[3]&&E(t,s[3])},d(s){s&&W(t),s&&W(n),s&&W(i),o=!1,x(l)}}}function Jr(e){let t,n,i,o,l,s,c,p,h,f,M,m,d,b,k,w=e[4]&&Ao(e);return{c(){t=u("div"),n=u("div"),i=u("input"),o=v(),l=u("button"),l.textContent="Run",s=v(),c=u("button"),c.textContent="Kill",p=v(),w&&w.c(),h=v(),f=u("div"),M=u("input"),m=v(),d=u("input"),a(l,"class","button"),a(c,"class","button"),a(M,"placeholder","Working directory"),a(d,"class","env-vars svelte-1g38c1n"),a(d,"placeholder","Environment variables")},m(P,C){O(P,t,C),r(t,n),r(n,i),E(i,e[0]),r(n,o),r(n,l),r(n,s),r(n,c),r(n,p),w&&w.m(n,null),r(t,h),r(t,f),r(f,M),E(M,e[1]),r(f,m),r(f,d),E(d,e[2]),b||(k=[T(i,"input",e[9]),T(l,"click",e[5]),T(c,"click",e[6]),T(M,"input",e[11]),T(d,"input",e[12])],b=!0)},p(P,[C]){C&1&&i.value!==P[0]&&E(i,P[0]),P[4]?w?w.p(P,C):(w=Ao(P),w.c(),w.m(n,null)):w&&(w.d(1),w=null),C&2&&M.value!==P[1]&&E(M,P[1]),C&4&&d.value!==P[2]&&E(d,P[2])},i:I,o:I,d(P){P&&W(t),w&&w.d(),b=!1,x(k)}}}function $r(e,t,n){const i=navigator.userAgent.includes("Windows");let o=i?"cmd":"sh",l=i?["/C"]:["-c"],{onMessage:s}=t,c='echo "hello world"',p=null,h="SOMETHING=value ANOTHER=2",f="",M;function m(){return h.split(" ").reduce((F,H)=>{let[A,U]=H.split("=");return Ii(Ui({},F),{[A]:U})},{})}function d(){n(4,M=null);const F=new to(o,[...l,c],{cwd:p||null,env:m()});F.on("close",H=>{s(`command finished with code ${H.code} and signal ${H.signal}`),n(4,M=null)}),F.on("error",H=>s(`command error: "${H}"`)),F.stdout.on("data",H=>s(`command stdout: "${H}"`)),F.stderr.on("data",H=>s(`command stderr: "${H}"`)),F.spawn().then(H=>{n(4,M=H)}).catch(s)}function b(){M.kill().then(()=>s("killed child process")).catch(s)}function k(){M.write(f).catch(s)}function w(){c=this.value,n(0,c)}function P(){f=this.value,n(3,f)}function C(){p=this.value,n(1,p)}function j(){h=this.value,n(2,h)}return e.$$set=F=>{"onMessage"in F&&n(8,s=F.onMessage)},[c,p,h,f,M,d,b,k,s,w,P,C,j]}class Kr extends K{constructor(t){super();X(this,t,$r,Jr,Y,{onMessage:8})}}function Lo(){return _(this,void 0,void 0,function(){function e(){t&&t(),t=void 0}var t;return g(this,function(n){return[2,new Promise(function(i,o){pe("tauri://update-status",function(l){var s;(s=l==null?void 0:l.payload).error?(e(),o(s.error)):s.status==="DONE"&&(e(),i())}).then(function(l){t=l}).catch(function(l){throw e(),l}),Ye("tauri://update-install").catch(function(l){throw e(),l})})]})})}function Eo(){return _(this,void 0,void 0,function(){function e(){t&&t(),t=void 0}var t;return g(this,function(n){return[2,new Promise(function(i,o){ao("tauri://update-available",function(l){var s;s=l==null?void 0:l.payload,e(),i({manifest:s,shouldUpdate:!0})}).catch(function(l){throw e(),l}),pe("tauri://update-status",function(l){var s;(s=l==null?void 0:l.payload).error?(e(),o(s.error)):s.status==="UPTODATE"&&(e(),i({shouldUpdate:!1}))}).then(function(l){t=l}).catch(function(l){throw e(),l}),Ye("tauri://update").catch(function(l){throw e(),l})})]})})}Object.freeze({__proto__:null,installUpdate:Lo,checkUpdate:Eo});function Xr(e){let t,n,i,o,l,s;return{c(){t=u("div"),n=u("button"),n.textContent="Check update",i=v(),o=u("button"),o.textContent="Install update",a(n,"class","button"),a(n,"id","check_update"),a(o,"class","button hidden"),a(o,"id","start_update")},m(c,p){O(c,t,p),r(t,n),r(t,i),r(t,o),l||(s=[T(n,"click",e[0]),T(o,"click",e[1])],l=!0)},p:I,i:I,o:I,d(c){c&&W(t),l=!1,x(s)}}}function Yr(e,t,n){let{onMessage:i}=t,o;$e(async()=>{o=await pe("tauri://update-status",i)}),On(()=>{o&&o()});async function l(){try{document.getElementById("check_update").classList.add("hidden");const{shouldUpdate:c,manifest:p}=await Eo();i(`Should update: ${c}`),i(p),c&&document.getElementById("start_update").classList.remove("hidden")}catch(c){i(c)}}async function s(){try{document.getElementById("start_update").classList.add("hidden"),await Lo(),i("Installation complete, restart required."),await En()}catch(c){i(c)}}return e.$$set=c=>{"onMessage"in c&&n(2,i=c.onMessage)},[l,s,i]}class Qr extends K{constructor(t){super();X(this,t,Yr,Xr,Y,{onMessage:2})}}function Do(e){return _(this,void 0,void 0,function(){return g(this,function(t){return[2,z({__tauriModule:"Clipboard",message:{cmd:"writeText",data:e}})]})})}function Ro(){return _(this,void 0,void 0,function(){return g(this,function(e){return[2,z({__tauriModule:"Clipboard",message:{cmd:"readText"}})]})})}Object.freeze({__proto__:null,writeText:Do,readText:Ro});function Zr(e){let t,n,i,o,l,s,c,p,h;return{c(){t=u("div"),n=u("div"),i=u("input"),o=v(),l=u("button"),l.textContent="Write",s=v(),c=u("button"),c.textContent="Read",a(i,"placeholder","Text to write to the clipboard"),a(l,"type","button"),a(c,"type","button")},m(f,M){O(f,t,M),r(t,n),r(n,i),E(i,e[0]),r(n,o),r(n,l),r(t,s),r(t,c),p||(h=[T(i,"input",e[4]),T(l,"click",e[1]),T(c,"click",e[2])],p=!0)},p(f,[M]){M&1&&i.value!==f[0]&&E(i,f[0])},i:I,o:I,d(f){f&&W(t),p=!1,x(h)}}}function xr(e,t,n){let{onMessage:i}=t,o="clipboard message";function l(){Do(o).then(()=>{i("Wrote to the clipboard")}).catch(i)}function s(){Ro().then(p=>{i(`Clipboard contents: ${p}`)}).catch(i)}function c(){o=this.value,n(0,o)}return e.$$set=p=>{"onMessage"in p&&n(3,i=p.onMessage)},[o,l,s,i,c]}class tl extends K{constructor(t){super();X(this,t,xr,Zr,Y,{onMessage:3})}}function el(e){let t;return{c(){t=u("div"),t.innerHTML=`

    Not available for Linux

    - `},m(n,i){O(n,t,i)},p:I,i:I,o:I,d(n){n&&W(t)}}}function nl(e,t,n){let{onMessage:i}=t;const o=window.constraints={audio:!0,video:!0};function l(c){const p=document.querySelector("video"),h=c.getVideoTracks();i("Got stream with constraints:",o),i(`Using video device: ${h[0].label}`),window.stream=c,p.srcObject=c}function s(c){if(c.name==="ConstraintNotSatisfiedError"){const p=o.video;i(`The resolution ${p.width.exact}x${p.height.exact} px is not supported by your device.`)}else c.name==="PermissionDeniedError"&&i("Permissions have not been granted to use your camera and microphone, you need to allow the page access to your devices in order for the demo to work.");i(`getUserMedia error: ${c.name}`,c)}return $e(async()=>{try{const c=await navigator.mediaDevices.getUserMedia(o);l(c)}catch(c){s(c)}}),On(()=>{window.stream.getTracks().forEach(function(c){c.stop()})}),e.$$set=c=>{"onMessage"in c&&n(0,i=c.onMessage)},[i]}class il extends K{constructor(t){super();X(this,t,nl,el,Y,{onMessage:0})}}function ol(e){let t,n,i,o,l,s,c,p,h,f,M,m;return{c(){t=u("input"),n=v(),i=u("input"),o=v(),l=u("button"),l.textContent="Post it.",s=v(),c=u("p"),c.textContent="Result:",p=v(),h=u("pre"),f=S(e[2]),a(l,"type","button")},m(d,b){O(d,t,b),E(t,e[0]),O(d,n,b),O(d,i,b),E(i,e[1]),O(d,o,b),O(d,l,b),O(d,s,b),O(d,c,b),O(d,p,b),O(d,h,b),r(h,f),M||(m=[T(t,"input",e[4]),T(i,"input",e[5]),T(l,"click",e[3])],M=!0)},p(d,[b]){b&1&&t.value!==d[0]&&E(t,d[0]),b&2&&i.value!==d[1]&&E(i,d[1]),b&4&&G(f,d[2])},i:I,o:I,d(d){d&&W(t),d&&W(n),d&&W(i),d&&W(o),d&&W(l),d&&W(s),d&&W(c),d&&W(p),d&&W(h),M=!1,x(m)}}}function rl(e,t,n){let i="baz",o="qux",l=null;async function s(){let h=navigator.userAgent.includes("Windows")?"https://customprotocol.test/example.html":"customprotocol://test/example.html";const M=await(await fetch(h,{method:"POST",body:JSON.stringify({foo:i,bar:o})})).json();n(2,l=JSON.stringify(M))}function c(){i=this.value,n(0,i)}function p(){o=this.value,n(1,o)}return[i,o,l,s,c,p]}class ll extends K{constructor(t){super();X(this,t,rl,ol,Y,{})}}function jo(e,t,n){const i=e.slice();return i[10]=t[n],i}function Fo(e,t,n){const i=e.slice();return i[13]=t[n],i}function Ho(e){let t,n=e[13].label+"",i,o,l,s,c;function p(){return e[9](e[13])}return{c(){t=u("p"),i=S(n),o=v(),a(t,"class",l="nv noselect "+(e[0]===e[13]?"nv_selected":""))},m(h,f){O(h,t,f),r(t,i),r(t,o),s||(c=T(t,"click",p),s=!0)},p(h,f){e=h,f&1&&l!==(l="nv noselect "+(e[0]===e[13]?"nv_selected":""))&&a(t,"class",l)},d(h){h&&W(t),s=!1,c()}}}function sl(e){let t,n=e[10].html+"",i;return{c(){i=Vi(),t=new Xo(i)},m(o,l){t.m(n,o,l),O(o,i,l)},p(o,l){l&2&&n!==(n=o[10].html+"")&&t.p(n)},d(o){o&&W(i),o&&t.d()}}}function ul(e){let t,n=e[10].text+"",i;return{c(){t=u("p"),i=S(n)},m(o,l){O(o,t,l),r(t,i)},p(o,l){l&2&&n!==(n=o[10].text+"")&&G(i,n)},d(o){o&&W(t)}}}function Uo(e){let t;function n(l,s){return l[10].text?ul:sl}let i=n(e),o=i(e);return{c(){o.c(),t=Vi()},m(l,s){o.m(l,s),O(l,t,s)},p(l,s){i===(i=n(l))&&o?o.p(l,s):(o.d(1),o=i(l),o&&(o.c(),o.m(t.parentNode,t)))},d(l){o.d(l),l&&W(t)}}}function al(e){let t,n,i,o,l,s,c,p,h,f,M,m,d,b,k,w,P,C,j,F,H,A,U=e[2],N=[];for(let R=0;RDocumentation
    - Github - Source`,c=v(),p=u("div"),h=u("div");for(let R=0;R{Yi(D,1)}),Jo()}tt?(m=new tt(ut(R)),Ji(m.$$.fragment),Xi(m.$$.fragment,1),$i(m,M,null)):m=null}if(nt&2){$=R[1];let D;for(D=0;D<$.length;D+=1){const Q=jo(R,$,D);B[D]?B[D].p(Q,nt):(B[D]=Uo(Q),B[D].c(),B[D].m(b,null))}for(;D{Ko(cl,()=>{fe("menu_toggle")})});const o=[{label:"Welcome",component:tr},{label:"Messages",component:lr},{label:"CLI",component:ir},{label:"Dialog",component:yr},{label:"File system",component:Tr},{label:"HTTP",component:Or},{label:"HTTP Form",component:ll},{label:"Notifications",component:Er},{label:"Window",component:Ir},{label:"Shortcuts",component:Vr},{label:"Shell",component:Kr},{label:"Updater",component:Qr},{label:"Clipboard",component:tl},{label:"WebRTC",component:il}];let l=o[0],s=Gi([]);Bi(e,s,d=>n(1,i=d));function c(d){n(0,l=d)}function p(d){s.update(b=>[{text:`[${new Date().toLocaleTimeString()}]: `+(typeof d=="string"?d:JSON.stringify(d))},...b])}function h(d){s.update(b=>[{html:d},...b])}function f(){s.update(()=>[])}function M(){Ln("https://tauri.studio/")}return[l,i,o,s,c,p,h,f,M,d=>c(d)]}class fl extends K{constructor(t){super();X(this,t,dl,al,Y,{})}}new fl({target:document.body}); +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))i(r);new MutationObserver(r=>{for(const a of r)if(a.type==="childList")for(const m of a.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&i(m)}).observe(document,{childList:!0,subtree:!0});function n(r){const a={};return r.integrity&&(a.integrity=r.integrity),r.referrerpolicy&&(a.referrerPolicy=r.referrerpolicy),r.crossorigin==="use-credentials"?a.credentials="include":r.crossorigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function i(r){if(r.ep)return;r.ep=!0;const a=n(r);fetch(r.href,a)}})();function $(){}function st(e){return e()}function Xe(){return Object.create(null)}function V(e){e.forEach(st)}function vt(e){return typeof e=="function"}function he(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}let ke;function bt(e,t){return ke||(ke=document.createElement("a")),ke.href=t,e===ke.href}function yt(e){return Object.keys(e).length===0}function wt(e,...t){if(e==null)return $;const n=e.subscribe(...t);return n.unsubscribe?()=>n.unsubscribe():n}function kt(e,t,n){e.$$.on_destroy.push(wt(t,n))}function o(e,t){e.appendChild(t)}function k(e,t,n){e.insertBefore(t,n||null)}function w(e){e.parentNode.removeChild(e)}function Ye(e,t){for(let n=0;ne.removeEventListener(t,n,i)}function l(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function $t(e){return Array.from(e.childNodes)}function Lt(e,t){t=""+t,e.wholeText!==t&&(e.data=t)}class xt{constructor(t=!1){this.is_svg=!1,this.is_svg=t,this.e=this.n=null}c(t){this.h(t)}m(t,n,i=null){this.e||(this.is_svg?this.e=Et(n.nodeName):this.e=f(n.nodeName),this.t=n,this.c(t)),this.i(i)}h(t){this.e.innerHTML=t,this.n=Array.from(this.e.childNodes)}i(t){for(let n=0;n{Le.delete(e),i&&(n&&e.d(1),i())}),e.o(t)}else i&&i()}function Qe(e){e&&e.c()}function Me(e,t,n,i){const{fragment:r,on_mount:a,on_destroy:m,after_update:c}=e.$$;r&&r.m(t,n),i||We(()=>{const u=a.map(st).filter(vt);m?m.push(...u):V(u),e.$$.on_mount=[]}),c.forEach(We)}function Re(e,t){const n=e.$$;n.fragment!==null&&(V(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function Nt(e,t){e.$$.dirty[0]===-1&&(ce.push(e),Ot(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const O=H.length?H[0]:S;return d.ctx&&r(d.ctx[v],d.ctx[v]=O)&&(!d.skip_bound&&d.bound[v]&&d.bound[v](O),E&&Nt(e,v)),S}):[],d.update(),E=!0,V(d.before_update),d.fragment=i?i(d.ctx):!1,t.target){if(t.hydrate){const v=$t(t.target);d.fragment&&d.fragment.l(v),v.forEach(w)}else d.fragment&&d.fragment.c();t.intro&&Ae(e.$$.fragment),Me(e,t.target,t.anchor,t.customElement),ut()}ue(u)}class Oe{$destroy(){Re(this,1),this.$destroy=$}$on(t,n){const i=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return i.push(n),()=>{const r=i.indexOf(n);r!==-1&&i.splice(r,1)}}$set(t){this.$$set&&!yt(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const K=[];function It(e,t=$){let n;const i=new Set;function r(c){if(he(e,c)&&(e=c,n)){const u=!K.length;for(const d of i)d[1](),K.push(d,e);if(u){for(let d=0;d{i.delete(d),i.size===0&&(n(),n=null)}}return{set:r,update:a,subscribe:m}}function Wt(e){let t;return{c(){t=f("p"),t.innerHTML=`This is a demo of Tauri's API capabilities using the @tauri-apps/api package. It's used as the main validation app, serving as the test bed of our + development process. In the future, this app will be used on Tauri's integration + tests.`},m(n,i){k(n,t,i)},p:$,i:$,o:$,d(n){n&&w(t)}}}class At extends Oe{constructor(t){super(),Se(this,t,null,Wt,he,{})}}var Mt=Object.defineProperty,dt=(e,t)=>{for(var n in t)Mt(e,n,{get:t[n],enumerable:!0})},ft=(e,t,n)=>{if(!t.has(e))throw TypeError("Cannot "+n)},Ze=(e,t,n)=>(ft(e,t,"read from private field"),n?n.call(e):t.get(e)),Rt=(e,t,n)=>{if(t.has(e))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(e):t.set(e,n)},Pt=(e,t,n,i)=>(ft(e,t,"write to private field"),i?i.call(e,n):t.set(e,n),n),Ht={};dt(Ht,{Channel:()=>ht,PluginListener:()=>mt,addPluginListener:()=>qt,convertFileSrc:()=>Ut,invoke:()=>P,transformCallback:()=>fe});function jt(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function fe(e,t=!1){let n=jt(),i=`_${n}`;return Object.defineProperty(window,i,{value:r=>(t&&Reflect.deleteProperty(window,i),e==null?void 0:e(r)),writable:!1,configurable:!0}),n}var ae,ht=class{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,Rt(this,ae,()=>{}),this.id=fe(e=>{Ze(this,ae).call(this,e)})}set onmessage(e){Pt(this,ae,e)}get onmessage(){return Ze(this,ae)}toJSON(){return`__CHANNEL__:${this.id}`}};ae=new WeakMap;var mt=class{constructor(e,t,n){this.plugin=e,this.event=t,this.channelId=n}async unregister(){return P(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}};async function qt(e,t,n){let i=new ht;return i.onmessage=n,P(`plugin:${e}|register_listener`,{event:t,handler:i}).then(()=>new mt(e,t,i.id))}async function P(e,t={}){return new Promise((n,i)=>{let r=fe(m=>{n(m),Reflect.deleteProperty(window,`_${a}`)},!0),a=fe(m=>{i(m),Reflect.deleteProperty(window,`_${r}`)},!0);window.__TAURI_IPC__({cmd:e,callback:r,error:a,...t})})}function Ut(e,t="asset"){let n=encodeURIComponent(e);return navigator.userAgent.includes("Windows")?`https://${t}.localhost/${n}`:`${t}://localhost/${n}`}var zt={};dt(zt,{TauriEvent:()=>pt,emit:()=>_t,listen:()=>Pe,once:()=>Ft});var pt=(e=>(e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_CREATED="tauri://window-created",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_FILE_DROP="tauri://file-drop",e.WINDOW_FILE_DROP_HOVER="tauri://file-drop-hover",e.WINDOW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled",e.MENU="tauri://menu",e))(pt||{});async function gt(e,t){await P("plugin:event|unlisten",{event:e,eventId:t})}async function Pe(e,t,n){return P("plugin:event|listen",{event:e,windowLabel:n==null?void 0:n.target,handler:fe(t)}).then(i=>async()=>gt(e,i))}async function Ft(e,t,n){return Pe(e,i=>{t(i),gt(e,i.id).catch(()=>{})},n)}async function _t(e,t,n){await P("plugin:event|emit",{event:e,windowLabel:n==null?void 0:n.target,payload:t})}function Vt(e){let t,n,i,r,a,m,c,u;return{c(){t=f("div"),n=f("button"),n.textContent="Call Log API",i=g(),r=f("button"),r.textContent="Call Request (async) API",a=g(),m=f("button"),m.textContent="Send event to Rust",l(n,"class","btn"),l(n,"id","log"),l(r,"class","btn"),l(r,"id","request"),l(m,"class","btn"),l(m,"id","event")},m(d,E){k(d,t,E),o(t,n),o(t,i),o(t,r),o(t,a),o(t,m),c||(u=[F(n,"click",e[0]),F(r,"click",e[1]),F(m,"click",e[2])],c=!0)},p:$,i:$,o:$,d(d){d&&w(t),c=!1,V(u)}}}function Bt(e,t,n){let{onMessage:i}=t,r;xe(async()=>{r=await Pe("rust-event",i)}),at(()=>{r&&r()});function a(){P("log_operation",{event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}function m(){P("perform_request",{endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(i).catch(i)}function c(){_t("js-event","this is the payload string")}return e.$$set=u=>{"onMessage"in u&&n(3,i=u.onMessage)},[a,m,c,i]}class Gt extends Oe{constructor(t){super(),Se(this,t,Bt,Vt,he,{onMessage:3})}}function Xt(e){let t;return{c(){t=f("div"),t.innerHTML=`
    Not available for Linux
    + `,l(t,"class","flex flex-col gap-2")},m(n,i){k(n,t,i)},p:$,i:$,o:$,d(n){n&&w(t)}}}function Yt(e,t,n){let{onMessage:i}=t;const r=window.constraints={audio:!0,video:!0};function a(c){const u=document.querySelector("video"),d=c.getVideoTracks();i("Got stream with constraints:",r),i(`Using video device: ${d[0].label}`),window.stream=c,u.srcObject=c}function m(c){if(c.name==="ConstraintNotSatisfiedError"){const u=r.video;i(`The resolution ${u.width.exact}x${u.height.exact} px is not supported by your device.`)}else c.name==="PermissionDeniedError"&&i("Permissions have not been granted to use your camera and microphone, you need to allow the page access to your devices in order for the demo to work.");i(`getUserMedia error: ${c.name}`,c)}return xe(async()=>{try{const c=await navigator.mediaDevices.getUserMedia(r);a(c)}catch(c){m(c)}}),at(()=>{window.stream.getTracks().forEach(function(c){c.stop()})}),e.$$set=c=>{"onMessage"in c&&n(0,i=c.onMessage)},[i]}class Jt extends Oe{constructor(t){super(),Se(this,t,Yt,Xt,he,{onMessage:0})}}function et(e,t,n){const i=e.slice();return i[25]=t[n],i}function tt(e,t,n){const i=e.slice();return i[28]=t[n],i}function Kt(e){let t;return{c(){t=f("span"),l(t,"class","i-codicon-menu animate-duration-300ms animate-fade-in")},m(n,i){k(n,t,i)},d(n){n&&w(t)}}}function Qt(e){let t;return{c(){t=f("span"),l(t,"class","i-codicon-close animate-duration-300ms animate-fade-in")},m(n,i){k(n,t,i)},d(n){n&&w(t)}}}function Zt(e){let t,n;return{c(){t=Q(`Switch to Dark mode + `),n=f("div"),l(n,"class","i-ph-moon")},m(i,r){k(i,t,r),k(i,n,r)},d(i){i&&w(t),i&&w(n)}}}function en(e){let t,n;return{c(){t=Q(`Switch to Light mode + `),n=f("div"),l(n,"class","i-ph-sun")},m(i,r){k(i,t,r),k(i,n,r)},d(i){i&&w(t),i&&w(n)}}}function tn(e){let t,n,i,r,a=e[28].label+"",m,c,u,d;function E(){return e[14](e[28])}return{c(){t=f("a"),n=f("div"),i=g(),r=f("p"),m=Q(a),l(n,"class",e[28].icon+" mr-2"),l(t,"href","##"),l(t,"class",c="nv "+(e[1]===e[28]?"nv_selected":""))},m(v,S){k(v,t,S),o(t,n),o(t,i),o(t,r),o(r,m),u||(d=F(t,"click",E),u=!0)},p(v,S){e=v,S&2&&c!==(c="nv "+(e[1]===e[28]?"nv_selected":""))&&l(t,"class",c)},d(v){v&&w(t),u=!1,d()}}}function nt(e){let t,n=e[28]&&tn(e);return{c(){n&&n.c(),t=lt()},m(i,r){n&&n.m(i,r),k(i,t,r)},p(i,r){i[28]&&n.p(i,r)},d(i){n&&n.d(i),i&&w(t)}}}function it(e){let t,n=e[25].html+"",i;return{c(){t=new xt(!1),i=lt(),t.a=i},m(r,a){t.m(n,r,a),k(r,i,a)},p(r,a){a&16&&n!==(n=r[25].html+"")&&t.p(n)},d(r){r&&w(i),r&&t.d()}}}function nn(e){let t,n,i,r,a,m,c,u,d,E,v,S,H,O,Z,I,me,b,j,C,q,B,ee,te,pe,ge,p,_,D,W,A,ne,U=e[1].label+"",Te,He,_e,ie,y,je,N,ve,qe,G,be,Ue,re,ze,oe,se,Ce,Fe;function Ve(s,T){return s[0]?Qt:Kt}let ye=Ve(e),M=ye(e);function Be(s,T){return s[2]?en:Zt}let we=Be(e),R=we(e),X=e[5],L=[];for(let s=0;s`,me=g(),b=f("a"),b.innerHTML=`GitHub + `,j=g(),C=f("a"),C.innerHTML=`Source + `,q=g(),B=f("br"),ee=g(),te=f("div"),pe=g(),ge=f("br"),p=g(),_=f("div");for(let s=0;s',ze=g(),oe=f("div");for(let s=0;s{Re(h,1)}),Dt()}Y?(y=new Y(Ge(s)),Qe(y.$$.fragment),Ae(y.$$.fragment,1),Me(y,ie,null)):y=null}if(T&16){J=s[4];let h;for(h=0;h{n(2,u=localStorage&&localStorage.getItem("theme")=="dark"),ot(u)});function d(){n(2,u=!u),ot(u)}let E=It([]);kt(e,E,p=>n(4,i=p));function v(p){E.update(_=>[{html:`
    [${new Date().toLocaleTimeString()}]: `+(typeof p=="string"?p:JSON.stringify(p,null,1))+"
    "},..._])}function S(p){E.update(_=>[{html:`
    [${new Date().toLocaleTimeString()}]: `+p+"
    "},..._])}function H(){E.update(()=>[])}let O,Z,I;function me(p){I=p.clientY;const _=window.getComputedStyle(O);Z=parseInt(_.height,10);const D=A=>{const ne=A.clientY-I,U=Z-ne;n(3,O.style.height=`${U{document.removeEventListener("mouseup",W),document.removeEventListener("mousemove",D)};document.addEventListener("mouseup",W),document.addEventListener("mousemove",D)}let b=!1,j,C,q=!1,B=0,ee=0;const te=(p,_,D)=>Math.min(Math.max(_,p),D);xe(()=>{n(13,j=document.querySelector("#sidebar")),C=document.querySelector("#sidebarToggle"),document.addEventListener("click",p=>{C.contains(p.target)?n(0,b=!b):b&&!j.contains(p.target)&&n(0,b=!1)}),document.addEventListener("touchstart",p=>{if(C.contains(p.target))return;const _=p.touches[0].clientX;(0<_&&_<20&&!b||b)&&(q=!0,B=_)}),document.addEventListener("touchmove",p=>{if(q){const _=p.touches[0].clientX;ee=_;const D=(_-B)/10;j.style.setProperty("--translate-x",`-${te(0,b?0-D:18.75-D,18.75)}rem`)}}),document.addEventListener("touchend",()=>{if(q){const p=(ee-B)/10;n(0,b=b?p>-(18.75/2):p>18.75/2)}q=!1})});const pe=p=>{c(p),n(0,b=!1)};function ge(p){Ne[p?"unshift":"push"](()=>{O=p,n(3,O)})}return e.$$.update=()=>{if(e.$$.dirty&1){const p=document.querySelector("#sidebar");p&&rn(p,b)}},[b,m,u,O,i,a,c,d,E,v,S,H,me,j,pe,ge]}class sn extends Oe{constructor(t){super(),Se(this,t,on,nn,he,{})}}new sn({target:document.querySelector("#app")}); diff --git a/examples/api/dist/assets/vendor.js b/examples/api/dist/assets/vendor.js deleted file mode 100644 index 3a009e0cb5cd..000000000000 --- a/examples/api/dist/assets/vendor.js +++ /dev/null @@ -1,9 +0,0 @@ -function w(){}function U(e){return e()}function z(){return Object.create(null)}function K(e){e.forEach(U)}function se(e){return typeof e=="function"}function fe(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}function oe(e){return Object.keys(e).length===0}function ae(e,...t){if(e==null)return w;const n=e.subscribe(...t);return n.unsubscribe?()=>n.unsubscribe():n}function Ae(e,t,n){e.$$.on_destroy.push(ae(t,n))}function Se(e,t){e.appendChild(t)}function ue(e,t,n){e.insertBefore(t,n||null)}function F(e){e.parentNode.removeChild(e)}function je(e,t){for(let n=0;ne.removeEventListener(t,n,i)}function Le(e){return function(t){return t.preventDefault(),e.call(this,t)}}function Te(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function Be(e){return e===""?null:+e}function le(e){return Array.from(e.childNodes)}function De(e,t){t=""+t,e.wholeText!==t&&(e.data=t)}function He(e,t){e.value=t==null?"":t}function Ne(e,t){for(let n=0;n{S.delete(e),i&&(n&&e.d(1),i())}),e.o(t)}}function Xe(e){e&&e.c()}function ye(e,t,n,i){const{fragment:r,on_mount:c,on_destroy:o,after_update:f}=e.$$;r&&r.m(t,n),i||P(()=>{const s=c.map(U).filter(se);o?o.push(...s):K(s),e.$$.on_mount=[]}),f.forEach(P)}function ge(e,t){const n=e.$$;n.fragment!==null&&(K(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function _e(e,t){e.$$.dirty[0]===-1&&(O.push(e),pe(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const y=m.length?m[0]:h;return s.ctx&&r(s.ctx[a],s.ctx[a]=y)&&(!s.skip_bound&&s.bound[a]&&s.bound[a](y),l&&_e(e,a)),h}):[],s.update(),l=!0,K(s.before_update),s.fragment=i?i(s.ctx):!1,t.target){if(t.hydrate){const a=le(t.target);s.fragment&&s.fragment.l(a),a.forEach(F)}else s.fragment&&s.fragment.c();t.intro&&me(e.$$.fragment),ye(e,t.target,t.anchor,t.customElement),J()}C(f)}class Qe{$destroy(){ge(this,1),this.$destroy=w}$on(t,n){const i=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return i.push(n),()=>{const r=i.indexOf(n);r!==-1&&i.splice(r,1)}}$set(t){this.$$set&&!oe(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const x=[];function We(e,t=w){let n;const i=[];function r(f){if(fe(e,f)&&(e=f,n)){const s=!x.length;for(let l=0;l{const a=i.indexOf(l);a!==-1&&i.splice(a,1),i.length===0&&(n(),n=null)}}return{set:r,update:c,subscribe:o}}/*! - * hotkeys-js v3.8.5 - * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies. - * - * Copyright (c) 2021 kenny wong - * http://jaywcjlove.github.io/hotkeys - * - * Licensed under the MIT license. - */var B=typeof navigator!="undefined"?navigator.userAgent.toLowerCase().indexOf("firefox")>0:!1;function D(e,t,n){e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent&&e.attachEvent("on".concat(t),function(){n(window.event)})}function Q(e,t){for(var n=t.slice(0,t.length-1),i=0;i=0;)t[n-1]+=",",t.splice(n,1),n=t.lastIndexOf("");return t}function be(e,t){for(var n=e.length>=t.length?e:t,i=e.length>=t.length?t:e,r=!0,c=0;c=0&&u.splice(n,1),e.key&&e.key.toLowerCase()==="meta"&&u.splice(0,u.length),(t===93||t===224)&&(t=91),t in p){p[t]=!1;for(var i in v)v[i]===t&&(_[i]=!1)}}function $e(e){if(!e)Object.keys(d).forEach(function(o){return delete d[o]});else if(Array.isArray(e))e.forEach(function(o){o.key&&H(o)});else if(typeof e=="object")e.key&&H(e);else if(typeof e=="string"){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i1?Q(v,l):[];d[m]=d[m].map(function(g){var re=r?g.method===r:!0;return re&&g.scope===i&&be(g.mods,y)?{}:g})}})};function ne(e,t,n){var i;if(t.scope===n||t.scope==="all"){i=t.mods.length>0;for(var r in p)Object.prototype.hasOwnProperty.call(p,r)&&(!p[r]&&t.mods.indexOf(+r)>-1||p[r]&&t.mods.indexOf(+r)===-1)&&(i=!1);(t.mods.length===0&&!p[16]&&!p[18]&&!p[17]&&!p[91]||i||t.shortcut==="*")&&t.method(e,t)===!1&&(e.preventDefault?e.preventDefault():e.returnValue=!1,e.stopPropagation&&e.stopPropagation(),e.cancelBubble&&(e.cancelBubble=!0))}}function ie(e){var t=d["*"],n=e.keyCode||e.which||e.charCode;if(!!_.filter.call(this,e)){if((n===93||n===224)&&(n=91),u.indexOf(n)===-1&&n!==229&&u.push(n),["ctrlKey","altKey","shiftKey","metaKey"].forEach(function(y){var g=Z[y];e[y]&&u.indexOf(g)===-1?u.push(g):!e[y]&&u.indexOf(g)>-1?u.splice(u.indexOf(g),1):y==="metaKey"&&e[y]&&u.length===3&&(e.ctrlKey||e.shiftKey||e.altKey||(u=u.slice(u.indexOf(g))))}),n in p){p[n]=!0;for(var i in v)v[i]===n&&(_[i]=!0);if(!t)return}for(var r in p)Object.prototype.hasOwnProperty.call(p,r)&&(p[r]=e[Z[r]]);e.getModifierState&&!(e.altKey&&!e.ctrlKey)&&e.getModifierState("AltGraph")&&(u.indexOf(17)===-1&&u.push(17),u.indexOf(18)===-1&&u.push(18),p[17]=!0,p[18]=!0);var c=$();if(t)for(var o=0;o-1}function _(e,t,n){u=[];var i=W(e),r=[],c="all",o=document,f=0,s=!1,l=!0,a="+";for(n===void 0&&typeof t=="function"&&(n=t),Object.prototype.toString.call(t)==="[object Object]"&&(t.scope&&(c=t.scope),t.element&&(o=t.element),t.keyup&&(s=t.keyup),t.keydown!==void 0&&(l=t.keydown),typeof t.splitKey=="string"&&(a=t.splitKey)),typeof t=="string"&&(c=t);f1&&(r=Q(v,e)),e=e[e.length-1],e=e==="*"?"*":k(e),e in d||(d[e]=[]),d[e].push({keyup:s,keydown:l,scope:c,mods:r,shortcut:i[f],method:n,key:i[f],splitKey:a});typeof o!="undefined"&&!Ee(o)&&window&&(ee.push(o),D(o,"keydown",function(h){ie(h)}),D(window,"focus",function(){u=[]}),D(o,"keyup",function(h){ie(h),Oe(h)}))}var N={setScope:te,getScope:$,deleteScope:Ke,getPressedKeyCodes:ve,isPressed:xe,filter:we,unbind:$e};for(var q in N)Object.prototype.hasOwnProperty.call(N,q)&&(_[q]=N[q]);if(typeof window!="undefined"){var Ce=window.hotkeys;_.noConflict=function(e){return e&&window.hotkeys===_&&(window.hotkeys=Ce),_},window.hotkeys=_}export{Xe as A,ye as B,Ve as C,Re as D,me as E,ge as F,Ge as G,_ as H,Ue as I,Qe as S,ke as a,Te as b,ue as c,Se as d,ce as e,De as f,F as g,Fe as h,Je as i,He as j,je as k,Pe as l,V as m,w as n,ze as o,Le as p,P as q,K as r,fe as s,G as t,Ne as u,qe as v,Be as w,Ae as x,We as y,Me as z}; diff --git a/examples/api/dist/global.css b/examples/api/dist/global.css deleted file mode 100644 index 3645dd44467e..000000000000 --- a/examples/api/dist/global.css +++ /dev/null @@ -1,192 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Tauri&display=swap'); - -* { - font-family: Tauri, Arial, Helvetica, sans-serif; -} - -body { - background: rgb(24, 25, 26, 0.8); -} - -.noselect { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.logo-container { - width: 95%; - margin: 0px auto; -} - -.logo-link { - font-weight: 700; - position: absolute; - top: 150px; - right: 10px; -} - -.logo { - cursor: pointer; -} - -#response { - height: 100%; - max-height: 100%; - margin-top: 1em; - background: rgb(36, 37, 38, 0.8); - color: #f0f4f5; - border: solid 1px rgba(255, 255, 255, 0.055); - box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%); - font-family: 'Courier New', Courier, monospace; - font-size: 12px; - word-wrap: break-word; - padding: 0px 15px; - overflow-y: auto; -} - -input, -select, -textarea { - background: rgb(53, 53, 53, 0.9); - color: #fff; - font-family: system-ui, sans-serif; - border: none !important; - border-radius: 0.25rem; - font-size: 1rem; - line-height: 1.2; - padding: 0.25rem 0.5rem; - margin: 0.25rem; - transition: 0.2s ease; -} - -button:hover, -button:focus { - background: #ffe07a; -} - -button:focus { - outline: 1px solid #fff; - outline-offset: -4px; -} - -button:active { - transform: scale(0.99); -} - -.button { - border: 0; - border-radius: 0.25rem; - background: #67d6ed; - color: rgb(0, 0, 0); - font-family: system-ui, sans-serif; - font-size: 1rem; - line-height: 1.2; - white-space: nowrap; - text-decoration: none; - padding: 0.25rem 0.5rem; - margin: 0.25rem; - cursor: pointer; - transition: 0.2s ease; -} - -.dark-link { - color: white; - text-decoration: none !important; - padding: 0.5em; - background: rgb(36, 37, 38); - transition: 0.2s ease; - border: solid 1px rgba(255, 255, 255, 0.055); - box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%); -} - -.dark-link:hover { - background: #3d392a; -} - -.nv { - color: #fff; - cursor: pointer; - transition: 0.25s ease; -} - -.nv:hover { - color: #ffe07a; - padding-left: 8px; - border-left: solid 5px #ffe07a; -} - -.nv_selected { - color: #67d6ed; - padding-left: 8px; - border-left: solid 5px #67d6ed; -} - -.content { - background: rgb(36, 37, 38, 0.5); - color: #f0f4f5; - padding: 20px; - width: 100%; - border: solid 1px rgba(255, 255, 255, 0.055); - box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%); -} - -main { - height: 100%; -} - -[type='radio']:checked ~ label { - background: rgb(36, 37, 38); - color: #67d6ed; - border-bottom: 1px solid transparent; - z-index: 2; -} - -[type='radio']:checked ~ label ~ .content { - z-index: 1; -} - -.flex { - display: flex; -} - -.row { - flex-direction: row; -} - -.col { - flex-direction: column; -} - -.just-around { - justify-content: space-between; -} - -.hidden { - display: none; -} - -.alert { - width: auto; - height: 40px; - display: flex; - justify-content: left; - align-items: center; - border-radius: 5px; - padding-left: 10px; - padding-right: 40px; - font-size: 15px; - color: #000; - margin-bottom: 10px; - margin-top: 10px; - box-shadow: rgba(0, 0, 0, 0.06) 0px 0px 10px; - border-left: 6px solid #ff0000; - background: #f0f4f5; -} - -#file-response { - height: 400px; -} diff --git a/examples/api/dist/index.html b/examples/api/dist/index.html index 988c1f27a1b9..a3d79e1136b6 100644 --- a/examples/api/dist/index.html +++ b/examples/api/dist/index.html @@ -1,12 +1,10 @@ - + - Svelte + Vite App - diff --git a/examples/api/dist/tauri logo.png b/examples/api/dist/tauri_logo.png similarity index 100% rename from examples/api/dist/tauri logo.png rename to examples/api/dist/tauri_logo.png diff --git a/examples/api/index.html b/examples/api/index.html index c2847e399947..3b39b0f66a9f 100644 --- a/examples/api/index.html +++ b/examples/api/index.html @@ -1,8 +1,7 @@ - + - Svelte + Vite App diff --git a/examples/api/isolation-dist/index.html b/examples/api/isolation-dist/index.html index 1a12b0bc4607..27c8d3f396ba 100644 --- a/examples/api/isolation-dist/index.html +++ b/examples/api/isolation-dist/index.html @@ -1,10 +1,10 @@ - - - Isolation Secure Script - - - - + + + Isolation Secure Script + + + + diff --git a/examples/api/isolation-dist/index.js b/examples/api/isolation-dist/index.js index f3a30af5d778..7e2df30de85d 100644 --- a/examples/api/isolation-dist/index.js +++ b/examples/api/isolation-dist/index.js @@ -1,3 +1,7 @@ -window.__TAURI_ISOLATION_HOOK__= (payload) => { +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +window.__TAURI_ISOLATION_HOOK__ = (payload) => { return payload } diff --git a/examples/api/package.json b/examples/api/package.json index 51b706c4c3bc..29cff8b61354 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -3,18 +3,22 @@ "version": "1.0.0", "type": "module", "scripts": { - "dev": "vite --clearScreen false --port 5000", + "dev": "vite --clearScreen false", "build": "vite build", "serve": "vite preview", - "tauri": "node ../../tooling/cli.rs/node/tauri.js" + "tauri": "node ../../tooling/cli/node/tauri.js" }, "dependencies": { "@tauri-apps/api": "../../tooling/api/dist", - "hotkeys-js": "^3.8.5" + "@zerodevx/svelte-json-view": "0.2.1" }, "devDependencies": { - "svelte": "3.35.0", - "@sveltejs/vite-plugin-svelte": "^1.0.0-next.11", - "vite": "^2.6.4" + "@iconify-json/codicon": "^1.1.10", + "@iconify-json/ph": "^1.1.1", + "@sveltejs/vite-plugin-svelte": "^1.0.1", + "internal-ip": "^7.0.0", + "svelte": "^3.49.0", + "unocss": "^0.39.3", + "vite": "^3.0.9" } -} \ No newline at end of file +} diff --git a/examples/api/public/global.css b/examples/api/public/global.css deleted file mode 100644 index 3645dd44467e..000000000000 --- a/examples/api/public/global.css +++ /dev/null @@ -1,192 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Tauri&display=swap'); - -* { - font-family: Tauri, Arial, Helvetica, sans-serif; -} - -body { - background: rgb(24, 25, 26, 0.8); -} - -.noselect { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.logo-container { - width: 95%; - margin: 0px auto; -} - -.logo-link { - font-weight: 700; - position: absolute; - top: 150px; - right: 10px; -} - -.logo { - cursor: pointer; -} - -#response { - height: 100%; - max-height: 100%; - margin-top: 1em; - background: rgb(36, 37, 38, 0.8); - color: #f0f4f5; - border: solid 1px rgba(255, 255, 255, 0.055); - box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%); - font-family: 'Courier New', Courier, monospace; - font-size: 12px; - word-wrap: break-word; - padding: 0px 15px; - overflow-y: auto; -} - -input, -select, -textarea { - background: rgb(53, 53, 53, 0.9); - color: #fff; - font-family: system-ui, sans-serif; - border: none !important; - border-radius: 0.25rem; - font-size: 1rem; - line-height: 1.2; - padding: 0.25rem 0.5rem; - margin: 0.25rem; - transition: 0.2s ease; -} - -button:hover, -button:focus { - background: #ffe07a; -} - -button:focus { - outline: 1px solid #fff; - outline-offset: -4px; -} - -button:active { - transform: scale(0.99); -} - -.button { - border: 0; - border-radius: 0.25rem; - background: #67d6ed; - color: rgb(0, 0, 0); - font-family: system-ui, sans-serif; - font-size: 1rem; - line-height: 1.2; - white-space: nowrap; - text-decoration: none; - padding: 0.25rem 0.5rem; - margin: 0.25rem; - cursor: pointer; - transition: 0.2s ease; -} - -.dark-link { - color: white; - text-decoration: none !important; - padding: 0.5em; - background: rgb(36, 37, 38); - transition: 0.2s ease; - border: solid 1px rgba(255, 255, 255, 0.055); - box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%); -} - -.dark-link:hover { - background: #3d392a; -} - -.nv { - color: #fff; - cursor: pointer; - transition: 0.25s ease; -} - -.nv:hover { - color: #ffe07a; - padding-left: 8px; - border-left: solid 5px #ffe07a; -} - -.nv_selected { - color: #67d6ed; - padding-left: 8px; - border-left: solid 5px #67d6ed; -} - -.content { - background: rgb(36, 37, 38, 0.5); - color: #f0f4f5; - padding: 20px; - width: 100%; - border: solid 1px rgba(255, 255, 255, 0.055); - box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%); -} - -main { - height: 100%; -} - -[type='radio']:checked ~ label { - background: rgb(36, 37, 38); - color: #67d6ed; - border-bottom: 1px solid transparent; - z-index: 2; -} - -[type='radio']:checked ~ label ~ .content { - z-index: 1; -} - -.flex { - display: flex; -} - -.row { - flex-direction: row; -} - -.col { - flex-direction: column; -} - -.just-around { - justify-content: space-between; -} - -.hidden { - display: none; -} - -.alert { - width: auto; - height: 40px; - display: flex; - justify-content: left; - align-items: center; - border-radius: 5px; - padding-left: 10px; - padding-right: 40px; - font-size: 15px; - color: #000; - margin-bottom: 10px; - margin-top: 10px; - box-shadow: rgba(0, 0, 0, 0.06) 0px 0px 10px; - border-left: 6px solid #ff0000; - background: #f0f4f5; -} - -#file-response { - height: 400px; -} diff --git a/examples/api/public/tauri logo.png b/examples/api/public/tauri_logo.png similarity index 100% rename from examples/api/public/tauri logo.png rename to examples/api/public/tauri_logo.png diff --git a/examples/api/src-tauri/.gitignore b/examples/api/src-tauri/.gitignore index c1237045915f..e5f47c4d3d32 100644 --- a/examples/api/src-tauri/.gitignore +++ b/examples/api/src-tauri/.gitignore @@ -1,4 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ -WixTools + +# cargo-mobile +.cargo/ +/gen diff --git a/examples/api/src-tauri/.license_template b/examples/api/src-tauri/.license_template deleted file mode 100644 index 9601f8a1b49f..000000000000 --- a/examples/api/src-tauri/.license_template +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/examples/api/src-tauri/.taurignore b/examples/api/src-tauri/.taurignore new file mode 100644 index 000000000000..cbff5293847b --- /dev/null +++ b/examples/api/src-tauri/.taurignore @@ -0,0 +1 @@ +tauri-plugin-sample/ \ No newline at end of file diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index c7af093a03ed..e4bcbf9ab0b3 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -8,38 +8,32 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - [[package]] name = "aead" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ + "crypto-common", "generic-array", ] [[package]] name = "aes" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", "cipher", "cpufeatures", - "opaque-debug", ] [[package]] name = "aes-gcm" -version = "0.9.4" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead", "aes", @@ -51,153 +45,143 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "alloc-no-stdlib" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] -name = "anyhow" -version = "1.0.53" +name = "alloc-stdlib" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" - -[[package]] -name = "api" -version = "0.1.0" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "serde", - "serde_json", - "tauri", - "tauri-build", + "alloc-no-stdlib", ] [[package]] -name = "arrayref" -version = "0.3.6" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] -name = "arrayvec" -version = "0.5.2" +name = "android_log-sys" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" [[package]] -name = "arrayvec" -version = "0.7.2" +name = "android_logger" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "8619b80c242aa7bd638b5c7ddd952addeecb71f69c75e33f1d47b2804f8f883a" +dependencies = [ + "android_log-sys", + "env_logger", + "log", + "once_cell", +] [[package]] -name = "async-broadcast" -version = "0.3.4" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90622698a1218e0b2fb846c97b5f19a0831f6baddee73d9454156365ccfa473b" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "easy-parallel", - "event-listener", - "futures-core", + "libc", ] [[package]] -name = "async-channel" -version = "1.6.1" +name = "anstream" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", ] [[package]] -name = "async-executor" -version = "1.4.1" +name = "anstyle" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", -] +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] -name = "async-io" -version = "1.6.0" +name = "anstyle-parse" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" dependencies = [ - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "winapi", + "utf8parse", ] [[package]] -name = "async-lock" -version = "2.4.0" +name = "anstyle-query" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "event-listener", + "windows-sys 0.48.0", ] [[package]] -name = "async-recursion" -version = "0.3.2" +name = "anstyle-wincon" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ - "proc-macro2", - "quote", - "syn", + "anstyle", + "windows-sys 0.48.0", ] [[package]] -name = "async-task" -version = "4.1.0" +name = "anyhow" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] -name = "async-trait" -version = "0.1.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +name = "api" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-cli", + "tauri-plugin-log", + "tauri-plugin-sample", + "tiny_http", + "window-shadows", ] +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "atk" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +checksum = "39991bc421ddf72f70159011b323ff49b0f783cc676a7287c59453da2e2531cf" dependencies = [ "atk-sys", "bitflags", @@ -207,43 +191,14 @@ dependencies = [ [[package]] name = "atk-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" -dependencies = [ - "glib-sys 0.15.5", - "gobject-sys 0.15.5", - "libc", - "system-deps 6.0.1", -] - -[[package]] -name = "attohttpc" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69e13a99a7e6e070bb114f7ff381e58c7ccc188630121fc4c2fe4bcf24cd072" -dependencies = [ - "flate2", - "http", - "log", - "native-tls", - "openssl", - "serde", - "serde_json", - "serde_urlencoded", - "url", - "wildmatch", -] - -[[package]] -name = "atty" -version = "0.2.14" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148" dependencies = [ - "hermit-abi", + "glib-sys", + "gobject-sys", "libc", - "winapi", + "system-deps", ] [[package]] @@ -254,18 +209,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "bincode" -version = "1.3.3" +name = "base64" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -273,32 +225,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq", -] - -[[package]] -name = "blake3" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" -dependencies = [ - "arrayref", - "arrayvec 0.7.2", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.1", - "rayon", -] - [[package]] name = "block" version = "0.1.6" @@ -307,146 +233,137 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] -name = "block-buffer" -version = "0.10.2" +name = "brotli" +version = "3.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" dependencies = [ - "generic-array", + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", ] [[package]] -name = "bstr" -version = "0.2.17" +name = "brotli-decompressor" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" dependencies = [ - "memchr", + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] -name = "byteorder" -version = "1.4.3" +name = "byte-unit" +version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" +dependencies = [ + "serde", + "utf8-width", +] [[package]] -name = "bytes" -version = "1.1.0" +name = "bytemuck" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] -name = "bzip2" -version = "0.4.3" +name = "byteorder" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" -dependencies = [ - "bzip2-sys", - "libc", -] +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" +name = "bytes" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" dependencies = [ - "cc", - "libc", - "pkg-config", + "serde", ] -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - [[package]] name = "cairo-rs" -version = "0.15.1" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b869e97a87170f96762f9f178eae8c461147e722ba21dd8814105bf5716bf14a" +checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" dependencies = [ "bitflags", "cairo-sys-rs", "glib", "libc", + "once_cell", "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.15.1" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" dependencies = [ - "glib-sys 0.15.5", + "glib-sys", "libc", - "system-deps 6.0.1", + "system-deps", ] [[package]] name = "cargo_toml" -version = "0.10.3" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363c7cfaa15f101415c4ac9e68706ca4a2277773932828b33f96e59d28c68e62" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" dependencies = [ "serde", - "serde_derive", "toml", ] [[package]] name = "cc" -version = "1.0.72" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" -dependencies = [ - "jobserver", -] +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] -name = "cfb" -version = "0.4.0" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca453e8624711b2f0f4eb47076a318feda166252a827ee25d067b43de83dcba0" -dependencies = [ - "byteorder", - "uuid", -] +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] -name = "cfg-expr" -version = "0.8.1" +name = "cfb" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ - "smallvec", + "byteorder", + "fnv", + "uuid", ] [[package]] name = "cfg-expr" -version = "0.9.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" +checksum = "e70d3ad08698a0568b0562f22710fe6bfc1f4a61a367c77d0398c562eadd453a" dependencies = [ "smallvec", + "target-lexicon", ] [[package]] @@ -455,54 +372,68 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ - "libc", - "num-integer", + "android-tzdata", + "iana-time-zone", "num-traits", - "time", + "serde", "winapi", ] +[[package]] +name = "chunked_transfer" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" + [[package]] name = "cipher" -version = "0.3.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "generic-array", + "crypto-common", + "inout", ] [[package]] name = "clap" -version = "3.0.14" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" dependencies = [ - "atty", + "anstream", + "anstyle", "bitflags", - "indexmap", - "os_str_bytes", - "strsim 0.10.0", - "termcolor", - "textwrap", + "clap_lex", + "strsim", ] +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + [[package]] name = "cocoa" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ "bitflags", "block", @@ -516,9 +447,9 @@ dependencies = [ [[package]] name = "cocoa-foundation" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" dependencies = [ "bitflags", "block", @@ -530,19 +461,36 @@ dependencies = [ ] [[package]] -name = "concurrent-queue" -version = "1.2.2" +name = "codespan-reporting" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ - "cache-padded", + "termcolor", + "unicode-width", ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "convert_case" @@ -562,9 +510,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" @@ -593,9 +541,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -611,55 +559,32 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.7" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", - "lazy_static", ] [[package]] name = "crypto-common" -version = "0.1.1" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", + "typenum", ] [[package]] @@ -676,143 +601,115 @@ dependencies = [ "proc-macro2", "quote", "smallvec", - "syn", + "syn 1.0.109", ] [[package]] name = "cssparser-macros" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "ctor" -version = "0.1.21" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "ctr" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher", ] [[package]] -name = "cty" -version = "0.2.2" +name = "cxx" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] [[package]] -name = "darling" -version = "0.10.2" +name = "cxx-build" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" dependencies = [ - "darling_core 0.10.2", - "darling_macro 0.10.2", + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 1.0.109", ] [[package]] -name = "darling" -version = "0.13.1" +name = "cxxbridge-flags" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" -dependencies = [ - "darling_core 0.13.1", - "darling_macro 0.13.1", -] +checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" [[package]] -name = "darling_core" -version = "0.10.2" +name = "cxxbridge-macro" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" dependencies = [ - "fnv", - "ident_case", "proc-macro2", "quote", - "strsim 0.9.3", - "syn", + "syn 1.0.109", ] [[package]] -name = "darling_core" -version = "0.13.1" +name = "darling" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn", + "darling_core", + "darling_macro", ] [[package]] -name = "darling_macro" -version = "0.10.2" +name = "darling_core" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" dependencies = [ - "darling_core 0.10.2", + "fnv", + "ident_case", + "proc-macro2", "quote", - "syn", + "strsim", + "syn 2.0.18", ] [[package]] name = "darling_macro" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" -dependencies = [ - "darling_core 0.13.1", - "quote", - "syn", -] - -[[package]] -name = "deflate" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "derivative" -version = "2.2.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ - "proc-macro2", + "darling_core", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -824,40 +721,18 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", - "syn", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", + "rustc_version", + "syn 1.0.109", ] [[package]] name = "digest" -version = "0.10.1" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.2", + "block-buffer", "crypto-common", - "generic-array", - "subtle", -] - -[[package]] -name = "dirs" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -dependencies = [ - "libc", - "redox_users 0.3.5", - "winapi", ] [[package]] @@ -877,7 +752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users 0.4.0", + "redox_users", "winapi", ] @@ -889,30 +764,37 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dtoa" -version = "0.4.8" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" [[package]] name = "dtoa-short" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" dependencies = [ "dtoa", ] [[package]] -name = "easy-parallel" -version = "3.2.0" +name = "dunce" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6907e25393cdcc1f4f3f513d9aac1e840eb1cc341a0fccb01171f7d14d10b946" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] -name = "either" -version = "1.6.1" +name = "embed-resource" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a" +dependencies = [ + "cc", + "rustc_version", + "toml", + "vswhom", + "winreg 0.11.0", +] [[package]] name = "embed_plist" @@ -921,73 +803,90 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] -name = "enumflags2" -version = "0.7.3" +name = "encoding_rs" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25c90b056b3f84111cf183cbeddef0d3a0bbe9a674f057e1a1533c315f24def" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ - "enumflags2_derive", - "serde", + "cfg-if", ] [[package]] -name = "enumflags2_derive" -version = "0.7.3" +name = "env_logger" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144ec79496cbab6f84fa125dc67be9264aef22eb8a28da8454d9c33f15108da4" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "log", + "regex", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", ] [[package]] -name = "event-listener" -version = "2.5.2" +name = "errno-dragonfly" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] [[package]] name = "fastrand" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] -name = "field-offset" -version = "0.3.4" +name = "fdeflate" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" dependencies = [ - "memoffset", - "rustc_version 0.3.3", + "simd-adler32", ] [[package]] -name = "filetime" -version = "0.2.15" +name = "fern" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.2.10", - "winapi", + "log", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", ] [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ - "cfg-if", "crc32fast", - "libc", - "miniz_oxide 0.4.4", + "miniz_oxide", ] [[package]] @@ -1013,11 +912,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ - "matches", "percent-encoding", ] @@ -1031,42 +929,26 @@ dependencies = [ "new_debug_unreachable", ] -[[package]] -name = "futures" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", - "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1075,55 +957,39 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" - -[[package]] -name = "futures-lite" -version = "1.12.0" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1146,9 +1012,9 @@ dependencies = [ [[package]] name = "gdk" -version = "0.15.2" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614258e81ec35ed8770e64a0838f3a47f95b398bc51e724d3b3fa09c1ee0f8d5" +checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1" dependencies = [ "bitflags", "cairo-rs", @@ -1162,9 +1028,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.15.4" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73aa2f5de1b45710da90a55863276667dc3a3264aaf6a2aeace62bb015244d49" +checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" dependencies = [ "bitflags", "gdk-pixbuf-sys", @@ -1175,65 +1041,79 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.15.1" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413424d9818621fa3cfc8a3a915cdb89a7c3c507d56761b4ec83a9a98e587171" +checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" dependencies = [ - "gio-sys 0.15.5", - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "gio-sys", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.0.1", + "system-deps", ] [[package]] name = "gdk-sys" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +checksum = "d76354f97a913e55b984759a997b693aa7dc71068c9e98bcce51aa167a0a5c5a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", - "gio-sys 0.15.5", - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "gio-sys", + "glib-sys", + "gobject-sys", "libc", "pango-sys", "pkg-config", - "system-deps 6.0.1", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4511710212ed3020b61a8622a37aa6f0dd2a84516575da92e9b96928dcbe83ba" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", ] [[package]] name = "gdkx11-sys" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +checksum = "9fa2bf8b5b8c414bc5d05e48b271896d0fd3ddb57464a3108438082da61de6af" dependencies = [ "gdk-sys", - "glib-sys 0.15.5", + "glib-sys", "libc", - "system-deps 6.0.1", + "system-deps", "x11", ] [[package]] name = "generator" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a" dependencies = [ "cc", "libc", "log", "rustversion", - "winapi", + "windows 0.48.0", ] [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1252,20 +1132,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "ghash" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug", "polyval", @@ -1273,61 +1153,53 @@ dependencies = [ [[package]] name = "gio" -version = "0.15.5" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59105fa464928adf56b159c8d980cc11fbfbe414befb904caac5163d383049bf" +checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-io", - "gio-sys 0.15.5", + "futures-util", + "gio-sys", "glib", "libc", "once_cell", + "pin-project-lite", + "smallvec", "thiserror", ] [[package]] name = "gio-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a41df66e57fcc287c4bcf74fc26b884f31901ea9792ec75607289b456f48fa" -dependencies = [ - "glib-sys 0.14.0", - "gobject-sys 0.14.0", - "libc", - "system-deps 3.2.0", - "winapi", -] - -[[package]] -name = "gio-sys" -version = "0.15.5" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f0bc4cfc9ebcdd05cc5057bc51b99c32f8f9bf246274f6a556ffd27279f8fe3" +checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" dependencies = [ - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.0.1", + "system-deps", "winapi", ] [[package]] name = "glib" -version = "0.15.5" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dcfbdb6cc6c02aee163339465d8a40d6f3f64c3a43f729a4195f0e153338b7" +checksum = "16aa2475c9debed5a32832cb5ff2af5a3f9e1ab9e69df58eaadc1ab2004d6eba" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", + "futures-util", + "gio-sys", "glib-macros", - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "glib-sys", + "gobject-sys", "libc", "once_cell", "smallvec", @@ -1336,85 +1208,51 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.15.3" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58b262ff65ef771003873cea8c10e0fe854f1c508d48d62a4111a1ff163f7d1" +checksum = "fb1a9325847aa46f1e96ffea37611b9d51fc4827e67f79e7de502a297560a67b" dependencies = [ "anyhow", - "heck 0.4.0", - "proc-macro-crate 1.1.0", + "heck", + "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1d60554a212445e2a858e42a0e48cece1bd57b311a19a9468f70376cf554ae" -dependencies = [ - "libc", - "system-deps 3.2.0", + "syn 1.0.109", ] [[package]] name = "glib-sys" -version = "0.15.5" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1d4e1a63d8574541e5b92931e4e669ddc87ffa85d58e84e631dba13ad2e10c" +checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" dependencies = [ "libc", - "system-deps 6.0.1", + "system-deps", ] [[package]] name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "globset" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "gobject-sys" -version = "0.14.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa92cae29759dae34ab5921d73fff5ad54b3d794ab842c117e36cafc7994c3f5" -dependencies = [ - "glib-sys 0.14.0", - "libc", - "system-deps 3.2.0", -] +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gobject-sys" -version = "0.15.5" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6859463843c20cf3837e3a9069b6ab2051aeeadf4c899d33344f4aea83189a" +checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" dependencies = [ - "glib-sys 0.15.5", + "glib-sys", "libc", - "system-deps 6.0.1", + "system-deps", ] [[package]] name = "gtk" -version = "0.15.3" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7978eaec05bea63947c801d29a21372f2ed39aec0bf56bf7725d3599094675e" +checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6" dependencies = [ "atk", "bitflags", @@ -1435,66 +1273,82 @@ dependencies = [ [[package]] name = "gtk-sys" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +checksum = "89b5f8946685d5fe44497007786600c2f368ff6b1e61a16251c89f72a97520a3" dependencies = [ "atk-sys", "cairo-sys-rs", "gdk-pixbuf-sys", "gdk-sys", - "gio-sys 0.15.5", - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "gio-sys", + "glib-sys", + "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.1", + "system-deps", ] [[package]] name = "gtk3-macros" -version = "0.15.1" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c891188af69e77a1e8a0b1746fbd03b9b396e7d34d518c5331b15950259f541" +checksum = "096eb63c6fedf03bafe65e5924595785eaf1bcb7200dac0f2cbe9c9738f05ad8" dependencies = [ "anyhow", - "proc-macro-crate 1.1.0", + "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] -name = "hashbrown" -version = "0.11.2" +name = "h2" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] -name = "heck" -version = "0.3.3" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -1503,106 +1357,191 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "html5ever" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" dependencies = [ "log", "mac", "markup5ever", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "http" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa 1.0.6", ] [[package]] -name = "http-range" -version = "0.1.4" +name = "http-body" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee9694f83d9b7c09682fdb32213682939507884e5bcf227be9aff5d644b90dc" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] [[package]] -name = "ico" -version = "0.1.0" +name = "http-range" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804" -dependencies = [ - "byteorder", - "png 0.11.0", -] +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" [[package]] -name = "ident_case" -version = "1.0.1" +name = "httparse" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] -name = "idna" -version = "0.2.3" +name = "httpdate" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] -name = "ignore" -version = "0.4.18" +name = "hyper" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ - "crossbeam-utils", - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local", - "walkdir", - "winapi-util", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.6", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "ico" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "ico" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", ] [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] name = "infer" -version = "0.4.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92b41dab759f9e8427c03f519c344a14655490b8db548dac1e57a75b3258391" +checksum = "f178e61cdbfe084aa75a2f4f7a25a5bb09701a47ae1753608f194b15783c937a" dependencies = [ "cfb", ] [[package]] -name = "inflate" -version = "0.3.4" +name = "infer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" +dependencies = [ + "cfb", +] + +[[package]] +name = "inout" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "adler32", + "generic-array", ] [[package]] @@ -1615,12 +1554,32 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.10.3" +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "either", + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -1631,15 +1590,15 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "javascriptcore-rs" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" +checksum = "110b9902c80c12bf113c432d0b71c7a94490b294a8234f326fd0abca2fac0b00" dependencies = [ "bitflags", "glib", @@ -1648,48 +1607,54 @@ dependencies = [ [[package]] name = "javascriptcore-rs-sys" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" +checksum = "98a216519a52cd941a733a0ad3f1023cfdb1cd47f3955e8e863ed56f558f916c" dependencies = [ - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "glib-sys", + "gobject-sys", "libc", - "system-deps 5.0.0", + "system-deps", ] [[package]] -name = "jni-sys" -version = "0.3.0" +name = "jni" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] [[package]] -name = "jobserver" -version = "0.1.24" +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] [[package]] name = "json-patch" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d" +checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" dependencies = [ "serde", "serde_json", + "thiserror", "treediff", ] @@ -1713,9 +1678,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libappindicator" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b29fab3280d59f3d06725f75da9ef9a1b001b2c748b1abfebd1c966c61d7de" +checksum = "89e1edfdc9b0853358306c6dfb4b77c79c779174256fe93d80c0b5ebca451a2f" dependencies = [ "glib", "gtk", @@ -1726,43 +1691,79 @@ dependencies = [ [[package]] name = "libappindicator-sys" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bdcb8c5cfc11febe2ff3f18386d6cb7d29f464cbaf6b286985c3f1a501d74f" +checksum = "08fcb2bea89cee9613982501ec83eaa2d09256b24540ae463c52a28906163918" dependencies = [ "gtk-sys", - "pkg-config", + "libloading", + "once_cell", ] [[package]] name = "libc" -version = "0.2.117" +version = "0.2.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" dependencies = [ - "cfg-if", + "value-bag", ] [[package]] name = "loom" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ "cfg-if", "generator", @@ -1779,18 +1780,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -[[package]] -name = "mac-notification-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb6b71a9a89cd38b395d994214297447e8e63b1ba5708a9a2b0b1048ceda76" -dependencies = [ - "cc", - "chrono", - "dirs", - "objc-foundation", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1825,73 +1814,57 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] -name = "minisign-verify" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ccf091884470c4b3a80ad6daadbb2e7736611d631cbf0c9e603bb7dbcfdfd9" - -[[package]] -name = "miniz_oxide" -version = "0.3.7" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", - "autocfg", + "simd-adler32", ] [[package]] -name = "native-tls" -version = "0.2.8" +name = "mio" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ - "lazy_static", "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", ] [[package]] name = "ndk" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" dependencies = [ "bitflags", "jni-sys", @@ -1901,57 +1874,26 @@ dependencies = [ ] [[package]] -name = "ndk-glue" -version = "0.4.0" +name = "ndk-context" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e9e94628f24e7a3cb5b96a2dc5683acd9230bf11991c2a1677b87695138420" -dependencies = [ - "lazy_static", - "libc", - "log", - "ndk", - "ndk-macro", - "ndk-sys", -] +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] -name = "ndk-macro" -version = "0.2.0" +name = "ndk-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" dependencies = [ - "darling 0.10.2", - "proc-macro-crate 0.1.5", - "proc-macro2", - "quote", - "syn", + "jni-sys", ] -[[package]] -name = "ndk-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" - [[package]] name = "new_debug_unreachable" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" -[[package]] -name = "nix" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "memoffset", -] - [[package]] name = "nodrop" version = "0.1.14" @@ -1959,34 +1901,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] -name = "notify-rust" -version = "4.5.6" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367e1355a950d3e758e414f3ca1b3981a57a2aa1fa3338eb0059f5b230b6ffa4" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "mac-notification-sys", - "serde", - "winrt-notification", - "zbus", - "zvariant", - "zvariant_derive", + "overload", + "winapi", ] [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] -name = "num-iter" -version = "0.1.42" +name = "num-rational" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-integer", @@ -1995,63 +1933,61 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "num_enum" -version = "0.5.6" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.6" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] -name = "objc" -version = "0.2.7" +name = "num_threads" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ - "malloc_buf", - "objc_exception", + "libc", ] [[package]] -name = "objc-foundation" -version = "0.1.1" +name = "objc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ - "block", - "objc", - "objc_id", + "malloc_buf", + "objc_exception", ] [[package]] @@ -2074,9 +2010,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -2085,95 +2021,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "open" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176ee4b630d174d2da8241336763bb459281dddc0f4d87f72c3b1efc9a6109b7" -dependencies = [ - "pathdiff", - "winapi", -] - -[[package]] -name = "openssl" -version = "0.10.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-stream" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44630c059eacfd6e08bdaa51b1db2ce33119caa4ddc1235e923109aa5f25ccb1" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "os_info" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023df84d545ef479cf67fd2f4459a613585c9db4852c2fad12ab70587859d340" -dependencies = [ - "log", - "serde", - "winapi", -] - -[[package]] -name = "os_pipe" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3492ebca331b895fe23ed427dce2013d9b2e00c45964f12040b0db38b8ab27" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "os_str_bytes" -version = "6.0.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pango" -version = "0.15.2" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79211eff430c29cc38c69e0ab54bc78fa1568121ca9737707eee7f92a8417a94" +checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" dependencies = [ "bitflags", + "gio", "glib", "libc", "once_cell", @@ -2182,67 +2042,44 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.15.1" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7022c2fb88cd2d9d55e1a708a8c53a3ae8678234c4a54bf623400aeb7f31fac2" +checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" dependencies = [ - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.0.1", + "system-deps", ] -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", - "instant", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.3.5", "smallvec", - "winapi", + "windows-targets 0.48.0", ] -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pest" -version = "2.1.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "phf" @@ -2293,7 +2130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared 0.10.0", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -2307,7 +2144,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2321,7 +2158,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2344,9 +2181,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -2356,52 +2193,42 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] -name = "png" -version = "0.11.0" +name = "plist" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925" +checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ - "bitflags", - "deflate 0.7.20", - "inflate", - "num-iter", + "base64 0.21.2", + "indexmap", + "line-wrap", + "quick-xml", + "serde", + "time", ] [[package]] name = "png" -version = "0.16.8" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" dependencies = [ "bitflags", "crc32fast", - "deflate 0.8.6", - "miniz_oxide 0.3.7", -] - -[[package]] -name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" -dependencies = [ - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "winapi", + "fdeflate", + "flate2", + "miniz_oxide", ] [[package]] name = "polyval" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ "cfg-if", "cpufeatures", @@ -2411,9 +2238,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precomputed-hash" @@ -2423,21 +2250,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-crate" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "thiserror", - "toml", + "once_cell", + "toml_edit", ] [[package]] @@ -2449,7 +2267,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -2466,24 +2284,33 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.19" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" dependencies = [ - "unicode-xid", + "memchr", ] [[package]] name = "quote" -version = "1.0.15" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -2498,20 +2325,19 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_hc", "rand_pcg", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", + "rand_core 0.6.4", ] [[package]] @@ -2531,7 +2357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2545,11 +2371,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.10", ] [[package]] @@ -2561,15 +2387,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "rand_pcg" version = "0.2.1" @@ -2581,83 +2398,48 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7" -dependencies = [ - "cty", -] - -[[package]] -name = "rayon" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "lazy_static", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] -name = "redox_users" +name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "getrandom 0.1.16", - "redox_syscall 0.1.57", - "rust-argon2", + "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.4", - "redox_syscall 0.2.10", + "getrandom 0.2.10", + "redox_syscall 0.2.16", + "thiserror", ] [[package]] name = "regex" -version = "1.5.4" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] [[package]] @@ -2666,103 +2448,97 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", ] [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] -name = "rfd" -version = "0.6.4" +name = "reqwest" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e3a2a1461f8ef8023ff00ce76f877c112cf1ffd601d95ba01a41e820af1f3d" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "block", - "dispatch", - "glib-sys 0.15.5", - "gobject-sys 0.15.5", - "gtk-sys", + "base64 0.21.2", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "ipnet", "js-sys", - "lazy_static", - "objc", - "objc-foundation", - "objc_id", - "raw-window-handle", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-util", + "tower-service", + "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "windows 0.30.0", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rust-argon2" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" -dependencies = [ - "base64", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", + "winreg 0.10.1", ] [[package]] name = "rustc_version" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 0.11.0", + "semver", ] [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rustix" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ - "semver 1.0.5", + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", ] [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "safemem" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" [[package]] name = "same-file" @@ -2773,21 +2549,11 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -2796,27 +2562,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "security-framework" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" +name = "scratch" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "selectors" @@ -2840,68 +2589,62 @@ dependencies = [ [[package]] name = "semver" -version = "0.11.0" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", + "serde", ] [[package]] name = "serde" -version = "1.0.136" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ - "itoa 1.0.1", + "itoa 1.0.6", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", +] + +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", ] [[package]] @@ -2911,39 +2654,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa 1.0.6", "ryu", "serde", ] [[package]] name = "serde_with" -version = "1.12.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1e6ec4d8950e5b1e894eac0d360742f3b1407a6078a604a731c4b3f49cefbc" +checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" dependencies = [ - "rustversion", + "base64 0.21.2", + "chrono", + "hex", + "indexmap", "serde", + "serde_json", "serde_with_macros", + "time", ] [[package]] name = "serde_with_macros" -version = "1.5.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e" +checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" dependencies = [ - "darling 0.13.1", + "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "serialize-to-javascript" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9186a81e763ca2a5e13ba76d843534620bffe0cf1e76e1a2f2d231ba1db79b82" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" dependencies = [ "serde", "serde_json", @@ -2952,51 +2700,34 @@ dependencies = [ [[package]] name = "serialize-to-javascript-impl" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3ff83d7962f690dbe675ca0fd516c097194723b6bda8c7c500d35f0b02902e" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "servo_arc" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" dependencies = [ - "sha1_smol", + "nodrop", + "stable_deref_trait", ] -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "sha2" -version = "0.9.9" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest", ] [[package]] @@ -3009,62 +2740,69 @@ dependencies = [ ] [[package]] -name = "shared_child" -version = "1.0.0" +name = "simd-adler32" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" -dependencies = [ - "libc", - "winapi", -] +checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" [[package]] name = "siphasher" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] [[package]] -name = "soup2-sys" -version = "0.1.0" +name = "soup3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f056675eda9a7417163e5f742bb119e8e1d385edd2ada8f7031a7230a3ec10a" +checksum = "82bc46048125fefd69d30b32b9d263d6556c9ffe82a7a7df181a86d912da5616" dependencies = [ "bitflags", - "gio-sys 0.14.0", - "glib-sys 0.14.0", - "gobject-sys 0.14.0", + "futures-channel", + "gio", + "glib", "libc", - "system-deps 5.0.0", + "once_cell", + "soup3-sys", ] [[package]] -name = "spin" -version = "0.5.2" +name = "soup3-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "014bbeb1c4cdb30739dc181e8d98b7908f124d9555843afa89b5570aaf4ec62b" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] [[package]] name = "stable_deref_trait" @@ -3074,27 +2812,21 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "state" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" dependencies = [ "loom", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "string_cache" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ - "lazy_static", "new_debug_unreachable", + "once_cell", "parking_lot", "phf_shared 0.10.0", "precomputed-hash", @@ -3103,22 +2835,16 @@ dependencies = [ [[package]] name = "string_cache_codegen" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro2", "quote", ] -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - [[package]] name = "strsim" version = "0.10.0" @@ -3126,120 +2852,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "strum" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" - -[[package]] -name = "strum" -version = "0.22.0" +name = "subtle" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e" -dependencies = [ - "strum_macros 0.22.0", -] +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] -name = "strum_macros" -version = "0.21.1" +name = "swift-rs" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +checksum = "05e51d6f2b5fff4808614f429f8a7655ac8bcfe218185413f3a60c508482c2d6" dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "syn", + "base64 0.21.2", + "serde", + "serde_json", ] [[package]] -name = "strum_macros" -version = "0.22.0" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "heck 0.3.3", "proc-macro2", "quote", - "syn", + "unicode-ident", ] -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - [[package]] name = "syn" -version = "1.0.86" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", - "unicode-xid", -] - -[[package]] -name = "sys-info" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "system-deps" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6" -dependencies = [ - "anyhow", - "cfg-expr 0.8.1", - "heck 0.3.3", - "itertools", - "pkg-config", - "strum 0.21.0", - "strum_macros 0.21.1", - "thiserror", - "toml", - "version-compare 0.0.11", -] - -[[package]] -name = "system-deps" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" -dependencies = [ - "cfg-expr 0.9.1", - "heck 0.3.3", - "pkg-config", - "toml", - "version-compare 0.0.11", + "unicode-ident", ] [[package]] name = "system-deps" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3a97fdef3daf935d929b3e97e5a6a680cd4622e40c2941ca0875d6566416f8" +checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" dependencies = [ - "cfg-expr 0.9.1", - "heck 0.4.0", + "cfg-expr", + "heck", "pkg-config", "toml", - "version-compare 0.1.0", + "version-compare", ] [[package]] name = "tao" -version = "0.6.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d2bba60298cbc0ff0651fe5a0bdfb73438b91231f700dcc8d539548237cae7" +checksum = "746ae5d0ca57ae275a792f109f6e992e0b41a443abdf3f5c6eff179ef5b3443a" dependencies = [ "bitflags", "cairo-rs", @@ -3248,97 +2916,94 @@ dependencies = [ "core-foundation", "core-graphics", "crossbeam-channel", + "dirs-next", "dispatch", "gdk", "gdk-pixbuf", "gdk-sys", + "gdkwayland-sys", "gdkx11-sys", "gio", "glib", - "glib-sys 0.15.5", + "glib-sys", "gtk", + "image", "instant", + "jni", "lazy_static", "libappindicator", "libc", "log", "ndk", - "ndk-glue", + "ndk-context", "ndk-sys", "objc", + "once_cell", "parking_lot", + "png", "raw-window-handle", "scopeguard", "serde", - "tao-core-video-sys", + "tao-macros", "unicode-segmentation", - "windows 0.30.0", - "windows_macros", + "uuid", + "windows 0.44.0", + "windows-implement", "x11-dl", ] [[package]] -name = "tao-core-video-sys" -version = "0.2.0" +name = "tao-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445" dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "objc", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "tar" -version = "0.4.38" +name = "target-lexicon" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" -dependencies = [ - "filetime", - "libc", - "xattr", -] +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "tauri" -version = "1.0.0-beta.8" +version = "2.0.0-alpha.9" dependencies = [ - "attohttpc", - "base64", - "bincode", - "cfg_aliases", - "clap", + "anyhow", + "bytes", + "cocoa", "dirs-next", - "either", "embed_plist", - "flate2", - "futures", - "futures-lite", + "futures-util", "glib", "glob", "gtk", + "heck", "http", - "ignore", - "minisign-verify", - "notify-rust", + "ico 0.2.0", + "infer 0.9.0", + "jni", + "libc", + "log", + "objc", "once_cell", - "open", - "os_info", - "os_pipe", "percent-encoding", - "rand 0.8.4", + "png", + "rand 0.8.5", "raw-window-handle", - "regex", - "rfd", - "semver 1.0.5", + "reqwest", + "semver", "serde", "serde_json", "serde_repr", "serialize-to-javascript", - "shared_child", "state", - "tar", + "swift-rs", + "tauri-build", "tauri-macros", "tauri-runtime", "tauri-runtime-wry", @@ -3348,101 +3013,168 @@ dependencies = [ "tokio", "url", "uuid", - "zip", + "webkit2gtk", + "webview2-com", + "windows 0.44.0", ] [[package]] name = "tauri-build" -version = "1.0.0-beta.4" +version = "2.0.0-alpha.5" dependencies = [ "anyhow", "cargo_toml", + "heck", + "json-patch", + "quote", + "semver", + "serde", "serde_json", + "swift-rs", "tauri-codegen", "tauri-utils", - "winres", + "tauri-winres", + "walkdir", ] [[package]] name = "tauri-codegen" -version = "1.0.0-beta.4" +version = "2.0.0-alpha.5" dependencies = [ - "base64", - "blake3", + "base64 0.21.2", + "brotli", + "ico 0.3.0", + "json-patch", + "plist", + "png", "proc-macro2", "quote", - "regex", + "semver", "serde", "serde_json", "sha2", "tauri-utils", "thiserror", + "time", + "url", "uuid", "walkdir", - "zstd", ] [[package]] name = "tauri-macros" -version = "1.0.0-beta.5" +version = "2.0.0-alpha.5" dependencies = [ - "heck 0.3.3", + "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "tauri-codegen", "tauri-utils", ] +[[package]] +name = "tauri-plugin-cli" +version = "0.0.0" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=next#4a10f218f0e1fdd66a549dc0bf16be3efb17ea49" +dependencies = [ + "clap", + "log", + "serde", + "serde_json", + "tauri", + "thiserror", +] + +[[package]] +name = "tauri-plugin-log" +version = "0.0.0" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=next#4a10f218f0e1fdd66a549dc0bf16be3efb17ea49" +dependencies = [ + "android_logger", + "byte-unit", + "cocoa", + "fern", + "log", + "objc", + "serde", + "serde_json", + "serde_repr", + "swift-rs", + "tauri", + "tauri-build", + "time", +] + +[[package]] +name = "tauri-plugin-sample" +version = "0.1.0" +dependencies = [ + "log", + "serde", + "tauri", + "tauri-build", + "thiserror", +] + [[package]] name = "tauri-runtime" -version = "0.2.1" +version = "0.13.0-alpha.5" dependencies = [ "gtk", "http", "http-range", - "infer", + "jni", + "rand 0.8.5", + "raw-window-handle", "serde", "serde_json", "tauri-utils", "thiserror", + "url", "uuid", - "webview2-com", - "windows 0.30.0", + "windows 0.44.0", ] [[package]] name = "tauri-runtime-wry" -version = "0.2.1" +version = "0.13.0-alpha.5" dependencies = [ + "cocoa", "gtk", - "ico", - "infer", - "png 0.16.8", + "jni", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", "tauri-runtime", "tauri-utils", "uuid", + "webkit2gtk", "webview2-com", - "windows 0.30.0", + "windows 0.44.0", "wry", ] [[package]] name = "tauri-utils" -version = "1.0.0-beta.3" +version = "2.0.0-alpha.5" dependencies = [ "aes-gcm", + "brotli", "ctor", + "dunce", + "getrandom 0.2.10", "glob", - "heck 0.4.0", + "heck", "html5ever", + "infer 0.12.0", "json-patch", "kuchiki", - "once_cell", + "memchr", "phf 0.10.1", "proc-macro2", "quote", - "ring", + "semver", "serde", "serde_json", "serde_with", @@ -3450,28 +3182,38 @@ dependencies = [ "thiserror", "url", "walkdir", - "zstd", + "windows 0.44.0", +] + +[[package]] +name = "tauri-winres" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +dependencies = [ + "embed-resource", + "toml", ] [[package]] name = "tempfile" -version = "3.3.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", - "libc", - "redox_syscall 0.2.10", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", ] [[package]] name = "tendril" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", @@ -3480,19 +3222,13 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" - [[package]] name = "thin-slice" version = "0.1.1" @@ -3501,84 +3237,166 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.1.43" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ + "itoa 1.0.6", "libc", - "winapi", + "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + +[[package]] +name = "tiny_http" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d6ef4e10d23c1efb862eecad25c5054429a71958b4eeef85eb5e7170b477ca" +dependencies = [ + "ascii", + "chunked_transfer", + "log", + "time", + "url", ] [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.16.1" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ + "autocfg", "bytes", - "memchr", + "libc", + "mio", "num_cpus", "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ + "indexmap", "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" -version = "0.1.30" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", @@ -3588,30 +3406,30 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", @@ -3620,13 +3438,13 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.8" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74786ce43333fcf51efe947aed9718fbe46d5c7328ec3f1029e818083966d9aa" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ - "ansi_term", - "lazy_static", "matchers", + "nu-ansi-term", + "once_cell", "regex", "sharded-slab", "smallvec", @@ -3638,77 +3456,76 @@ dependencies = [ [[package]] name = "treediff" -version = "3.0.2" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" dependencies = [ "serde_json", ] [[package]] -name = "typenum" -version = "1.15.0" +name = "try-lock" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] -name = "ucd-trie" -version = "0.1.3" +name = "typenum" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "unicode-width" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "generic-array", + "crypto-common", "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "url" -version = "2.2.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", "serde", ] @@ -3719,13 +3536,25 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" -version = "0.8.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.10", ] [[package]] @@ -3735,22 +3564,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.0.11" +name = "value-bag" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" +checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" [[package]] name = "version-compare" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" @@ -3759,22 +3582,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "waker-fn" -version = "1.1.0" +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3783,15 +3629,15 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3799,24 +3645,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.29" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -3826,9 +3672,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3836,28 +3682,41 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" + +[[package]] +name = "wasm-streams" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -3865,102 +3724,86 @@ dependencies = [ [[package]] name = "webkit2gtk" -version = "0.17.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cbd39499e917de9dad36eb11c09f665eb984d432638ae7971feed98eb96df88" +checksum = "d8eea819afe15eb8dcdff4f19d8bfda540bae84d874c10e6f4b8faf2d6704bd1" dependencies = [ "bitflags", "cairo-rs", "gdk", "gdk-sys", "gio", - "gio-sys 0.15.5", + "gio-sys", "glib", - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "glib-sys", + "gobject-sys", "gtk", "gtk-sys", "javascriptcore-rs", "libc", "once_cell", + "soup3", "webkit2gtk-sys", ] [[package]] name = "webkit2gtk-sys" -version = "0.17.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddcce6f1e0fc7715d651dba29875741509f5fc12f4e2976907272a74405f2b01" +checksum = "d0ac7a95ddd3fdfcaf83d8e513b4b1ad101b95b413b6aa6662ed95f284fc3d5b" dependencies = [ - "atk-sys", "bitflags", "cairo-sys-rs", - "gdk-pixbuf-sys", "gdk-sys", - "gio-sys 0.15.5", - "glib-sys 0.15.5", - "gobject-sys 0.15.5", + "gio-sys", + "glib-sys", + "gobject-sys", "gtk-sys", "javascriptcore-rs-sys", "libc", - "pango-sys", "pkg-config", - "soup2-sys", - "system-deps 5.0.0", + "soup3-sys", + "system-deps", ] [[package]] name = "webview2-com" -version = "0.11.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1975ce3573344c099935fe3903f1708dac69efe8539f1efee3ae54d8f9315fbb" +checksum = "11296e5daf3a653b79bf47d66c380e4143d5b9c975818871179a3bda79499562" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.30.0", - "windows_macros", + "windows 0.44.0", + "windows-implement", ] [[package]] name = "webview2-com-macros" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515c6c82fcee93f6edaacc72c8e233dbe4ff3ca569dce1901dfc36c404a3e99" +checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "webview2-com-sys" -version = "0.11.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a746838a94b7391f707209a246e3436d81d1e71832126a65a897d3ee5511040" +checksum = "cde542bed28058a5b028d459689ee57f1d06685bb6c266da3b91b1be6703952f" dependencies = [ "regex", "serde", "serde_json", "thiserror", - "windows 0.30.0", + "windows 0.44.0", "windows-bindgen", + "windows-metadata", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - -[[package]] -name = "wildmatch" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c48bd20df7e4ced539c12f570f937c6b4884928a87fee70a479d72f031d4e0" - [[package]] name = "winapi" version = "0.3.9" @@ -3993,341 +3836,303 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.24.0" +name = "window-shadows" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9f39345ae0c8ab072c0ac7fe8a8b411636aa34f89be19ddd0d9226544f13944" +checksum = "29d30320647cfc3dc45554c8ad825b84831def81f967a2f7589931328ff9b16d" dependencies = [ - "windows_i686_gnu 0.24.0", - "windows_i686_msvc 0.24.0", - "windows_x86_64_gnu 0.24.0", - "windows_x86_64_msvc 0.24.0", + "cocoa", + "objc", + "raw-window-handle", + "windows-sys 0.42.0", ] [[package]] name = "windows" -version = "0.30.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b749ebd2304aa012c5992d11a25d07b406bdbe5f79d371cb7a918ce501a19eb0" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu 0.30.0", - "windows_i686_msvc 0.30.0", - "windows_x86_64_gnu 0.30.0", - "windows_x86_64_msvc 0.30.0", + "windows-implement", + "windows-interface", + "windows-targets 0.42.2", ] [[package]] -name = "windows-bindgen" -version = "0.30.0" +name = "windows" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944c545fcae9dd66488308f8b69aa3ba34f53714416ecfcdcbbfa4b6821e27c6" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_quote", - "windows_reader", + "windows-targets 0.48.0", ] [[package]] -name = "windows_aarch64_msvc" -version = "0.30.0" +name = "windows-bindgen" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca" +checksum = "222204ecf46521382a4d88b4a1bbefca9f8855697b4ab7d20803901425e061a3" +dependencies = [ + "windows-metadata", + "windows-tokens", +] [[package]] -name = "windows_gen" -version = "0.30.0" +name = "windows-implement" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30dff4d91d22520628bb94b66f2bb313cb16a09a515a32320a84a1b449bc94c0" +checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6" dependencies = [ - "windows_quote", - "windows_reader", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "windows_i686_gnu" -version = "0.24.0" +name = "windows-interface" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd" +checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "windows_i686_gnu" -version = "0.30.0" +name = "windows-metadata" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8" +checksum = "ee78911e3f4ce32c1ad9d3c7b0bd95389662ad8d8f1a3155688fed70bd96e2b6" [[package]] -name = "windows_i686_msvc" -version = "0.24.0" +name = "windows-sys" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] [[package]] -name = "windows_i686_msvc" -version = "0.30.0" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] [[package]] -name = "windows_macros" -version = "0.30.0" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ae44ab917e9005fe710d99d52d227ca0164b10a09be90649142cc3fab825d3" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "syn", - "windows_gen", - "windows_quote", - "windows_reader", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] -name = "windows_quote" -version = "0.30.0" +name = "windows-targets" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71f02c51a77e6248c1206aaa920802c32d50a05205e229b118d7f3afd3036667" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] [[package]] -name = "windows_reader" -version = "0.30.0" +name = "windows-tokens" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44e6df0da993cda589c5ac852272fbb2a0ead67a031a017dd3eac11528a2d72" +checksum = "fa4251900975a0d10841c5d4bde79c56681543367ef811f3fabb8d1803b0959b" [[package]] -name = "windows_x86_64_gnu" -version = "0.24.0" +name = "windows_aarch64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] -name = "windows_x86_64_gnu" -version = "0.30.0" +name = "windows_aarch64_gnullvm" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] -name = "windows_x86_64_msvc" -version = "0.24.0" +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] -name = "windows_x86_64_msvc" -version = "0.30.0" +name = "windows_aarch64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] -name = "winres" -version = "0.1.12" +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" -dependencies = [ - "toml", -] +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] -name = "winrt-notification" -version = "0.5.1" +name = "windows_i686_gnu" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4" -dependencies = [ - "strum 0.22.0", - "windows 0.24.0", - "xml-rs", -] +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] -name = "wry" -version = "0.13.1" +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194b2750d8fe10fef189af5e2ca09e56cb8c5458a365d2b32842b024351f58c9" -dependencies = [ - "cocoa", - "core-graphics", - "gdk", - "gio", - "glib", - "gtk", - "http", - "libc", - "log", - "objc", - "objc_id", - "once_cell", - "serde", - "serde_json", - "sys-info", - "tao", - "thiserror", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.30.0", - "windows_macros", -] +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] -name = "x11" -version = "2.19.1" +name = "windows_i686_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a" -dependencies = [ - "libc", - "pkg-config", -] +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] -name = "x11-dl" -version = "2.19.1" +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" -dependencies = [ - "lazy_static", - "libc", - "pkg-config", -] +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] -name = "xattr" -version = "0.2.2" +name = "windows_x86_64_gnu" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" -dependencies = [ - "libc", -] +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] -name = "xml-rs" -version = "0.8.4" +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] -name = "zbus" -version = "2.1.1" +name = "windows_x86_64_gnullvm" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb86f3d4592e26a48b2719742aec94f8ae6238ebde20d98183ee185d1275e9a" -dependencies = [ - "async-broadcast", - "async-channel", - "async-executor", - "async-io", - "async-lock", - "async-recursion", - "async-task", - "async-trait", - "byteorder", - "derivative", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "lazy_static", - "nix", - "once_cell", - "ordered-stream", - "rand 0.8.4", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "winapi", - "zbus_macros", - "zbus_names", - "zvariant", -] +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] -name = "zbus_macros" -version = "2.1.1" +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36823cc10fddc3c6b19f048903262dacaf8274170e9a255784bdd8b4570a8040" -dependencies = [ - "proc-macro-crate 1.1.0", - "proc-macro2", - "quote", - "regex", - "syn", -] +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "zbus_names" -version = "2.1.0" +name = "windows_x86_64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45dfcdcf87b71dad505d30cc27b1b7b88a64b6d1c435648f48f9dbc1fdc4b7e1" -dependencies = [ - "serde", - "static_assertions", - "zvariant", -] +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] -name = "zip" -version = "0.5.13" +name = "winnow" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ - "byteorder", - "bzip2", - "crc32fast", - "flate2", - "thiserror", - "time", + "memchr", ] [[package]] -name = "zstd" -version = "0.10.0+zstd.1.5.2" +name = "winreg" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ - "zstd-safe", + "winapi", ] [[package]] -name = "zstd-safe" -version = "4.1.4+zstd.1.5.2" +name = "winreg" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" dependencies = [ - "libc", - "zstd-sys", + "cfg-if", + "winapi", ] [[package]] -name = "zstd-sys" -version = "1.6.3+zstd.1.5.2" +name = "wry" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +checksum = "7d15f9f827d537cefe6d047be3930f5d89b238dfb85e08ba6a319153217635aa" dependencies = [ - "cc", + "base64 0.13.1", + "block", + "cocoa", + "core-graphics", + "crossbeam-channel", + "dunce", + "gdk", + "gio", + "glib", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "kuchiki", "libc", + "log", + "objc", + "objc_id", + "once_cell", + "serde", + "serde_json", + "sha2", + "soup3", + "tao", + "thiserror", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.44.0", + "windows-implement", ] [[package]] -name = "zvariant" -version = "3.1.2" +name = "x11" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ea5dc38b2058fae6a5b79009388143dadce1e91c26a67f984a0fc0381c8033" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" dependencies = [ - "byteorder", - "enumflags2", "libc", - "serde", - "static_assertions", - "zvariant_derive", + "pkg-config", ] [[package]] -name = "zvariant_derive" -version = "3.1.2" +name = "x11-dl" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2cecc5a61c2a053f7f653a24cd15b3b0195d7f7ddb5042c837fb32e161fb7a" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "proc-macro-crate 1.1.0", - "proc-macro2", - "quote", - "syn", + "libc", + "once_cell", + "pkg-config", ] diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index 221b474246c7..8bd1e11634e7 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -3,19 +3,54 @@ name = "api" version = "0.1.0" description = "An example Tauri Application showcasing the api" edition = "2021" -rust-version = "1.57" +rust-version = "1.65" license = "Apache-2.0 OR MIT" +[lib] +name = "api_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + [build-dependencies] -tauri-build = { path = "../../../core/tauri-build", features = ["isolation"] } +tauri-build = { path = "../../../core/tauri-build", features = ["codegen", "isolation"] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = [ "derive" ] } -tauri = { path = "../../../core/tauri", features = ["api-all", "cli", "isolation", "macos-private-api", "system-tray", "updater"] } +tiny_http = "0.11" +log = "0.4" +tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "next" } +tauri-plugin-sample = { path = "./tauri-plugin-sample/" } + +[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 = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "next" } + +[patch.crates-io] +tauri = { path = "../../../core/tauri" } +tauri-build = { path = "../../../core/tauri-build" } + +[patch.'https://github.com/tauri-apps/tauri'] +tauri = { path = "../../../core/tauri" } +tauri-build = { path = "../../../core/tauri-build" } + +[dependencies.tauri] +path = "../../../core/tauri" +features = [ + "protocol-asset", + "icon-ico", + "icon-png", + "isolation", + "macos-private-api", + "system-tray" +] + +[dev-dependencies.tauri] +path = "../../../core/tauri" +features = ["test"] + +[target."cfg(target_os = \"windows\")".dependencies] +window-shadows= "0.2" [features] -default = [ "custom-protocol" ] custom-protocol = [ "tauri/custom-protocol" ] # default to small, optimized release binaries diff --git a/examples/api/src-tauri/Cross.toml b/examples/api/src-tauri/Cross.toml new file mode 100644 index 000000000000..a957b1e33881 --- /dev/null +++ b/examples/api/src-tauri/Cross.toml @@ -0,0 +1,11 @@ +[build.env] +# must set ICONS_VOLUME, DIST_VOLUME and ISOLATION_VOLUME environment variables +# ICONS_VOLUME: absolute path to the .icons folder +# DIST_VOLUME: absolute path to the dist folder +# ISOLATION_VOLUME: absolute path to the isolation dist folder +# this can be done running `$ . .setup-cross.sh` in the examples/api folder +volumes = ["ICONS_VOLUME", "DIST_VOLUME", "ISOLATION_VOLUME"] + +[target.aarch64-unknown-linux-gnu] +image = "aarch64-unknown-linux-gnu:latest" +#image = "ghcr.io/tauri-apps/tauri/aarch64-unknown-linux-gnu:latest" diff --git a/examples/api/src-tauri/build.rs b/examples/api/src-tauri/build.rs index 2ad1cb4e66a1..2154ff35f5e3 100644 --- a/examples/api/src-tauri/build.rs +++ b/examples/api/src-tauri/build.rs @@ -1,14 +1,12 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use tauri_build::{try_build, Attributes, WindowsAttributes}; - fn main() { - if let Err(error) = try_build( - Attributes::new() - .windows_attributes(WindowsAttributes::new().window_icon_path("../../.icons/icon.ico")), - ) { - panic!("error found during tauri-build: {}", error); + let mut codegen = tauri_build::CodegenContext::new(); + if !cfg!(feature = "custom-protocol") { + codegen = codegen.dev(); } + codegen.build(); + tauri_build::build(); } diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs index 525246f3283b..221881fc1b3b 100644 --- a/examples/api/src-tauri/src/cmd.rs +++ b/examples/api/src-tauri/src/cmd.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ use serde::Deserialize; use tauri::command; #[derive(Debug, Deserialize)] -#[allow(dead_code)] +#[allow(unused)] pub struct RequestBody { id: i32, name: String, @@ -14,7 +14,7 @@ pub struct RequestBody { #[command] pub fn log_operation(event: String, payload: Option) { - println!("{} {:?}", event, payload); + log::info!("{} {:?}", event, payload); } #[command] diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs new file mode 100644 index 000000000000..13e428037e37 --- /dev/null +++ b/examples/api/src-tauri/src/lib.rs @@ -0,0 +1,159 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +mod cmd; +#[cfg(desktop)] +mod tray; + +use serde::Serialize; +use tauri::{window::WindowBuilder, App, AppHandle, RunEvent, Runtime, WindowUrl}; +use tauri_plugin_sample::{PingRequest, SampleExt}; + +#[derive(Clone, Serialize)] +struct Reply { + data: String, +} + +pub type SetupHook = Box Result<(), Box> + Send>; +pub type OnEvent = Box; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + run_app(tauri::Builder::default(), |_app| {}) +} + +pub fn run_app) + Send + 'static>( + builder: tauri::Builder, + setup: F, +) { + #[allow(unused_mut)] + let mut builder = builder + .plugin( + tauri_plugin_log::Builder::default() + .level(log::LevelFilter::Info) + .build(), + ) + .plugin(tauri_plugin_sample::init()) + .setup(move |app| { + #[cfg(desktop)] + { + tray::create_tray(app)?; + + app.handle().plugin(tauri_plugin_cli::init())?; + } + + let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()); + #[cfg(desktop)] + { + window_builder = window_builder + .title("Tauri API Validation") + .inner_size(1000., 800.) + .min_inner_size(600., 400.) + .content_protected(true); + } + + let window = window_builder.build().unwrap(); + + #[cfg(debug_assertions)] + window.open_devtools(); + + let value = Some("test".to_string()); + let response = app.sample().ping(PingRequest { + value: value.clone(), + }); + log::info!("got response: {:?}", response); + if let Ok(res) = response { + assert_eq!(res.value, value); + } + + #[cfg(desktop)] + std::thread::spawn(|| { + let server = match tiny_http::Server::http("localhost:3003") { + Ok(s) => s, + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + }; + loop { + if let Ok(mut request) = server.recv() { + let mut body = Vec::new(); + let _ = request.as_reader().read_to_end(&mut body); + let response = tiny_http::Response::new( + tiny_http::StatusCode(200), + request.headers().to_vec(), + std::io::Cursor::new(body), + request.body_length(), + None, + ); + let _ = request.respond(response); + } + } + }); + + setup(app); + + Ok(()) + }) + .on_page_load(|window, _| { + let window_ = window.clone(); + window.listen("js-event", move |event| { + println!("got js-event with message '{:?}'", event.payload()); + let reply = Reply { + data: "something else".to_string(), + }; + + window_ + .emit("rust-event", Some(reply)) + .expect("failed to emit"); + }); + }); + + #[cfg(target_os = "macos")] + { + builder = builder.menu(tauri::Menu::os_default("Tauri API Validation")); + } + + #[allow(unused_mut)] + let mut app = builder + .invoke_handler(tauri::generate_handler![ + cmd::log_operation, + cmd::perform_request, + ]) + .build(tauri::tauri_build_context!()) + .expect("error while building tauri application"); + + #[cfg(target_os = "macos")] + app.set_activation_policy(tauri::ActivationPolicy::Regular); + + app.run(move |_app_handle, _event| { + #[cfg(all(desktop, not(test)))] + if let RunEvent::ExitRequested { api, .. } = &_event { + // Keep the event loop running even if all windows are closed + // This allow us to catch system tray events when there is no window + api.prevent_exit(); + } + }) +} + +#[cfg(test)] +mod tests { + use tauri::Manager; + + #[test] + fn run_app() { + super::run_app(tauri::test::mock_builder(), |app| { + let window = app.get_window("main").unwrap(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs(1)); + window.close().unwrap(); + }); + }) + } +} diff --git a/examples/api/src-tauri/src/main.rs b/examples/api/src-tauri/src/main.rs index 8da65e657182..33ab0aa16d1c 100644 --- a/examples/api/src-tauri/src/main.rs +++ b/examples/api/src-tauri/src/main.rs @@ -1,263 +1,10 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] - -mod cmd; -mod menu; - -#[cfg(target_os = "linux")] -use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, Ordering}; - -use serde::{Deserialize, Serialize}; -use tauri::{ - api::dialog::ask, http::ResponseBuilder, CustomMenuItem, GlobalShortcutManager, Manager, - RunEvent, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowUrl, -}; - -#[derive(Clone, Serialize)] -struct Reply { - data: String, -} - -#[derive(Serialize, Deserialize)] -struct HttpPost { - foo: String, - bar: String, -} - -#[derive(Serialize)] -struct HttpReply { - msg: String, - request: HttpPost, -} - -#[tauri::command] -async fn menu_toggle(window: tauri::Window) { - window.menu_handle().toggle().unwrap(); -} +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - let tray_menu1 = SystemTrayMenu::new() - .add_item(CustomMenuItem::new("toggle", "Toggle")) - .add_item(CustomMenuItem::new("new", "New window")) - .add_item(CustomMenuItem::new("icon_1", "Tray Icon 1")) - .add_item(CustomMenuItem::new("icon_2", "Tray Icon 2")) - .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) - .add_item(CustomMenuItem::new("exit_app", "Quit")); - let tray_menu2 = SystemTrayMenu::new() - .add_item(CustomMenuItem::new("toggle", "Toggle")) - .add_item(CustomMenuItem::new("new", "New window")) - .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) - .add_item(CustomMenuItem::new("exit_app", "Quit")); - let is_menu1 = AtomicBool::new(true); - - #[allow(unused_mut)] - let mut app = tauri::Builder::default() - .setup(|app| { - #[cfg(debug_assertions)] - app.get_window("main").unwrap().open_devtools(); - Ok(()) - }) - .on_page_load(|window, _| { - let window_ = window.clone(); - window.listen("js-event", move |event| { - println!("got js-event with message '{:?}'", event.payload()); - let reply = Reply { - data: "something else".to_string(), - }; - - window_ - .emit("rust-event", Some(reply)) - .expect("failed to emit"); - }); - }) - .register_uri_scheme_protocol("customprotocol", move |_app_handle, request| { - if request.method() == "POST" { - let request: HttpPost = serde_json::from_slice(request.body()).unwrap(); - return ResponseBuilder::new() - .mimetype("application/json") - .header("Access-Control-Allow-Origin", "*") - .status(200) - .body(serde_json::to_vec(&HttpReply { - request, - msg: "Hello from rust!".to_string(), - })?); - } - - ResponseBuilder::new() - .mimetype("text/html") - .status(404) - .body(Vec::new()) - }) - .menu(menu::get_menu()) - .on_menu_event(|event| { - println!("{:?}", event.menu_item_id()); - }) - .system_tray(SystemTray::new().with_menu(tray_menu1.clone())) - .on_system_tray_event(move |app, event| match event { - SystemTrayEvent::LeftClick { - position: _, - size: _, - .. - } => { - let window = app.get_window("main").unwrap(); - window.show().unwrap(); - window.set_focus().unwrap(); - } - SystemTrayEvent::MenuItemClick { id, .. } => { - let item_handle = app.tray_handle().get_item(&id); - match id.as_str() { - "exit_app" => { - // exit the app - app.exit(0); - } - "toggle" => { - let window = app.get_window("main").unwrap(); - let new_title = if window.is_visible().unwrap() { - window.hide().unwrap(); - "Show" - } else { - window.show().unwrap(); - "Hide" - }; - item_handle.set_title(new_title).unwrap(); - } - "new" => { - app - .create_window( - "new", - WindowUrl::App("index.html".into()), - |window_builder, webview_attributes| { - (window_builder.title("Tauri"), webview_attributes) - }, - ) - .unwrap(); - } - #[cfg(target_os = "macos")] - "icon_1" => { - app.tray_handle().set_icon_as_template(true).unwrap(); - - app - .tray_handle() - .set_icon(tauri::Icon::Raw( - include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(), - )) - .unwrap(); - } - #[cfg(target_os = "macos")] - "icon_2" => { - app.tray_handle().set_icon_as_template(true).unwrap(); - - app - .tray_handle() - .set_icon(tauri::Icon::Raw( - include_bytes!("../../../.icons/tray_icon.png").to_vec(), - )) - .unwrap(); - } - #[cfg(target_os = "linux")] - "icon_1" => app - .tray_handle() - .set_icon(tauri::Icon::File(PathBuf::from( - "../../../.icons/tray_icon_with_transparency.png", - ))) - .unwrap(), - #[cfg(target_os = "linux")] - "icon_2" => app - .tray_handle() - .set_icon(tauri::Icon::File(PathBuf::from( - "../../../.icons/tray_icon.png", - ))) - .unwrap(), - #[cfg(target_os = "windows")] - "icon_1" => app - .tray_handle() - .set_icon(tauri::Icon::Raw( - include_bytes!("../../../.icons/tray_icon_with_transparency.ico").to_vec(), - )) - .unwrap(), - #[cfg(target_os = "windows")] - "icon_2" => app - .tray_handle() - .set_icon(tauri::Icon::Raw( - include_bytes!("../../../.icons/icon.ico").to_vec(), - )) - .unwrap(), - "switch_menu" => { - let flag = is_menu1.load(Ordering::Relaxed); - app - .tray_handle() - .set_menu(if flag { - tray_menu2.clone() - } else { - tray_menu1.clone() - }) - .unwrap(); - is_menu1.store(!flag, Ordering::Relaxed); - } - _ => {} - } - } - _ => {} - }) - .invoke_handler(tauri::generate_handler![ - cmd::log_operation, - cmd::perform_request, - menu_toggle, - ]) - .build(tauri::generate_context!()) - .expect("error while building tauri application"); - - #[cfg(target_os = "macos")] - app.set_activation_policy(tauri::ActivationPolicy::Regular); - - app.run(|app_handle, e| match e { - // Application is ready (triggered only once) - RunEvent::Ready => { - let app_handle = app_handle.clone(); - app_handle - .global_shortcut_manager() - .register("CmdOrCtrl+1", move || { - let app_handle = app_handle.clone(); - let window = app_handle.get_window("main").unwrap(); - window.set_title("New title!").unwrap(); - }) - .unwrap(); - } - - // Triggered when a window is trying to close - RunEvent::CloseRequested { label, api, .. } => { - let app_handle = app_handle.clone(); - let window = app_handle.get_window(&label).unwrap(); - // use the exposed close api, and prevent the event loop to close - api.prevent_close(); - // ask the user if he wants to quit - ask( - Some(&window), - "Tauri API", - "Are you sure that you want to close this window?", - move |answer| { - if answer { - // .close() cannot be called on the main thread - std::thread::spawn(move || { - app_handle.get_window(&label).unwrap().close().unwrap(); - }); - } - }, - ); - } - - // Keep the event loop running even if all windows are closed - // This allow us to catch system tray events when there is no window - RunEvent::ExitRequested { api, .. } => { - api.prevent_exit(); - } - _ => {} - }) + api_lib::run(); } diff --git a/examples/api/src-tauri/src/menu.rs b/examples/api/src-tauri/src/menu.rs deleted file mode 100644 index 026f3b7a470c..000000000000 --- a/examples/api/src-tauri/src/menu.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use tauri::{CustomMenuItem, Menu, MenuItem, Submenu}; - -pub fn get_menu() -> Menu { - #[allow(unused_mut)] - let mut disable_item = - CustomMenuItem::new("disable-menu", "Disable menu").accelerator("CmdOrControl+D"); - #[allow(unused_mut)] - let mut test_item = CustomMenuItem::new("test", "Test").accelerator("CmdOrControl+T"); - #[cfg(target_os = "macos")] - { - disable_item = disable_item.native_image(tauri::NativeImage::MenuOnState); - test_item = test_item.native_image(tauri::NativeImage::Add); - } - - // create a submenu - let my_sub_menu = Menu::with_items([disable_item.into()]); - - let my_app_menu = Menu::new() - .add_native_item(MenuItem::Copy) - .add_submenu(Submenu::new("Sub menu", my_sub_menu)); - - let test_menu = Menu::new() - .add_item(CustomMenuItem::new( - "selected/disabled", - "Selected and disabled", - )) - .add_native_item(MenuItem::Separator) - .add_item(test_item); - - // add all our childs to the menu (order is how they'll appear) - Menu::new() - .add_submenu(Submenu::new("My app", my_app_menu)) - .add_submenu(Submenu::new("Other menu", test_menu)) -} diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs new file mode 100644 index 000000000000..96a8bbd9aef8 --- /dev/null +++ b/examples/api/src-tauri/src/tray.rs @@ -0,0 +1,124 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::sync::atomic::{AtomicBool, Ordering}; +use tauri::{ + CustomMenuItem, Manager, Runtime, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, + WindowUrl, +}; + +pub fn create_tray(app: &tauri::App) -> tauri::Result<()> { + let mut tray_menu1 = SystemTrayMenu::new() + .add_item(CustomMenuItem::new("toggle", "Toggle")) + .add_item(CustomMenuItem::new("new", "New window")) + .add_item(CustomMenuItem::new("icon_1", "Tray Icon 1")) + .add_item(CustomMenuItem::new("icon_2", "Tray Icon 2")); + + #[cfg(target_os = "macos")] + { + tray_menu1 = tray_menu1.add_item(CustomMenuItem::new("set_title", "Set Title")); + } + + tray_menu1 = tray_menu1 + .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) + .add_item(CustomMenuItem::new("exit_app", "Quit")) + .add_item(CustomMenuItem::new("destroy", "Destroy")); + + let tray_menu2 = SystemTrayMenu::new() + .add_item(CustomMenuItem::new("toggle", "Toggle")) + .add_item(CustomMenuItem::new("new", "New window")) + .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) + .add_item(CustomMenuItem::new("exit_app", "Quit")) + .add_item(CustomMenuItem::new("destroy", "Destroy")); + let is_menu1 = AtomicBool::new(true); + + let handle = app.handle(); + let tray_id = "my-tray".to_string(); + SystemTray::new() + .with_id(&tray_id) + .with_menu(tray_menu1.clone()) + .with_tooltip("Tauri") + .on_event(move |event| { + let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap(); + match event { + SystemTrayEvent::LeftClick { + position: _, + size: _, + .. + } => { + let window = handle.get_window("main").unwrap(); + window.show().unwrap(); + window.set_focus().unwrap(); + } + SystemTrayEvent::MenuItemClick { id, .. } => { + let item_handle = tray_handle.get_item(&id); + match id.as_str() { + "exit_app" => { + // exit the app + handle.exit(0); + } + "destroy" => { + tray_handle.destroy().unwrap(); + } + "toggle" => { + let window = handle.get_window("main").unwrap(); + let new_title = if window.is_visible().unwrap() { + window.hide().unwrap(); + "Show" + } else { + window.show().unwrap(); + "Hide" + }; + item_handle.set_title(new_title).unwrap(); + } + "new" => { + WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into())) + .title("Tauri") + .build() + .unwrap(); + } + "set_title" => { + #[cfg(target_os = "macos")] + tray_handle.set_title("Tauri").unwrap(); + } + "icon_1" => { + #[cfg(target_os = "macos")] + tray_handle.set_icon_as_template(true).unwrap(); + + tray_handle + .set_icon(tauri::Icon::Raw( + include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(), + )) + .unwrap(); + } + "icon_2" => { + #[cfg(target_os = "macos")] + tray_handle.set_icon_as_template(true).unwrap(); + + tray_handle + .set_icon(tauri::Icon::Raw( + include_bytes!("../../../.icons/icon.ico").to_vec(), + )) + .unwrap(); + } + "switch_menu" => { + let flag = is_menu1.load(Ordering::Relaxed); + let (menu, tooltip) = if flag { + (tray_menu2.clone(), "Menu 2") + } else { + (tray_menu1.clone(), "Tauri") + }; + tray_handle.set_menu(menu).unwrap(); + tray_handle.set_tooltip(tooltip).unwrap(); + is_menu1.store(!flag, Ordering::Relaxed); + } + _ => {} + } + } + _ => {} + } + }) + .build(app) + .map(|_| ()) +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/.gitignore b/examples/api/src-tauri/tauri-plugin-sample/.gitignore new file mode 100644 index 000000000000..24ae128058bf --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/.gitignore @@ -0,0 +1 @@ +.tauri diff --git a/examples/api/src-tauri/tauri-plugin-sample/Cargo.lock b/examples/api/src-tauri/tauri-plugin-sample/Cargo.lock new file mode 100644 index 000000000000..cb47713eba71 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/Cargo.lock @@ -0,0 +1,3181 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "atk" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39991bc421ddf72f70159011b323ff49b0f783cc676a7287c59453da2e2531cf" +dependencies = [ + "atk-sys", + "bitflags", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "attohttpc" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85f766c20e6ae766956f7a2fcc4e0931e79a7e1f48b29132b5d647021114914" +dependencies = [ + "flate2", + "http", + "log", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cairo-rs" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "cargo_toml" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfbc36312494041e2cdd5f06697b7e89d4b76f42773a0b5556ac290ff22acc2" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "field-offset" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +dependencies = [ + "memoffset", + "rustc_version 0.3.3", +] + +[[package]] +name = "filetime" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" +dependencies = [ + "bitflags", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76354f97a913e55b984759a997b693aa7dc71068c9e98bcce51aa167a0a5c5a" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4511710212ed3020b61a8622a37aa6f0dd2a84516575da92e9b96928dcbe83ba" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa2bf8b5b8c414bc5d05e48b271896d0fd3ddb57464a3108438082da61de6af" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generator" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.39.0", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gio" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf" +dependencies = [ + "anyhow", + "heck", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "gobject-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6" +dependencies = [ + "atk", + "bitflags", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "once_cell", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b5f8946685d5fe44497007786600c2f368ff6b1e61a16251c89f72a97520a3" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfd6557b1018b773e43c8de9d0d13581d6b36190d0501916cbec4731db5ccff" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "html5ever" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.5", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "ico" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "infer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" +dependencies = [ + "cfb", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "javascriptcore-rs" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110b9902c80c12bf113c432d0b71c7a94490b294a8234f326fd0abca2fac0b00" +dependencies = [ + "bitflags", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a216519a52cd941a733a0ad3f1023cfdb1cd47f3955e8e863ed56f558f916c" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "json-patch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e712e62827c382a77b87f590532febb1f8b2fdbc3eefa1ee37fe7281687075ef" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "treediff", +] + +[[package]] +name = "kuchiki" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" +dependencies = [ + "cssparser", + "html5ever", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf 0.8.0", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pango" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" +dependencies = [ + "bitflags", + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pest" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4257b4a04d91f7e9e6290be5d3da4804dd5784fafde3a497d73eb2b4a158c30a" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "plist" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +dependencies = [ + "base64 0.13.1", + "indexmap", + "line-wrap", + "serde", + "time", + "xml-rs", +] + +[[package]] +name = "png" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +dependencies = [ + "bitflags", + "crc32fast", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.8", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.16", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa 1.0.5", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.5", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "soup3" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82bc46048125fefd69d30b32b9d263d6556c9ffe82a7a7df181a86d912da5616" +dependencies = [ + "bitflags", + "futures-channel", + "gio", + "glib", + "libc", + "once_cell", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "014bbeb1c4cdb30739dc181e8d98b7908f124d9555843afa89b5570aaf4ec62b" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "swift-rs" +version = "0.3.0" +source = "git+https://github.com/Brendonovich/swift-rs?rev=eb6de914ad57501da5019154d476d45660559999#eb6de914ad57501da5019154d476d45660559999" +dependencies = [ + "base64 0.13.1", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.18.0" +source = "git+https://github.com/tauri-apps/tao?branch=dev#382ea30300b8e909cae5896b57c7c623c9825655" +dependencies = [ + "bitflags", + "cairo-rs", + "cc", + "cocoa", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "gdk", + "gdk-pixbuf", + "gdk-sys", + "gdkwayland-sys", + "gdkx11-sys", + "gio", + "glib", + "glib-sys", + "gtk", + "image", + "instant", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "png", + "raw-window-handle", + "scopeguard", + "serde", + "tao-macros", + "unicode-segmentation", + "uuid", + "windows 0.44.0", + "windows-implement", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.1" +source = "git+https://github.com/tauri-apps/tao?branch=dev#382ea30300b8e909cae5896b57c7c623c9825655" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tauri" +version = "2.0.0-alpha.3" +dependencies = [ + "anyhow", + "attohttpc", + "cocoa", + "dirs-next", + "embed_plist", + "encoding_rs", + "flate2", + "futures-util", + "glib", + "glob", + "gtk", + "heck", + "http", + "ignore", + "jni", + "libc", + "log", + "objc", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "semver 1.0.16", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "state", + "swift-rs", + "tar", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "tempfile", + "thiserror", + "tokio", + "url", + "uuid", + "webkit2gtk", + "webview2-com", + "windows 0.44.0", +] + +[[package]] +name = "tauri-build" +version = "2.0.0-alpha.1" +dependencies = [ + "anyhow", + "cargo_toml", + "filetime", + "heck", + "json-patch", + "semver 1.0.16", + "serde", + "serde_json", + "swift-rs", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.0.0-alpha.1" +dependencies = [ + "base64 0.21.0", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver 1.0.16", + "serde", + "serde_json", + "sha2", + "tauri-utils", + "thiserror", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.0.0-alpha.1" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin-sample" +version = "0.1.0" +dependencies = [ + "log", + "tauri", + "tauri-build", +] + +[[package]] +name = "tauri-runtime" +version = "0.13.0-alpha.1" +dependencies = [ + "gtk", + "http", + "http-range", + "jni", + "rand 0.8.5", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "url", + "uuid", + "webview2-com", + "windows 0.44.0", +] + +[[package]] +name = "tauri-runtime-wry" +version = "0.13.0-alpha.1" +dependencies = [ + "cocoa", + "gtk", + "jni", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "tauri-runtime", + "tauri-utils", + "uuid", + "webkit2gtk", + "webview2-com", + "windows 0.44.0", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.0.0-alpha.1" +dependencies = [ + "brotli", + "ctor", + "glob", + "heck", + "html5ever", + "infer", + "json-patch", + "kuchiki", + "memchr", + "phf 0.10.1", + "proc-macro2", + "quote", + "semver 1.0.16", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", + "walkdir", + "windows 0.44.0", +] + +[[package]] +name = "tauri-winres" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b7a78dc04f75fb5ab815e66ac561c81e92a968a40f29e7c21afd152d694fad8" +dependencies = [ + "toml", + "version_check", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa 1.0.5", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +dependencies = [ + "autocfg", + "bytes", + "memchr", + "num_cpus", + "pin-project-lite", + "windows-sys", +] + +[[package]] +name = "toml" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "treediff" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" +dependencies = [ + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "webkit2gtk" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eea819afe15eb8dcdff4f19d8bfda540bae84d874c10e6f4b8faf2d6704bd1" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ac7a95ddd3fdfcaf83d8e513b4b1ad101b95b413b6aa6662ed95f284fc3d5b" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03411e89ec447e29c08b3c086edeb88c5f8fd782cbdd4d6d316bea439be7a244" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.44.0", + "windows-implement", +] + +[[package]] +name = "webview2-com-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "webview2-com-sys" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c0f5ce43e9611c5b2983a33156d6abe31abf39185bad84a6766c80ba1dbf1ab" +dependencies = [ + "regex", + "serde", + "serde_json", + "thiserror", + "windows 0.44.0", + "windows-bindgen", + "windows-metadata", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +dependencies = [ + "windows_aarch64_msvc 0.39.0", + "windows_i686_gnu 0.39.0", + "windows_i686_msvc 0.39.0", + "windows_x86_64_gnu 0.39.0", + "windows_x86_64_msvc 0.39.0", +] + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-targets", +] + +[[package]] +name = "windows-bindgen" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222204ecf46521382a4d88b4a1bbefca9f8855697b4ab7d20803901425e061a3" +dependencies = [ + "windows-metadata", + "windows-tokens", +] + +[[package]] +name = "windows-implement" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-metadata" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78911e3f4ce32c1ad9d3c7b0bd95389662ad8d8f1a3155688fed70bd96e2b6" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-tokens" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4251900975a0d10841c5d4bde79c56681543367ef811f3fabb8d1803b0959b" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "wry" +version = "0.26.0" +source = "git+https://github.com/tauri-apps/wry?branch=dev#077eb3a7ca520d07e73f899da60ce23eef941e6f" +dependencies = [ + "base64 0.13.1", + "block", + "cocoa", + "core-graphics", + "crossbeam-channel", + "dunce", + "gdk", + "gio", + "glib", + "gtk", + "html5ever", + "http", + "kuchiki", + "libc", + "log", + "objc", + "objc_id", + "once_cell", + "serde", + "serde_json", + "sha2", + "soup3", + "tao", + "thiserror", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.44.0", + "windows-implement", +] + +[[package]] +name = "x11" +version = "2.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2638d5b9c17ac40575fb54bb461a4b1d2a8d1b4ffcc4ff237d254ec59ddeb82" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/examples/api/src-tauri/tauri-plugin-sample/Cargo.toml b/examples/api/src-tauri/tauri-plugin-sample/Cargo.toml new file mode 100644 index 000000000000..41c002a1a098 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tauri-plugin-sample" +version = "0.1.0" +edition = "2021" +links = "tauri-plugin-sample" + +[dependencies] +tauri = { path = "../../../../core/tauri" } +log = "0.4" +serde = "1" +thiserror = "1" + +[build-dependencies] +tauri-build = { path = "../../../../core/tauri-build/" } diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/.gitignore b/examples/api/src-tauri/tauri-plugin-sample/android/.gitignore new file mode 100644 index 000000000000..ae485e87f4d8 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/.gitignore @@ -0,0 +1,2 @@ +/build +.tauri diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/build.gradle.kts b/examples/api/src-tauri/tauri-plugin-sample/android/build.gradle.kts new file mode 100644 index 000000000000..05d495993eea --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.plugin.sample" + compileSdk = 32 + + defaultConfig { + minSdk = 21 + targetSdk = 33 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/proguard-rules.pro b/examples/api/src-tauri/tauri-plugin-sample/android/proguard-rules.pro new file mode 100644 index 000000000000..481bb4348141 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/settings.gradle b/examples/api/src-tauri/tauri-plugin-sample/android/settings.gradle new file mode 100644 index 000000000000..14a752e43365 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/src/androidTest/java/com/plugin/sample/ExampleInstrumentedTest.kt b/examples/api/src-tauri/tauri-plugin-sample/android/src/androidTest/java/com/plugin/sample/ExampleInstrumentedTest.kt new file mode 100644 index 000000000000..5e343b5db447 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/src/androidTest/java/com/plugin/sample/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package com.plugin.sample + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.plugin.sample", appContext.packageName) + } +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/src/main/AndroidManifest.xml b/examples/api/src-tauri/tauri-plugin-sample/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..a5918e68abcd --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/src/main/java/com/plugin/sample/Example.kt b/examples/api/src-tauri/tauri-plugin-sample/android/src/main/java/com/plugin/sample/Example.kt new file mode 100644 index 000000000000..a824086528c5 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/src/main/java/com/plugin/sample/Example.kt @@ -0,0 +1,14 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package com.plugin.sample + +import android.util.Log + +class Example { + fun pong(value: String): String { + Log.i("Pong", value) + return value + } +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/src/main/java/com/plugin/sample/ExamplePlugin.kt b/examples/api/src-tauri/tauri-plugin-sample/android/src/main/java/com/plugin/sample/ExamplePlugin.kt new file mode 100644 index 000000000000..74ee8396e179 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/src/main/java/com/plugin/sample/ExamplePlugin.kt @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package com.plugin.sample + +import android.app.Activity +import app.tauri.annotation.Command +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import app.tauri.plugin.Invoke + +@TauriPlugin +class ExamplePlugin(private val activity: Activity): Plugin(activity) { + private val implementation = Example() + + @Command + fun ping(invoke: Invoke) { + val value = invoke.getString("value") ?: "" + val ret = JSObject() + ret.put("value", implementation.pong(value)) + invoke.resolve(ret) + } +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/android/src/test/java/com/plugin/sample/ExampleUnitTest.kt b/examples/api/src-tauri/tauri-plugin-sample/android/src/test/java/com/plugin/sample/ExampleUnitTest.kt new file mode 100644 index 000000000000..15ca36349274 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/android/src/test/java/com/plugin/sample/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package com.plugin.sample + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/build.rs b/examples/api/src-tauri/tauri-plugin-sample/build.rs new file mode 100644 index 000000000000..35bb9b5a57cc --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/build.rs @@ -0,0 +1,16 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::process::exit; + +fn main() { + if let Err(error) = tauri_build::mobile::PluginBuilder::new() + .android_path("android") + .ios_path("ios") + .run() + { + println!("{error:#}"); + exit(1); + } +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/ios/.gitignore b/examples/api/src-tauri/tauri-plugin-sample/ios/.gitignore new file mode 100644 index 000000000000..5922fdaa5638 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/examples/api/src-tauri/tauri-plugin-sample/ios/Package.swift b/examples/api/src-tauri/tauri-plugin-sample/ios/Package.swift new file mode 100644 index 000000000000..87bf327038c0 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-sample", + platforms: [ + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-sample", + type: .static, + targets: ["tauri-plugin-sample"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(name: "Tauri", path: "../../../../../core/tauri/mobile/ios-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-sample", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/examples/api/src-tauri/tauri-plugin-sample/ios/README.md b/examples/api/src-tauri/tauri-plugin-sample/ios/README.md new file mode 100644 index 000000000000..545904443264 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin sample + +A description of this package. diff --git a/examples/api/src-tauri/tauri-plugin-sample/ios/Sources/ExamplePlugin.swift b/examples/api/src-tauri/tauri-plugin-sample/ios/Sources/ExamplePlugin.swift new file mode 100644 index 000000000000..8837a5a7be84 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/ios/Sources/ExamplePlugin.swift @@ -0,0 +1,20 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import UIKit +import WebKit +import Tauri +import SwiftRs + +class ExamplePlugin: Plugin { + @objc public func ping(_ invoke: Invoke) throws { + let value = invoke.getString("value") + invoke.resolve(["value": value as Any]) + } +} + +@_cdecl("init_plugin_sample") +func initPlugin() -> Plugin { + return ExamplePlugin() +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/ios/Tests/PluginTests/PluginTests.swift b/examples/api/src-tauri/tauri-plugin-sample/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 000000000000..99992ce4c33c --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/src/desktop.rs b/examples/api/src-tauri/tauri-plugin-sample/src/desktop.rs new file mode 100644 index 000000000000..a0b975f776c4 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/src/desktop.rs @@ -0,0 +1,26 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::de::DeserializeOwned; +use tauri::{plugin::PluginApi, AppHandle, Runtime}; + +use crate::models::*; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(Sample(app.clone())) +} + +/// A helper class to access the sample APIs. +pub struct Sample(AppHandle); + +impl Sample { + pub fn ping(&self, payload: PingRequest) -> crate::Result { + Ok(PingResponse { + value: payload.value, + }) + } +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/src/error.rs b/examples/api/src-tauri/tauri-plugin-sample/src/error.rs new file mode 100644 index 000000000000..bd7381ed8dd0 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/src/error.rs @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +pub type Result = std::result::Result; diff --git a/examples/api/src-tauri/tauri-plugin-sample/src/lib.rs b/examples/api/src-tauri/tauri-plugin-sample/src/lib.rs new file mode 100644 index 000000000000..d7b9af348200 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/src/lib.rs @@ -0,0 +1,50 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod error; +mod models; + +#[cfg(desktop)] +use desktop::Sample; +#[cfg(mobile)] +use mobile::Sample; + +pub use error::*; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the sample APIs. +pub trait SampleExt { + fn sample(&self) -> &Sample; +} + +impl> crate::SampleExt for T { + fn sample(&self) -> &Sample { + self.state::>().inner() + } +} + +pub fn init() -> TauriPlugin { + Builder::new("sample") + .setup(|app, api| { + #[cfg(mobile)] + let sample = mobile::init(app, api)?; + #[cfg(desktop)] + let sample = desktop::init(app, api)?; + app.manage(sample); + + Ok(()) + }) + .build() +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/src/mobile.rs b/examples/api/src-tauri/tauri-plugin-sample/src/mobile.rs new file mode 100644 index 000000000000..74f1114f9171 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/src/mobile.rs @@ -0,0 +1,41 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::de::DeserializeOwned; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "com.plugin.sample"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_sample); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_sample)?; + Ok(Sample(handle)) +} + +/// A helper class to access the sample APIs. +pub struct Sample(PluginHandle); + +impl Sample { + pub fn ping(&self, payload: PingRequest) -> crate::Result { + self + .0 + .run_mobile_plugin("ping", payload) + .map_err(Into::into) + } +} diff --git a/examples/api/src-tauri/tauri-plugin-sample/src/models.rs b/examples/api/src-tauri/tauri-plugin-sample/src/models.rs new file mode 100644 index 000000000000..e2ea913d36d1 --- /dev/null +++ b/examples/api/src-tauri/tauri-plugin-sample/src/models.rs @@ -0,0 +1,15 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize)] +pub struct PingRequest { + pub value: Option, +} + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct PingResponse { + pub value: Option, +} diff --git a/examples/api/src-tauri/tauri.conf.json b/examples/api/src-tauri/tauri.conf.json index 612a21735763..fc7eef0ed0fb 100644 --- a/examples/api/src-tauri/tauri.conf.json +++ b/examples/api/src-tauri/tauri.conf.json @@ -1,22 +1,17 @@ { + "$schema": "../../../core/tauri-config-schema/schema.json", "build": { "distDir": "../dist", - "devPath": "http://localhost:5000", + "devPath": "http://localhost:5173", "beforeDevCommand": "yarn dev", - "beforeBuildCommand": "yarn build" + "beforeBuildCommand": "yarn build", + "withGlobalTauri": true }, "package": { "productName": "Tauri API", - "version": "../package.json" + "version": "1.0.0" }, - "tauri": { - "pattern": { - "use": "isolation", - "options": { - "dir": "../isolation-dist/" - } - }, - "macOSPrivateApi": true, + "plugins": { "cli": { "description": "Tauri API example", "args": [ @@ -36,7 +31,6 @@ { "short": "v", "name": "verbose", - "multipleOccurrences": true, "description": "Verbosity level" } ], @@ -52,7 +46,16 @@ ] } } + } + }, + "tauri": { + "pattern": { + "use": "isolation", + "options": { + "dir": "../isolation-dist/" + } }, + "macOSPrivateApi": true, "bundle": { "active": true, "identifier": "com.tauri.api", @@ -72,53 +75,36 @@ } } } - } - }, - "updater": { - "active": true, - "dialog": false, - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK", - "endpoints": [ - "https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}" - ] - }, - "allowlist": { - "all": true, - "fs": { - "scope": ["$APP/db", "$DOWNLOAD/**", "$RESOURCE/**"] - }, - "shell": { - "scope": [ - { - "name": "sh", - "cmd": "sh" - }, - { - "name": "cmd", - "cmd": "cmd" - } - ] - }, - "protocol": { - "asset": true, - "assetScope": ["$RESOURCE/**", "$APP/**"] }, - "http": { - "scope": ["https://jsonplaceholder.typicode.com/todos/*"] + "updater": { + "active": true, + "pubkey": "asdasd", + "windows": { + "installMode": "passive" + } } }, - "windows": [ - { - "title": "Tauri API Validation", - "transparent": true - } - ], + "windows": [], "security": { - "csp": "default-src 'self' customprotocol: img-src: 'self'; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com; img-src 'self' asset: https://asset.localhost blob: data:; font-src https://fonts.gstatic.com" + "csp": { + "default-src": "'self' customprotocol: asset:", + "font-src": ["https://fonts.gstatic.com"], + "img-src": "'self' asset: https://asset.localhost blob: data:", + "style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com" + }, + "freezePrototype": true, + "assetProtocol": { + "enable": true, + "scope": { + "allow": ["$APPDATA/db/**", "$RESOURCE/**"], + "deny": ["$APPDATA/db/*.stronghold"] + } + } }, "systemTray": { "iconPath": "../../.icons/tray_icon_with_transparency.png", - "iconAsTemplate": true + "iconAsTemplate": true, + "menuOnLeftClick": false } } } diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte index cf966d5aaeec..264e9a0bed2b 100644 --- a/examples/api/src/App.svelte +++ b/examples/api/src/App.svelte @@ -1,173 +1,304 @@ -
    - -
    -
    + + + +
    + +
    +
    +

    {selected.label}

    +
    +
    + +
    +
    -
    -
    -

    - Tauri Console - clear -

    - {#each $responses as r} - {#if r.text} -

    {r.text}

    - {:else} - {@html r.html} - {/if} - {/each} -
    -
    - +
    +
    +
    +

    Console

    +
    +
    +
    +
    +
    + {#each $messages as r} + {@html r.html} + {/each} +
    +
    + +
    diff --git a/examples/api/src/app.css b/examples/api/src/app.css new file mode 100644 index 000000000000..b1ab957c500a --- /dev/null +++ b/examples/api/src/app.css @@ -0,0 +1,41 @@ +*:not(h1, h2, h3, h4, h5, h6) { + margin: 0; + padding: 0; +} + +* { + box-sizing: border-box; + font-family: 'Rubik', sans-serif; +} + +::-webkit-scrollbar { + width: 0.25rem; + height: 3px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + border-radius: 0.25rem; +} + +code { + padding: 0.05rem 0.25rem; +} + +code.code-block { + padding: 0.5rem; +} + +#sidebar { + width: 18.75rem; +} + +@media screen and (max-width: 640px) { + #sidebar { + --translate-x: -18.75rem; + transform: translateX(var(--translate-x)); + } +} diff --git a/examples/api/src/components/Cli.svelte b/examples/api/src/components/Cli.svelte deleted file mode 100644 index a38b4f2e6c54..000000000000 --- a/examples/api/src/components/Cli.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - -
    - This binary can be run on the terminal and takes the following arguments: -
      -
    • --config PATH
    • -
    • --theme light|dark|system
    • -
    • --verbose
    • -
    - Additionally, it has a update --background subcommand. - Note that the arguments are only parsed, not implemented. -
    - -
    diff --git a/examples/api/src/components/Clipboard.svelte b/examples/api/src/components/Clipboard.svelte deleted file mode 100644 index 57430243a14e..000000000000 --- a/examples/api/src/components/Clipboard.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - -
    -
    - - -
    - -
    diff --git a/examples/api/src/components/Communication.svelte b/examples/api/src/components/Communication.svelte deleted file mode 100644 index c25a2731e862..000000000000 --- a/examples/api/src/components/Communication.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - -
    - - - -
    diff --git a/examples/api/src/components/Dialog.svelte b/examples/api/src/components/Dialog.svelte deleted file mode 100644 index ed065df9a789..000000000000 --- a/examples/api/src/components/Dialog.svelte +++ /dev/null @@ -1,122 +0,0 @@ - - -
    - - -
    - - -
    -
    - - -
    - - - -
    - - diff --git a/examples/api/src/components/FileSystem.svelte b/examples/api/src/components/FileSystem.svelte deleted file mode 100644 index e128249ab065..000000000000 --- a/examples/api/src/components/FileSystem.svelte +++ /dev/null @@ -1,99 +0,0 @@ - - -
    - - - - - - file -
    diff --git a/examples/api/src/components/Http.svelte b/examples/api/src/components/Http.svelte deleted file mode 100644 index 2dc92169aca6..000000000000 --- a/examples/api/src/components/Http.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - -
    - - -
    -