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/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/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-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/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/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/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/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/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/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-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-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-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/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/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/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-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 7d7bbe20e12d..33ce3a189261 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -12,12 +12,11 @@ "defaultChangeTag": "changes", "pkgManagers": { "rust": { - "errorOnVersionRange": "^2.0.0-0", "version": true, "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 webkit2gtk-4.0 libayatana-appindicator3-dev", + "sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev", "cargo install cargo-audit --features=fix", { "command": "cargo generate-lockfile", @@ -77,7 +76,6 @@ ] }, "javascript": { - "errorOnVersionRange": "^2.0.0-0", "version": true, "getPublishedVersion": "node ../../.scripts/covector/package-latest-version.js npm ${ pkgFile.pkg.name } ${ pkgFile.pkg.version }", "prepublish": [ @@ -114,7 +112,7 @@ "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 }, @@ -236,7 +234,8 @@ "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 }" }, 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-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-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-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-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-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/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/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-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/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/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/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/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/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/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-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-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-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-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-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-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-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-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-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-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-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-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/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/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/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/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/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-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-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/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-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_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/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/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/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/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/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/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/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/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-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/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/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-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-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/pre.json b/.changes/pre.json new file mode 100644 index 000000000000..043b4fba8bbc --- /dev/null +++ b/.changes/pre.json @@ -0,0 +1,153 @@ +{ + "tag": "alpha", + "changes": [ + ".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/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/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/readme.md b/.changes/readme.md index 73ee22d9790e..318eea024e77 100644 --- a/.changes/readme.md +++ b/.changes/readme.md @@ -10,8 +10,8 @@ Use the following format: ```md --- -'package-a': patch -'package-b': patch +'package-a': 'patch:enhance' +'package-b': 'patch:enhance' --- Change summary goes here @@ -33,7 +33,7 @@ Additionally you could specify a tag for the change file to group it with other ```md --- -'package-a': patch:bug +'package-a': 'patch:enhance' --- Change summary goes here 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-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/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-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-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-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-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-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-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.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/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/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/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/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/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/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/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-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-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-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-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/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/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/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/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/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/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/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/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/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 index 0435c8cf7b09..63d7bcd3ae6e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -6,7 +6,7 @@ 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.0-dev wget" +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/.docker/cross/aarch64.Dockerfile b/.docker/cross/aarch64.Dockerfile index 6dfb5bdaa718..498d055b75f9 100644 --- a/.docker/cross/aarch64.Dockerfile +++ b/.docker/cross/aarch64.Dockerfile @@ -41,4 +41,4 @@ ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ 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.0-dev:arm64 libappindicator3-1:arm64 librsvg2-dev:arm64 patchelf:arm64 +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/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5ddc5cd30346..cdf2d5c619c6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -71,7 +71,7 @@ The code for the bundler is located in `[Tauri repo root]/tooling/bundler`, and ### 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 diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 432e82239893..6738a4d563eb 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -52,7 +52,7 @@ jobs: run: | python -m pip install --upgrade pip sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev 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 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 33f7b7c361fc..16b3eb0f5ce3 100644 --- a/.github/workflows/covector-version-or-publish.yml +++ b/.github/workflows/covector-version-or-publish.yml @@ -30,7 +30,7 @@ jobs: if: matrix.platform == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev libfuse2 + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev libfuse2 - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fd02565f0e0d..f9b066b5dc8b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -115,7 +115,7 @@ jobs: - name: install dependencies run: | sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev - name: Test run: | diff --git a/.github/workflows/lint-core.yml b/.github/workflows/lint-core.yml index e41264d2ad33..06c62a115766 100644 --- a/.github/workflows/lint-core.yml +++ b/.github/workflows/lint-core.yml @@ -50,11 +50,10 @@ jobs: clippy: - { args: '', key: 'empty' } - { - args: '--features compression,wry,linux-protocol-headers,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart,test', + args: '--features compression,wry,linux-protocol-headers,isolation,custom-protocol,system-tray,test', key: 'all' } - { args: '--features custom-protocol', key: 'custom-protocol' } - - { args: '--features api-all', key: 'api-all' } steps: - uses: actions/checkout@v2 @@ -62,7 +61,7 @@ jobs: - name: install dependencies run: | sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev - uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/publish-cli-js.yml b/.github/workflows/publish-cli-js.yml index 4a02dcbd4030..9dab2702317b 100644 --- a/.github/workflows/publish-cli-js.yml +++ b/.github/workflows/publish-cli-js.yml @@ -43,7 +43,7 @@ jobs: - host: windows-latest architecture: x64 target: aarch64-pc-windows-msvc - build: yarn build:release --target aarch64-pc-windows-msvc --features native-tls,native-tls-vendored --cargo-flags="--no-default-features" + 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 @@ -261,7 +261,7 @@ jobs: - name: install system dependencies run: | sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev - name: Test bindings run: yarn test test-linux-x64-musl-binding: @@ -355,7 +355,7 @@ jobs: 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 webkit2gtk-4.0 libayatana-appindicator3-dev + 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 @@ -397,8 +397,8 @@ jobs: - name: Publish run: | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - npm publish + npm publish --tag next env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.ORG_NPM_TOKEN }} - RELEASE_ID: ${{ github.event.client_payload.releaseId || github.event.inputs.releaseId }} + 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 index d7d6df216405..911442eeefe3 100644 --- a/.github/workflows/publish-cli-rs.yml +++ b/.github/workflows/publish-cli-rs.yml @@ -37,11 +37,10 @@ jobs: - os: windows-latest rust_target: aarch64-pc-windows-msvc ext: '.exe' - args: '--no-default-features --features native-tls,native-tls-vendored' + args: '--no-default-features --features native-tls-vendored' steps: - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - name: 'Setup Rust' uses: dtolnay/rust-toolchain@stable 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-cli-js.yml b/.github/workflows/test-cli-js.yml index e9717674d79c..aaeea54f6310 100644 --- a/.github/workflows/test-cli-js.yml +++ b/.github/workflows/test-cli-js.yml @@ -51,7 +51,7 @@ jobs: if: matrix.platform == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/test-cli-rs.yml b/.github/workflows/test-cli-rs.yml index 20485b551676..c1e0a5a1b64b 100644 --- a/.github/workflows/test-cli-rs.yml +++ b/.github/workflows/test-cli-rs.yml @@ -24,24 +24,40 @@ concurrency: jobs: test: - runs-on: ${{ matrix.platform }} + 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 + } + - { + 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: install stable - uses: actions-rs/toolchain@v1 + - name: 'Setup Rust' + uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - override: true + targets: ${{ matrix.platform.target }} - name: install Linux dependencies - if: matrix.platform == 'ubuntu-latest' + if: matrix.platform.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev @@ -54,4 +70,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --manifest-path ./tooling/cli/Cargo.toml + 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 7ba58dcc96d0..6d1376684914 100644 --- a/.github/workflows/test-core.yml +++ b/.github/workflows/test-core.yml @@ -34,17 +34,37 @@ jobs: - { target: x86_64-pc-windows-msvc, os: windows-latest, - toolchain: '1.61.0' + toolchain: '1.65.0', + cross: false, + command: 'test' } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, - toolchain: '1.60.0' + toolchain: '1.65.0', + cross: false, + command: 'test' } - { target: x86_64-apple-darwin, os: macos-latest, - toolchain: '1.60.0' + 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: - { @@ -52,11 +72,7 @@ jobs: key: no-default } - { - args: --features api-all, - key: api-all - } - - { - args: --features compression,wry,linux-protocol-headers,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart,test, + args: --features compression,wry,linux-protocol-headers,isolation,custom-protocol,system-tray,test, key: all } @@ -75,25 +91,16 @@ jobs: if: contains(matrix.platform.target, 'unknown-linux') run: | sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev - uses: Swatinem/rust-cache@v2 with: workspaces: core -> ../target save-if: ${{ matrix.features.key == 'all' }} - - name: Downgrade crates with MSRV conflict - # The --precise flag can only be used once per invocation. - run: | - cargo update -p toml:0.7.4 --precise 0.7.3 - cargo update -p toml_edit --precise 0.19.8 - cargo update -p toml_datetime --precise 0.6.1 - cargo update -p serde_spanned --precise 0.6.1 - cargo update -p winnow --precise 0.4.1 - cargo update -p time --precise 0.3.15 - cargo update -p ignore --precise 0.4.18 - cargo update -p raw-window-handle --precise 0.5.0 - cargo update -p cargo_toml:0.15.3 --precise 0.15.2 - - name: test - run: cargo test --target ${{ matrix.platform.target }} ${{ matrix.features.args }} + 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-updater-artifacts.yml b/.github/workflows/test-updater-artifacts.yml deleted file mode 100644 index dbe6d00ab7ca..000000000000 --- a/.github/workflows/test-updater-artifacts.yml +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright 2019-2023 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: test updater artifacts -on: - schedule: - - cron: '0 0 * * *' - pull_request: - paths: - - '.github/workflows/test-updater-artifacts.yml' - - 'examples/updater/**' - workflow_dispatch: - -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 - - - name: install Linux dependencies - if: matrix.platform == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: | - core -> ../target - tooling/cli - - - name: build and install `tauri-cli` - run: cargo install --path tooling/cli --force - - 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 --verbose - 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== - TAURI_KEY_PASSWORD: - # 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 --verbose - env: - TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg== - TAURI_KEY_PASSWORD: - # 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/udeps.yml b/.github/workflows/udeps.yml index 470afbd17625..65a1ded28a87 100644 --- a/.github/workflows/udeps.yml +++ b/.github/workflows/udeps.yml @@ -157,7 +157,7 @@ jobs: - name: Install required packages run: | sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev - uses: actions-rs/cargo@v1 with: diff --git a/.scripts/ci/check-license-header.js b/.scripts/ci/check-license-header.js index ae3dfcb1edf7..d0d6dc68f98f 100644 --- a/.scripts/ci/check-license-header.js +++ b/.scripts/ci/check-license-header.js @@ -14,7 +14,7 @@ SPDX-License-Identifier: MIT` const bundlerLicense = '// Copyright 2016-2019 Cargo-Bundle developers ' -const extensions = ['.rs', '.js', '.ts', '.yml'] +const extensions = ['.rs', '.js', '.ts', '.yml', '.swift', '.kt'] const ignore = [ 'target', 'templates', @@ -26,7 +26,7 @@ const ignore = [ ] async function checkFile(file) { - if (extensions.some((e) => file.endsWith(e))) { + if (extensions.some((e) => file.endsWith(e)) && !ignore.some((i) => file.includes(`/${i}/`))) { const fileStream = fs.createReadStream(file) const rl = readline.createInterface({ input: fileStream, @@ -40,6 +40,7 @@ async function checkFile(file) { if ( line.length === 0 || line.startsWith('#!') || + line.startsWith('// swift-tools-version:') || line === bundlerLicense ) { continue diff --git a/.scripts/covector/sync-cli-metadata.js b/.scripts/covector/sync-cli-metadata.js index 79d540e89e0c..4edef073a515 100644 --- a/.scripts/covector/sync-cli-metadata.js +++ b/.scripts/covector/sync-cli-metadata.js @@ -12,6 +12,7 @@ rust binaries. */ const { readFileSync, writeFileSync } = require('fs') +const { resolve } = require('path') const packageNickname = process.argv[2] const filePath = @@ -23,6 +24,7 @@ let index = null switch (bump) { case 'major': + case 'premajor': index = 0 break case 'minor': @@ -31,6 +33,9 @@ switch (bump) { case 'patch': index = 2 break + case 'prerelease': + index = 3 + break default: throw new Error('unexpected bump ' + bump) } @@ -44,6 +49,10 @@ const inc = (version) => { 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('.') } diff --git a/.scripts/update-lockfiles.sh b/.scripts/update-lockfiles.sh index 9d71ba830348..961d0fb4c5a5 100755 --- a/.scripts/update-lockfiles.sh +++ b/.scripts/update-lockfiles.sh @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: MIT -declare -a examples=("api" "sidecar" "updater" "resources" "tauri-dynamic-lib" "workspace") +declare -a examples=("api" "sidecar" "resources" "tauri-dynamic-lib" "workspace") declare -a tooling=("bench" "cli" "webdriver") for example in "${examples[@]}" diff --git a/Cargo.toml b/Cargo.toml index af5d14713f18..e31b3c9d07d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,19 +12,26 @@ members = [ # integration tests "core/tests/restart", - "core/tests/app-updater", ] 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/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/README.md b/README.md index 1320cd4adcc6..18903b7b5f03 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ For **developing** Tauri apps refer to the [Getting Started guide on tauri.app]( 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.0-37`, `libgtk-3-0`, `libayatana-appindicator3-1`1 + - `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: diff --git a/core/tauri-build/CHANGELOG.md b/core/tauri-build/CHANGELOG.md index ccf6cebbc427..5eda582b7536 100644 --- a/core/tauri-build/CHANGELOG.md +++ b/core/tauri-build/CHANGELOG.md @@ -1,5 +1,44 @@ # 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 diff --git a/core/tauri-build/Cargo.toml b/core/tauri-build/Cargo.toml index 4f92f8a467ab..1804ba295e16 100644 --- a/core/tauri-build/Cargo.toml +++ b/core/tauri-build/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "tauri-build" -version = "1.4.0" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.app" -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.60" 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,16 +21,20 @@ rustdoc-args = [ "--cfg", "doc_cfg" ] [dependencies] anyhow = "1" quote = { version = "1", optional = true } -tauri-codegen = { version = "1.4.0", path = "../tauri-codegen", optional = true } -tauri-utils = { version = "1.4.0", path = "../tauri-utils", features = [ "build", "resources" ] } +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(target_os = \"macos\")".dependencies] +swift-rs = { version = "1.0.4", features = [ "build" ] } + [features] codegen = [ "tauri-codegen", "quote" ] isolation = [ "tauri-codegen/isolation", "tauri-utils/isolation" ] diff --git a/core/tauri-build/src/lib.rs b/core/tauri-build/src/lib.rs index 231f7d649a87..35539de45f13 100644 --- a/core/tauri-build/src/lib.rs +++ b/core/tauri-build/src/lib.rs @@ -4,6 +4,7 @@ #![cfg_attr(doc_cfg, feature(doc_cfg))] +use anyhow::Context; pub use anyhow::Result; use cargo_toml::Manifest; use heck::AsShoutySnakeCase; @@ -13,11 +14,16 @@ use tauri_utils::{ resources::{external_binaries, resource_relpath, ResourcePaths}, }; -use std::path::{Path, PathBuf}; +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")] @@ -103,17 +109,6 @@ fn cfg_alias(alias: &str, has_feature: bool) { #[derive(Debug, Default)] pub struct WindowsAttributes { window_icon_path: Option, - /// The path to the sdk location. - /// - /// For the GNU toolkit this has to be the path where MinGW put windres.exe and ar.exe. - /// This could be something like: "C:\Program Files\mingw-w64\x86_64-5.3.0-win32-seh-rt_v4-rev0\mingw64\bin" - /// - /// For MSVC the Windows SDK has to be installed. It comes with the resource compiler rc.exe. - /// This should be set to the root directory of the Windows SDK, e.g., "C:\Program Files (x86)\Windows Kits\10" or, - /// if multiple 10 versions are installed, set it directly to the correct bin directory "C:\Program Files (x86)\Windows Kits\10\bin\10.0.14393.0\x64" - /// - /// If it is left unset, it will look up a path in the registry, i.e. HKLM\SOFTWARE\Microsoft\Windows Kits\Installed Roots - sdk_dir: Option, /// A string containing an [application manifest] to be included with the application on Windows. /// /// Defaults to: @@ -159,14 +154,6 @@ impl WindowsAttributes { self } - /// Sets the sdk dir for windows. Currently only used on Windows. This must be a valid UTF-8 - /// path. Defaults to whatever the `winres` crate determines is best. - #[must_use] - pub fn sdk_dir>(mut self, sdk_dir: P) -> Self { - self.sdk_dir = Some(sdk_dir.as_ref().into()); - self - } - /// Sets the [application manifest] to be included with the application on Windows. /// /// Defaults to: @@ -305,6 +292,22 @@ pub fn try_build(attributes: Attributes) -> Result<()> { } 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 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()?; @@ -324,6 +327,9 @@ pub fn try_build(attributes: Attributes) -> Result<()> { allowlist::check(&config, &mut manifest)?; let target_triple = std::env::var("TARGET").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 = out_dir @@ -361,7 +367,6 @@ pub fn try_build(attributes: Attributes) -> Result<()> { } if target_triple.contains("windows") { - use anyhow::Context; use semver::Version; use tauri_winres::{VersionInfo, WindowsResource}; @@ -382,54 +387,47 @@ pub fn try_build(attributes: Attributes) -> Result<()> { .window_icon_path .unwrap_or_else(|| find_icon(&config, |i| i.ends_with(".ico"), "icons/icon.ico")); - if window_icon_path.exists() { - let mut res = WindowsResource::new(); + 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 { - res.set_manifest(include_str!("window-app-manifest.xml")); - } - - 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 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); + + 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); } - 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", + 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() - ) - })?; - } else { - return Err(anyhow!(format!( - "`{}` not found; required for generating a Windows Resource file during tauri-build", - window_icon_path.display() - ))); + ))); + } } let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); 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-codegen/CHANGELOG.md b/core/tauri-codegen/CHANGELOG.md index 6081cbc7b409..37ed4158f359 100644 --- a/core/tauri-codegen/CHANGELOG.md +++ b/core/tauri-codegen/CHANGELOG.md @@ -1,5 +1,41 @@ # 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 diff --git a/core/tauri-codegen/Cargo.toml b/core/tauri-codegen/Cargo.toml index 16d201e50673..291b0c118fd1 100644 --- a/core/tauri-codegen/Cargo.toml +++ b/core/tauri-codegen/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "tauri-codegen" -version = "1.4.0" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.app" -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.60" 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.21" @@ -19,16 +21,16 @@ proc-macro2 = "1" quote = "1" serde = { version = "1", features = [ "derive" ] } serde_json = "1" -tauri-utils = { version = "1.4.0", path = "../tauri-utils", features = [ "build" ] } +tauri-utils = { version = "2.0.0-alpha.5", path = "../tauri-utils", features = [ "build" ] } thiserror = "1" walkdir = "2" brotli = { version = "3", optional = true, default-features = false, features = [ "std" ] } -regex = { version = "1.7.1", optional = true } 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" @@ -38,6 +40,5 @@ time = { version = "0.3", features = [ "parsing", "formatting" ] } default = [ "compression" ] compression = [ "brotli", "tauri-utils/compression" ] isolation = [ "tauri-utils/isolation" ] -shell-scope = [ "regex" ] config-json5 = [ "tauri-utils/config-json5" ] config-toml = [ "tauri-utils/config-toml" ] diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index 778a7fc294b1..cf556e5120cc 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -16,9 +16,6 @@ use tauri_utils::html::{ inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, }; -#[cfg(feature = "shell-scope")] -use tauri_utils::config::{ShellAllowedArg, ShellAllowedArgs, ShellAllowlistScope}; - use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError}; /// Necessary data needed by [`context_codegen`] to generate code for a Tauri application context. @@ -125,6 +122,16 @@ enum Target { 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 { @@ -134,33 +141,34 @@ pub fn context_codegen(data: ContextData) -> Result Result Result Result Result Result Result quote!(::std::option::Option::None), - ShellAllowlistOpen::Flag(true) => { - quote!(::std::option::Option::Some(#root::regex::Regex::new(r#"^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+"#).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"), - }; - - quote!(#root::ShellScopeConfig { - open: #shell_scope_open, - scopes: #shell_scopes - }) - }; - - #[cfg(not(feature = "shell-scope"))] - let shell_scope_config = quote!(); - - Ok(quote!(#root::Context::new( - #config, - ::std::sync::Arc::new(#assets), - #default_window_icon, - #app_icon, - #system_tray_icon, - #package_info, - #info_plist, - #pattern, - #shell_scope_config - ))) + Ok(quote!({ + #[allow(unused_mut, clippy::let_and_return)] + let mut context = #root::Context::new( + #config, + ::std::sync::Arc::new(#assets), + #default_window_icon, + #app_icon, + #package_info, + #info_plist, + #pattern, + ); + #with_system_tray_icon_code + context + })) } fn ico_icon>( @@ -481,7 +465,7 @@ fn ico_icon>( let out_path = out_path.display().to_string(); - let icon = quote!(Some(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height })); + let icon = quote!(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height }); Ok(icon) } @@ -499,7 +483,9 @@ fn raw_icon>(out_dir: &Path, path: P) -> Result>( let out_path = out_path.display().to_string(); - let icon = quote!(Some(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height })); + let icon = quote!(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height }); Ok(icon) } @@ -572,78 +558,3 @@ fn find_icon bool>( .unwrap_or_else(|| default.to_string()); config_parent.join(icon_path) } - -#[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 - }) - } -} diff --git a/core/tauri-codegen/src/embedded_assets.rs b/core/tauri-codegen/src/embedded_assets.rs index cb9f2f459573..ce9e0fcb472f 100644 --- a/core/tauri-codegen/src/embedded_assets.rs +++ b/core/tauri-codegen/src/embedded_assets.rs @@ -433,7 +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)] + #[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-config-schema/Cargo.toml b/core/tauri-config-schema/Cargo.toml index 7c5fbbd90e69..64188842f46c 100644 --- a/core/tauri-config-schema/Cargo.toml +++ b/core/tauri-config-schema/Cargo.toml @@ -5,11 +5,10 @@ edition = "2021" publish = false [build-dependencies] -tauri-utils = { version = "1.0.0", features = [ +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" -serde_with = "1.12" url = { version = "2.3", features = ["serde"] } diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json index 4cf848842676..475211b88406 100644 --- a/core/tauri-config-schema/schema.json +++ b/core/tauri-config-schema/schema.json @@ -1,7 +1,7 @@ { "$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, enable the app updater, define a system tray, enable APIs via the allowlist and more.\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`, and `tauri.macos.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml` and `Tauri.macos.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\": { \"allowlist\": { \"all\": true }, \"bundle\": {}, \"security\": { \"csp\": null }, \"updater\": { \"active\": false }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```", + "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": { @@ -26,125 +26,31 @@ "tauri": { "description": "The Tauri configuration.", "default": { - "allowlist": { - "all": false, - "app": { - "all": false, - "hide": false, - "show": false - }, - "clipboard": { - "all": false, - "readText": false, - "writeText": false - }, - "dialog": { - "all": false, - "ask": false, - "confirm": false, - "message": false, - "open": false, - "save": false - }, - "fs": { - "all": false, - "copyFile": false, - "createDir": false, - "exists": false, - "readDir": false, - "readFile": false, - "removeDir": false, - "removeFile": false, - "renameFile": false, - "scope": [], - "writeFile": false - }, - "globalShortcut": { - "all": false - }, - "http": { - "all": false, - "request": false, - "scope": [] - }, - "notification": { - "all": false - }, - "os": { - "all": false - }, - "path": { - "all": false - }, - "process": { - "all": false, - "exit": false, - "relaunch": false, - "relaunchDangerousAllowSymlinkMacos": false - }, - "protocol": { - "all": false, - "asset": false, - "assetScope": [] - }, - "shell": { - "all": false, - "execute": false, - "open": false, - "scope": [], - "sidecar": false - }, - "window": { - "all": false, - "center": false, - "close": false, - "create": false, - "hide": false, - "maximize": false, - "minimize": false, - "print": false, - "requestUserAttention": false, - "setAlwaysOnTop": false, - "setClosable": false, - "setContentProtected": false, - "setCursorGrab": false, - "setCursorIcon": false, - "setCursorPosition": false, - "setCursorVisible": false, - "setDecorations": false, - "setFocus": false, - "setFullscreen": false, - "setIcon": false, - "setIgnoreCursorEvents": false, - "setMaxSize": false, - "setMaximizable": false, - "setMinSize": false, - "setMinimizable": false, - "setPosition": false, - "setResizable": false, - "setSize": false, - "setSkipTaskbar": false, - "setTitle": false, - "show": false, - "startDragging": false, - "unmaximize": false, - "unminimize": false - } - }, "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, @@ -165,19 +71,14 @@ "use": "brownfield" }, "security": { + "assetProtocol": { + "enable": false, + "scope": [] + }, "dangerousDisableAssetCspModification": false, "dangerousRemoteDomainIpcAccess": [], "freezePrototype": false }, - "updater": { - "active": false, - "dialog": true, - "pubkey": "", - "windows": { - "installMode": "passive", - "installerArgs": [] - } - }, "windows": [] }, "allOf": [ @@ -257,33 +158,33 @@ "$ref": "#/definitions/WindowConfig" } }, - "cli": { - "description": "The CLI configuration.", - "anyOf": [ - { - "$ref": "#/definitions/CliConfig" - }, - { - "type": "null" - } - ] - }, "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, @@ -305,122 +206,13 @@ } ] }, - "allowlist": { - "description": "The allowlist configuration.", - "default": { - "all": false, - "app": { - "all": false, - "hide": false, - "show": false - }, - "clipboard": { - "all": false, - "readText": false, - "writeText": false - }, - "dialog": { - "all": false, - "ask": false, - "confirm": false, - "message": false, - "open": false, - "save": false - }, - "fs": { - "all": false, - "copyFile": false, - "createDir": false, - "exists": false, - "readDir": false, - "readFile": false, - "removeDir": false, - "removeFile": false, - "renameFile": false, - "scope": [], - "writeFile": false - }, - "globalShortcut": { - "all": false - }, - "http": { - "all": false, - "request": false, - "scope": [] - }, - "notification": { - "all": false - }, - "os": { - "all": false - }, - "path": { - "all": false - }, - "process": { - "all": false, - "exit": false, - "relaunch": false, - "relaunchDangerousAllowSymlinkMacos": false - }, - "protocol": { - "all": false, - "asset": false, - "assetScope": [] - }, - "shell": { - "all": false, - "execute": false, - "open": false, - "scope": [], - "sidecar": false - }, - "window": { - "all": false, - "center": false, - "close": false, - "create": false, - "hide": false, - "maximize": false, - "minimize": false, - "print": false, - "requestUserAttention": false, - "setAlwaysOnTop": false, - "setClosable": false, - "setContentProtected": false, - "setCursorGrab": false, - "setCursorIcon": false, - "setCursorPosition": false, - "setCursorVisible": false, - "setDecorations": false, - "setFocus": false, - "setFullscreen": false, - "setIcon": false, - "setIgnoreCursorEvents": false, - "setMaxSize": false, - "setMaximizable": false, - "setMinSize": false, - "setMinimizable": false, - "setPosition": false, - "setResizable": false, - "setSize": false, - "setSkipTaskbar": false, - "setTitle": false, - "show": false, - "startDragging": false, - "unmaximize": false, - "unminimize": false - } - }, - "allOf": [ - { - "$ref": "#/definitions/AllowlistConfig" - } - ] - }, "security": { "description": "Security configuration.", "default": { + "assetProtocol": { + "enable": false, + "scope": [] + }, "dangerousDisableAssetCspModification": false, "dangerousRemoteDomainIpcAccess": [], "freezePrototype": false @@ -431,23 +223,6 @@ } ] }, - "updater": { - "description": "The updater configuration.", - "default": { - "active": false, - "dialog": true, - "pubkey": "", - "windows": { - "installMode": "passive", - "installerArgs": [] - } - }, - "allOf": [ - { - "$ref": "#/definitions/UpdaterConfig" - } - ] - }, "systemTray": { "description": "Configuration for app system tray.", "anyOf": [ @@ -723,6 +498,27 @@ "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 @@ -786,251 +582,270 @@ } ] }, - "CliConfig": { - "description": "describes a CLI configuration\n\nSee more: https://tauri.app/v1/api/config#cliconfig", + "WindowEffectsConfig": { + "description": "The window effects configuration object", "type": "object", + "required": [ + "effects" + ], "properties": { - "description": { - "description": "Command description which will be shown on the help information.", - "type": [ - "string", - "null" - ] - }, - "longDescription": { - "description": "Command long description which will be shown on the help information.", - "type": [ - "string", - "null" - ] - }, - "beforeHelp": { - "description": "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.", - "type": [ - "string", - "null" - ] + "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" + } }, - "afterHelp": { - "description": "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.", - "type": [ - "string", - "null" + "state": { + "description": "Window effect state **macOS Only**", + "anyOf": [ + { + "$ref": "#/definitions/WindowEffectState" + }, + { + "type": "null" + } ] }, - "args": { - "description": "List of arguments for the command", + "radius": { + "description": "Window effect corner radius **macOS Only**", "type": [ - "array", + "number", "null" ], - "items": { - "$ref": "#/definitions/CliArg" - } + "format": "double" }, - "subcommands": { - "description": "List of subcommands of this command", - "type": [ - "object", - "null" - ], - "additionalProperties": { - "$ref": "#/definitions/CliConfig" - } + "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 }, - "CliArg": { - "description": "A CLI argument definition.", - "type": "object", - "required": [ - "name" - ], - "properties": { - "short": { - "description": "The short version of the argument, without the preceding -.\n\nNOTE: Any leading `-` characters will be stripped, and only the first non-character will be used as the short version.", - "type": [ - "string", - "null" - ], - "maxLength": 1, - "minLength": 1 - }, - "name": { - "description": "The unique argument name", - "type": "string" - }, - "description": { - "description": "The argument description which will be shown on the help information. Typically, this is a short (one line) description of the arg.", - "type": [ - "string", - "null" + "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" ] }, - "longDescription": { - "description": "The argument long description which will be shown on the help information. Typically this a more detailed (multi-line) message that describes the argument.", - "type": [ - "string", - "null" + { + "description": "*macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "light" ] }, - "takesValue": { - "description": "Specifies that the argument takes a value at run time.\n\nNOTE: 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", - "default": false, - "type": "boolean" + { + "description": "*macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "dark" + ] }, - "multiple": { - "description": "Specifies that the argument may have an unknown number of multiple values. Without any other settings, this argument may appear only once.\n\nFor example, --opt val1 val2 is allowed, but --opt val1 val2 --opt val3 is not.\n\nNOTE: Setting this requires `takes_value` to be set to true.", - "default": false, - "type": "boolean" + { + "description": "*macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "mediumLight" + ] }, - "multipleOccurrences": { - "description": "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)\n\nFor example, --opt val1 --opt val2 is allowed, but --opt val1 val2 is not.", - "default": false, - "type": "boolean" + { + "description": "*macOS 10.14-**", + "deprecated": true, + "type": "string", + "enum": [ + "ultraDark" + ] }, - "numberOfValues": { - "description": "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.\n\n**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.\n\n**NOTE:** implicitly sets `takes_value = true` and `multiple_values = true`.", - "type": [ - "integer", - "null" - ], - "format": "uint", - "minimum": 0.0 + { + "description": "*macOS 10.10+**", + "type": "string", + "enum": [ + "titlebar" + ] }, - "possibleValues": { - "description": "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.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + { + "description": "*macOS 10.10+**", + "type": "string", + "enum": [ + "selection" + ] }, - "minValues": { - "description": "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.", - "type": [ - "integer", - "null" - ], - "format": "uint", - "minimum": 0.0 + { + "description": "*macOS 10.11+**", + "type": "string", + "enum": [ + "menu" + ] }, - "maxValues": { - "description": "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.", - "type": [ - "integer", - "null" - ], - "format": "uint", - "minimum": 0.0 + { + "description": "*macOS 10.11+**", + "type": "string", + "enum": [ + "popover" + ] }, - "required": { - "description": "Sets whether or not the argument is required by default.\n\n- Required by default means it is required, when no other conflicting rules have been evaluated - Conflicting rules take precedence over being required.", - "default": false, - "type": "boolean" + { + "description": "*macOS 10.11+**", + "type": "string", + "enum": [ + "sidebar" + ] }, - "requiredUnlessPresent": { - "description": "Sets an arg that override this arg's required setting i.e. this arg will be required unless this other argument is present.", - "type": [ - "string", - "null" + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "headerView" ] }, - "requiredUnlessPresentAll": { - "description": "Sets args that override this arg's required setting i.e. this arg will be required unless all these other arguments are present.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "sheet" + ] }, - "requiredUnlessPresentAny": { - "description": "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.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "windowBackground" + ] }, - "conflictsWith": { - "description": "Sets a conflicting argument by name i.e. when using this argument, the following argument can't be present and vice versa.", - "type": [ - "string", - "null" + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "hudWindow" ] }, - "conflictsWithAll": { - "description": "The same as conflictsWith but allows specifying multiple two-way conflicts per argument.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "fullScreenUI" + ] }, - "requires": { - "description": "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.", - "type": [ - "string", - "null" + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "tooltip" ] }, - "requiresAll": { - "description": "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.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "contentBackground" + ] }, - "requiresIf": { - "description": "Allows a conditional requirement with the signature [arg, value] the requirement will only become valid if `arg`'s value equals `${value}`.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "underWindowBackground" + ] }, - "requiredIfEq": { - "description": "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}`.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + { + "description": "*macOS 10.14+**", + "type": "string", + "enum": [ + "underPageBackground" + ] }, - "requireEquals": { - "description": "Requires that options use the --option=val syntax i.e. an equals between the option and associated value.", - "type": [ - "boolean", - "null" + { + "description": "*Windows 11 Only**", + "type": "string", + "enum": [ + "mica" ] }, - "index": { - "description": "The positional argument index, starting at 1.\n\nThe 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).", - "type": [ - "integer", - "null" - ], - "format": "uint", - "minimum": 1.0 + { + "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" + ] } - }, - "additionalProperties": false + ] + }, + "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", @@ -1174,6 +989,41 @@ "$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 @@ -1787,902 +1637,163 @@ } ] }, - "AllowlistConfig": { - "description": "Allowlist configuration. The allowlist is a translation of the [Cargo allowlist features](https://docs.rs/tauri/latest/tauri/#cargo-allowlist-features).\n\n# Notes\n\n- Endpoints that don't have their own allowlist option are enabled by default. - There is only \"opt-in\", no \"opt-out\". Setting an option to `false` has no effect.\n\n# Examples\n\n- * [`\"app-all\": true`](https://tauri.app/v1/api/config/#appallowlistconfig.all) will make the [hide](https://tauri.app/v1/api/js/app#hide) endpoint be available regardless of whether `hide` is set to `false` or `true` in the allowlist.", + "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": { - "all": { - "description": "Use this flag to enable all API features.", + "active": { + "description": "Whether the updater is active or not.", "default": false, "type": "boolean" }, - "fs": { - "description": "File system API allowlist.", - "default": { - "all": false, - "copyFile": false, - "createDir": false, - "exists": false, - "readDir": false, - "readFile": false, - "removeDir": false, - "removeFile": false, - "renameFile": false, - "scope": [], - "writeFile": false - }, - "allOf": [ - { - "$ref": "#/definitions/FsAllowlistConfig" - } - ] + "pubkey": { + "description": "Signature public key.", + "default": "", + "type": "string" }, - "window": { - "description": "Window API allowlist.", + "windows": { + "description": "The Windows configuration for the updater.", "default": { - "all": false, - "center": false, - "close": false, - "create": false, - "hide": false, - "maximize": false, - "minimize": false, - "print": false, - "requestUserAttention": false, - "setAlwaysOnTop": false, - "setClosable": false, - "setContentProtected": false, - "setCursorGrab": false, - "setCursorIcon": false, - "setCursorPosition": false, - "setCursorVisible": false, - "setDecorations": false, - "setFocus": false, - "setFullscreen": false, - "setIcon": false, - "setIgnoreCursorEvents": false, - "setMaxSize": false, - "setMaximizable": false, - "setMinSize": false, - "setMinimizable": false, - "setPosition": false, - "setResizable": false, - "setSize": false, - "setSkipTaskbar": false, - "setTitle": false, - "show": false, - "startDragging": false, - "unmaximize": false, - "unminimize": false + "installMode": "passive" }, "allOf": [ { - "$ref": "#/definitions/WindowAllowlistConfig" + "$ref": "#/definitions/UpdaterWindowsConfig" } ] - }, - "shell": { - "description": "Shell API allowlist.", - "default": { - "all": false, - "execute": false, - "open": false, - "scope": [], - "sidecar": false - }, + } + }, + "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/ShellAllowlistConfig" + "$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" + ] }, - "dialog": { - "description": "Dialog API allowlist.", - "default": { - "all": false, - "ask": false, - "confirm": false, - "message": false, - "open": false, - "save": false - }, - "allOf": [ - { - "$ref": "#/definitions/DialogAllowlistConfig" - } + { + "description": "The quiet mode means there's no user interaction required. Requires admin privileges if the installer does.", + "type": "string", + "enum": [ + "quiet" ] }, - "http": { - "description": "HTTP API allowlist.", - "default": { - "all": false, - "request": false, - "scope": [] - }, - "allOf": [ + { + "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" + }, { - "$ref": "#/definitions/HttpAllowlistConfig" + "type": "null" } ] }, - "notification": { - "description": "Notification API allowlist.", - "default": { - "all": false - }, - "allOf": [ + "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/NotificationAllowlistConfig" + "$ref": "#/definitions/Csp" + }, + { + "type": "null" } ] }, - "globalShortcut": { - "description": "Global shortcut API allowlist.", - "default": { - "all": false - }, + "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/GlobalShortcutAllowlistConfig" + "$ref": "#/definitions/DisabledCspModificationKind" } ] }, - "os": { - "description": "OS allowlist.", + "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": { - "all": false + "enable": false, + "scope": [] }, "allOf": [ { - "$ref": "#/definitions/OsAllowlistConfig" + "$ref": "#/definitions/AssetProtocolConfig" } ] - }, - "path": { - "description": "Path API allowlist.", - "default": { - "all": false - }, - "allOf": [ - { - "$ref": "#/definitions/PathAllowlistConfig" - } - ] - }, - "protocol": { - "description": "Custom protocol allowlist.", - "default": { - "all": false, - "asset": false, - "assetScope": [] - }, - "allOf": [ - { - "$ref": "#/definitions/ProtocolAllowlistConfig" - } - ] - }, - "process": { - "description": "Process API allowlist.", - "default": { - "all": false, - "exit": false, - "relaunch": false, - "relaunchDangerousAllowSymlinkMacos": false - }, - "allOf": [ - { - "$ref": "#/definitions/ProcessAllowlistConfig" - } - ] - }, - "clipboard": { - "description": "Clipboard APIs allowlist.", - "default": { - "all": false, - "readText": false, - "writeText": false - }, - "allOf": [ - { - "$ref": "#/definitions/ClipboardAllowlistConfig" - } - ] - }, - "app": { - "description": "App APIs allowlist.", - "default": { - "all": false, - "hide": false, - "show": false - }, - "allOf": [ - { - "$ref": "#/definitions/AppAllowlistConfig" - } - ] - } - }, - "additionalProperties": false - }, - "FsAllowlistConfig": { - "description": "Allowlist for the file system APIs.\n\nSee more: https://tauri.app/v1/api/config#fsallowlistconfig", - "type": "object", - "properties": { - "scope": { - "description": "The access scope for the filesystem APIs.", - "default": [], - "allOf": [ - { - "$ref": "#/definitions/FsAllowlistScope" - } - ] - }, - "all": { - "description": "Use this flag to enable all file system API features.", - "default": false, - "type": "boolean" - }, - "readFile": { - "description": "Read file from local filesystem.", - "default": false, - "type": "boolean" - }, - "writeFile": { - "description": "Write file to local filesystem.", - "default": false, - "type": "boolean" - }, - "readDir": { - "description": "Read directory from local filesystem.", - "default": false, - "type": "boolean" - }, - "copyFile": { - "description": "Copy file from local filesystem.", - "default": false, - "type": "boolean" - }, - "createDir": { - "description": "Create directory from local filesystem.", - "default": false, - "type": "boolean" - }, - "removeDir": { - "description": "Remove directory from local filesystem.", - "default": false, - "type": "boolean" - }, - "removeFile": { - "description": "Remove file from local filesystem.", - "default": false, - "type": "boolean" - }, - "renameFile": { - "description": "Rename file from local filesystem.", - "default": false, - "type": "boolean" - }, - "exists": { - "description": "Check if path exists on the local filesystem.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "FsAllowlistScope": { - "description": "Filesystem 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" - ] - } - } - } - ] - }, - "WindowAllowlistConfig": { - "description": "Allowlist for the window APIs.\n\nSee more: https://tauri.app/v1/api/config#windowallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all window API features.", - "default": false, - "type": "boolean" - }, - "create": { - "description": "Allows dynamic window creation.", - "default": false, - "type": "boolean" - }, - "center": { - "description": "Allows centering the window.", - "default": false, - "type": "boolean" - }, - "requestUserAttention": { - "description": "Allows requesting user attention on the window.", - "default": false, - "type": "boolean" - }, - "setResizable": { - "description": "Allows setting the resizable flag of the window.", - "default": false, - "type": "boolean" - }, - "setMaximizable": { - "description": "Allows setting whether the window's native maximize button is enabled or not.", - "default": false, - "type": "boolean" - }, - "setMinimizable": { - "description": "Allows setting whether the window's native minimize button is enabled or not.", - "default": false, - "type": "boolean" - }, - "setClosable": { - "description": "Allows setting whether the window's native close button is enabled or not.", - "default": false, - "type": "boolean" - }, - "setTitle": { - "description": "Allows changing the window title.", - "default": false, - "type": "boolean" - }, - "maximize": { - "description": "Allows maximizing the window.", - "default": false, - "type": "boolean" - }, - "unmaximize": { - "description": "Allows unmaximizing the window.", - "default": false, - "type": "boolean" - }, - "minimize": { - "description": "Allows minimizing the window.", - "default": false, - "type": "boolean" - }, - "unminimize": { - "description": "Allows unminimizing the window.", - "default": false, - "type": "boolean" - }, - "show": { - "description": "Allows showing the window.", - "default": false, - "type": "boolean" - }, - "hide": { - "description": "Allows hiding the window.", - "default": false, - "type": "boolean" - }, - "close": { - "description": "Allows closing the window.", - "default": false, - "type": "boolean" - }, - "setDecorations": { - "description": "Allows setting the decorations flag of the window.", - "default": false, - "type": "boolean" - }, - "setAlwaysOnTop": { - "description": "Allows setting the always_on_top flag of the window.", - "default": false, - "type": "boolean" - }, - "setContentProtected": { - "description": "Allows preventing the window contents from being captured by other apps.", - "default": false, - "type": "boolean" - }, - "setSize": { - "description": "Allows setting the window size.", - "default": false, - "type": "boolean" - }, - "setMinSize": { - "description": "Allows setting the window minimum size.", - "default": false, - "type": "boolean" - }, - "setMaxSize": { - "description": "Allows setting the window maximum size.", - "default": false, - "type": "boolean" - }, - "setPosition": { - "description": "Allows changing the position of the window.", - "default": false, - "type": "boolean" - }, - "setFullscreen": { - "description": "Allows setting the fullscreen flag of the window.", - "default": false, - "type": "boolean" - }, - "setFocus": { - "description": "Allows focusing the window.", - "default": false, - "type": "boolean" - }, - "setIcon": { - "description": "Allows changing the window icon.", - "default": false, - "type": "boolean" - }, - "setSkipTaskbar": { - "description": "Allows setting the skip_taskbar flag of the window.", - "default": false, - "type": "boolean" - }, - "setCursorGrab": { - "description": "Allows grabbing the cursor.", - "default": false, - "type": "boolean" - }, - "setCursorVisible": { - "description": "Allows setting the cursor visibility.", - "default": false, - "type": "boolean" - }, - "setCursorIcon": { - "description": "Allows changing the cursor icon.", - "default": false, - "type": "boolean" - }, - "setCursorPosition": { - "description": "Allows setting the cursor position.", - "default": false, - "type": "boolean" - }, - "setIgnoreCursorEvents": { - "description": "Allows ignoring cursor events.", - "default": false, - "type": "boolean" - }, - "startDragging": { - "description": "Allows start dragging on the window.", - "default": false, - "type": "boolean" - }, - "print": { - "description": "Allows opening the system dialog to print the window content.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "ShellAllowlistConfig": { - "description": "Allowlist for the shell APIs.\n\nSee more: https://tauri.app/v1/api/config#shellallowlistconfig", - "type": "object", - "properties": { - "scope": { - "description": "Access scope for the binary execution APIs. Sidecars are automatically enabled.", - "default": [], - "allOf": [ - { - "$ref": "#/definitions/ShellAllowlistScope" - } - ] - }, - "all": { - "description": "Use this flag to enable all shell API features.", - "default": false, - "type": "boolean" - }, - "execute": { - "description": "Enable binary execution.", - "default": false, - "type": "boolean" - }, - "sidecar": { - "description": "Enable sidecar execution, allowing the JavaScript layer to spawn a sidecar command, an executable that is shipped with the application. For more information see .", - "default": false, - "type": "boolean" - }, - "open": { - "description": "Open URL with the user's default application.", - "default": false, - "allOf": [ - { - "$ref": "#/definitions/ShellAllowlistOpen" - } - ] - } - }, - "additionalProperties": false - }, - "ShellAllowlistScope": { - "description": "Shell scope definition. It is a list of command names and associated CLI arguments that restrict the API access from the webview.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellAllowedCommand" - } - }, - "ShellAllowedCommand": { - "description": "A command allowed to be executed by the webview API.", - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "cmd": { - "description": "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`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "default": "", - "type": "string" - }, - "args": { - "description": "The allowed arguments for the command execution.", - "default": false, - "allOf": [ - { - "$ref": "#/definitions/ShellAllowedArgs" - } - ] - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "default": false, - "type": "boolean" - } - } - }, - "ShellAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA 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.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellAllowedArg" - } - } - ] - }, - "ShellAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellAllowlistOpen": { - "description": "Defines the `shell > open` api scope.", - "anyOf": [ - { - "description": "If the shell open API should be enabled.\n\nIf enabled, the default validation regex (`^((mailto:\\w+)|(tel:\\w+)|(https?://\\w+)).+`) is used.", - "type": "boolean" - }, - { - "description": "Enable the shell open API, with a custom regex that the opened path must match against.\n\nIf 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`.", - "type": "string" - } - ] - }, - "DialogAllowlistConfig": { - "description": "Allowlist for the dialog APIs.\n\nSee more: https://tauri.app/v1/api/config#dialogallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all dialog API features.", - "default": false, - "type": "boolean" - }, - "open": { - "description": "Allows the API to open a dialog window to pick files.", - "default": false, - "type": "boolean" - }, - "save": { - "description": "Allows the API to open a dialog window to pick where to save files.", - "default": false, - "type": "boolean" - }, - "message": { - "description": "Allows the API to show a message dialog window.", - "default": false, - "type": "boolean" - }, - "ask": { - "description": "Allows the API to show a dialog window with Yes/No buttons.", - "default": false, - "type": "boolean" - }, - "confirm": { - "description": "Allows the API to show a dialog window with Ok/Cancel buttons.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "HttpAllowlistConfig": { - "description": "Allowlist for the HTTP APIs.\n\nSee more: https://tauri.app/v1/api/config#httpallowlistconfig", - "type": "object", - "properties": { - "scope": { - "description": "The access scope for the HTTP APIs.", - "default": [], - "allOf": [ - { - "$ref": "#/definitions/HttpAllowlistScope" - } - ] - }, - "all": { - "description": "Use this flag to enable all HTTP API features.", - "default": false, - "type": "boolean" - }, - "request": { - "description": "Allows making HTTP requests.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "HttpAllowlistScope": { - "description": "HTTP API scope definition. It is a list of URLs that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples: - \"https://*\": allows all HTTPS urls - \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path - \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", - "type": "array", - "items": { - "type": "string", - "format": "uri" - } - }, - "NotificationAllowlistConfig": { - "description": "Allowlist for the notification APIs.\n\nSee more: https://tauri.app/v1/api/config#notificationallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all notification API features.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "GlobalShortcutAllowlistConfig": { - "description": "Allowlist for the global shortcut APIs.\n\nSee more: https://tauri.app/v1/api/config#globalshortcutallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all global shortcut API features.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "OsAllowlistConfig": { - "description": "Allowlist for the OS APIs.\n\nSee more: https://tauri.app/v1/api/config#osallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all OS API features.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "PathAllowlistConfig": { - "description": "Allowlist for the path APIs.\n\nSee more: https://tauri.app/v1/api/config#pathallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all path API features.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "ProtocolAllowlistConfig": { - "description": "Allowlist for the custom protocols.\n\nSee more: https://tauri.app/v1/api/config#protocolallowlistconfig", - "type": "object", - "properties": { - "assetScope": { - "description": "The access scope for the asset protocol.", - "default": [], - "allOf": [ - { - "$ref": "#/definitions/FsAllowlistScope" - } - ] - }, - "all": { - "description": "Use this flag to enable all custom protocols.", - "default": false, - "type": "boolean" - }, - "asset": { - "description": "Enables the asset protocol.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "ProcessAllowlistConfig": { - "description": "Allowlist for the process APIs.\n\nSee more: https://tauri.app/v1/api/config#processallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all process APIs.", - "default": false, - "type": "boolean" - }, - "relaunch": { - "description": "Enables the relaunch API.", - "default": false, - "type": "boolean" - }, - "relaunchDangerousAllowSymlinkMacos": { - "description": "Dangerous option that allows macOS to relaunch even if the binary contains a symlink.\n\nThis 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.", - "default": false, - "type": "boolean" - }, - "exit": { - "description": "Enables the exit API.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "ClipboardAllowlistConfig": { - "description": "Allowlist for the clipboard APIs.\n\nSee more: https://tauri.app/v1/api/config#clipboardallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all clipboard APIs.", - "default": false, - "type": "boolean" - }, - "writeText": { - "description": "Enables the clipboard's `writeText` API.", - "default": false, - "type": "boolean" - }, - "readText": { - "description": "Enables the clipboard's `readText` API.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "AppAllowlistConfig": { - "description": "Allowlist for the app APIs.\n\nSee more: https://tauri.app/v1/api/config#appallowlistconfig", - "type": "object", - "properties": { - "all": { - "description": "Use this flag to enable all app APIs.", - "default": false, - "type": "boolean" - }, - "show": { - "description": "Enables the app's `show` API.", - "default": false, - "type": "boolean" - }, - "hide": { - "description": "Enables the app's `hide` API.", - "default": false, - "type": "boolean" - } - }, - "additionalProperties": false - }, - "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" - } } }, "additionalProperties": false @@ -2768,111 +1879,69 @@ "items": { "type": "string" } - }, - "enableTauriAPI": { - "description": "Enables access to the Tauri API.", - "default": false, - "type": "boolean" } }, "additionalProperties": false }, - "UpdaterConfig": { - "description": "The Updater configuration object.\n\nSee more: https://tauri.app/v1/api/config#updaterconfig", + "AssetProtocolConfig": { + "description": "Config for the asset custom protocol.\n\nSee more: https://tauri.app/v1/api/config#assetprotocolconfig", "type": "object", "properties": { - "active": { - "description": "Whether the updater is active or not.", - "default": false, - "type": "boolean" - }, - "dialog": { - "description": "Display built-in dialog or use event system if disabled.", - "default": true, - "type": "boolean" - }, - "endpoints": { - "description": "The updater endpoints. TLS is enforced on production.\n\nThe updater URL can contain the following variables: - {{current_version}}: The version of the app that is requesting the update - {{target}}: The operating system name (one of `linux`, `windows` or `darwin`). - {{arch}}: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).\n\n# Examples - \"https://my.cdn.com/latest.json\": a raw JSON endpoint that returns the latest version and download links for each platform. - \"https://updates.app.dev/{{target}}?version={{current_version}}&arch={{arch}}\": a dedicated API with positional and query string arguments.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/UpdaterEndpoint" - } - }, - "pubkey": { - "description": "Signature public key.", - "default": "", - "type": "string" - }, - "windows": { - "description": "The Windows configuration for the updater.", - "default": { - "installMode": "passive", - "installerArgs": [] - }, + "scope": { + "description": "The access scope for the asset protocol.", + "default": [], "allOf": [ { - "$ref": "#/definitions/UpdaterWindowsConfig" + "$ref": "#/definitions/FsScope" } ] + }, + "enable": { + "description": "Enables the asset protocol.", + "default": false, + "type": "boolean" } }, "additionalProperties": false }, - "UpdaterEndpoint": { - "description": "A URL to an updater server.\n\nThe URL must use the `https` scheme on production.", - "type": "string", - "format": "uri" - }, - "UpdaterWindowsConfig": { - "description": "The updater configuration for Windows.\n\nSee more: https://tauri.app/v1/api/config#updaterwindowsconfig", - "type": "object", - "properties": { - "installerArgs": { - "description": "Additional arguments given to the NSIS or WiX installer.", - "default": [], + "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" } }, - "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" - ] + "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" + ] + } + } } ] }, diff --git a/core/tauri-macros/CHANGELOG.md b/core/tauri-macros/CHANGELOG.md index f3a4f4b782a6..acda17970000 100644 --- a/core/tauri-macros/CHANGELOG.md +++ b/core/tauri-macros/CHANGELOG.md @@ -1,5 +1,48 @@ # 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 diff --git a/core/tauri-macros/Cargo.toml b/core/tauri-macros/Cargo.toml index 2381ebfaba2d..7035d5b5b0af 100644 --- a/core/tauri-macros/Cargo.toml +++ b/core/tauri-macros/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "tauri-macros" -version = "1.4.0" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "os", "filesystem", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.app" -repository = "https://github.com/tauri-apps/tauri" +version = "2.0.0-alpha.5" description = "Macros for the tauri crate." -edition = "2021" -rust-version = "1.60" 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,13 +22,12 @@ proc-macro2 = "1" quote = "1" syn = { version = "1", features = [ "full" ] } heck = "0.4" -tauri-codegen = { version = "1.4.0", default-features = false, path = "../tauri-codegen" } -tauri-utils = { version = "1.4.0", 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" ] 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/src/command/handler.rs b/core/tauri-macros/src/command/handler.rs index 5c8051856530..62a9ace45840 100644 --- a/core/tauri-macros/src/command/handler.rs +++ b/core/tauri-macros/src/command/handler.rs @@ -4,26 +4,40 @@ 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/wrapper.rs b/core/tauri-macros/src/command/wrapper.rs index e529d8fcdc8d..19dbb2c379d8 100644 --- a/core/tauri-macros/src/command/wrapper.rs +++ b/core/tauri-macros/src/command/wrapper.rs @@ -4,17 +4,18 @@ use heck::{ToLowerCamelCase, ToSnakeCase}; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, quote_spanned}; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, parse_macro_input, spanned::Spanned, - FnArg, Ident, ItemFn, Lit, Meta, Pat, Token, Visibility, + FnArg, ItemFn, Lit, Meta, Pat, Token, Visibility, }; struct WrapperAttributes { + root: TokenStream2, execution_context: ExecutionContext, argument_case: ArgumentCase, } @@ -22,6 +23,7 @@ struct WrapperAttributes { 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, }; @@ -43,6 +45,17 @@ impl Parse for WrapperAttributes { } }; } + } 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)) => { @@ -161,21 +174,28 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { } // body to the command wrapper or a `compile_error!` of an error occurred while parsing it. - let body = syn::parse::(attributes) + let (body, attributes) = syn::parse::(attributes) .map(|mut attrs| { if function.sig.asyncness.is_some() { attrs.execution_context = ExecutionContext::Async; } attrs }) - .and_then(|attrs| match attrs.execution_context { - ExecutionContext::Async => body_async(&function, &invoke, attrs.argument_case), - ExecutionContext::Blocking => body_blocking(&function, &invoke, attrs.argument_case), + .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 @@ -188,10 +208,10 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // double braces because the item is expected to be a block expression ($path:path, $invoke:ident) => {{ #[allow(unused_imports)] - use ::tauri::command::private::*; + 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 }}; @@ -209,15 +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, case: ArgumentCase) -> syn::Result { +fn body_async( + function: &ItemFn, + invoke: &Invoke, + attributes: &WrapperAttributes, +) -> syn::Result { let Invoke { message, resolver } = invoke; - parse_args(function, message, case).map(|args| { + parse_args(function, message, attributes).map(|args| { quote! { #resolver.respond_async_serialized(async move { let result = $path(#(#args?),*); let kind = (&result).async_kind(); kind.future(result).await }); + return true; } }) } @@ -230,21 +255,22 @@ fn body_async(function: &ItemFn, invoke: &Invoke, case: ArgumentCase) -> syn::Re fn body_blocking( function: &ItemFn, invoke: &Invoke, - case: ArgumentCase, + attributes: &WrapperAttributes, ) -> syn::Result { let Invoke { message, resolver } = invoke; - let args = parse_args(function, message, case)?; + 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! { let result = $path(#(match #args #match_body),*); let kind = (&result).blocking_kind(); kind.block(result, #resolver); + return true; }) } @@ -252,13 +278,13 @@ fn body_blocking( fn parse_args( function: &ItemFn, message: &Ident, - case: ArgumentCase, + attributes: &WrapperAttributes, ) -> syn::Result> { function .sig .inputs .iter() - .map(|arg| parse_arg(&function.sig.ident, arg, message, case)) + .map(|arg| parse_arg(&function.sig.ident, arg, message, attributes)) .collect() } @@ -267,7 +293,7 @@ fn parse_arg( command: &Ident, arg: &FnArg, message: &Ident, - case: ArgumentCase, + attributes: &WrapperAttributes, ) -> syn::Result { // we have no use for self arguments let mut arg = match arg { @@ -302,7 +328,7 @@ fn parse_arg( )); } - match case { + match attributes.argument_case { ArgumentCase::Camel => { key = key.to_lower_camel_case(); } @@ -311,8 +337,10 @@ fn parse_arg( } } - 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, diff --git a/core/tauri-macros/src/command_module.rs b/core/tauri-macros/src/command_module.rs deleted file mode 100644 index 6bbbd16eb25e..000000000000 --- a/core/tauri-macros/src/command_module.rs +++ /dev/null @@ -1,303 +0,0 @@ -// 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::{Span, TokenStream as TokenStream2}; - -use quote::{format_ident, quote, quote_spanned}; -use syn::{ - parse::{Parse, ParseStream}, - parse_quote, - spanned::Spanned, - Data, DeriveInput, Error, Fields, Ident, ItemFn, LitStr, Token, -}; - -pub(crate) fn generate_command_enum(mut input: DeriveInput) -> TokenStream { - let mut deserialize_functions = TokenStream2::new(); - let mut errors = TokenStream2::new(); - - input.attrs.push(parse_quote!(#[allow(dead_code)])); - - match &mut input.data { - Data::Enum(data_enum) => { - for variant in &mut data_enum.variants { - let mut feature: Option = None; - let mut error_message: Option = None; - - for attr in &variant.attrs { - if attr.path.is_ident("cmd") { - let r = attr - .parse_args_with(|input: ParseStream| { - if let Ok(f) = input.parse::() { - feature.replace(f); - input.parse::()?; - let error_message_raw: LitStr = input.parse()?; - error_message.replace(error_message_raw.value()); - } - Ok(quote!()) - }) - .unwrap_or_else(syn::Error::into_compile_error); - errors.extend(r); - } - } - - if let Some(f) = feature { - let error_message = if let Some(e) = error_message { - let e = e.to_string(); - quote!(#e) - } else { - quote!("This API is not enabled in the allowlist.") - }; - - let deserialize_function_name = quote::format_ident!("__{}_deserializer", variant.ident); - deserialize_functions.extend(quote! { - #[cfg(not(#f))] - #[allow(non_snake_case)] - fn #deserialize_function_name<'de, D, T>(deserializer: D) -> ::std::result::Result - where - D: ::serde::de::Deserializer<'de>, - { - ::std::result::Result::Err(::serde::de::Error::custom(crate::Error::ApiNotAllowlisted(#error_message.into()).to_string())) - } - }); - - let deserialize_function_name = deserialize_function_name.to_string(); - - variant - .attrs - .push(parse_quote!(#[cfg_attr(not(#f), serde(deserialize_with = #deserialize_function_name))])); - } - } - } - _ => { - return Error::new( - Span::call_site(), - "`command_enum` is only implemented for enums", - ) - .to_compile_error() - .into() - } - }; - - TokenStream::from(quote! { - #errors - #input - #deserialize_functions - }) -} - -pub(crate) fn generate_run_fn(input: DeriveInput) -> TokenStream { - let name = &input.ident; - let data = &input.data; - - let mut errors = TokenStream2::new(); - - let mut is_async = false; - - let attrs = input.attrs; - for attr in attrs { - if attr.path.is_ident("cmd") { - let r = attr - .parse_args_with(|input: ParseStream| { - if let Ok(token) = input.parse::() { - is_async = token == "async"; - } - Ok(quote!()) - }) - .unwrap_or_else(syn::Error::into_compile_error); - errors.extend(r); - } - } - - 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 mut feature = None; - - for attr in &variant.attrs { - if attr.path.is_ident("cmd") { - let r = attr - .parse_args_with(|input: ParseStream| { - if let Ok(f) = input.parse::() { - feature.replace(f); - input.parse::()?; - let _: LitStr = input.parse()?; - } - Ok(quote!()) - }) - .unwrap_or_else(syn::Error::into_compile_error); - errors.extend(r); - } - } - - let maybe_feature_check = if let Some(f) = feature { - quote!(#[cfg(#f)]) - } else { - quote!() - }; - - 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() => #maybe_feature_check #name::#variant_name #fields_in_variant => #name::#variant_execute_function_name(context, #variables)#maybe_await.map(Into::into), - }); - } - - matcher.extend(quote! { - _ => Err(crate::error::into_anyhow("API not in the allowlist (https://tauri.app/docs/api/config#tauri.allowlist)")), - }); - } - _ => { - 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! { - #errors - impl #impl_generics #name #ty_generics #where_clause { - pub #maybe_async fn run(self, context: crate::endpoints::InvokeContext) -> super::Result { - match self { - #matcher - } - } - } - }; - - TokenStream::from(expanded) -} - -/// Attributes for the module enum variant handler. -pub struct HandlerAttributes { - allowlist: Ident, -} - -impl Parse for HandlerAttributes { - fn parse(input: ParseStream) -> syn::Result { - Ok(Self { - allowlist: input.parse()?, - }) - } -} - -pub enum AllowlistCheckKind { - Runtime, - Serde, -} - -pub struct HandlerTestAttributes { - allowlist: Ident, - error_message: String, - allowlist_check_kind: AllowlistCheckKind, -} - -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 allowlist_check_kind = - if let (Ok(_), Ok(i)) = (input.parse::(), input.parse::()) { - if i == "runtime" { - AllowlistCheckKind::Runtime - } else { - AllowlistCheckKind::Serde - } - } else { - AllowlistCheckKind::Serde - }; - - Ok(Self { - allowlist, - error_message, - allowlist_check_kind, - }) - } -} - -pub fn command_handler(attributes: HandlerAttributes, function: ItemFn) -> TokenStream2 { - let allowlist = attributes.allowlist; - - quote!( - #[cfg(#allowlist)] - #function - ) -} - -pub fn command_test(attributes: HandlerTestAttributes, function: ItemFn) -> TokenStream2 { - let allowlist = attributes.allowlist; - let error_message = attributes.error_message.as_str(); - let signature = &function.sig; - - let enum_variant_name = function.sig.ident.to_string().to_lower_camel_case(); - let response = match attributes.allowlist_check_kind { - AllowlistCheckKind::Runtime => { - let test_name = &signature.ident; - quote!(super::Cmd::#test_name(crate::test::mock_invoke_context())) - } - AllowlistCheckKind::Serde => quote! { - serde_json::from_str::(&format!(r#"{{ "cmd": "{}", "data": null }}"#, #enum_variant_name)) - }, - }; - - quote!( - #[cfg(#allowlist)] - #function - - #[cfg(not(#allowlist))] - #[allow(unused_variables)] - #[quickcheck_macros::quickcheck] - #signature { - if let Err(e) = #response { - assert!(e.to_string().contains(#error_message)); - } else { - panic!("unexpected response"); - } - } - ) -} diff --git a/core/tauri-macros/src/lib.rs b/core/tauri-macros/src/lib.rs index 93462d134c91..9d5616a8e5f9 100644 --- a/core/tauri-macros/src/lib.rs +++ b/core/tauri-macros/src/lib.rs @@ -4,10 +4,10 @@ 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,6 +24,11 @@ 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(). /// /// # Examples @@ -75,40 +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() } - -/// Prepares the command module enum. -#[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) -} - -/// 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 context and the variant's fields as arguments. -/// That function must also return the same `Result`. -#[doc(hidden)] -#[proc_macro_attribute] -pub fn command_enum(_: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - command_module::generate_command_enum(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-runtime-wry/CHANGELOG.md b/core/tauri-runtime-wry/CHANGELOG.md index 2840b148f3ba..ced4eff9033e 100644 --- a/core/tauri-runtime-wry/CHANGELOG.md +++ b/core/tauri-runtime-wry/CHANGELOG.md @@ -1,5 +1,49 @@ # 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 diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 843a95ff379a..736479fc84b5 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -1,40 +1,45 @@ [package] name = "tauri-runtime-wry" -version = "0.14.0" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.app" -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.60" 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.24.1", default-features = false, features = [ "file-drop", "protocol" ] } -tauri-runtime = { version = "0.14.0", path = "../tauri-runtime" } -tauri-utils = { version = "1.4.0", path = "../tauri-utils" } +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] -webview2-com = "0.19.1" +webview2-com = "0.22" [target."cfg(windows)".dependencies.windows] - version = "0.39.0" + 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" ] } -webkit2gtk = { version = "0.18.2", features = [ "v2_22" ] } +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/devtools", "tauri-runtime/devtools" ] @@ -45,6 +50,4 @@ macos-private-api = [ "tauri-runtime/macos-private-api" ] objc-exception = [ "wry/objc-exception" ] -global-shortcut = [ "tauri-runtime/global-shortcut" ] -clipboard = [ "tauri-runtime/clipboard" ] linux-headers = [ "wry/linux-headers", "webkit2gtk/v2_36" ] diff --git a/core/tauri-runtime-wry/src/clipboard.rs b/core/tauri-runtime-wry/src/clipboard.rs deleted file mode 100644 index 428f1abb3a1b..000000000000 --- a/core/tauri-runtime-wry/src/clipboard.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Clipboard implementation. - -use crate::{getter, Context, Message}; - -use std::sync::{ - mpsc::{channel, Sender}, - Arc, Mutex, -}; - -use tauri_runtime::{ClipboardManager, Result, UserEvent}; -pub use wry::application::clipboard::Clipboard; - -#[derive(Debug, Clone)] -pub enum ClipboardMessage { - WriteText(String, Sender<()>), - ReadText(Sender>), -} - -#[derive(Debug, Clone)] -pub struct ClipboardManagerWrapper { - pub 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: V) -> Result<()> { - let (tx, rx) = channel(); - getter!( - self, - rx, - Message::Clipboard(ClipboardMessage::WriteText(text.into(), tx)) - )?; - Ok(()) - } -} - -pub fn handle_clipboard_message( - message: ClipboardMessage, - clipboard_manager: &Arc>, -) { - 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(), - } -} diff --git a/core/tauri-runtime-wry/src/global_shortcut.rs b/core/tauri-runtime-wry/src/global_shortcut.rs deleted file mode 100644 index 4b3bbb61c559..000000000000 --- a/core/tauri-runtime-wry/src/global_shortcut.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Global shortcut implementation. - -use std::{ - collections::HashMap, - error::Error as StdError, - fmt, - sync::{ - mpsc::{channel, Sender}, - Arc, Mutex, - }, -}; - -use crate::{getter, Context, Message}; - -use tauri_runtime::{Error, GlobalShortcutManager, Result, UserEvent}; -#[cfg(desktop)] -pub use wry::application::{ - accelerator::{Accelerator, AcceleratorId, AcceleratorParseError}, - global_shortcut::{GlobalShortcut, ShortcutManager as WryShortcutManager}, -}; - -pub type GlobalShortcutListeners = Arc>>>; - -#[derive(Debug, Clone)] -pub enum GlobalShortcutMessage { - IsRegistered(Accelerator, Sender), - Register(Accelerator, Sender>), - Unregister(GlobalShortcutWrapper, Sender>), - UnregisterAll(Sender>), -} - -#[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 {} - -#[derive(Debug, Clone)] -struct AcceleratorParseErrorWrapper(AcceleratorParseError); -impl fmt::Display for AcceleratorParseErrorWrapper { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} -impl StdError for AcceleratorParseErrorWrapper {} - -/// Wrapper around [`WryShortcutManager`]. -#[derive(Clone)] -pub struct GlobalShortcutManagerHandle { - pub context: Context, - pub shortcuts: Arc>>, - pub 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() - .map_err(|e: AcceleratorParseError| Error::GlobalShortcut(Box::new( - AcceleratorParseErrorWrapper(e) - )))?, - tx - )) - ) - } - - fn register(&mut self, accelerator: &str, handler: F) -> Result<()> { - let wry_accelerator: Accelerator = - accelerator.parse().map_err(|e: AcceleratorParseError| { - Error::GlobalShortcut(Box::new(AcceleratorParseErrorWrapper(e))) - })?; - 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(()) - } -} - -pub fn handle_global_shortcut_message( - message: GlobalShortcutMessage, - global_shortcut_manager: &Arc>, -) { - 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(), - } -} diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index efd76c9b2e03..dfb672577503 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -12,7 +12,7 @@ use tauri_runtime::{ webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase}, window::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - CursorIcon, DetachedWindow, FileDropEvent, JsEventListenerKey, PendingWindow, WindowEvent, + CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, WindowEvent, }, DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType, UserEvent, @@ -67,11 +67,17 @@ use wry::{ 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(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}; @@ -86,7 +92,7 @@ use std::{ cell::RefCell, collections::{ hash_map::Entry::{Occupied, Vacant}, - HashMap, HashSet, + HashMap, }, fmt, ops::Deref, @@ -104,9 +110,7 @@ type FileDropHandler = dyn Fn(&Window, WryFileDropEvent) -> bool + 'static; #[cfg(all(desktop, feature = "system-tray"))] pub use tauri_runtime::TrayId; -#[cfg(desktop)] mod webview; -#[cfg(desktop)] pub use webview::Webview; #[cfg(all(desktop, feature = "system-tray"))] @@ -114,16 +118,6 @@ mod system_tray; #[cfg(all(desktop, feature = "system-tray"))] use system_tray::*; -#[cfg(all(desktop, feature = "global-shortcut"))] -mod global_shortcut; -#[cfg(all(desktop, feature = "global-shortcut"))] -use global_shortcut::*; - -#[cfg(feature = "clipboard")] -mod clipboard; -#[cfg(feature = "clipboard")] -use clipboard::*; - pub type WebContextStore = Arc, WebContext>>>; // window pub type WindowEventHandler = Box; @@ -172,10 +166,6 @@ pub(crate) fn send_user_message( message, UserMessageContext { webview_id_map: context.webview_id_map.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: context.main_thread.global_shortcut_manager.clone(), - #[cfg(feature = "clipboard")] - clipboard_manager: context.main_thread.clipboard_manager.clone(), windows: context.main_thread.windows.clone(), #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager: context.main_thread.system_tray_manager.clone(), @@ -197,6 +187,7 @@ pub struct Context { main_thread_id: ThreadId, pub proxy: WryEventLoopProxy>, main_thread: DispatcherMainThreadContext, + plugins: Arc + Send>>>>, } impl Context { @@ -216,7 +207,6 @@ impl Context { fn create_webview(&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 context = self.clone(); let window_id = rand::random(); @@ -238,7 +228,6 @@ impl Context { label, dispatcher, menu_ids, - js_event_listeners, }) } } @@ -247,10 +236,6 @@ impl Context { pub struct DispatcherMainThreadContext { pub window_target: EventLoopWindowTarget>, pub web_context: WebContextStore, - #[cfg(all(desktop, feature = "global-shortcut"))] - pub global_shortcut_manager: Arc>, - #[cfg(feature = "clipboard")] - pub clipboard_manager: Arc>, pub windows: Arc>>, #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager: SystemTrayManager, @@ -302,7 +287,7 @@ impl From for HttpResponseWrapper { res_builder = res_builder.header(name, val); } - let res = res_builder.body(Cow::Owned(body)).unwrap(); + let res = res_builder.body(body).unwrap(); Self(res) } } @@ -712,21 +697,7 @@ impl WindowBuilder for WindowBuilderWrapper { } 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) - .maximizable(config.maximizable) - .minimizable(config.minimizable) - .closable(config.closable) - .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); + let mut window = WindowBuilderWrapper::new(); #[cfg(target_os = "macos")] { @@ -760,18 +731,35 @@ impl WindowBuilder for WindowBuilderWrapper { window.inner = window.inner.with_cursor_moved_event(false); } - 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); - } + #[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(); + if config.center { + window = window.center(); + } } window @@ -885,6 +873,18 @@ impl WindowBuilder for WindowBuilderWrapper { 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); @@ -1009,10 +1009,10 @@ fn decode_path(path: PathBuf) -> PathBuf { impl From for FileDropEvent { fn from(event: FileDropEventWrapper) -> Self { match event.0 { - WryFileDropEvent::Hovered(paths) => { + WryFileDropEvent::Hovered { paths, position: _ } => { FileDropEvent::Hovered(paths.into_iter().map(decode_path).collect()) } - WryFileDropEvent::Dropped(paths) => { + WryFileDropEvent::Dropped { paths, position: _ } => { FileDropEvent::Dropped(paths.into_iter().map(decode_path).collect()) } // default to cancelled @@ -1051,7 +1051,6 @@ pub enum ApplicationMessage { } pub enum WindowMessage { - #[cfg(desktop)] WithWebview(Box), AddEventListener(Uuid, Box), AddMenuEventListener(Uuid, Box), @@ -1112,6 +1111,7 @@ pub enum WindowMessage { Hide, Close, SetDecorations(bool), + SetShadow(bool), SetAlwaysOnTop(bool), SetContentProtected(bool), SetSize(Size), @@ -1179,10 +1179,6 @@ pub enum Message { Box (String, WryWindowBuilder) + Send>, Sender>>, ), - #[cfg(all(desktop, feature = "global-shortcut"))] - GlobalShortcut(GlobalShortcutMessage), - #[cfg(feature = "clipboard")] - Clipboard(ClipboardMessage), UserEvent(T), } @@ -1192,10 +1188,6 @@ impl Clone for Message { Self::Webview(i, m) => Self::Webview(*i, m.clone()), #[cfg(all(desktop, feature = "system-tray"))] Self::Tray(i, m) => Self::Tray(*i, m.clone()), - #[cfg(all(desktop, feature = "global-shortcut"))] - Self::GlobalShortcut(m) => Self::GlobalShortcut(m.clone()), - #[cfg(feature = "clipboard")] - Self::Clipboard(m) => Self::Clipboard(m.clone()), Self::UserEvent(t) => Self::UserEvent(t.clone()), _ => unimplemented!(), } @@ -1213,16 +1205,6 @@ pub struct WryDispatcher { #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Sync for WryDispatcher {} -impl WryDispatcher { - #[cfg(desktop)] - pub fn with_webview(&self, f: F) -> Result<()> { - send_user_message( - &self.context, - Message::Window(self.window_id, WindowMessage::WithWebview(Box::new(f))), - ) - } -} - impl Dispatch for WryDispatcher { type Runtime = Wry; type WindowBuilder = WindowBuilderWrapper; @@ -1249,6 +1231,16 @@ impl Dispatch for WryDispatcher { 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( @@ -1529,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, @@ -1775,15 +1774,6 @@ pub trait Plugin { /// A Tauri [`Runtime`] wrapper around wry. pub struct Wry { context: Context, - - plugins: Vec>>, - - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager_handle: GlobalShortcutManagerHandle, - - #[cfg(feature = "clipboard")] - clipboard_manager_handle: ClipboardManagerWrapper, - event_loop: EventLoop>, } @@ -1801,24 +1791,6 @@ impl fmt::Debug for Wry { &self.context.main_thread.system_tray_manager, ); - #[cfg(all(desktop, feature = "global-shortcut"))] - #[cfg(feature = "global-shortcut")] - d.field( - "global_shortcut_manager", - &self.context.main_thread.global_shortcut_manager, - ) - .field( - "global_shortcut_manager_handle", - &self.global_shortcut_manager_handle, - ); - - #[cfg(feature = "clipboard")] - d.field( - "clipboard_manager", - &self.context.main_thread.clipboard_manager, - ) - .field("clipboard_manager_handle", &self.clipboard_manager_handle); - d.finish() } } @@ -1868,6 +1840,18 @@ 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 { @@ -1928,6 +1912,26 @@ impl RuntimeHandle for WryHandle { Message::Application(ApplicationMessage::Hide), ) } + + #[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 { @@ -1935,12 +1939,6 @@ impl Wry { let main_thread_id = current_thread().id(); let web_context = WebContextStore::default(); - #[cfg(all(desktop, feature = "global-shortcut"))] - let global_shortcut_manager = Arc::new(Mutex::new(WryShortcutManager::new(&event_loop))); - - #[cfg(feature = "clipboard")] - let clipboard_manager = Arc::new(Mutex::new(Clipboard::new())); - let windows = Arc::new(RefCell::new(HashMap::default())); let webview_id_map = WebviewIdStore::default(); @@ -1954,61 +1952,24 @@ impl Wry { main_thread: DispatcherMainThreadContext { window_target: event_loop.deref().clone(), web_context, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager, - #[cfg(feature = "clipboard")] - clipboard_manager, windows, #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager, }, - }; - - #[cfg(all(desktop, feature = "global-shortcut"))] - let global_shortcut_manager_handle = GlobalShortcutManagerHandle { - context: context.clone(), - shortcuts: Default::default(), - listeners: Default::default(), - }; - - #[cfg(feature = "clipboard")] - #[allow(clippy::redundant_clone)] - let clipboard_manager_handle = ClipboardManagerWrapper { - context: context.clone(), + plugins: Default::default(), }; Ok(Self { context, - - plugins: Default::default(), - - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager_handle, - - #[cfg(feature = "clipboard")] - clipboard_manager_handle, - event_loop, }) } - - pub fn plugin + 'static>(&mut self, plugin: P) { - self - .plugins - .push(Box::new(plugin.build(self.context.clone()))); - } } impl Runtime for Wry { type Dispatcher = WryDispatcher; type Handle = WryHandle; - #[cfg(all(desktop, feature = "global-shortcut"))] - type GlobalShortcutManager = GlobalShortcutManagerHandle; - - #[cfg(feature = "clipboard")] - type ClipboardManager = ClipboardManagerWrapper; - #[cfg(all(desktop, feature = "system-tray"))] type TrayHandler = SystemTrayHandle; @@ -2039,20 +2000,9 @@ impl Runtime for Wry { } } - #[cfg(all(desktop, feature = "global-shortcut"))] - fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager { - self.global_shortcut_manager_handle.clone() - } - - #[cfg(feature = "clipboard")] - fn clipboard_manager(&self) -> Self::ClipboardManager { - self.clipboard_manager_handle.clone() - } - 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 window_id = rand::random(); let webview = create_webview( @@ -2079,7 +2029,6 @@ impl Runtime for Wry { label, dispatcher, menu_ids, - js_event_listeners, }) } @@ -2160,17 +2109,10 @@ impl Runtime for Wry { 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 = &mut self.plugins; + let plugins = self.context.plugins.clone(); #[cfg(all(desktop, feature = "system-tray"))] let system_tray_manager = self.context.main_thread.system_tray_manager.clone(); - #[cfg(all(desktop, feature = "global-shortcut"))] - let global_shortcut_manager = self.context.main_thread.global_shortcut_manager.clone(); - #[cfg(all(desktop, feature = "global-shortcut"))] - let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone(); - - #[cfg(feature = "clipboard")] - let clipboard_manager = self.context.main_thread.clipboard_manager.clone(); let mut iteration = RunIteration::default(); let proxy = self.event_loop.create_proxy(); @@ -2183,7 +2125,7 @@ impl Runtime for Wry { *control_flow = ControlFlow::Exit; } - for p in plugins.iter_mut() { + for p in plugins.lock().unwrap().iter_mut() { let prevent_default = p.on_event( &event, event_loop, @@ -2193,12 +2135,6 @@ impl Runtime for Wry { callback: &mut callback, webview_id_map: webview_id_map.clone(), windows: windows.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: global_shortcut_manager.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager_handle: &global_shortcut_manager_handle, - #[cfg(feature = "clipboard")] - clipboard_manager: clipboard_manager.clone(), #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager: system_tray_manager.clone(), }, @@ -2217,12 +2153,6 @@ impl Runtime for Wry { callback: &mut callback, windows: windows.clone(), webview_id_map: webview_id_map.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: global_shortcut_manager.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager_handle: &global_shortcut_manager_handle, - #[cfg(feature = "clipboard")] - clipboard_manager: clipboard_manager.clone(), #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager: system_tray_manager.clone(), }, @@ -2237,23 +2167,15 @@ impl Runtime for Wry { 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 mut plugins = self.plugins; + let plugins = self.context.plugins.clone(); #[cfg(all(desktop, feature = "system-tray"))] let system_tray_manager = self.context.main_thread.system_tray_manager; - #[cfg(all(desktop, feature = "global-shortcut"))] - let global_shortcut_manager = self.context.main_thread.global_shortcut_manager.clone(); - #[cfg(all(desktop, feature = "global-shortcut"))] - let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone(); - - #[cfg(feature = "clipboard")] - let clipboard_manager = self.context.main_thread.clipboard_manager.clone(); - let proxy = self.event_loop.create_proxy(); self.event_loop.run(move |event, event_loop, control_flow| { - for p in &mut plugins { + for p in plugins.lock().unwrap().iter_mut() { let prevent_default = p.on_event( &event, event_loop, @@ -2263,12 +2185,6 @@ impl Runtime for Wry { callback: &mut callback, webview_id_map: webview_id_map.clone(), windows: windows.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: global_shortcut_manager.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager_handle: &global_shortcut_manager_handle, - #[cfg(feature = "clipboard")] - clipboard_manager: clipboard_manager.clone(), #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager: system_tray_manager.clone(), }, @@ -2286,12 +2202,6 @@ impl Runtime for Wry { callback: &mut callback, webview_id_map: webview_id_map.clone(), windows: windows.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: global_shortcut_manager.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager_handle: &global_shortcut_manager_handle, - #[cfg(feature = "clipboard")] - clipboard_manager: clipboard_manager.clone(), #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager: system_tray_manager.clone(), }, @@ -2305,12 +2215,6 @@ 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 = "global-shortcut"))] - pub global_shortcut_manager: Arc>, - #[cfg(all(desktop, feature = "global-shortcut"))] - pub global_shortcut_manager_handle: &'a GlobalShortcutManagerHandle, - #[cfg(feature = "clipboard")] - pub clipboard_manager: Arc>, #[cfg(all(desktop, feature = "system-tray"))] pub system_tray_manager: SystemTrayManager, } @@ -2318,10 +2222,6 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> { struct UserMessageContext { windows: Arc>>, webview_id_map: WebviewIdStore, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: Arc>, - #[cfg(feature = "clipboard")] - clipboard_manager: Arc>, #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager: SystemTrayManager, } @@ -2334,10 +2234,6 @@ fn handle_user_message( ) -> RunIteration { let UserMessageContext { webview_id_map, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager, - #[cfg(feature = "clipboard")] - clipboard_manager, windows, #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager, @@ -2379,7 +2275,6 @@ fn handle_user_message( }); if let Some((Some(window), window_event_listeners, menu_event_listeners)) = w { match window_message { - #[cfg(desktop)] WindowMessage::WithWebview(f) => { if let WindowHandle::Webview { inner: w, .. } = &window { #[cfg(any( @@ -2402,13 +2297,26 @@ fn handle_user_message( 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()) + } } } @@ -2526,6 +2434,12 @@ fn handle_user_message( 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) @@ -2723,12 +2637,6 @@ fn handle_user_message( } } } - #[cfg(all(desktop, feature = "global-shortcut"))] - Message::GlobalShortcut(message) => { - handle_global_shortcut_message(message, &global_shortcut_manager) - } - #[cfg(feature = "clipboard")] - Message::Clipboard(message) => handle_clipboard_message(message, &clipboard_manager), Message::UserEvent(_) => (), } @@ -2749,12 +2657,6 @@ fn handle_event_loop( callback, webview_id_map, windows, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager_handle, - #[cfg(feature = "clipboard")] - clipboard_manager, #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager, } = context; @@ -2779,14 +2681,6 @@ fn handle_event_loop( callback(RunEvent::Exit); } - #[cfg(all(desktop, feature = "global-shortcut"))] - Event::GlobalShortcutEvent(accelerator_id) => { - for (id, handler) in &*global_shortcut_manager_handle.listeners.lock().unwrap() { - if accelerator_id == *id { - handler(); - } - } - } Event::MenuEvent { window_id, menu_id, @@ -2992,10 +2886,6 @@ fn handle_event_loop( message, UserMessageContext { webview_id_map, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager, - #[cfg(feature = "clipboard")] - clipboard_manager, windows, #[cfg(all(desktop, feature = "system-tray"))] system_tray_manager, @@ -3045,7 +2935,7 @@ fn on_close_requested<'a, T: UserEvent>( } fn on_window_close(window_id: WebviewId, windows: Arc>>) { - if let Some(mut window_wrapper) = windows.borrow_mut().get_mut(&window_id) { + if let Some(window_wrapper) = windows.borrow_mut().get_mut(&window_id) { window_wrapper.inner = None; } } @@ -3115,7 +3005,8 @@ fn create_webview( label, url, menu_ids, - js_event_listeners, + #[cfg(target_os = "android")] + on_webview_created, .. } = pending; let webview_id_map = context.webview_id_map.clone(); @@ -3198,7 +3089,6 @@ fn create_webview( context, label.clone(), menu_ids, - js_event_listeners, handler, )); } @@ -3243,11 +3133,28 @@ 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_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 .with_web_context(web_context) .build() @@ -3308,7 +3215,6 @@ fn create_ipc_handler( context: Context, label: String, menu_ids: Arc>>, - js_event_listeners: Arc>>>, handler: WebviewIpcHandler>, ) -> Box { Box::new(move |window, request| { @@ -3321,7 +3227,6 @@ fn create_ipc_handler( }, label: label.clone(), menu_ids: menu_ids.clone(), - js_event_listeners: js_event_listeners.clone(), }, request, ); diff --git a/core/tauri-runtime-wry/src/webview.rs b/core/tauri-runtime-wry/src/webview.rs index 834b6a4d19e6..283c32fb5c3b 100644 --- a/core/tauri-runtime-wry/src/webview.rs +++ b/core/tauri-runtime-wry/src/webview.rs @@ -26,6 +26,17 @@ mod imp { } } +#[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; @@ -34,4 +45,10 @@ mod imp { } } +#[cfg(target_os = "android")] +mod imp { + use wry::webview::JniHandle; + pub type Webview = JniHandle; +} + pub use imp::*; diff --git a/core/tauri-runtime/CHANGELOG.md b/core/tauri-runtime/CHANGELOG.md index 2a411df2be54..589748c30c05 100644 --- a/core/tauri-runtime/CHANGELOG.md +++ b/core/tauri-runtime/CHANGELOG.md @@ -1,5 +1,48 @@ # 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 diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 1c255a94ae78..c9b16271d2ad 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "tauri-runtime" -version = "0.14.0" -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.app" -repository = "https://github.com/tauri-apps/tauri" +version = "0.13.0-alpha.5" description = "Runtime for Tauri applications" -edition = "2021" -rust-version = "1.60" 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,7 +28,7 @@ targets = [ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" thiserror = "1.0" -tauri-utils = { version = "1.4.0", path = "../tauri-utils" } +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" @@ -34,19 +36,17 @@ raw-window-handle = "0.5" rand = "0.8" url = { version = "2" } -[target."cfg(windows)".dependencies] -webview2-com = "0.19.1" - - [target."cfg(windows)".dependencies.windows] - version = "0.39.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] -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 = [ ] system-tray = [ ] macos-private-api = [ ] -global-shortcut = [ ] -clipboard = [ ] diff --git a/core/tauri-runtime/src/http/response.rs b/core/tauri-runtime/src/http/response.rs index 6c427a0ffed9..a004d4dc0d5f 100644 --- a/core/tauri-runtime/src/http/response.rs +++ b/core/tauri-runtime/src/http/response.rs @@ -7,7 +7,7 @@ use super::{ status::StatusCode, version::Version, }; -use std::fmt; +use std::{borrow::Cow, fmt}; type Result = core::result::Result>; @@ -33,7 +33,7 @@ type Result = core::result::Result>; /// pub struct Response { head: ResponseParts, - body: Vec, + body: Cow<'static, [u8]>, } /// Component parts of an HTTP `Response` @@ -67,7 +67,7 @@ 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, @@ -81,7 +81,7 @@ impl Response { /// This API is used internally. It may have breaking changes in the future. #[inline] #[doc(hidden)] - pub fn into_parts(self) -> (ResponseParts, Vec) { + pub fn into_parts(self) -> (ResponseParts, Cow<'static, [u8]>) { (self.head, self.body) } @@ -129,13 +129,13 @@ impl Response { /// Returns a mutable reference to the associated HTTP body. #[inline] - pub fn body_mut(&mut self) -> &mut Vec { + 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 } } @@ -143,7 +143,7 @@ impl Response { impl Default for Response { #[inline] fn default() -> Response { - Response::new(Vec::new()) + Response::new(Default::default()) } } @@ -280,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 0671fa25378e..5cd05cac3047 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -248,10 +248,6 @@ pub enum Error { /// Failed to get monitor on window operation. #[error("failed to get monitor")] FailedToGetMonitor, - /// Global shortcut error. - #[cfg(all(desktop, feature = "global-shortcut"))] - #[error(transparent)] - GlobalShortcut(Box), #[error("Invalid header name: {0}")] InvalidHeaderName(#[from] InvalidHeaderName), #[error("Invalid header value: {0}")] @@ -394,31 +390,25 @@ pub trait RuntimeHandle: Debug + Clone + Send + Sync + Sized + 'st #[cfg(target_os = "macos")] #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] fn hide(&self) -> Result<()>; -} - -/// A global shortcut manager. -#[cfg(all(desktop, feature = "global-shortcut"))] -pub trait GlobalShortcutManager: Debug + Clone + Send + Sync { - /// Whether the application has registered the given `accelerator`. - fn is_registered(&self, accelerator: &str) -> Result; - - /// Register a global shortcut of `accelerator`. - fn register(&mut self, accelerator: &str, handler: F) -> Result<()>; - /// Unregister all accelerators registered by the manager instance. - fn unregister_all(&mut self) -> Result<()>; - - /// Unregister the provided `accelerator`. - fn unregister(&mut self, accelerator: &str) -> 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>; -/// Clipboard manager. -#[cfg(feature = "clipboard")] -pub trait ClipboardManager: Debug + Clone + Send + Sync { - /// 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>; + /// 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; } pub trait EventLoopProxy: Debug + Clone + Send + Sync { @@ -431,12 +421,6 @@ pub trait Runtime: Debug + Sized + 'static { type Dispatcher: Dispatch; /// The runtime handle type. type Handle: RuntimeHandle; - /// The global shortcut manager type. - #[cfg(all(desktop, feature = "global-shortcut"))] - type GlobalShortcutManager: GlobalShortcutManager; - /// The clipboard manager type. - #[cfg(feature = "clipboard")] - type ClipboardManager: ClipboardManager; /// The tray handler type. #[cfg(all(desktop, feature = "system-tray"))] type TrayHandler: menu::TrayHandle; @@ -457,14 +441,6 @@ pub trait Runtime: Debug + Sized + 'static { /// Gets a runtime handle. fn handle(&self) -> Self::Handle; - /// Gets the global shortcut manager. - #[cfg(all(desktop, feature = "global-shortcut"))] - fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager; - - /// Gets the clipboard manager. - #[cfg(feature = "clipboard")] - fn clipboard_manager(&self) -> Self::ClipboardManager; - /// Create a new webview window. fn create_window(&self, pending: PendingWindow) -> Result>; @@ -531,6 +507,9 @@ pub trait Dispatch: Debug + Clone + Send + Sync + 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); @@ -718,9 +697,12 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static /// Closes the window. fn close(&self) -> Result<()>; - /// Updates the hasDecorations flag. + /// 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) -> Result<()>; diff --git a/core/tauri-runtime/src/webview.rs b/core/tauri-runtime/src/webview.rs index 0161cc296e63..af2277f8a467 100644 --- a/core/tauri-runtime/src/webview.rs +++ b/core/tauri-runtime/src/webview.rs @@ -9,7 +9,7 @@ use crate::{menu::Menu, window::DetachedWindow, Icon}; #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; use tauri_utils::{ - config::{WindowConfig, WindowUrl}, + config::{WindowConfig, WindowEffectsConfig, WindowUrl}, Theme, }; @@ -29,11 +29,14 @@ pub struct WebviewAttributes { 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(); @@ -44,10 +47,12 @@ impl From<&WindowConfig> for WebviewAttributes { 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 { @@ -60,6 +65,8 @@ impl WebviewAttributes { clipboard: false, accept_first_mouse: false, additional_browser_args: None, + window_effects: None, + incognito: false, } } @@ -114,6 +121,20 @@ impl WebviewAttributes { 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). @@ -238,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. diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 7c154cdea58d..fbb7899f9b92 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -15,7 +15,7 @@ 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}, @@ -101,9 +101,10 @@ fn get_menu_ids(map: &mut HashMap, menu: &Menu) { /// Describes the appearance of the mouse cursor. #[non_exhaustive] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] pub enum CursorIcon { /// The platform-dependent default cursor. + #[default] Default, /// A simple crosshair. Crosshair, @@ -205,10 +206,11 @@ impl<'de> Deserialize<'de> for CursorIcon { } } -impl Default for CursorIcon { - fn default() -> Self { - 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. @@ -230,16 +232,18 @@ pub struct PendingWindow> { /// 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>>>, - /// A handler to decide if incoming url is allowed to navigate. pub navigation_handler: Option bool + Send>>, - pub web_resource_request_handler: Option>, - /// The resolved URL to load on the webview. pub url: String, + + #[cfg(target_os = "android")] + #[allow(clippy::type_complexity)] + pub on_webview_created: + Option) -> Result<(), jni::errors::Error> + Send>>, + + pub web_resource_request_handler: Option>, } pub fn is_label_valid(label: &str) -> bool { @@ -277,10 +281,11 @@ impl> PendingWindow { label, ipc_handler: None, menu_ids: Arc::new(Mutex::new(menu_ids)), - js_event_listeners: Default::default(), navigation_handler: Default::default(), - web_resource_request_handler: Default::default(), url: "tauri://localhost".to_string(), + #[cfg(target_os = "android")] + on_webview_created: None, + web_resource_request_handler: Default::default(), }) } } @@ -308,10 +313,11 @@ impl> PendingWindow { label, ipc_handler: None, menu_ids: Arc::new(Mutex::new(menu_ids)), - js_event_listeners: Default::default(), navigation_handler: Default::default(), - web_resource_request_handler: Default::default(), url: "tauri://localhost".to_string(), + #[cfg(target_os = "android")] + on_webview_created: None, + web_resource_request_handler: Default::default(), }) } } @@ -338,15 +344,17 @@ 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. @@ -360,9 +368,6 @@ 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 { @@ -371,7 +376,6 @@ impl> Clone for DetachedWindow { label: self.label.clone(), dispatcher: self.dispatcher.clone(), menu_ids: self.menu_ids.clone(), - js_event_listeners: self.js_event_listeners.clone(), } } } diff --git a/core/tauri-utils/CHANGELOG.md b/core/tauri-utils/CHANGELOG.md index 835f3ed36794..ad110dde0937 100644 --- a/core/tauri-utils/CHANGELOG.md +++ b/core/tauri-utils/CHANGELOG.md @@ -1,5 +1,45 @@ # 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 diff --git a/core/tauri-utils/Cargo.toml b/core/tauri-utils/Cargo.toml index 86076c606f22..271628a05a8e 100644 --- a/core/tauri-utils/Cargo.toml +++ b/core/tauri-utils/Cargo.toml @@ -1,16 +1,19 @@ [package] name = "tauri-utils" -version = "1.4.0" -authors = [ "Tauri Programme within The Commons Conservancy" ] -license = "Apache-2.0 OR MIT" -homepage = "https://tauri.app" -repository = "https://github.com/tauri-apps/tauri" +version = "2.0.0-alpha.5" description = "Utilities for Tauri" -edition = "2021" -rust-version = "1.60" 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", features = [ "derive" ] } serde_json = "1" @@ -42,7 +45,7 @@ dunce = "1" heck = "0.4" [target."cfg(windows)".dependencies.windows] -version = "0.39.0" +version = "0.44.0" features = [ "implement", "Win32_Foundation", diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index afff520a8ba2..b8a1792934d4 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -34,7 +34,7 @@ use std::{ /// Items to help with parsing content into a [`Config`]. pub mod parse; -use crate::TitleBarStyle; +use crate::{TitleBarStyle, WindowEffect, WindowEffectState}; pub use self::parse::parse; @@ -624,6 +624,65 @@ impl Default for WindowsConfig { } } +/// 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 @@ -692,191 +751,39 @@ pub struct BundleConfig { /// 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, Eq, 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. - #[serde(alias = "long-description")] - 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 - #[serde(default, alias = "takes-value")] - pub takes_value: bool, - /// 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. - #[serde(default)] - pub multiple: bool, - /// 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. - #[serde(default, alias = "multiple-occurrences")] - pub multiple_occurrences: bool, - /// 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`. - #[serde(alias = "number-of-values")] - 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. - #[serde(alias = "possible-values")] - 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. - #[serde(alias = "min-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. - #[serde(alias = "max-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. - #[serde(default)] - pub required: bool, - /// Sets an arg that override this arg's required setting - /// i.e. this arg will be required unless this other argument is present. - #[serde(alias = "required-unless-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. - #[serde(alias = "required-unless-present-all")] - 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. - #[serde(alias = "required-unless-present-any")] - 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. - #[serde(alias = "conflicts-with")] - pub conflicts_with: Option, - /// The same as conflictsWith but allows specifying multiple two-way conflicts per argument. - #[serde(alias = "conflicts-with-all")] - 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. - #[serde(alias = "requires-all")] - 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}`. - #[serde(alias = "requires-if")] - 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}`. - #[serde(alias = "requires-if-eq")] - pub required_if_eq: Option>, - /// Requires that options use the --option=val syntax - /// i.e. an equals between the option and associated value. - #[serde(alias = "requires-equals")] - 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). - #[cfg_attr(feature = "schema", validate(range(min = 1)))] - pub index: Option, -} +pub struct Color(pub u8, pub u8, pub u8, pub u8); -/// describes a CLI configuration -/// -/// See more: https://tauri.app/v1/api/config#cliconfig +/// The window effects configuration object #[skip_serializing_none] -#[derive(Debug, PartialEq, Eq, 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. - #[serde(alias = "long-description")] - 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. - #[serde(alias = "before-help")] - 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. - #[serde(alias = "after-help")] - 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. @@ -1010,6 +917,34 @@ pub struct WindowConfig { /// 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 { @@ -1048,6 +983,9 @@ impl Default for WindowConfig { accept_first_mouse: false, tabbing_identifier: None, additional_browser_args: None, + shadow: true, + window_effects: None, + incognito: false, } } } @@ -1225,81 +1163,9 @@ pub struct RemoteDomainAccessScope { /// The list of plugins that are allowed in this scope. #[serde(default)] pub plugins: Vec, - /// Enables access to the Tauri API. - #[serde(default, rename = "enableTauriAPI", alias = "enable-tauri-api")] - pub enable_tauri_api: bool, -} - -/// 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 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 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, -} - -/// 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. +/// 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. @@ -1310,7 +1176,7 @@ macro_rules! check_feature { #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[serde(untagged)] #[cfg_attr(feature = "schema", derive(JsonSchema))] -pub enum FsAllowlistScope { +pub enum FsScope { /// A list of paths that are allowed by this scope. AllowedPaths(Vec), /// A complete scope configuration. @@ -1336,13 +1202,13 @@ pub enum FsAllowlistScope { }, } -impl Default for FsAllowlistScope { +impl Default for FsScope { fn default() -> Self { Self::AllowedPaths(Vec::new()) } } -impl FsAllowlistScope { +impl FsScope { /// The list of allowed paths. pub fn allowed_paths(&self) -> &Vec { match self { @@ -1360,1019 +1226,75 @@ impl FsAllowlistScope { } } -/// Allowlist for the file system APIs. +/// Config for the asset custom protocol. /// -/// See more: https://tauri.app/v1/api/config#fsallowlistconfig +/// 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 FsAllowlistConfig { - /// The access scope for the filesystem APIs. - #[serde(default)] - pub scope: FsAllowlistScope, - /// Use this flag to enable all file system API features. +pub struct AssetProtocolConfig { + /// The access scope for the asset protocol. #[serde(default)] - pub all: bool, - /// Read file from local filesystem. - #[serde(default, alias = "read-file")] - pub read_file: bool, - /// Write file to local filesystem. - #[serde(default, alias = "write-file")] - pub write_file: bool, - /// Read directory from local filesystem. - #[serde(default, alias = "read-dir")] - pub read_dir: bool, - /// Copy file from local filesystem. - #[serde(default, alias = "copy-file")] - pub copy_file: bool, - /// Create directory from local filesystem. - #[serde(default, alias = "create-dir")] - pub create_dir: bool, - /// Remove directory from local filesystem. - #[serde(default, alias = "remove-dir")] - pub remove_dir: bool, - /// Remove file from local filesystem. - #[serde(default, alias = "remove-file")] - pub remove_file: bool, - /// Rename file from local filesystem. - #[serde(default, alias = "rename-file")] - pub rename_file: bool, - /// Check if path exists on the local filesystem. + pub scope: FsScope, + /// Enables the asset protocol. #[serde(default)] - pub exists: 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, - exists: 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"); - check_feature!(self, features, exists, "fs-exists"); - features - } - } + pub enable: bool, } -/// Allowlist for the window APIs. +/// Security configuration. /// -/// See more: https://tauri.app/v1/api/config#windowallowlistconfig +/// 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 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, alias = "request-user-attention")] - pub request_user_attention: bool, - /// Allows setting the resizable flag of the window. - #[serde(default, alias = "set-resizable")] - pub set_resizable: bool, - /// Allows setting whether the window's native maximize button is enabled or not. - #[serde(default, alias = "set-maximizable")] - pub set_maximizable: bool, - /// Allows setting whether the window's native minimize button is enabled or not. - #[serde(default, alias = "set-minimizable")] - pub set_minimizable: bool, - /// Allows setting whether the window's native close button is enabled or not. - #[serde(default, alias = "set-closable")] - pub set_closable: bool, - /// Allows changing the window title. - #[serde(default, alias = "set-title")] - 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, alias = "set-decorations")] - pub set_decorations: bool, - /// Allows setting the always_on_top flag of the window. - #[serde(default, alias = "set-always-on-top")] - pub set_always_on_top: bool, - /// Allows preventing the window contents from being captured by other apps. - #[serde(default, alias = "set-content-protected")] - pub set_content_protected: bool, - /// Allows setting the window size. - #[serde(default, alias = "set-size")] - pub set_size: bool, - /// Allows setting the window minimum size. - #[serde(default, alias = "set-min-size")] - pub set_min_size: bool, - /// Allows setting the window maximum size. - #[serde(default, alias = "set-max-size")] - pub set_max_size: bool, - /// Allows changing the position of the window. - #[serde(default, alias = "set-position")] - pub set_position: bool, - /// Allows setting the fullscreen flag of the window. - #[serde(default, alias = "set-fullscreen")] - pub set_fullscreen: bool, - /// Allows focusing the window. - #[serde(default, alias = "set-focus")] - pub set_focus: bool, - /// Allows changing the window icon. - #[serde(default, alias = "set-icon")] - pub set_icon: bool, - /// Allows setting the skip_taskbar flag of the window. - #[serde(default, alias = "set-skip-taskbar")] - pub set_skip_taskbar: bool, - /// Allows grabbing the cursor. - #[serde(default, alias = "set-cursor-grab")] - pub set_cursor_grab: bool, - /// Allows setting the cursor visibility. - #[serde(default, alias = "set-cursor-visible")] - pub set_cursor_visible: bool, - /// Allows changing the cursor icon. - #[serde(default, alias = "set-cursor-icon")] - pub set_cursor_icon: bool, - /// Allows setting the cursor position. - #[serde(default, alias = "set-cursor-position")] - pub set_cursor_position: bool, - /// Allows ignoring cursor events. - #[serde(default, alias = "set-ignore-cursor-events")] - pub set_ignore_cursor_events: bool, - /// Allows start dragging on the window. - #[serde(default, alias = "start-dragging")] - 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_maximizable: true, - set_minimizable: true, - set_closable: 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_content_protected: false, - 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, - set_cursor_grab: true, - set_cursor_visible: true, - set_cursor_icon: true, - set_cursor_position: true, - set_ignore_cursor_events: 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_maximizable, "window-set-maximizable"); - check_feature!(self, features, set_minimizable, "window-set-minimizable"); - check_feature!(self, features, set_closable, "window-set-closable"); - 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_content_protected, - "window-set-content-protected" - ); - 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, set_cursor_grab, "window-set-cursor-grab"); - check_feature!( - self, - features, - set_cursor_visible, - "window-set-cursor-visible" - ); - check_feature!(self, features, set_cursor_icon, "window-set-cursor-icon"); - check_feature!( - self, - features, - set_cursor_position, - "window-set-cursor-position" - ); - check_feature!( - self, - features, - set_ignore_cursor_events, - "window-set-ignore-cursor-events" - ); - check_feature!(self, features, start_dragging, "window-start-dragging"); - check_feature!(self, features, print, "window-print"); - features - } - } -} - -/// A command allowed to be executed by the webview API. -#[derive(Debug, PartialEq, Eq, Clone, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -pub struct ShellAllowedCommand { - /// The name for this allowed shell command configuration. +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 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`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, - /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`. - #[serde(rename = "cmd", default)] // use default just so the schema doesn't flag it as required - 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, -} - -impl<'de> Deserialize<'de> for ShellAllowedCommand { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct InnerShellAllowedCommand { - name: String, - #[serde(rename = "cmd")] - command: Option, - #[serde(default)] - args: ShellAllowedArgs, - #[serde(default)] - sidecar: bool, - } - - let config = InnerShellAllowedCommand::deserialize(deserializer)?; - - if !config.sidecar && config.command.is_none() { - return Err(DeError::custom( - "The shell scope `command` value is required.", - )); - } - - Ok(ShellAllowedCommand { - name: config.name, - command: config.command.unwrap_or_default(), - args: config.args, - sidecar: config.sidecar, - }) - } -} - -/// 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, Eq, 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) - } -} - -/// A command argument allowed to be executed by the webview API. -#[derive(Debug, PartialEq, Eq, 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. + /// 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. /// - 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 - 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, Eq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -pub struct ShellAllowlistScope(pub Vec); - -/// Defines the `shell > open` api scope. -#[derive(Debug, PartialEq, Eq, 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. + /// 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. /// - /// If enabled, the default validation regex (`^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`) is used. - Flag(bool), - - /// Enable the shell open API, with a custom regex that the opened path must match against. + /// 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. /// - /// 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) - } -} - -/// Allowlist for the shell APIs. -/// -/// See more: https://tauri.app/v1/api/config#shellallowlistconfig -#[derive(Debug, Default, 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") - } - - features - } - } -} - -/// Allowlist for the dialog APIs. -/// -/// See more: https://tauri.app/v1/api/config#dialogallowlistconfig -#[derive(Debug, Default, PartialEq, Eq, 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 - } - } -} - -/// HTTP API scope definition. -/// It is a list of URLs that can be accessed by the webview when using the HTTP APIs. -/// The scoped URL is matched against the request URL using a glob pattern. -/// -/// Examples: -/// - "https://*": allows all HTTPS urls -/// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path -/// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/" -#[allow(rustdoc::bare_urls)] -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] -// TODO: in v2, parse into a String or a custom type that perserves the -// glob string because Url type will add a trailing slash -#[cfg_attr(feature = "schema", derive(JsonSchema))] -pub struct HttpAllowlistScope(pub Vec); - -/// Allowlist for the HTTP APIs. -/// -/// See more: https://tauri.app/v1/api/config#httpallowlistconfig -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] -#[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, -} - -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 - } - } -} - -/// Allowlist for the notification APIs. -/// -/// See more: https://tauri.app/v1/api/config#notificationallowlistconfig -#[derive(Debug, Default, PartialEq, Eq, 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![] - } - } -} - -/// Allowlist for the global shortcut APIs. -/// -/// See more: https://tauri.app/v1/api/config#globalshortcutallowlistconfig -#[derive(Debug, Default, 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. - #[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![] - } - } -} - -/// Allowlist for the OS APIs. -/// -/// See more: https://tauri.app/v1/api/config#osallowlistconfig -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] -#[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, -} - -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![] - } - } -} - -/// Allowlist for the path APIs. -/// -/// See more: https://tauri.app/v1/api/config#pathallowlistconfig -#[derive(Debug, Default, PartialEq, Eq, 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 - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["path-all"] - } else { - vec![] - } - } -} - -/// Allowlist for the custom protocols. -/// -/// See more: https://tauri.app/v1/api/config#protocolallowlistconfig -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct ProtocolAllowlistConfig { - /// The access scope for the asset protocol. - #[serde(default, alias = "asset-scope")] - pub asset_scope: FsAllowlistScope, - /// Use this flag to enable all custom protocols. - #[serde(default)] - pub all: bool, - /// Enables the asset protocol. - #[serde(default)] - pub asset: 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. -/// -/// See more: https://tauri.app/v1/api/config#processallowlistconfig -#[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. + /// 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. /// - /// 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, - alias = "relaunchDangerousAllowSymlinkMacOS", - alias = "relaunch-dangerous-allow-symlink-macos" - )] - 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. -/// -/// See more: https://tauri.app/v1/api/config#clipboardallowlistconfig -#[derive(Debug, Default, PartialEq, Eq, 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, alias = "writeText")] - pub write_text: bool, - /// Enables the clipboard's `readText` API. - #[serde(default, alias = "readText")] - 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 for the app APIs. -/// -/// See more: https://tauri.app/v1/api/config#appallowlistconfig -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct AppAllowlistConfig { - /// Use this flag to enable all app APIs. - #[serde(default)] - pub all: bool, - /// Enables the app's `show` API. - #[serde(default)] - pub show: bool, - /// Enables the app's `hide` API. - #[serde(default)] - pub hide: bool, -} - -impl Allowlist for AppAllowlistConfig { - fn all_features() -> Vec<&'static str> { - let allowlist = Self { - all: false, - show: true, - hide: true, - }; - let mut features = allowlist.to_features(); - features.push("app-all"); - features - } - - fn to_features(&self) -> Vec<&'static str> { - if self.all { - vec!["app-all"] - } else { - let mut features = Vec::new(); - check_feature!(self, features, show, "app-show"); - check_feature!(self, features, hide, "app-hide"); - features - } - } -} - -/// Allowlist configuration. The allowlist is a translation of the [Cargo allowlist features](https://docs.rs/tauri/latest/tauri/#cargo-allowlist-features). -/// -/// # Notes -/// -/// - Endpoints that don't have their own allowlist option are enabled by default. -/// - There is only "opt-in", no "opt-out". Setting an option to `false` has no effect. -/// -/// # Examples -/// -/// - * [`"app-all": true`](https://tauri.app/v1/api/config/#appallowlistconfig.all) will make the [hide](https://tauri.app/v1/api/js/app#hide) endpoint be available regardless of whether `hide` is set to `false` or `true` in the allowlist. -#[derive(Debug, Default, PartialEq, Eq, 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, alias = "global-shortcut")] - 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, - /// App APIs allowlist. - #[serde(default)] - pub app: AppAllowlistConfig, -} - -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.extend(AppAllowlistConfig::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.extend(self.app.to_features()); - features - } - } + /// **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. @@ -2410,20 +1332,12 @@ pub struct TauriConfig { /// The windows configuration. #[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, @@ -2435,26 +1349,17 @@ pub struct TauriConfig { impl TauriConfig { /// Returns all Cargo features. 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. 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"); } @@ -2464,43 +1369,14 @@ impl TauriConfig { 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, Eq, 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)) - } -} - /// Install modes for the Windows update. #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "schema", derive(JsonSchema))] @@ -2591,95 +1467,11 @@ impl<'de> Deserialize<'de> for WindowsUpdateInstallMode { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct UpdaterWindowsConfig { - /// Additional arguments given to the NSIS or WiX installer. - #[serde(default, alias = "installer-args")] - pub installer_args: Vec, /// The installation mode for the update on Windows. Defaults to `passive`. #[serde(default, alias = "install-mode")] pub install_mode: WindowsUpdateInstallMode, } -/// 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, - /// Display built-in dialog or use event system if disabled. - #[serde(default = "default_true")] - pub dialog: bool, - /// The updater endpoints. TLS is enforced on production. - /// - /// The updater URL can contain the following variables: - /// - {{current_version}}: The version of the app that is requesting the update - /// - {{target}}: The operating system name (one of `linux`, `windows` or `darwin`). - /// - {{arch}}: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`). - /// - /// # Examples - /// - "https://my.cdn.com/latest.json": a raw JSON endpoint that returns the latest version and download links for each platform. - /// - "https://updates.app.dev/{{target}}?version={{current_version}}&arch={{arch}}": a dedicated API with positional and query string arguments. - #[allow(rustdoc::bare_urls)] - pub endpoints: Option>, - /// 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, - #[serde(default = "default_true")] - dialog: bool, - endpoints: Option>, - 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, - dialog: config.dialog, - endpoints: config.endpoints, - pubkey: config.pubkey.unwrap_or_default(), - windows: config.windows, - }) - } -} - -impl Default for UpdaterConfig { - fn default() -> Self { - Self { - active: false, - dialog: true, - endpoints: None, - pubkey: "".into(), - windows: Default::default(), - } - } -} - /// Configuration for application system tray icon. /// /// See more: https://tauri.app/v1/api/config#systemtrayconfig @@ -2701,6 +1493,42 @@ pub struct SystemTrayConfig { pub title: Option, } +/// 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, +} + +/// 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))] @@ -2932,8 +1760,7 @@ impl PackageConfig { /// The Tauri configuration object. /// It is read from a file where you can define your frontend assets, -/// configure the bundler, enable the app updater, define a system tray, -/// enable APIs via the allowlist and more. +/// 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 @@ -2953,8 +1780,8 @@ impl PackageConfig { /// /// In addition to the default configuration file, Tauri can /// read a platform-specific configuration from `tauri.linux.conf.json`, -/// `tauri.windows.conf.json`, and `tauri.macos.conf.json` -/// (or `Tauri.linux.toml`, `Tauri.windows.toml` and `Tauri.macos.toml` if the `Tauri.toml` format is used), +/// `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 @@ -2979,16 +1806,10 @@ impl PackageConfig { /// "version": "0.1.0" /// }, /// "tauri": { -/// "allowlist": { -/// "all": true -/// }, /// "bundle": {}, /// "security": { /// "csp": null /// }, -/// "updater": { -/// "active": false -/// }, /// "windows": [ /// { /// "fullscreen": false, @@ -3203,7 +2024,7 @@ mod build { ::tauri::utils::config::$struct { $($field: #$field),+ } - }); + }) }; } @@ -3235,6 +2056,23 @@ mod build { } } + 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 }; @@ -3247,6 +2085,51 @@ mod build { } } + 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); @@ -3282,6 +2165,9 @@ mod build { 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, @@ -3318,104 +2204,10 @@ mod build { hidden_title, accept_first_mouse, tabbing_identifier, - additional_browser_args - ); - } - } - - 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 = self.takes_value; - let multiple = self.multiple; - let multiple_occurrences = self.multiple_occurrences; - 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 = self.required; - 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 + additional_browser_args, + shadow, + window_effects, + incognito ); } } @@ -3477,6 +2269,16 @@ mod build { } } + 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); @@ -3494,6 +2296,9 @@ mod build { 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, @@ -3512,7 +2317,10 @@ mod build { deb, macos, external_bin, - windows + windows, + ios, + android, + updater ); } } @@ -3574,39 +2382,7 @@ mod build { impl ToTokens for UpdaterWindowsConfig { fn to_tokens(&self, tokens: &mut TokenStream) { let install_mode = &self.install_mode; - let installer_args = vec_lit(&self.installer_args, str_lit); - literal_struct!(tokens, UpdaterWindowsConfig, install_mode, installer_args); - } - } - - impl ToTokens for UpdaterConfig { - 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 windows = &self.windows; - - literal_struct!( - tokens, - UpdaterConfig, - active, - dialog, - pubkey, - endpoints, - windows - ); + literal_struct!(tokens, UpdaterWindowsConfig, install_mode); } } @@ -3671,7 +2447,6 @@ mod build { let domain = str_lit(&self.domain); let windows = vec_lit(&self.windows, str_lit); let plugins = vec_lit(&self.plugins, str_lit); - let enable_tauri_api = self.enable_tauri_api; literal_struct!( tokens, @@ -3679,8 +2454,7 @@ mod build { scheme, domain, windows, - plugins, - enable_tauri_api + plugins ); } } @@ -3693,6 +2467,7 @@ mod build { 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; literal_struct!( tokens, @@ -3701,7 +2476,8 @@ mod build { dev_csp, freeze_prototype, dangerous_disable_asset_csp_modification, - dangerous_remote_domain_ipc_access + dangerous_remote_domain_ipc_access, + asset_protocol ); } } @@ -3723,9 +2499,9 @@ mod build { } } - impl ToTokens for FsAllowlistScope { + impl ToTokens for FsScope { fn to_tokens(&self, tokens: &mut TokenStream) { - let prefix = quote! { ::tauri::utils::config::FsAllowlistScope }; + let prefix = quote! { ::tauri::utils::config::FsScope }; tokens.append_all(match self { Self::AllowedPaths(allow) => { @@ -3742,110 +2518,10 @@ mod build { } } - impl ToTokens for FsAllowlistConfig { - fn to_tokens(&self, tokens: &mut TokenStream) { - let scope = &self.scope; - tokens.append_all(quote! { ::tauri::utils::config::FsAllowlistConfig { scope: #scope, ..Default::default() } }) - } - } - - 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() } }) - } - } - - impl ToTokens for HttpAllowlistScope { - 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) }) - } - } - - 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() } }) - } - } - - impl ToTokens for ShellAllowedCommand { - 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; - - literal_struct!(tokens, ShellAllowedCommand, name, command, args, sidecar); - } - } - - impl ToTokens for ShellAllowedArgs { - fn to_tokens(&self, tokens: &mut TokenStream) { - let prefix = quote! { ::tauri::utils::config::ShellAllowedArgs }; - - 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)) - } - }) - } - } - - impl ToTokens for ShellAllowedArg { - 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 }) - } - }) - } - } - - impl ToTokens for ShellAllowlistOpen { - fn to_tokens(&self, tokens: &mut TokenStream) { - let prefix = quote! { ::tauri::utils::config::ShellAllowlistOpen }; - - 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) }) - } - } - - 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() } }) } } @@ -3853,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!( @@ -3866,12 +2539,9 @@ mod build { TauriConfig, pattern, windows, - cli, bundle, - updater, security, system_tray, - allowlist, macos_private_api ); } @@ -3930,8 +2600,6 @@ mod test { 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 { @@ -3953,14 +2621,9 @@ mod test { macos: Default::default(), external_bin: None, windows: Default::default(), - }, - cli: None, - updater: UpdaterConfig { - active: false, - dialog: true, - pubkey: "".into(), - endpoints: None, - windows: Default::default(), + ios: Default::default(), + android: Default::default(), + updater: Default::default(), }, security: SecurityConfig { csp: None, @@ -3968,8 +2631,8 @@ mod test { 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, }; @@ -3992,7 +2655,6 @@ 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( diff --git a/core/tauri-utils/src/config/parse.rs b/core/tauri-utils/src/config/parse.rs index a643fb1aeacd..6590620edd34 100644 --- a/core/tauri-utils/src/config/parse.rs +++ b/core/tauri-utils/src/config/parse.rs @@ -54,6 +54,10 @@ impl ConfigFormat { "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" } @@ -63,6 +67,10 @@ impl ConfigFormat { "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" } @@ -72,6 +80,10 @@ impl ConfigFormat { "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" } @@ -170,11 +182,13 @@ pub fn is_configuration_file(path: &Path) -> bool { /// 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. diff --git a/core/tauri-utils/src/lib.rs b/core/tauri-utils/src/lib.rs index ca36e83ca365..8d50acf243f1 100644 --- a/core/tauri-utils/src/lib.rs +++ b/core/tauri-utils/src/lib.rs @@ -4,6 +4,7 @@ //! Tauri utility helpers #![warn(missing_docs, rust_2018_idioms)] +#![allow(clippy::deprecated_semver)] use std::{ fmt::Display, @@ -37,6 +38,8 @@ pub struct PackageInfo { pub authors: &'static str, /// The crate description. pub description: &'static str, + /// The crate name. + pub crate_name: &'static str, } impl PackageInfo { @@ -53,6 +56,95 @@ 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))] diff --git a/core/tauri-utils/src/platform.rs b/core/tauri-utils/src/platform.rs index 5e2e77192326..0ca4fd617966 100644 --- a/core/tauri-utils/src/platform.rs +++ b/core/tauri-utils/src/platform.rs @@ -204,7 +204,7 @@ pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result Option { + /// 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()); diff --git a/core/tauri/CHANGELOG.md b/core/tauri/CHANGELOG.md index 50bdba64ffb3..b024eb05e056 100644 --- a/core/tauri/CHANGELOG.md +++ b/core/tauri/CHANGELOG.md @@ -1,5 +1,145 @@ # 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 diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index c4a3a64afe02..a7f369b691bd 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -1,31 +1,29 @@ [package] -authors = [ "Tauri Programme within The Commons Conservancy" ] -categories = [ "gui", "web-programming" ] +name = "tauri" +version = "2.0.0-alpha.9" description = "Make tiny, secure apps for all desktop platforms with Tauri" -edition = "2021" -rust-version = "1.60" exclude = [ "/test", "/.scripts", "CHANGELOG.md", "/target" ] -homepage = "https://tauri.app" -license = "Apache-2.0 OR MIT" -name = "tauri" readme = "README.md" -repository = "https://github.com/tauri-apps/tauri" -version = "1.4.0" +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] no-default-features = true features = [ "wry", "custom-protocol", - "api-all", - "windows7-compat", - "cli", - "updater", - "fs-extract-api", "system-tray", "devtools", - "http-multipart", "icon-png", + "protocol-asset", "test", "dox" ] @@ -39,6 +37,7 @@ targets = [ [package.metadata.cargo-udeps.ignore] normal = [ "reqwest" ] +build = [ "tauri-build" ] [dependencies] serde_json = { version = "1.0", features = [ "raw_value" ] } @@ -50,51 +49,32 @@ url = { version = "2.3" } anyhow = "1.0" thiserror = "1.0" once_cell = "1" -tauri-runtime = { version = "0.14.0", path = "../tauri-runtime" } -tauri-macros = { version = "1.4.0", path = "../tauri-macros" } -tauri-utils = { version = "1.4.0", features = [ "resources" ], path = "../tauri-utils" } -tauri-runtime-wry = { version = "0.14.0", path = "../tauri-runtime-wry", optional = true } +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 = { version = "1.0", features = [ "serde" ] } serde_repr = "0.1" state = "0.5" -tar = "0.4.38" tempfile = "3" -zip = { version = "0.6", default-features = false, optional = true } -ignore = "0.4" -flate2 = "1.0" http = "0.2" dirs-next = "2.0" percent-encoding = "2.2" -base64 = { version = "0.21", optional = true } -clap = { version = "3", optional = true } -reqwest = { version = "0.11", features = [ "json", "stream" ], optional = true } -bytes = { version = "1", features = [ "serde" ], optional = true } -open = { version = "3.2", optional = true } -shared_child = { version = "1.0", optional = true } -os_pipe = { version = "1.0", optional = true } +reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ] } +bytes = { version = "1", features = [ "serde" ] } raw-window-handle = "0.5" -minisign-verify = { version = "0.2", optional = true } -time = { version = "0.3", features = [ "parsing", "formatting" ], optional = true } -os_info = { version = "3", optional = true } -regex = { version = "1.6.0", optional = true } glob = "0.3" 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 } -encoding_rs = "0.8.31" -sys-locale = { version = "0.2.3", optional = true } - -[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] -rfd = { version = "0.10", optional = true, features = [ "gtk3", "common-controls-v6" ] } -notify-rust = { version = "4.5", 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" -webkit2gtk = { version = "0.18.2", features = [ "v2_22" ] } +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" @@ -102,28 +82,39 @@ cocoa = "0.24" objc = "0.2" [target."cfg(windows)".dependencies] -webview2-com = "0.19.1" -win7-notifications = { version = "0.3.1", optional = true } +webview2-com = "0.22" [target."cfg(windows)".dependencies.windows] - version = "0.39.0" + 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] heck = "0.4" once_cell = "1" +tauri-build = { path = "../tauri-build/", version = "2.0.0-alpha.1" } [dev-dependencies] -mockito = "0.31" proptest = "1.0.0" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" tauri = { path = ".", default-features = false, features = [ "wry" ] } -tokio-test = "0.4.2" tokio = { version = "1", features = [ "full" ] } -cargo_toml = "0.11" +cargo_toml = "0.15" [features] default = [ "wry", "compression", "objc-exception" ] @@ -134,171 +125,19 @@ 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" ] -updater = [ - "minisign-verify", - "time", - "base64", - "http-api", - "dialog-ask", - "fs-extract-api" -] -http-api = [ "reqwest", "bytes" ] -http-multipart = [ "reqwest/multipart" ] -os-api = [ "sys-locale" ] -shell-open-api = [ "open", "regex", "tauri-macros/shell-scope" ] -fs-extract-api = [ "zip" ] -reqwest-client = [ "http-api" ] -reqwest-native-tls-vendored = [ "native-tls-vendored" ] +native-tls = [ "reqwest/native-tls" ] native-tls-vendored = [ "reqwest/native-tls-vendored" ] -process-command-api = [ "shared_child", "os_pipe" ] -global-shortcut = [ - "tauri-runtime/global-shortcut", - "tauri-runtime-wry/global-shortcut" -] -clipboard = [ "tauri-runtime/clipboard", "tauri-runtime-wry/clipboard" ] -dialog = [ "rfd" ] -notification = [ "notify-rust" ] -cli = [ "clap" ] +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" ] -windows7-compat = [ "win7-notifications" ] 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", - "app-all" -] -clipboard-all = [ "clipboard-write-text", "clipboard-read-text" ] -clipboard-read-text = [ "clipboard" ] -clipboard-write-text = [ "clipboard" ] -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-exists", - "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-exists = [ ] -fs-read-file = [ ] -fs-read-dir = [ ] -fs-remove-dir = [ ] -fs-remove-file = [ ] -fs-rename-file = [ ] -fs-write-file = [ ] -global-shortcut-all = [ "global-shortcut" ] -http-all = [ "http-request" ] -http-request = [ "http-api" ] -notification-all = [ "notification", "dialog-ask" ] -os-all = [ "os_info", "os-api" ] -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 = [ "process-command-api", "regex", "tauri-macros/shell-scope" ] -shell-sidecar = [ "process-command-api", "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-maximizable", - "window-set-minimizable", - "window-set-closable", - "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-content-protected", - "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-set-cursor-grab", - "window-set-cursor-visible", - "window-set-cursor-icon", - "window-set-cursor-position", - "window-set-ignore-cursor-events", - "window-start-dragging", - "window-print" -] -window-create = [ ] -window-center = [ ] -window-request-user-attention = [ ] -window-set-resizable = [ ] -window-set-maximizable = [ ] -window-set-minimizable = [ ] -window-set-closable = [ ] -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-content-protected = [ ] -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-set-cursor-grab = [ ] -window-set-cursor-visible = [ ] -window-set-cursor-icon = [ ] -window-set-cursor-position = [ ] -window-set-ignore-cursor-events = [ ] -window-start-dragging = [ ] -window-print = [ ] -app-all = [ "app-show", "app-hide" ] -app-show = [ ] -app-hide = [ ] config-json5 = [ "tauri-macros/config-json5" ] config-toml = [ "tauri-macros/config-toml" ] icon-ico = [ "infer", "ico" ] @@ -315,7 +154,6 @@ path = "../../examples/helloworld/main.rs" [[example]] name = "multiwindow" path = "../../examples/multiwindow/main.rs" -required-features = [ "window-create" ] [[example]] name = "parent-window" @@ -324,7 +162,6 @@ path = "../../examples/parent-window/main.rs" [[example]] name = "navigation" path = "../../examples/navigation/main.rs" -required-features = [ "window-create" ] [[example]] name = "splashscreen" diff --git a/core/tauri/build.rs b/core/tauri/build.rs index bd095e6b06fc..29ea69f97a24 100644 --- a/core/tauri/build.rs +++ b/core/tauri/build.rs @@ -3,12 +3,18 @@ // SPDX-License-Identifier: MIT use heck::AsShoutySnakeCase; -use heck::AsSnakeCase; -use heck::ToSnakeCase; use once_cell::sync::OnceCell; -use std::{path::Path, sync::Mutex}; +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(); @@ -38,107 +44,13 @@ fn alias(alias: &str, has_feature: bool) { fn main() { alias("custom_protocol", has_feature("custom-protocol")); alias("dev", !has_feature("custom-protocol")); - alias("updater", has_feature("updater")); 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 api_all = has_feature("api-all"); - alias("api_all", api_all); - - alias_module( - "fs", - &[ - "read-file", - "write-file", - "read-dir", - "copy-file", - "create-dir", - "remove-dir", - "remove-file", - "rename-file", - "exists", - ], - api_all, - ); - - alias_module( - "window", - &[ - "create", - "center", - "request-user-attention", - "set-resizable", - "set-maximizable", - "set-minimizable", - "set-closable", - "set-title", - "maximize", - "unmaximize", - "minimize", - "unminimize", - "show", - "hide", - "close", - "set-decorations", - "set-always-on-top", - "set-content-protected", - "set-size", - "set-min-size", - "set-max-size", - "set-position", - "set-fullscreen", - "set-focus", - "set-icon", - "set-skip-taskbar", - "set-cursor-grab", - "set-cursor-visible", - "set-cursor-icon", - "set-cursor-position", - "set-ignore-cursor-events", - "start-dragging", - "print", - ], - api_all, - ); - - alias_module("shell", &["execute", "sidecar", "open"], api_all); - // helper for the command module macro - let shell_script = has_feature("shell-execute") || has_feature("shell-sidecar"); - alias("shell_script", shell_script); - alias("shell_scope", has_feature("shell-open-api") || shell_script); - - if !mobile { - alias_module( - "dialog", - &["open", "save", "message", "ask", "confirm"], - api_all, - ); - } - - alias_module("http", &["request"], api_all); - - alias("cli", has_feature("cli")); - - if !mobile { - alias_module("notification", &[], api_all); - alias_module("global-shortcut", &[], api_all); - } - alias_module("os", &[], api_all); - alias_module("path", &[], api_all); - - alias_module("protocol", &["asset"], api_all); - - alias_module("process", &["relaunch", "exit"], api_all); - - alias_module("clipboard", &["write-text", "read-text"], api_all); - - alias_module("app", &["show", "hide"], api_all); - - let checked_features_out_path = - Path::new(&std::env::var("OUT_DIR").unwrap()).join("checked_features"); + 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(","), @@ -147,43 +59,78 @@ fn main() { // workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error // see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864 - let target_os = std::env::var("CARGO_CFG_TARGET_OS"); 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 - && Ok("windows") == target_os.as_deref() - && Ok("msvc") == target_env.as_deref() - { + if is_tauri_workspace && target_os == "windows" && Ok("msvc") == target_env.as_deref() { add_manifest(); } -} -// create aliases for the given module with its apis. -// each api is translated into a feature flag in the format of `-` -// and aliased as `_`. -// -// The `-all` feature is also aliased to `_all`. -// -// If any of the features is enabled, the `_any` alias is created. -// -// Note that both `module` and `apis` strings must be written in kebab case. -fn alias_module(module: &str, apis: &[&str], api_all: bool) { - let all_feature_name = format!("{module}-all"); - let all = has_feature(&all_feature_name) || api_all; - alias(&all_feature_name.to_snake_case(), all); - - let mut any = all; - - for api in apis { - let has = has_feature(&format!("{module}-{api}")) || all; - alias( - &format!("{}_{}", AsSnakeCase(module), AsSnakeCase(api)), - has, - ); - any = any || has; + 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()); } - alias(&format!("{}_any", AsSnakeCase(module)), any); + #[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() { 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/tests/app-updater/build.rs b/core/tauri/mobile/android/src/main/java/app/tauri/JniMethod.kt similarity index 59% rename from core/tests/app-updater/build.rs rename to core/tauri/mobile/android/src/main/java/app/tauri/JniMethod.kt index b055ec37c771..d12778b5ffc1 100644 --- a/core/tests/app-updater/build.rs +++ b/core/tauri/mobile/android/src/main/java/app/tauri/JniMethod.kt @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -fn main() { - tauri_build::build() -} +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/examples/updater/src-tauri/build.rs b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/ActivityCallback.kt similarity index 50% rename from examples/updater/src-tauri/build.rs rename to core/tauri/mobile/android/src/main/java/app/tauri/annotation/ActivityCallback.kt index b055ec37c771..7da7c5fb8785 100644 --- a/examples/updater/src-tauri/build.rs +++ b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/ActivityCallback.kt @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -fn main() { - tauri_build::build() -} +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/examples/sidecar/src-tauri/build.rs b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/PluginMethod.kt similarity index 59% rename from examples/sidecar/src-tauri/build.rs rename to core/tauri/mobile/android/src/main/java/app/tauri/annotation/PluginMethod.kt index b055ec37c771..e102782aeb14 100644 --- a/examples/sidecar/src-tauri/build.rs +++ b/core/tauri/mobile/android/src/main/java/app/tauri/annotation/PluginMethod.kt @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -fn main() { - tauri_build::build() -} +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 index cb155ddfef67..3ae27b270d74 100644 --- a/core/tauri/scripts/bundle.global.js +++ b/core/tauri/scripts/bundle.global.js @@ -1,8 +1,2 @@ -"use strict";var __TAURI_IIFE__=(()=>{var L=Object.defineProperty;var pe=Object.getOwnPropertyDescriptor;var ge=Object.getOwnPropertyNames;var he=Object.prototype.hasOwnProperty;var d=(i,e)=>{for(var t in e)L(i,t,{get:e[t],enumerable:!0})},ye=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ge(e))!he.call(i,s)&&s!==t&&L(i,s,{get:()=>e[s],enumerable:!(r=pe(e,s))||r.enumerable});return i};var fe=i=>ye(L({},"__esModule",{value:!0}),i);var Yt={};d(Yt,{app:()=>k,cli:()=>U,clipboard:()=>z,dialog:()=>I,event:()=>V,fs:()=>j,globalShortcut:()=>q,http:()=>$,invoke:()=>Qt,notification:()=>J,os:()=>ne,path:()=>K,process:()=>Q,shell:()=>Y,tauri:()=>R,updater:()=>X,window:()=>ie});var k={};d(k,{getName:()=>we,getTauriVersion:()=>ve,getVersion:()=>Pe,hide:()=>Te,show:()=>Me});var R={};d(R,{convertFileSrc:()=>_e,invoke:()=>f,transformCallback:()=>c});function be(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function c(i,e=!1){let t=be(),r=`_${t}`;return Object.defineProperty(window,r,{value:s=>(e&&Reflect.deleteProperty(window,r),i?.(s)),writable:!1,configurable:!0}),t}async function f(i,e={}){return new Promise((t,r)=>{let s=c(l=>{t(l),Reflect.deleteProperty(window,`_${a}`)},!0),a=c(l=>{r(l),Reflect.deleteProperty(window,`_${s}`)},!0);window.__TAURI_IPC__({cmd:i,callback:s,error:a,...e})})}function _e(i,e="asset"){let t=encodeURIComponent(i);return navigator.userAgent.includes("Windows")?`https://${e}.localhost/${t}`:`${e}://localhost/${t}`}async function n(i){return f("tauri",i)}async function Pe(){return n({__tauriModule:"App",message:{cmd:"getAppVersion"}})}async function we(){return n({__tauriModule:"App",message:{cmd:"getAppName"}})}async function ve(){return n({__tauriModule:"App",message:{cmd:"getTauriVersion"}})}async function Me(){return n({__tauriModule:"App",message:{cmd:"show"}})}async function Te(){return n({__tauriModule:"App",message:{cmd:"hide"}})}var U={};d(U,{getMatches:()=>Oe});async function Oe(){return n({__tauriModule:"Cli",message:{cmd:"cliMatches"}})}var z={};d(z,{readText:()=>Ce,writeText:()=>Fe});async function Fe(i){return n({__tauriModule:"Clipboard",message:{cmd:"writeText",data:i}})}async function Ce(){return n({__tauriModule:"Clipboard",message:{cmd:"readText",data:null}})}var I={};d(I,{ask:()=>De,confirm:()=>Se,message:()=>We,open:()=>Ee,save:()=>Ae});async function Ee(i={}){return typeof i=="object"&&Object.freeze(i),n({__tauriModule:"Dialog",message:{cmd:"openDialog",options:i}})}async function Ae(i={}){return typeof i=="object"&&Object.freeze(i),n({__tauriModule:"Dialog",message:{cmd:"saveDialog",options:i}})}async function We(i,e){let t=typeof e=="string"?{title:e}:e;return n({__tauriModule:"Dialog",message:{cmd:"messageDialog",message:i.toString(),title:t?.title?.toString(),type:t?.type,buttonLabel:t?.okLabel?.toString()}})}async function De(i,e){let t=typeof e=="string"?{title:e}:e;return n({__tauriModule:"Dialog",message:{cmd:"askDialog",message:i.toString(),title:t?.title?.toString(),type:t?.type,buttonLabels:[t?.okLabel?.toString()??"Yes",t?.cancelLabel?.toString()??"No"]}})}async function Se(i,e){let t=typeof e=="string"?{title:e}:e;return n({__tauriModule:"Dialog",message:{cmd:"confirmDialog",message:i.toString(),title:t?.title?.toString(),type:t?.type,buttonLabels:[t?.okLabel?.toString()??"Ok",t?.cancelLabel?.toString()??"Cancel"]}})}var V={};d(V,{TauriEvent:()=>M,emit:()=>T,listen:()=>N,once:()=>H});async function re(i,e){return n({__tauriModule:"Event",message:{cmd:"unlisten",event:i,eventId:e}})}async function w(i,e,t){await n({__tauriModule:"Event",message:{cmd:"emit",event:i,windowLabel:e,payload:t}})}async function b(i,e,t){return n({__tauriModule:"Event",message:{cmd:"listen",event:i,windowLabel:e,handler:c(t)}}).then(r=>async()=>re(i,r))}async function v(i,e,t){return b(i,e,r=>{t(r),re(i,r.id).catch(()=>{})})}var M=(u=>(u.WINDOW_RESIZED="tauri://resize",u.WINDOW_MOVED="tauri://move",u.WINDOW_CLOSE_REQUESTED="tauri://close-requested",u.WINDOW_CREATED="tauri://window-created",u.WINDOW_DESTROYED="tauri://destroyed",u.WINDOW_FOCUS="tauri://focus",u.WINDOW_BLUR="tauri://blur",u.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",u.WINDOW_THEME_CHANGED="tauri://theme-changed",u.WINDOW_FILE_DROP="tauri://file-drop",u.WINDOW_FILE_DROP_HOVER="tauri://file-drop-hover",u.WINDOW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled",u.MENU="tauri://menu",u.CHECK_UPDATE="tauri://update",u.UPDATE_AVAILABLE="tauri://update-available",u.INSTALL_UPDATE="tauri://update-install",u.STATUS_UPDATE="tauri://update-status",u.DOWNLOAD_PROGRESS="tauri://update-download-progress",u))(M||{});async function N(i,e){return b(i,null,e)}async function H(i,e){return v(i,null,e)}async function T(i,e){return w(i,void 0,e)}var j={};d(j,{BaseDirectory:()=>O,Dir:()=>O,copyFile:()=>He,createDir:()=>Ie,exists:()=>qe,readBinaryFile:()=>Re,readDir:()=>ze,readTextFile:()=>Le,removeDir:()=>Ne,removeFile:()=>Ve,renameFile:()=>je,writeBinaryFile:()=>Ue,writeFile:()=>ke,writeTextFile:()=>ke});var O=(o=>(o[o.Audio=1]="Audio",o[o.Cache=2]="Cache",o[o.Config=3]="Config",o[o.Data=4]="Data",o[o.LocalData=5]="LocalData",o[o.Desktop=6]="Desktop",o[o.Document=7]="Document",o[o.Download=8]="Download",o[o.Executable=9]="Executable",o[o.Font=10]="Font",o[o.Home=11]="Home",o[o.Picture=12]="Picture",o[o.Public=13]="Public",o[o.Runtime=14]="Runtime",o[o.Template=15]="Template",o[o.Video=16]="Video",o[o.Resource=17]="Resource",o[o.App=18]="App",o[o.Log=19]="Log",o[o.Temp=20]="Temp",o[o.AppConfig=21]="AppConfig",o[o.AppData=22]="AppData",o[o.AppLocalData=23]="AppLocalData",o[o.AppCache=24]="AppCache",o[o.AppLog=25]="AppLog",o))(O||{});async function Le(i,e={}){return n({__tauriModule:"Fs",message:{cmd:"readTextFile",path:i,options:e}})}async function Re(i,e={}){let t=await n({__tauriModule:"Fs",message:{cmd:"readFile",path:i,options:e}});return Uint8Array.from(t)}async function ke(i,e,t){typeof t=="object"&&Object.freeze(t),typeof i=="object"&&Object.freeze(i);let r={path:"",contents:""},s=t;return typeof i=="string"?r.path=i:(r.path=i.path,r.contents=i.contents),typeof e=="string"?r.contents=e??"":s=e,n({__tauriModule:"Fs",message:{cmd:"writeFile",path:r.path,contents:Array.from(new TextEncoder().encode(r.contents)),options:s}})}async function Ue(i,e,t){typeof t=="object"&&Object.freeze(t),typeof i=="object"&&Object.freeze(i);let r={path:"",contents:[]},s=t;return typeof i=="string"?r.path=i:(r.path=i.path,r.contents=i.contents),e&&"dir"in e?s=e:typeof i=="string"&&(r.contents=e??[]),n({__tauriModule:"Fs",message:{cmd:"writeFile",path:r.path,contents:Array.from(r.contents instanceof ArrayBuffer?new Uint8Array(r.contents):r.contents),options:s}})}async function ze(i,e={}){return n({__tauriModule:"Fs",message:{cmd:"readDir",path:i,options:e}})}async function Ie(i,e={}){return n({__tauriModule:"Fs",message:{cmd:"createDir",path:i,options:e}})}async function Ne(i,e={}){return n({__tauriModule:"Fs",message:{cmd:"removeDir",path:i,options:e}})}async function He(i,e,t={}){return n({__tauriModule:"Fs",message:{cmd:"copyFile",source:i,destination:e,options:t}})}async function Ve(i,e={}){return n({__tauriModule:"Fs",message:{cmd:"removeFile",path:i,options:e}})}async function je(i,e,t={}){return n({__tauriModule:"Fs",message:{cmd:"renameFile",oldPath:i,newPath:e,options:t}})}async function qe(i,e={}){return n({__tauriModule:"Fs",message:{cmd:"exists",path:i,options:e}})}var q={};d(q,{isRegistered:()=>Je,register:()=>Ge,registerAll:()=>$e,unregister:()=>Ke,unregisterAll:()=>Qe});async function Ge(i,e){return n({__tauriModule:"GlobalShortcut",message:{cmd:"register",shortcut:i,handler:c(e)}})}async function $e(i,e){return n({__tauriModule:"GlobalShortcut",message:{cmd:"registerAll",shortcuts:i,handler:c(e)}})}async function Je(i){return n({__tauriModule:"GlobalShortcut",message:{cmd:"isRegistered",shortcut:i}})}async function Ke(i){return n({__tauriModule:"GlobalShortcut",message:{cmd:"unregister",shortcut:i}})}async function Qe(){return n({__tauriModule:"GlobalShortcut",message:{cmd:"unregisterAll"}})}var $={};d($,{Body:()=>p,Client:()=>C,Response:()=>F,ResponseType:()=>se,fetch:()=>Ye,getClient:()=>ae});var se=(r=>(r[r.JSON=1]="JSON",r[r.Text=2]="Text",r[r.Binary=3]="Binary",r))(se||{}),p=class{constructor(e,t){this.type=e,this.payload=t}static form(e){let t={},r=(s,a)=>{if(a!==null){let l;typeof a=="string"?l=a:a instanceof Uint8Array||Array.isArray(a)?l=Array.from(a):a instanceof File?l={file:a.name,mime:a.type,fileName:a.name}:typeof a.file=="string"?l={file:a.file,mime:a.mime,fileName:a.fileName}:l={file:Array.from(a.file),mime:a.mime,fileName:a.fileName},t[String(s)]=l}};if(e instanceof FormData)for(let[s,a]of e)r(s,a);else for(let[s,a]of Object.entries(e))r(s,a);return new p("Form",t)}static json(e){return new p("Json",e)}static text(e){return new p("Text",e)}static bytes(e){return new p("Bytes",Array.from(e instanceof ArrayBuffer?new Uint8Array(e):e))}},F=class{constructor(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}},C=class{constructor(e){this.id=e}async drop(){return n({__tauriModule:"Http",message:{cmd:"dropClient",client:this.id}})}async request(e){let t=!e.responseType||e.responseType===1;return t&&(e.responseType=2),n({__tauriModule:"Http",message:{cmd:"httpRequest",client:this.id,options:e}}).then(r=>{let s=new F(r);if(t){try{s.data=JSON.parse(s.data)}catch(a){if(s.ok&&s.data==="")s.data={};else if(s.ok)throw Error(`Failed to parse response \`${s.data}\` as JSON: ${a}; - try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.`)}return s}return s})}async get(e,t){return this.request({method:"GET",url:e,...t})}async post(e,t,r){return this.request({method:"POST",url:e,body:t,...r})}async put(e,t,r){return this.request({method:"PUT",url:e,body:t,...r})}async patch(e,t){return this.request({method:"PATCH",url:e,...t})}async delete(e,t){return this.request({method:"DELETE",url:e,...t})}};async function ae(i){return n({__tauriModule:"Http",message:{cmd:"createClient",options:i}}).then(e=>new C(e))}var G=null;async function Ye(i,e){return G===null&&(G=await ae()),G.request({url:i,method:e?.method??"GET",...e})}var J={};d(J,{isPermissionGranted:()=>Ze,requestPermission:()=>Xe,sendNotification:()=>Be});async function Ze(){return window.Notification.permission!=="default"?Promise.resolve(window.Notification.permission==="granted"):n({__tauriModule:"Notification",message:{cmd:"isNotificationPermissionGranted"}})}async function Xe(){return window.Notification.requestPermission()}function Be(i){typeof i=="string"?new window.Notification(i):new window.Notification(i.title,i)}var K={};d(K,{BaseDirectory:()=>O,appCacheDir:()=>nt,appConfigDir:()=>oe,appDataDir:()=>tt,appDir:()=>et,appLocalDataDir:()=>it,appLogDir:()=>le,audioDir:()=>rt,basename:()=>Wt,cacheDir:()=>st,configDir:()=>at,dataDir:()=>ot,delimiter:()=>Tt,desktopDir:()=>lt,dirname:()=>Et,documentDir:()=>ut,downloadDir:()=>dt,executableDir:()=>ct,extname:()=>At,fontDir:()=>mt,homeDir:()=>pt,isAbsolute:()=>Dt,join:()=>Ct,localDataDir:()=>gt,logDir:()=>vt,normalize:()=>Ft,pictureDir:()=>ht,publicDir:()=>yt,resolve:()=>Ot,resolveResource:()=>bt,resourceDir:()=>ft,runtimeDir:()=>_t,sep:()=>Mt,templateDir:()=>Pt,videoDir:()=>wt});function _(){return navigator.appVersion.includes("Win")}async function et(){return oe()}async function oe(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:21}})}async function tt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:22}})}async function it(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:23}})}async function nt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:24}})}async function rt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:1}})}async function st(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:2}})}async function at(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:3}})}async function ot(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:4}})}async function lt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:6}})}async function ut(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:7}})}async function dt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:8}})}async function ct(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:9}})}async function mt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:10}})}async function pt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:11}})}async function gt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:5}})}async function ht(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:12}})}async function yt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:13}})}async function ft(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:17}})}async function bt(i){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:i,directory:17}})}async function _t(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:14}})}async function Pt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:15}})}async function wt(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:16}})}async function vt(){return le()}async function le(){return n({__tauriModule:"Path",message:{cmd:"resolvePath",path:"",directory:25}})}var Mt=_()?"\\":"/",Tt=_()?";":":";async function Ot(...i){return n({__tauriModule:"Path",message:{cmd:"resolve",paths:i}})}async function Ft(i){return n({__tauriModule:"Path",message:{cmd:"normalize",path:i}})}async function Ct(...i){return n({__tauriModule:"Path",message:{cmd:"join",paths:i}})}async function Et(i){return n({__tauriModule:"Path",message:{cmd:"dirname",path:i}})}async function At(i){return n({__tauriModule:"Path",message:{cmd:"extname",path:i}})}async function Wt(i,e){return n({__tauriModule:"Path",message:{cmd:"basename",path:i,ext:e}})}async function Dt(i){return n({__tauriModule:"Path",message:{cmd:"isAbsolute",path:i}})}var Q={};d(Q,{exit:()=>St,relaunch:()=>xt});async function St(i=0){return n({__tauriModule:"Process",message:{cmd:"exit",exitCode:i}})}async function xt(){return n({__tauriModule:"Process",message:{cmd:"relaunch"}})}var Y={};d(Y,{Child:()=>E,Command:()=>P,EventEmitter:()=>g,open:()=>Rt});async function Lt(i,e,t=[],r){return typeof t=="object"&&Object.freeze(t),n({__tauriModule:"Shell",message:{cmd:"execute",program:e,args:t,options:r,onEventFn:c(i)}})}var g=class{constructor(){this.eventListeners=Object.create(null)}addListener(e,t){return this.on(e,t)}removeListener(e,t){return this.off(e,t)}on(e,t){return e in this.eventListeners?this.eventListeners[e].push(t):this.eventListeners[e]=[t],this}once(e,t){let r=(...s)=>{this.removeListener(e,r),t(...s)};return this.addListener(e,r)}off(e,t){return e in this.eventListeners&&(this.eventListeners[e]=this.eventListeners[e].filter(r=>r!==t)),this}removeAllListeners(e){return e?delete this.eventListeners[e]:this.eventListeners=Object.create(null),this}emit(e,...t){if(e in this.eventListeners){let r=this.eventListeners[e];for(let s of r)s(...t);return!0}return!1}listenerCount(e){return e in this.eventListeners?this.eventListeners[e].length:0}prependListener(e,t){return e in this.eventListeners?this.eventListeners[e].unshift(t):this.eventListeners[e]=[t],this}prependOnceListener(e,t){let r=(...s)=>{this.removeListener(e,r),t(...s)};return this.prependListener(e,r)}},E=class{constructor(e){this.pid=e}async write(e){return n({__tauriModule:"Shell",message:{cmd:"stdinWrite",pid:this.pid,buffer:typeof e=="string"?e:Array.from(e)}})}async kill(){return n({__tauriModule:"Shell",message:{cmd:"killChild",pid:this.pid}})}},P=class extends g{constructor(t,r=[],s){super();this.stdout=new g;this.stderr=new g;this.program=t,this.args=typeof r=="string"?[r]:r,this.options=s??{}}static sidecar(t,r=[],s){let a=new P(t,r,s);return a.options.sidecar=!0,a}async spawn(){return Lt(t=>{switch(t.event){case"Error":this.emit("error",t.payload);break;case"Terminated":this.emit("close",t.payload);break;case"Stdout":this.stdout.emit("data",t.payload);break;case"Stderr":this.stderr.emit("data",t.payload);break}},this.program,this.args,this.options).then(t=>new E(t))}async execute(){return new Promise((t,r)=>{this.on("error",r);let s=[],a=[];this.stdout.on("data",l=>{s.push(l)}),this.stderr.on("data",l=>{a.push(l)}),this.on("close",l=>{t({code:l.code,signal:l.signal,stdout:s.join(` -`),stderr:a.join(` -`)})}),this.spawn().catch(r)})}};async function Rt(i,e){return n({__tauriModule:"Shell",message:{cmd:"open",path:i,with:e}})}var X={};d(X,{checkUpdate:()=>Ut,installUpdate:()=>kt,onUpdaterEvent:()=>Z});async function Z(i){return N("tauri://update-status",e=>{i(e?.payload)})}async function kt(){let i;function e(){i&&i(),i=void 0}return new Promise((t,r)=>{function s(a){if(a.error){e(),r(a.error);return}a.status==="DONE"&&(e(),t())}Z(s).then(a=>{i=a}).catch(a=>{throw e(),a}),T("tauri://update-install").catch(a=>{throw e(),a})})}async function Ut(){let i;function e(){i&&i(),i=void 0}return new Promise((t,r)=>{function s(l){e(),t({manifest:l,shouldUpdate:!0})}function a(l){if(l.error){e(),r(l.error);return}l.status==="UPTODATE"&&(e(),t({shouldUpdate:!1}))}H("tauri://update-available",l=>{s(l?.payload)}).catch(l=>{throw e(),l}),Z(a).then(l=>{i=l}).catch(l=>{throw e(),l}),T("tauri://update").catch(l=>{throw e(),l})})}var ie={};d(ie,{CloseRequestedEvent:()=>x,LogicalPosition:()=>W,LogicalSize:()=>A,PhysicalPosition:()=>y,PhysicalSize:()=>h,UserAttentionType:()=>de,WebviewWindow:()=>m,WebviewWindowHandle:()=>D,WindowManager:()=>S,appWindow:()=>ee,availableMonitors:()=>Ht,currentMonitor:()=>It,getAll:()=>B,getCurrent:()=>zt,primaryMonitor:()=>Nt});var A=class{constructor(e,t){this.type="Logical";this.width=e,this.height=t}},h=class{constructor(e,t){this.type="Physical";this.width=e,this.height=t}toLogical(e){return new A(this.width/e,this.height/e)}},W=class{constructor(e,t){this.type="Logical";this.x=e,this.y=t}},y=class{constructor(e,t){this.type="Physical";this.x=e,this.y=t}toLogical(e){return new W(this.x/e,this.y/e)}},de=(t=>(t[t.Critical=1]="Critical",t[t.Informational=2]="Informational",t))(de||{});function zt(){return new m(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0})}function B(){return window.__TAURI_METADATA__.__windows.map(i=>new m(i.label,{skip:!0}))}var ue=["tauri://created","tauri://error"],D=class{constructor(e){this.label=e,this.listeners=Object.create(null)}async listen(e,t){return this._handleTauriEvent(e,t)?Promise.resolve(()=>{let r=this.listeners[e];r.splice(r.indexOf(t),1)}):b(e,this.label,t)}async once(e,t){return this._handleTauriEvent(e,t)?Promise.resolve(()=>{let r=this.listeners[e];r.splice(r.indexOf(t),1)}):v(e,this.label,t)}async emit(e,t){if(ue.includes(e)){for(let r of this.listeners[e]||[])r({event:e,id:-1,windowLabel:this.label,payload:t});return Promise.resolve()}return w(e,this.label,t)}_handleTauriEvent(e,t){return ue.includes(e)?(e in this.listeners?this.listeners[e].push(t):this.listeners[e]=[t],!0):!1}},S=class extends D{async scaleFactor(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"scaleFactor"}}}})}async innerPosition(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"innerPosition"}}}}).then(({x:e,y:t})=>new y(e,t))}async outerPosition(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"outerPosition"}}}}).then(({x:e,y:t})=>new y(e,t))}async innerSize(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"innerSize"}}}}).then(({width:e,height:t})=>new h(e,t))}async outerSize(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"outerSize"}}}}).then(({width:e,height:t})=>new h(e,t))}async isFullscreen(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isFullscreen"}}}})}async isMinimized(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMinimized"}}}})}async isMaximized(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMaximized"}}}})}async isFocused(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isFocused"}}}})}async isDecorated(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isDecorated"}}}})}async isResizable(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isResizable"}}}})}async isMaximizable(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMaximizable"}}}})}async isMinimizable(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMinimizable"}}}})}async isClosable(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isClosable"}}}})}async isVisible(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isVisible"}}}})}async title(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"title"}}}})}async theme(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"theme"}}}})}async center(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"center"}}}})}async requestUserAttention(e){let t=null;return e&&(e===1?t={type:"Critical"}:t={type:"Informational"}),n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"requestUserAttention",payload:t}}}})}async setResizable(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setResizable",payload:e}}}})}async setMaximizable(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setMaximizable",payload:e}}}})}async setMinimizable(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setMinimizable",payload:e}}}})}async setClosable(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setClosable",payload:e}}}})}async setTitle(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setTitle",payload:e}}}})}async maximize(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"maximize"}}}})}async unmaximize(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"unmaximize"}}}})}async toggleMaximize(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"toggleMaximize"}}}})}async minimize(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"minimize"}}}})}async unminimize(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"unminimize"}}}})}async show(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"show"}}}})}async hide(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"hide"}}}})}async close(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"close"}}}})}async setDecorations(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setDecorations",payload:e}}}})}async setAlwaysOnTop(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setAlwaysOnTop",payload:e}}}})}async setContentProtected(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setContentProtected",payload:e}}}})}async setSize(e){if(!e||e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setSize",payload:{type:e.type,data:{width:e.width,height:e.height}}}}}})}async setMinSize(e){if(e&&e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setMinSize",payload:e?{type:e.type,data:{width:e.width,height:e.height}}:null}}}})}async setMaxSize(e){if(e&&e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setMaxSize",payload:e?{type:e.type,data:{width:e.width,height:e.height}}:null}}}})}async setPosition(e){if(!e||e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setPosition",payload:{type:e.type,data:{x:e.x,y:e.y}}}}}})}async setFullscreen(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setFullscreen",payload:e}}}})}async setFocus(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setFocus"}}}})}async setIcon(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setIcon",payload:{icon:typeof e=="string"?e:Array.from(e)}}}}})}async setSkipTaskbar(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setSkipTaskbar",payload:e}}}})}async setCursorGrab(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setCursorGrab",payload:e}}}})}async setCursorVisible(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setCursorVisible",payload:e}}}})}async setCursorIcon(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setCursorIcon",payload:e}}}})}async setCursorPosition(e){if(!e||e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setCursorPosition",payload:{type:e.type,data:{x:e.x,y:e.y}}}}}})}async setIgnoreCursorEvents(e){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setIgnoreCursorEvents",payload:e}}}})}async startDragging(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"startDragging"}}}})}async onResized(e){return this.listen("tauri://resize",t=>{t.payload=me(t.payload),e(t)})}async onMoved(e){return this.listen("tauri://move",t=>{t.payload=ce(t.payload),e(t)})}async onCloseRequested(e){return this.listen("tauri://close-requested",t=>{let r=new x(t);Promise.resolve(e(r)).then(()=>{if(!r.isPreventDefault())return this.close()})})}async onFocusChanged(e){let t=await this.listen("tauri://focus",s=>{e({...s,payload:!0})}),r=await this.listen("tauri://blur",s=>{e({...s,payload:!1})});return()=>{t(),r()}}async onScaleChanged(e){return this.listen("tauri://scale-change",e)}async onMenuClicked(e){return this.listen("tauri://menu",e)}async onFileDropEvent(e){let t=await this.listen("tauri://file-drop",a=>{e({...a,payload:{type:"drop",paths:a.payload}})}),r=await this.listen("tauri://file-drop-hover",a=>{e({...a,payload:{type:"hover",paths:a.payload}})}),s=await this.listen("tauri://file-drop-cancelled",a=>{e({...a,payload:{type:"cancel"}})});return()=>{t(),r(),s()}}async onThemeChanged(e){return this.listen("tauri://theme-changed",e)}},x=class{constructor(e){this._preventDefault=!1;this.event=e.event,this.windowLabel=e.windowLabel,this.id=e.id}preventDefault(){this._preventDefault=!0}isPreventDefault(){return this._preventDefault}},m=class extends S{constructor(e,t={}){super(e),t?.skip||n({__tauriModule:"Window",message:{cmd:"createWebview",data:{options:{label:e,...t}}}}).then(async()=>this.emit("tauri://created")).catch(async r=>this.emit("tauri://error",r))}static getByLabel(e){return B().some(t=>t.label===e)?new m(e,{skip:!0}):null}static async getFocusedWindow(){for(let e of B())if(await e.isFocused())return e;return null}},ee;"__TAURI_METADATA__"in window?ee=new m(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0}):(console.warn(`Could not find "window.__TAURI_METADATA__". The "appWindow" value will reference the "main" window label. -Note that this is not an issue if running this frontend on a browser instead of a Tauri window.`),ee=new m("main",{skip:!0}));function te(i){return i===null?null:{name:i.name,scaleFactor:i.scaleFactor,position:ce(i.position),size:me(i.size)}}function ce(i){return new y(i.x,i.y)}function me(i){return new h(i.width,i.height)}async function It(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"currentMonitor"}}}}).then(te)}async function Nt(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"primaryMonitor"}}}}).then(te)}async function Ht(){return n({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"availableMonitors"}}}}).then(i=>i.map(te))}var ne={};d(ne,{EOL:()=>Vt,arch:()=>$t,locale:()=>Kt,platform:()=>jt,tempdir:()=>Jt,type:()=>Gt,version:()=>qt});var Vt=_()?`\r -`:` -`;async function jt(){return n({__tauriModule:"Os",message:{cmd:"platform"}})}async function qt(){return n({__tauriModule:"Os",message:{cmd:"version"}})}async function Gt(){return n({__tauriModule:"Os",message:{cmd:"osType"}})}async function $t(){return n({__tauriModule:"Os",message:{cmd:"arch"}})}async function Jt(){return n({__tauriModule:"Os",message:{cmd:"tempdir"}})}async function Kt(){return n({__tauriModule:"Os",message:{cmd:"locale"}})}var Qt=f;return fe(Yt);})(); +"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 4a7da586b92c..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?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},_setPrototypeOf(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.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},_getPrototypeOf(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;--a){var o=this.tryEntries[a],i=o.completion;if("root"===o.tryLoc)return n("end");if(o.tryLoc<=this.prev){var u=r.call(o,"catchLoc"),s=r.call(o,"finallyLoc");if(u&&s){if(this.prev=0;--n){var a=this.tryEntries[n];if(a.tryLoc<=this.prev&&r.call(a,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),R(r),p}},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;R(r)}return a}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,r){return this.delegate={iterator:x(e),resultName:t,nextLoc:r},"next"===this.method&&(this.arg=void 0),p}},e}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var r=0;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),m}},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:A(e),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=t),m}},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,"_".concat(i))}),!0),i=a((function(e){n(e),Reflect.deleteProperty(window,"_".concat(o))}),!0);window.__TAURI_IPC__(_objectSpread({cmd:t,callback:o,error:i},r))})));case 2:case"end":return e.stop()}}),e)}))),i.apply(this,arguments)}var u=Object.freeze({__proto__:null,transformCallback:a,invoke:o,convertFileSrc:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"asset",r=encodeURIComponent(e);return navigator.userAgent.includes("Windows")?"https://".concat(t,".localhost/").concat(r):"".concat(t,"://").concat(r)}});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 d(){return(d=_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 m=Object.freeze({__proto__:null,getMatches:function(){return d.apply(this,arguments)}});function _(){return(_=_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 y(){return(y=_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",data:null}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var g=Object.freeze({__proto__:null,writeText:function(e){return _.apply(this,arguments)},readText:function(){return y.apply(this,arguments)}});function v(e,t){return null!=e?e:t()}function w(e){for(var t=void 0,r=e[0],n=1;n0&&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)}))),b.apply(this,arguments)}function R(){return R=_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)}))),R.apply(this,arguments)}function k(){return k=_asyncToGenerator(_regeneratorRuntime().mark((function e(t,r){var n;return _regeneratorRuntime().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n="string"==typeof r?{title:r}:r,e.abrupt("return",s({__tauriModule:"Dialog",message:{cmd:"messageDialog",message:t.toString(),title:w([n,"optionalAccess",function(e){return e.title},"optionalAccess",function(e){return e.toString},"call",function(e){return e()}]),type:w([n,"optionalAccess",function(e){return e.type}]),buttonLabel:w([n,"optionalAccess",function(e){return e.okLabel},"optionalAccess",function(e){return e.toString},"call",function(e){return e()}])}}));case 2:case"end":return e.stop()}}),e)}))),k.apply(this,arguments)}function x(){return x=_asyncToGenerator(_regeneratorRuntime().mark((function e(t,r){var n;return _regeneratorRuntime().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n="string"==typeof r?{title:r}:r,e.abrupt("return",s({__tauriModule:"Dialog",message:{cmd:"askDialog",message:t.toString(),title:w([n,"optionalAccess",function(e){return e.title},"optionalAccess",function(e){return e.toString},"call",function(e){return e()}]),type:w([n,"optionalAccess",function(e){return e.type}]),buttonLabels:[v(w([n,"optionalAccess",function(e){return e.okLabel},"optionalAccess",function(e){return e.toString},"call",function(e){return e()}]),(function(){return"Yes"})),v(w([n,"optionalAccess",function(e){return e.cancelLabel},"optionalAccess",function(e){return e.toString},"call",function(e){return e()}]),(function(){return"No"}))]}}));case 2:case"end":return e.stop()}}),e)}))),x.apply(this,arguments)}function T(){return T=_asyncToGenerator(_regeneratorRuntime().mark((function e(t,r){var n;return _regeneratorRuntime().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n="string"==typeof r?{title:r}:r,e.abrupt("return",s({__tauriModule:"Dialog",message:{cmd:"confirmDialog",message:t.toString(),title:w([n,"optionalAccess",function(e){return e.title},"optionalAccess",function(e){return e.toString},"call",function(e){return e()}]),type:w([n,"optionalAccess",function(e){return e.type}]),buttonLabels:[v(w([n,"optionalAccess",function(e){return e.okLabel},"optionalAccess",function(e){return e.toString},"call",function(e){return e()}]),(function(){return"Ok"})),v(w([n,"optionalAccess",function(e){return e.cancelLabel},"optionalAccess",function(e){return e.toString},"call",function(e){return e()}]),(function(){return"Cancel"}))]}}));case 2:case"end":return e.stop()}}),e)}))),T.apply(this,arguments)}var G,P=Object.freeze({__proto__:null,open:function(){return b.apply(this,arguments)},save:function(){return R.apply(this,arguments)},message:function(e,t){return k.apply(this,arguments)},ask:function(e,t){return x.apply(this,arguments)},confirm:function(e,t){return T.apply(this,arguments)}});function O(e,t){return A.apply(this,arguments)}function A(){return A=_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:"Event",message:{cmd:"unlisten",event:t,eventId:r}}));case 1:case"end":return e.stop()}}),e)}))),A.apply(this,arguments)}function L(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)}))),M.apply(this,arguments)}function E(e,t,r){return D.apply(this,arguments)}function D(){return D=_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 r(){return _regeneratorRuntime().wrap((function(r){for(;;)switch(r.prev=r.next){case 0:return r.abrupt("return",O(t,e));case 1:case"end":return r.stop()}}),r)})))})));case 1:case"end":return e.stop()}}),e)}))),D.apply(this,arguments)}function C(e,t,r){return S.apply(this,arguments)}function S(){return S=_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",E(t,r,(function(e){n(e),O(t,e.id).catch((function(){}))})));case 1:case"end":return e.stop()}}),e)}))),S.apply(this,arguments)}function j(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",E(t,null,r));case 1:case"end":return e.stop()}}),e)}))),W.apply(this,arguments)}function N(e,t){return I.apply(this,arguments)}function I(){return I=_asyncToGenerator(_regeneratorRuntime().mark((function e(t,r){return _regeneratorRuntime().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",C(t,null,r));case 1:case"end":return e.stop()}}),e)}))),I.apply(this,arguments)}function z(e,t){return F.apply(this,arguments)}function F(){return F=_asyncToGenerator(_regeneratorRuntime().mark((function e(t,r){return _regeneratorRuntime().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",L(t,void 0,r));case 1:case"end":return e.stop()}}),e)}))),F.apply(this,arguments)}!function(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.CHECK_UPDATE="tauri://update";e.UPDATE_AVAILABLE="tauri://update-available";e.INSTALL_UPDATE="tauri://update-install";e.STATUS_UPDATE="tauri://update-status";e.DOWNLOAD_PROGRESS="tauri://update-download-progress"}(G||(G={}));var U,H=Object.freeze({__proto__:null,listen:j,once:N,emit:z});function V(e,t){return null!=e?e:t()}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:"readTextFile",path:t,options:r}}));case 2:case"end":return e.stop()}}),e)}))),B.apply(this,arguments)}function q(){return q=_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)}))),q.apply(this,arguments)}function J(e,t,r){return Y.apply(this,arguments)}function Y(){return Y=_asyncToGenerator(_regeneratorRuntime().mark((function e(t,r,n){var a,o;return _regeneratorRuntime().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return"object"===_typeof(n)&&Object.freeze(n),"object"===_typeof(t)&&Object.freeze(t),a={path:"",contents:""},o=n,"string"==typeof t?a.path=t:(a.path=t.path,a.contents=t.contents),"string"==typeof r?a.contents=V(r,(function(){return""})):o=r,e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"writeFile",path:a.path,contents:Array.from((new TextEncoder).encode(a.contents)),options:o}}));case 7:case"end":return e.stop()}}),e)}))),Y.apply(this,arguments)}function K(){return K=_asyncToGenerator(_regeneratorRuntime().mark((function e(t,r,n){var a,o;return _regeneratorRuntime().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return"object"===_typeof(n)&&Object.freeze(n),"object"===_typeof(t)&&Object.freeze(t),a={path:"",contents:[]},o=n,"string"==typeof t?a.path=t:(a.path=t.path,a.contents=t.contents),r&&"dir"in r?o=r:"string"==typeof t&&(a.contents=V(r,(function(){return[]}))),e.abrupt("return",s({__tauriModule:"Fs",message:{cmd:"writeFile",path:a.path,contents:Array.from(a.contents instanceof ArrayBuffer?new Uint8Array(a.contents):a.contents),options:o}}));case 7:case"end":return e.stop()}}),e)}))),K.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 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)}))),Q.apply(this,arguments)}function Z(){return Z=_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)}))),Z.apply(this,arguments)}function $(){return $=_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 X(){return X=_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)}))),X.apply(this,arguments)}function ee(){return ee=_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)}))),ee.apply(this,arguments)}function te(){return te=_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)}))),te.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";e[e.Temp=20]="Temp"}(U||(U={}));var re=Object.freeze({__proto__:null,get BaseDirectory(){return U},get Dir(){return U},readTextFile:function(e){return B.apply(this,arguments)},readBinaryFile:function(e){return q.apply(this,arguments)},writeTextFile:J,writeFile:J,writeBinaryFile:function(e,t,r){return K.apply(this,arguments)},readDir:function(e){return Q.apply(this,arguments)},createDir:function(e){return Z.apply(this,arguments)},removeDir:function(e){return $.apply(this,arguments)},copyFile:function(e,t){return X.apply(this,arguments)},removeFile:function(e){return ee.apply(this,arguments)},renameFile:function(e,t){return te.apply(this,arguments)}});function ne(){return(ne=_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 ae(){return(ae=_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 oe(){return(oe=_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 ie(){return(ie=_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 ue(){return(ue=_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 se,ce=Object.freeze({__proto__:null,register:function(e,t){return ne.apply(this,arguments)},registerAll:function(e,t){return ae.apply(this,arguments)},isRegistered:function(e){return oe.apply(this,arguments)},unregister:function(e){return ie.apply(this,arguments)},unregisterAll:function(){return ue.apply(this,arguments)}});function pe(e,t){return null!=e?e:t()}function le(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})),de=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===se.JSON)&&(t.responseType=se.Text),e.abrupt("return",s({__tauriModule:"Http",message:{cmd:"httpRequest",client:this.id,options:t}}).then((function(e){var t=new he(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 me(e){return _e.apply(this,arguments)}function _e(){return(_e=_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 de(e)})));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var ye=null;function ge(){return(ge=_asyncToGenerator(_regeneratorRuntime().mark((function e(t,r){return _regeneratorRuntime().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(null!==ye){e.next=4;break}return e.next=3,me();case 3:ye=e.sent;case 4:return e.abrupt("return",ye.request(_objectSpread({url:t,method:pe(le([r,"optionalAccess",function(e){return e.method}]),(function(){return"GET"}))},r)));case 5:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var ve=Object.freeze({__proto__:null,getClient:me,fetch:function(e,t){return ge.apply(this,arguments)},Body:fe,Client:de,Response:he,get ResponseType(){return se}});function we(){return(we=_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 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",window.Notification.requestPermission());case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var Re=Object.freeze({__proto__:null,sendNotification:function(e){"string"==typeof e?new window.Notification(e):new window.Notification(e.title,e)},requestPermission:function(){return be.apply(this,arguments)},isPermissionGranted:function(){return we.apply(this,arguments)}});function ke(){return navigator.appVersion.includes("Win")}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:U.App}}));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:U.Audio}}));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:U.Cache}}));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:U.Config}}));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:U.Data}}));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:U.Desktop}}));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:U.Document}}));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:U.Download}}));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:U.Executable}}));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:U.Font}}));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:U.Home}}));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:U.LocalData}}));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:U.Picture}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}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:U.Public}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ne(){return(Ne=_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:U.Resource}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ie(){return(Ie=_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:"Path",message:{cmd:"resolvePath",path:t,directory:U.Resource}}));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:U.Runtime}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Fe(){return(Fe=_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:U.Template}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ue(){return(Ue=_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:U.Video}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function He(){return(He=_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:U.Log}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var Ve=ke()?"\\":"/",Be=ke()?";":":";function qe(){return qe=_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)}))),et.apply(this,arguments)}function tt(){return(tt=_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 rt=Object.freeze({__proto__:null,exit:function(){return et.apply(this,arguments)},relaunch:function(){return tt.apply(this,arguments)}});function nt(e,t){return null!=e?e:t()}function at(e,t){return ot.apply(this,arguments)}function ot(){return ot=_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)}))),ot.apply(this,arguments)}var it=function(){function e(){_classCallCheck(this,e),e.prototype.__init.call(this)}return _createClass(e,[{key:"__init",value:function(){this.eventListeners=Object.create(null)}},{key:"addListener",value:function(e,t){return this.on(e,t)}},{key:"removeListener",value:function(e,t){return this.off(e,t)}},{key:"on",value:function(e,t){return e in this.eventListeners?this.eventListeners[e].push(t):this.eventListeners[e]=[t],this}},{key:"once",value:function(e,t){var r=this;return this.addListener(e,(function n(){r.removeListener(e,n),t.apply(void 0,arguments)}))}},{key:"off",value:function(e,t){return e in this.eventListeners&&(this.eventListeners[e]=this.eventListeners[e].filter((function(e){return e!==t}))),this}},{key:"removeAllListeners",value:function(e){return e?delete this.eventListeners[e]:this.eventListeners=Object.create(null),this}},{key:"emit",value:function(e){if(e in this.eventListeners){for(var t=this.eventListeners[e],r=arguments.length,n=new Array(r>1?r-1:0),a=1;a1&&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=nt(o,(function(){return{}})),t}return _createClass(a,[{key:"__init2",value:function(){this.stdout=new it}},{key:"__init3",value:function(){this.stderr=new it}},{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",at((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 ut(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}(it);function ct(){return ct=_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)}))),ct.apply(this,arguments)}var pt=Object.freeze({__proto__:null,Command:st,Child:ut,EventEmitter:it,open:function(e,t){return ct.apply(this,arguments)}});function lt(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),yt([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 kt().some((function(t){return t.label===e}))?new r(e,{skip:!0}):null}}]),r}(Pt);function Lt(){return(Lt=_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 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:"Window",message:{cmd:"manage",data:{cmd:{type:"primaryMonitor"}}}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Et(){return(Et=_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)}"__TAURI_METADATA__"in window?xt=new At(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0}):(console.warn('Could not find "window.__TAURI_METADATA__". The "appWindow" value will reference the "main" window label.\nNote that this is not an issue if running this frontend on a browser instead of a Tauri window.'),xt=new At("main",{skip:!0}));var Dt=Object.freeze({__proto__:null,WebviewWindow:At,WebviewWindowHandle:Gt,WindowManager:Pt,CloseRequestedEvent:Ot,getCurrent:function(){return new At(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0})},getAll:kt,get appWindow(){return xt},LogicalSize:vt,PhysicalSize:wt,LogicalPosition:bt,PhysicalPosition:Rt,get UserAttentionType(){return gt},currentMonitor:function(){return Lt.apply(this,arguments)},primaryMonitor:function(){return Mt.apply(this,arguments)},availableMonitors:function(){return Et.apply(this,arguments)}}),Ct=ke()?"\r\n":"\n";function St(){return(St=_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 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:"version"}}));case 1:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Wt(){return(Wt=_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 Nt(){return(Nt=_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 It(){return(It=_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 zt=Object.freeze({__proto__:null,EOL:Ct,platform:function(){return St.apply(this,arguments)},version:function(){return jt.apply(this,arguments)},type:function(){return Wt.apply(this,arguments)},arch:function(){return Nt.apply(this,arguments)},tempdir:function(){return It.apply(this,arguments)}}),Ft=o;e.app=h,e.cli=m,e.clipboard=g,e.dialog=P,e.event=H,e.fs=re,e.globalShortcut=ce,e.http=ve,e.invoke=Ft,e.notification=Re,e.os=zt,e.path=Xe,e.process=rt,e.shell=pt,e.tauri=u,e.updater=_t,e.window=Dt,Object.defineProperty(e,"__esModule",{value:!0})})); diff --git a/core/tauri/scripts/core.js b/core/tauri/scripts/core.js index 0ac489544be9..9af28ee1b610 100644 --- a/core/tauri/scripts/core.js +++ b/core/tauri/scripts/core.js @@ -85,196 +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 && - (['http://', 'https://', 'mailto:', 'tel:'].some(v => target.href.startsWith(v))) && - 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) { - // prevents text cursor - e.preventDefault() - // fix #2549: double click on drag region edge causes content to maximize without window sizing change - // https://github.com/tauri-apps/tauri/issues/2549#issuecomment-1250036908 - e.stopImmediatePropagation() - - // 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' - } - } - } - }) - } - }) - - 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.toString() - } - }) - } - - window.confirm = function (message) { - return window.__TAURI_INVOKE__('tauri', { - __tauriModule: 'Dialog', - message: { - cmd: 'confirmDialog', - message: message.toString() - } - }) - } - - // 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/init.js b/core/tauri/scripts/init.js index 75761bb587f7..0a242919bb88 100644 --- a/core/tauri/scripts/init.js +++ b/core/tauri/scripts/init.js @@ -5,10 +5,6 @@ ; (function () { __RAW_freeze_prototype__ - ; (function () { - __RAW_hotkeys__ - })() - __RAW_pattern_script__ __RAW_ipc_script__ diff --git a/core/tauri/scripts/stringify-ipc-message-fn.js b/core/tauri/scripts/stringify-ipc-message-fn.js index f601b5514161..5330a1358815 100644 --- a/core/tauri/scripts/stringify-ipc-message-fn.js +++ b/core/tauri/scripts/stringify-ipc-message-fn.js @@ -8,6 +8,8 @@ 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 4cef96abb4e4..000000000000 --- a/core/tauri/src/api/cli.rs +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2019-2023 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::{Arg, ArgMatches, ErrorKind}; -use serde::Serialize; -use serde_json::Value; -use std::collections::HashMap; - -#[macro_use] -mod macros; - -mod clapfix { - //! Compatibility between `clap` 3.0 and 3.1+ without deprecation errors. - #![allow(deprecated)] - - pub type ClapCommand<'help> = clap::App<'help>; - - pub trait ErrorExt { - fn kind(&self) -> clap::ErrorKind; - } - - impl ErrorExt for clap::Error { - fn kind(&self) -> clap::ErrorKind { - self.kind - } - } -} - -use clapfix::{ClapCommand as App, ErrorExt}; - -/// 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. -/// -/// This is a low level API. If the application has been built, -/// prefer [`App::get_cli_matches`](`crate::App#method.get_cli_matches`). -/// -/// # Examples -/// -/// ```rust,no_run -/// use tauri::api::cli::get_matches; -/// tauri::Builder::default() -/// .setup(|app| { -/// let matches = get_matches(app.config().tauri.cli.as_ref().unwrap(), app.package_info())?; -/// Ok(()) -/// }); -/// ``` -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 version = &*package_info.version.to_string(); - let app = get_app(package_info, version, &package_info.name, Some(&about), cli); - match app.try_get_matches() { - Ok(matches) => Ok(get_matches_internal(cli, &matches)), - Err(e) => match ErrorExt::kind(&e) { - 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() { - if let Some(subcommand_config) = config - .subcommands - .as_ref() - .and_then(|s| s.get(subcommand_name)) - { - cli_matches.set_subcommand( - subcommand_name.to_string(), - get_matches_internal(subcommand_config, subcommand_matches), - ); - } - } - - cli_matches -} - -fn map_matches(config: &CliConfig, matches: &ArgMatches, cli_matches: &mut Matches) { - if let Some(args) = config.args() { - for arg in args { - #[allow(deprecated)] - let occurrences = matches.occurrences_of(arg.name.clone()); - let value = if occurrences == 0 || !arg.takes_value { - Value::Bool(occurrences > 0) - } else if arg.multiple { - #[allow(deprecated)] - 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 { - #[allow(deprecated)] - 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, - version: &'a str, - 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(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, - version, - 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 = clap_arg.takes_value(arg.takes_value); - clap_arg = clap_arg.multiple_values(arg.multiple); - #[allow(deprecated)] - { - clap_arg = clap_arg.multiple_occurrences(arg.multiple_occurrences); - } - clap_arg = bind_value_arg!(arg, clap_arg, number_of_values); - #[allow(deprecated)] - { - 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 = clap_arg.required(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 87d82194b208..000000000000 --- a/core/tauri/src/api/cli/macros.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2019-2023 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 7fc4c1477a7f..000000000000 --- a/core/tauri/src/api/dialog.rs +++ /dev/null @@ -1,733 +0,0 @@ -// Copyright 2019-2023 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); - }); - }); - }}; -} - -#[cfg(not(target_os = "linux"))] -macro_rules! run_file_dialog { - ($e:expr, $h: ident) => {{ - std::thread::spawn(move || { - let response = crate::async_runtime::block_on($e); - $h(response); - }); - }}; -} - -#[cfg(target_os = "linux")] -macro_rules! run_file_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_file_dialog!($e, cb); - rx.recv().unwrap() - }}; -} - -macro_rules! file_dialog_builder { - () => { - #[cfg(target_os = "linux")] - type FileDialog = rfd::FileDialog; - #[cfg(not(target_os = "linux"))] - type FileDialog = rfd::AsyncFileDialog; - - /// The file dialog builder. - /// - /// Constructs file picker dialogs that can select single/multiple files or directories. - #[derive(Debug, Default)] - pub struct FileDialogBuilder(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 - } - } - }; -} - -macro_rules! message_dialog_builder { - () => { - /// A builder for message dialogs. - pub struct MessageDialogBuilder(rfd::MessageDialog); - - impl MessageDialogBuilder { - /// Creates a new message dialog builder. - pub fn new(title: impl AsRef, message: impl AsRef) -> Self { - let title = title.as_ref().to_string(); - let message = message.as_ref().to_string(); - Self( - rfd::MessageDialog::new() - .set_title(&title) - .set_description(&message), - ) - } - - /// Set parent windows explicitly (optional) - /// - /// ## Platform-specific - /// - /// - **Linux:** Unsupported. - pub fn parent(mut self, parent: &W) -> Self { - self.0 = self.0.set_parent(parent); - self - } - - /// Set the set of button that will be displayed on the dialog. - pub fn buttons(mut self, buttons: MessageDialogButtons) -> Self { - self.0 = self.0.set_buttons(buttons.into()); - self - } - - /// Set type of a dialog. - /// - /// Depending on the system it can result in type specific icon to show up, - /// the will inform user it message is a error, warning or just information. - pub fn kind(mut self, kind: MessageDialogKind) -> Self { - self.0 = self.0.set_level(kind.into()); - self - } - } - }; -} - -/// Options for action buttons on message dialogs. -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum MessageDialogButtons { - /// Ok button. - Ok, - /// Ok and Cancel buttons. - OkCancel, - /// Yes and No buttons. - YesNo, - /// OK button with customized text. - OkWithLabel(String), - /// Ok and Cancel buttons with customized text. - OkCancelWithLabels(String, String), -} - -impl From for rfd::MessageButtons { - fn from(kind: MessageDialogButtons) -> Self { - match kind { - MessageDialogButtons::Ok => Self::Ok, - MessageDialogButtons::OkCancel => Self::OkCancel, - MessageDialogButtons::YesNo => Self::YesNo, - MessageDialogButtons::OkWithLabel(ok_text) => Self::OkCustom(ok_text), - MessageDialogButtons::OkCancelWithLabels(ok_text, cancel_text) => { - Self::OkCancelCustom(ok_text, cancel_text) - } - } - } -} - -/// Types of message, ask and confirm dialogs. -#[non_exhaustive] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum MessageDialogKind { - /// Information dialog. - Info, - /// Warning dialog. - Warning, - /// Error dialog. - Error, -} - -impl From for rfd::MessageLevel { - fn from(kind: MessageDialogKind) -> Self { - match kind { - MessageDialogKind::Info => Self::Info, - MessageDialogKind::Warning => Self::Warning, - MessageDialogKind::Error => Self::Error, - } - } -} - -/// 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 [`crate::api::dialog`] methods instead. -/// Examples of main thread context are the [`crate::App::run`] closure and non-async commands. -pub mod blocking { - use super::{MessageDialogButtons, MessageDialogKind}; - use crate::{Runtime, Window}; - use std::path::{Path, PathBuf}; - use std::sync::mpsc::sync_channel; - - file_dialog_builder!(); - message_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. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// async 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 { - #[allow(clippy::let_and_return)] - let response = run_dialog_sync!(self.0.pick_file()); - #[cfg(not(target_os = "linux"))] - let response = response.map(|p| p.path().to_path_buf()); - response - } - - /// Shows the dialog to select multiple files. - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// async 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> { - #[allow(clippy::let_and_return)] - let response = run_dialog_sync!(self.0.pick_files()); - #[cfg(not(target_os = "linux"))] - let response = - response.map(|paths| paths.into_iter().map(|p| p.path().to_path_buf()).collect()); - response - } - - /// 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. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// async 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 { - #[allow(clippy::let_and_return)] - let response = run_dialog_sync!(self.0.pick_folder()); - #[cfg(not(target_os = "linux"))] - let response = response.map(|p| p.path().to_path_buf()); - response - } - - /// Shows the dialog to select multiple folders. - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// async fn my_command() { - /// let folder_paths = FileDialogBuilder::new().pick_folders(); - /// // do something with the optional folder paths here - /// // the folder paths value is `None` if the user closed the dialog - /// } - /// ``` - pub fn pick_folders(self) -> Option> { - #[allow(clippy::let_and_return)] - let response = run_dialog_sync!(self.0.pick_folders()); - #[cfg(not(target_os = "linux"))] - let response = - response.map(|paths| paths.into_iter().map(|p| p.path().to_path_buf()).collect()); - response - } - - /// Shows the dialog to save a file. - /// This is a blocking operation, - /// and should *NOT* be used when running on the main thread context. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; - /// #[tauri::command] - /// async 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 { - #[allow(clippy::let_and_return)] - let response = run_dialog_sync!(self.0.save_file()); - #[cfg(not(target_os = "linux"))] - let response = response.map(|p| p.path().to_path_buf()); - response - } - } - - impl MessageDialogBuilder { - //// Shows a message dialog. - /// - /// - In `Ok` dialog, it will return `true` when `OK` was pressed. - /// - In `OkCancel` dialog, it will return `true` when `OK` was pressed. - /// - In `YesNo` dialog, it will return `true` when `Yes` was pressed. - pub fn show(self) -> bool { - let (tx, rx) = sync_channel(1); - let f = move |response| { - tx.send(response).unwrap(); - }; - run_dialog!(self.0.show(), f); - rx.recv().unwrap() - } - } - - /// 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. - /// - /// # Examples - /// - /// ```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. - /// - /// # Examples - /// - /// ```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. - /// - /// # Examples - /// - /// ```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, - MessageDialogKind::Info, - move |response| { - tx.send(response).unwrap(); - }, - ); - rx.recv().unwrap() - } -} - -mod nonblocking { - use super::{MessageDialogButtons, MessageDialogKind}; - use crate::{Runtime, Window}; - use std::path::{Path, PathBuf}; - - file_dialog_builder!(); - message_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`]. - /// - /// # Examples - /// - /// ```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) { - #[cfg(not(target_os = "linux"))] - let f = |path: Option| f(path.map(|p| p.path().to_path_buf())); - run_file_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. - /// - /// # Examples - /// - /// ```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) { - #[cfg(not(target_os = "linux"))] - let f = |paths: Option>| { - f(paths.map(|list| list.into_iter().map(|p| p.path().to_path_buf()).collect())) - }; - run_file_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. - /// - /// # Examples - /// - /// ```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) { - #[cfg(not(target_os = "linux"))] - let f = |path: Option| f(path.map(|p| p.path().to_path_buf())); - run_file_dialog!(self.0.pick_folder(), f) - } - - /// Shows the dialog to select multiple folders. - /// This is not a blocking operation, - /// and should be used when running on the main thread to avoid deadlocks with the event loop. - /// - /// # Examples - /// - /// ```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_folders(|file_paths| { - /// // do something with the optional folder paths here - /// // the folder paths value is `None` if the user closed the dialog - /// }) - /// }) - /// ``` - pub fn pick_folders>) + Send + 'static>(self, f: F) { - #[cfg(not(target_os = "linux"))] - let f = |paths: Option>| { - f(paths.map(|list| list.into_iter().map(|p| p.path().to_path_buf()).collect())) - }; - run_file_dialog!(self.0.pick_folders(), 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. - /// - /// # Examples - /// - /// ```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) { - #[cfg(not(target_os = "linux"))] - let f = |path: Option| f(path.map(|p| p.path().to_path_buf())); - run_file_dialog!(self.0.save_file(), f) - } - } - - impl MessageDialogBuilder { - /// Shows a message dialog: - /// - /// - In `Ok` dialog, it will call the closure with `true` when `OK` was pressed - /// - In `OkCancel` dialog, it will call the closure with `true` when `OK` was pressed - /// - In `YesNo` dialog, it will call the closure with `true` when `Yes` was pressed - pub fn show(self, f: F) { - run_dialog!(self.0.show(), 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. - /// - /// # Examples - /// - /// ```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, - MessageDialogKind::Info, - 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. - /// - /// # Examples - /// - /// ```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, - MessageDialogKind::Info, - 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. - /// - /// # Examples - /// - /// ```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, - MessageDialogKind::Info, - |_| {}, - ) - } - - #[allow(unused_variables)] - pub(crate) fn run_message_dialog( - parent_window: Option<&Window>, - title: impl AsRef, - message: impl AsRef, - buttons: rfd::MessageButtons, - level: MessageDialogKind, - 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(level.into()); - - #[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/error.rs b/core/tauri/src/api/error.rs index 5b584b02aebd..0fce6a32c26e 100644 --- a/core/tauri/src/api/error.rs +++ b/core/tauri/src/api/error.rs @@ -6,40 +6,6 @@ #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { - /// Command error. - #[error("Command Error: {0}")] - Command(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(feature = "http-api")] - #[error("Network Error: {0}")] - Network(#[from] reqwest::Error), - /// HTTP method error. - #[error(transparent)] - HttpMethod(#[from] http::method::InvalidMethod), - /// Invalid HTTP header value. - #[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), @@ -49,46 +15,4 @@ pub enum Error { /// IO error. #[error(transparent)] Io(#[from] std::io::Error), - /// Ignore error. - #[error("failed to walkdir: {0}")] - Ignore(#[from] ignore::Error), - /// ZIP error. - #[cfg(feature = "fs-extract-api")] - #[error(transparent)] - Zip(#[from] zip::result::ZipError), - /// Extract error. - #[cfg(feature = "fs-extract-api")] - #[error("Failed to extract: {0}")] - Extract(String), - /// 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), - /// HTTP error. - #[error(transparent)] - Http(#[from] http::Error), -} - -#[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 9e19456fb7d0..46ace7fa3ac8 100644 --- a/core/tauri/src/api/file.rs +++ b/core/tauri/src/api/file.rs @@ -4,62 +4,7 @@ //! Types and functions related to file operations. -#[cfg(feature = "fs-extract-api")] -mod extract; -mod file_move; - -use std::{ - fs, - path::{Display, Path}, -}; - -#[cfg(feature = "fs-extract-api")] -pub use extract::*; -pub use file_move::*; - -use serde::{de::Error as DeError, Deserialize, Deserializer}; - -#[derive(Clone, Debug)] -pub(crate) struct SafePathBuf(std::path::PathBuf); - -impl SafePathBuf { - pub fn new(path: std::path::PathBuf) -> Result { - if path - .components() - .any(|x| matches!(x, std::path::Component::ParentDir)) - { - Err("cannot traverse directory, rewrite the path without the use of `../`") - } else { - Ok(Self(path)) - } - } - - #[allow(dead_code)] - pub unsafe fn new_unchecked(path: std::path::PathBuf) -> Self { - Self(path) - } - - #[allow(dead_code)] - 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) -> Result - where - D: Deserializer<'de>, - { - let path = std::path::PathBuf::deserialize(deserializer)?; - SafePathBuf::new(path).map_err(DeError::custom) - } -} +use std::{fs, path::Path}; /// Reads the entire contents of a file into a string. pub fn read_string>(file: P) -> crate::api::Result { @@ -76,19 +21,6 @@ mod test { use super::*; #[cfg(not(windows))] use crate::api::Error; - 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)) - } - } #[test] fn check_read_string() { diff --git a/core/tauri/src/api/file/extract.rs b/core/tauri/src/api/file/extract.rs deleted file mode 100644 index 572a994f35dd..000000000000 --- a/core/tauri/src/api/file/extract.rs +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::{ - borrow::Cow, - fs, - io::{self, Cursor, Read, Seek}, - path::{self, Path, PathBuf}, -}; - -/// The archive reader. -#[derive(Debug)] -pub enum ArchiveReader { - /// A plain reader. - Plain(R), - /// A GZ- compressed reader (decoder). - GzCompressed(Box>), -} - -impl Read for ArchiveReader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - match self { - Self::Plain(r) => r.read(buf), - Self::GzCompressed(decoder) => decoder.read(buf), - } - } -} - -impl ArchiveReader { - #[allow(dead_code)] - fn get_mut(&mut self) -> &mut R { - match self { - Self::Plain(r) => r, - Self::GzCompressed(decoder) => decoder.get_mut(), - } - } -} - -/// The supported archive formats. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[non_exhaustive] -pub enum ArchiveFormat { - /// Tar archive. - Tar(Option), - /// Zip archive. - Zip, -} - -/// The supported compression types. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[non_exhaustive] -pub enum Compression { - /// Gz compression (e.g. `.tar.gz` archives) - Gz, -} - -/// The zip entry. -pub struct ZipEntry { - path: PathBuf, - is_dir: bool, - file_contents: Vec, -} - -/// A read-only view into an entry of an archive. -#[non_exhaustive] -pub enum Entry<'a, R: Read> { - /// An entry of a tar archive. - #[non_exhaustive] - Tar(Box>), - /// An entry of a zip archive. - #[non_exhaustive] - Zip(ZipEntry), -} - -impl<'a, R: Read> Entry<'a, R> { - /// The entry path. - pub fn path(&self) -> crate::api::Result> { - match self { - Self::Tar(e) => e.path().map_err(Into::into), - Self::Zip(e) => Ok(Cow::Borrowed(&e.path)), - } - } - - /// Extract this entry into `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(self, into_path: &path::Path) -> crate::api::Result<()> { - match self { - Self::Tar(mut entry) => { - // 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)?; - } - } - } - Self::Zip(entry) => { - if entry.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 Cursor::new(entry.file_contents), &mut out_file)?; - } - } - } - - Ok(()) - } -} - -/// The extract manager to retrieve files from archives. -pub struct Extract<'a, R: Read + Seek> { - reader: ArchiveReader, - archive_format: ArchiveFormat, - tar_archive: Option>>, -} - -impl<'a, R: std::fmt::Debug + Read + Seek> std::fmt::Debug for Extract<'a, R> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Extract") - .field("reader", &self.reader) - .field("archive_format", &self.archive_format) - .finish() - } -} - -impl<'a, R: Read + Seek> Extract<'a, R> { - /// Create archive from reader. - pub fn from_cursor(mut reader: R, archive_format: ArchiveFormat) -> Extract<'a, R> { - if reader.rewind().is_err() { - #[cfg(debug_assertions)] - eprintln!("Could not seek to start of the file"); - } - let compression = if let ArchiveFormat::Tar(compression) = archive_format { - compression - } else { - None - }; - Extract { - reader: match compression { - Some(Compression::Gz) => { - ArchiveReader::GzCompressed(Box::new(flate2::read::GzDecoder::new(reader))) - } - _ => ArchiveReader::Plain(reader), - }, - archive_format, - tar_archive: None, - } - } - - /// Reads the archive content. - pub fn with_files< - E: Into, - F: FnMut(Entry<'_, &mut ArchiveReader>) -> std::result::Result, - >( - &'a mut self, - mut f: F, - ) -> crate::api::Result<()> { - match self.archive_format { - ArchiveFormat::Tar(_) => { - let archive = tar::Archive::new(&mut self.reader); - self.tar_archive.replace(archive); - for entry in self.tar_archive.as_mut().unwrap().entries()? { - let entry = entry?; - if entry.path().is_ok() { - let stop = f(Entry::Tar(Box::new(entry))).map_err(Into::into)?; - if stop { - break; - } - } - } - } - - ArchiveFormat::Zip => { - #[cfg(feature = "fs-extract-api")] - { - let mut archive = zip::ZipArchive::new(self.reader.get_mut())?; - let file_names = archive - .file_names() - .map(|f| f.to_string()) - .collect::>(); - for path in file_names { - let mut zip_file = archive.by_name(&path)?; - let is_dir = zip_file.is_dir(); - let mut file_contents = Vec::new(); - zip_file.read_to_end(&mut file_contents)?; - let stop = f(Entry::Zip(ZipEntry { - path: path.into(), - is_dir, - file_contents, - })) - .map_err(Into::into)?; - if stop { - break; - } - } - } - } - } - - Ok(()) - } - - /// 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<()> { - match self.archive_format { - ArchiveFormat::Tar(_) => { - let mut archive = tar::Archive::new(&mut self.reader); - archive.unpack(into_dir)?; - } - - ArchiveFormat::Zip => { - #[cfg(feature = "fs-extract-api")] - { - let mut archive = zip::ZipArchive::new(self.reader.get_mut())?; - 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(()) - } -} - -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 {mode:o} \ - for `{}`", - 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 c8b7e6891770..000000000000 --- a/core/tauri/src/api/file/file_move.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2019-2023 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 3f7b9d45f273..000000000000 --- a/core/tauri/src/api/http.rs +++ /dev/null @@ -1,542 +0,0 @@ -// Copyright 2019-2023 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::Method; -pub use http::StatusCode; -use serde::{Deserialize, Deserializer, Serialize}; -use serde_json::Value; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use url::Url; - -use std::{collections::HashMap, path::PathBuf, time::Duration}; - -pub use reqwest::header; - -use header::{HeaderName, HeaderValue}; - -#[derive(Deserialize)] -#[serde(untagged)] -enum SerdeDuration { - Seconds(u64), - Duration(Duration), -} - -fn deserialize_duration<'de, D: Deserializer<'de>>( - deserializer: D, -) -> Result, D::Error> { - if let Some(duration) = Option::::deserialize(deserializer)? { - Ok(Some(match duration { - SerdeDuration::Seconds(s) => Duration::from_secs(s), - SerdeDuration::Duration(d) => d, - })) - } else { - Ok(None) - } -} - -/// 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 for the request. - #[serde(deserialize_with = "deserialize_duration", default)] - 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: Duration) -> Self { - self.connect_timeout.replace(connect_timeout); - self - } - - /// Builds the 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(if max_redirections == 0 { - reqwest::redirect::Policy::none() - } else { - reqwest::redirect::Policy::limited(max_redirections) - }); - } - - if let Some(connect_timeout) = self.connect_timeout { - client_builder = client_builder.connect_timeout(connect_timeout); - } - - let client = client_builder.build()?; - Ok(Client(client)) - } -} - -/// The HTTP client based on [`reqwest`]. -#[derive(Debug, Clone)] -pub struct Client(reqwest::Client); - -impl Client { - /// Executes an HTTP request - /// - /// # Examples - pub async fn send(&self, mut 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(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) => { - #[allow(unused_variables)] - fn send_form( - request_builder: reqwest::RequestBuilder, - headers: &mut Option, - form_body: FormBody, - ) -> crate::api::Result { - #[cfg(feature = "http-multipart")] - if matches!( - headers - .as_ref() - .and_then(|h| h.0.get("content-type")) - .map(|v| v.as_bytes()), - Some(b"multipart/form-data") - ) { - // the Content-Type header will be set by reqwest in the `.multipart` call - headers.as_mut().map(|h| h.0.remove("content-type")); - let mut multipart = reqwest::multipart::Form::new(); - - for (name, part) in form_body.0 { - let part = match part { - FormPart::File { - file, - mime, - file_name, - } => { - let bytes: Vec = file.try_into()?; - let mut part = reqwest::multipart::Part::bytes(bytes); - if let Some(mime) = mime { - part = part.mime_str(&mime)?; - } - if let Some(file_name) = file_name { - part = part.file_name(file_name); - } - part - } - FormPart::Text(value) => reqwest::multipart::Part::text(value), - }; - - multipart = multipart.part(name, part); - } - - return Ok(request_builder.multipart(multipart)); - } - - let mut form = Vec::new(); - for (name, part) in form_body.0 { - match part { - FormPart::File { file, .. } => { - let bytes: Vec = file.try_into()?; - form.push((name, serde_json::to_string(&bytes)?)) - } - FormPart::Text(value) => form.push((name, value)), - } - } - Ok(request_builder.form(&form)) - } - send_form(request_builder, &mut request.headers, form_body)? - } - }; - } - - if let Some(headers) = request.headers { - request_builder = request_builder.headers(headers.0); - } - - let http_request = request_builder.build()?; - - 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, -} - -/// A file path or contents. -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum FilePart { - /// File path. - Path(PathBuf), - /// File contents. - Contents(Vec), -} - -impl TryFrom for Vec { - type Error = crate::api::Error; - fn try_from(file: FilePart) -> crate::api::Result { - let bytes = match file { - FilePart::Path(path) => std::fs::read(path)?, - FilePart::Contents(bytes) => bytes, - }; - Ok(bytes) - } -} - -/// [`FormBody`] data types. -#[derive(Debug, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum FormPart { - /// A string value. - Text(String), - /// A file value. - #[serde(rename_all = "camelCase")] - File { - /// File path or content. - file: FilePart, - /// Mime type of this part. - /// Only used when the `Content-Type` header is set to `multipart/form-data`. - mime: Option, - /// File name. - /// Only used when the `Content-Type` header is set to `multipart/form-data`. - file_name: Option, - }, -} - -/// Form body definition. -#[derive(Debug, Deserialize)] -pub struct FormBody(pub(crate) 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 form body. - Form(FormBody), - /// A JSON body. - Json(Value), - /// A text string body. - Text(String), - /// A byte array body. - Bytes(Vec), -} - -/// A set of HTTP headers. -#[derive(Debug, Default)] -pub struct HeaderMap(header::HeaderMap); - -impl<'de> Deserialize<'de> for HeaderMap { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let map = HashMap::::deserialize(deserializer)?; - let mut headers = header::HeaderMap::default(); - for (key, value) in map { - if let (Ok(key), Ok(value)) = ( - header::HeaderName::from_bytes(key.as_bytes()), - header::HeaderValue::from_str(&value), - ) { - headers.insert(key, value); - } else { - return Err(serde::de::Error::custom(format!( - "invalid header `{key}` `{value}`" - ))); - } - } - Ok(Self(headers)) - } -} - -/// The builder for a HTTP request. -/// -/// # Examples -/// ```rust,no_run -/// use tauri::api::http::{HttpRequestBuilder, ResponseType, ClientBuilder}; -/// async fn run() { -/// let client = ClientBuilder::new() -/// .max_redirections(3) -/// .build() -/// .unwrap(); -/// let request = HttpRequestBuilder::new("GET", "http://example.com").unwrap() -/// .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 - #[serde(deserialize_with = "deserialize_duration", default)] - 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 - } - - /// Adds a header. - pub fn header(mut self, key: K, value: V) -> crate::api::Result - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - let key: Result = key.try_into().map_err(Into::into); - let value: Result = value.try_into().map_err(Into::into); - self - .headers - .get_or_insert_with(Default::default) - .0 - .insert(key?, value?); - Ok(self) - } - - /// Sets the request headers. - #[must_use] - pub fn headers(mut self, headers: header::HeaderMap) -> Self { - self.headers.replace(HeaderMap(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: Duration) -> Self { - self.timeout.replace(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. -#[derive(Debug)] -pub struct Response(ResponseType, reqwest::Response); - -impl Response { - /// Get the [`StatusCode`] of this Response. - pub fn status(&self) -> StatusCode { - self.1.status() - } - - /// Get the headers of this Response. - pub fn headers(&self) -> &header::HeaderMap { - self.1.headers() - } - - /// Reads the response as raw bytes. - pub async fn bytes(self) -> crate::api::Result { - let status = self.status().as_u16(); - let data = self.1.bytes().await?.to_vec(); - Ok(RawResponse { status, data }) - } - - // Convert the response into a Stream of [`bytes::Bytes`] from the body. - // - // # Examples - // - // ```no_run - // use futures_util::StreamExt; - // - // # async fn run() -> Result<(), Box> { - // let client = tauri::api::http::ClientBuilder::new().build()?; - // let mut stream = client.send(tauri::api::http::HttpRequestBuilder::new("GET", "http://httpbin.org/ip")?) - // .await? - // .bytes_stream(); - // - // while let Some(item) = stream.next().await { - // println!("Chunk: {:?}", item?); - // } - // # Ok(()) - // # } - // ``` - #[allow(dead_code)] - pub(crate) fn bytes_stream( - self, - ) -> impl futures_util::Stream> { - use futures_util::StreamExt; - self.1.bytes_stream().map(|res| res.map_err(Into::into)) - } - - /// Reads the response. - /// - /// Note that the body is serialized to a [`Value`]. - pub async fn read(self) -> crate::api::Result { - let url = self.1.url().clone(); - - 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(); - - 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?)?, - }; - - 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 95a511601d25..e4fccc99e45c 100644 --- a/core/tauri/src/api/ipc.rs +++ b/core/tauri/src/api/ipc.rs @@ -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). diff --git a/core/tauri/src/api/mod.rs b/core/tauri/src/api/mod.rs index cd296db7dad5..f3d24f4c39b4 100644 --- a/core/tauri/src/api/mod.rs +++ b/core/tauri/src/api/mod.rs @@ -4,37 +4,11 @@ //! The Tauri API interface. -#[cfg(all(desktop, feature = "dialog"))] -#[cfg_attr(doc_cfg, doc(cfg(all(desktop, 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; -#[cfg(feature = "os-api")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "os-api")))] -pub mod os; -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(all(desktop, feature = "notification"))] -#[cfg_attr(doc_cfg, doc(cfg(all(desktop, 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 aaf9249339b4..000000000000 --- a/core/tauri/src/api/notification.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2019-2023 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 as SEP; - -/// The desktop notification definition. -/// -/// Allows you to construct a Notification data and send it. -/// -/// # Examples -/// ```rust,no_run -/// use tauri::api::notification::Notification; -/// // first we build the application to access the Tauri configuration -/// let 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"); -/// -/// // shows a notification with the given title and body -/// Notification::new(&app.config().tauri.bundle.identifier) -/// .title("New message") -/// .body("You've got a new message.") -/// .show(); -/// -/// // run the app -/// app.run(|_app_handle, _event| {}); -/// ``` -#[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. - /// - /// # Examples - /// - /// ```no_run - /// use tauri::api::notification::Notification; - /// - /// // on an actual app, remove the string argument - /// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"); - /// Notification::new(&context.config().tauri.bundle.identifier) - /// .title("Tauri") - /// .body("Tauri is awesome!") - /// .show() - /// .unwrap(); - /// ``` - /// - /// ## Platform-specific - /// - /// - **Windows**: Not supported on Windows 7. If your app targets it, enable the `windows7-compat` feature and use [`Self::notify`]. - #[cfg_attr( - all(not(doc_cfg), feature = "windows7-compat"), - deprecated = "This function does not work on Windows 7. Use `Self::notify` instead." - )] - 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); - } else { - notification.auto_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!("{SEP}target{SEP}debug").as_str()) - || curr_dir.ends_with(format!("{SEP}target{SEP}release").as_str())) - { - notification.app_id(&self.identifier); - } - - // will be parsed as a `::winrt_notification::Sound` - notification.sound_name("Default"); - } - #[cfg(target_os = "macos")] - { - let _ = notify_rust::set_application(if cfg!(feature = "custom-protocol") { - &self.identifier - } else { - "com.apple.Terminal" - }); - } - - crate::async_runtime::spawn(async move { - let _ = notification.show(); - }); - - Ok(()) - } - - /// Shows the notification. This API is similar to [`Self::show`], but it also works on Windows 7. - /// - /// # Examples - /// - /// ```no_run - /// use tauri::api::notification::Notification; - /// - /// // on an actual app, remove the string argument - /// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"); - /// let identifier = context.config().tauri.bundle.identifier.clone(); - /// - /// tauri::Builder::default() - /// .setup(move |app| { - /// Notification::new(&identifier) - /// .title("Tauri") - /// .body("Tauri is awesome!") - /// .notify(&app.handle()) - /// .unwrap(); - /// Ok(()) - /// }) - /// .run(context) - /// .expect("error while running tauri application"); - /// ``` - #[cfg(feature = "windows7-compat")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "windows7-compat")))] - #[allow(unused_variables)] - pub fn notify(self, app: &crate::AppHandle) -> crate::api::Result<()> { - #[cfg(windows)] - { - if crate::utils::platform::is_windows_7() { - self.notify_win7(app) - } else { - #[allow(deprecated)] - self.show() - } - } - #[cfg(not(windows))] - { - #[allow(deprecated)] - self.show() - } - } - - #[cfg(all(windows, feature = "windows7-compat"))] - fn notify_win7(self, app: &crate::AppHandle) -> crate::api::Result<()> { - let app = app.clone(); - let default_window_icon = app.manager.inner.default_window_icon.clone(); - let _ = app.run_on_main_thread(move || { - let mut notification = win7_notifications::Notification::new(); - if let Some(body) = self.body { - notification.body(&body); - } - if let Some(title) = self.title { - notification.summary(&title); - } - if let Some(crate::Icon::Rgba { - rgba, - width, - height, - }) = default_window_icon - { - notification.icon(rgba, width, height); - } - let _ = notification.show(); - }); - - Ok(()) - } -} diff --git a/core/tauri/src/api/path.rs b/core/tauri/src/api/path.rs deleted file mode 100644 index 7edd76c3e049..000000000000 --- a/core/tauri/src/api/path.rs +++ /dev/null @@ -1,604 +0,0 @@ -// 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 system path operations. - -use std::{ - env::temp_dir, - path::{Component, Path, PathBuf}, -}; - -use crate::{Config, Env, PackageInfo}; - -use serde_repr::{Deserialize_repr, Serialize_repr}; - -// we have to wrap the BaseDirectory enum in a module for #[allow(deprecated)] -// to work, because the procedural macros on the enum prevent it from working directly -// TODO: remove this workaround in v2 along with deprecated variants -#[allow(deprecated)] -mod base_directory { - use super::*; - - /// 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, 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 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`]`/{bundle_identifier}`. - #[deprecated( - since = "1.2.0", - note = "Will be removed in 2.0.0. Use `BaseDirectory::AppConfig` or BaseDirectory::AppData` instead." - )] - App, - /// 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. - #[deprecated( - since = "1.2.0", - note = "Will be removed in 2.0.0. Use `BaseDirectory::AppLog` instead." - )] - Log, - /// A temporary directory. - /// Resolves to [`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, - } -} -pub use base_directory::BaseDirectory; - -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", - #[allow(deprecated)] - Self::App => "$APP", - #[allow(deprecated)] - Self::Log => "$LOG", - Self::Temp => "$TEMP", - Self::AppConfig => "$APPCONFIG", - Self::AppData => "$APPDATA", - Self::AppLocalData => "$APPLOCALDATA", - Self::AppCache => "$APPCACHE", - Self::AppLog => "$APPLOG", - } - } - - /// 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, - #[allow(deprecated)] - "$APP" => Self::App, - #[allow(deprecated)] - "$LOG" => Self::Log, - "$TEMP" => Self::Temp, - "$APPCONFIG" => Self::AppConfig, - "$APPDATA" => Self::AppData, - "$APPLOCALDATA" => Self::AppLocalData, - "$APPCACHE" => Self::AppCache, - "$APPLOG" => Self::AppLog, - _ => return None, - }; - Some(res) - } -} - -/// 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 = tauri::api::path::parse(&app.config(), app.package_info(), &app.env(), "$HOME/.bashrc")?; -/// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.bashrc"); -/// Ok(()) -/// }); -/// ``` -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(); - match components.next() { - Some(Component::Normal(str)) => { - 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); - } - } - Some(component) => p.push(component), - None => (), - } - - for component in components { - if let Component::ParentDir = component { - continue; - } - p.push(component); - } - - Ok(p) -} - -/// Resolves the path with the optional base directory. -/// -/// This is a low level API. If the application has been built, -/// prefer the [path resolver API](`crate::AppHandle#method.path_resolver`). -/// -/// # Examples -/// -/// ## Before initializing the application -/// -/// ```rust,no_run -/// use tauri::{api::path::{BaseDirectory, resolve_path}, Env}; -/// // on an actual app, remove the string argument -/// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"); -/// let path = resolve_path( -/// context.config(), -/// context.package_info(), -/// &Env::default(), -/// "db/tauri.sqlite", -/// Some(BaseDirectory::AppData)) -/// .expect("failed to resolve path"); -/// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/com.tauri.app/db/tauri.sqlite"); -/// -/// tauri::Builder::default().run(context).expect("error while running tauri application"); -/// ``` -/// -/// ## With an initialized app -/// ```rust,no_run -/// use tauri::{api::path::{BaseDirectory, resolve_path}, Manager}; -/// tauri::Builder::default() -/// .setup(|app| { -/// let path = resolve_path( -/// &app.config(), -/// app.package_info(), -/// &app.env(), -/// "path/to/something", -/// Some(BaseDirectory::Config) -/// )?; -/// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/path/to/something"); -/// Ok(()) -/// }); -/// ``` -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), - #[allow(deprecated)] - BaseDirectory::App => app_config_dir(config), - #[allow(deprecated)] - BaseDirectory::Log => app_log_dir(config), - BaseDirectory::Temp => Some(temp_dir()), - BaseDirectory::AppConfig => app_config_dir(config), - BaseDirectory::AppData => app_data_dir(config), - BaseDirectory::AppLocalData => app_local_data_dir(config), - BaseDirectory::AppCache => app_cache_dir(config), - BaseDirectory::AppLog => app_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. -/// -/// ## 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() -> Option { - dirs_next::audio_dir() -} - -/// 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() -> Option { - dirs_next::cache_dir() -} - -/// 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() -> Option { - dirs_next::config_dir() -} - -/// 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() -> Option { - dirs_next::data_dir() -} - -/// 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() -> Option { - dirs_next::data_local_dir() -} - -/// 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() -> Option { - dirs_next::desktop_dir() -} - -/// 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() -> Option { - dirs_next::document_dir() -} - -/// 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() -> Option { - dirs_next::download_dir() -} - -/// 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() -> Option { - dirs_next::executable_dir() -} - -/// 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() -> Option { - dirs_next::font_dir() -} - -/// 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() -> Option { - dirs_next::home_dir() -} - -/// 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() -> Option { - dirs_next::picture_dir() -} - -/// 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() -> Option { - dirs_next::public_dir() -} - -/// 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() -> Option { - dirs_next::runtime_dir() -} - -/// 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() -> Option { - dirs_next::template_dir() -} - -/// 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() -> Option { - dirs_next::video_dir() -} - -/// Returns the path to the resource directory of this app. -/// -/// See [`PathResolver::resource_dir`](crate::PathResolver#method.resource_dir) for a more convenient helper function. -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's config files. -/// -/// Resolves to [`config_dir`]`/${bundle_identifier}`. -/// -/// See [`PathResolver::app_config_dir`](crate::PathResolver#method.app_config_dir) for a more convenient helper function. -pub fn app_config_dir(config: &Config) -> Option { - dirs_next::config_dir().map(|dir| dir.join(&config.tauri.bundle.identifier)) -} - -/// Returns the path to the suggested directory for your app's data files. -/// -/// Resolves to [`data_dir`]`/${bundle_identifier}`. -/// -/// See [`PathResolver::app_data_dir`](crate::PathResolver#method.app_data_dir) for a more convenient helper function. -pub fn app_data_dir(config: &Config) -> Option { - dirs_next::data_dir().map(|dir| dir.join(&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}`. -/// -/// See [`PathResolver::app_local_data_dir`](crate::PathResolver#method.app_local_data_dir) for a more convenient helper function. -pub fn app_local_data_dir(config: &Config) -> Option { - dirs_next::data_local_dir().map(|dir| dir.join(&config.tauri.bundle.identifier)) -} - -/// Returns the path to the suggested directory for your app's cache files. -/// -/// Resolves to [`cache_dir`]`/${bundle_identifier}`. -/// -/// See [`PathResolver::app_cache_dir`](crate::PathResolver#method.app_cache_dir) for a more convenient helper function. -pub fn app_cache_dir(config: &Config) -> Option { - dirs_next::cache_dir().map(|dir| dir.join(&config.tauri.bundle.identifier)) -} - -/// Returns the path to the suggested directory for your app's log files. -/// -/// ## Platform-specific -/// -/// - **Linux:** Resolves to [`config_dir`]`/${bundle_identifier}/logs`. -/// - **macOS:** Resolves to [`home_dir`]`/Library/Logs/${bundle_identifier}` -/// - **Windows:** Resolves to [`config_dir`]`/${bundle_identifier}/logs`. -/// -/// See [`PathResolver::app_log_dir`](crate::PathResolver#method.app_log_dir) for a more convenient helper function. -pub fn app_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 -} - -/// Returns the path to the suggested directory for your app's config files. -/// -/// Resolves to [`config_dir`]`/${bundle_identifier}`. -/// -/// See [`PathResolver::app_config_dir`](crate::PathResolver#method.app_config_dir) for a more convenient helper function. -#[deprecated( - since = "1.2.0", - note = "Will be removed in 2.0.0. Use `app_config_dir` or `app_data_dir` instead." -)] -pub fn app_dir(config: &Config) -> Option { - app_config_dir(config) -} - -/// Returns the path to the suggested directory for your app's log files. -/// -/// ## Platform-specific -/// -/// - **Linux:** Resolves to [`config_dir`]`/${bundle_identifier}`. -/// - **macOS:** Resolves to [`home_dir`]`/Library/Logs/${bundle_identifier}` -/// - **Windows:** Resolves to [`config_dir`]`/${bundle_identifier}`. -/// -/// See [`PathResolver::app_log_dir`](crate::PathResolver#method.app_log_dir) for a more convenient helper function. -#[deprecated( - since = "1.2.0", - note = "Will be removed in 2.0.0. Use `app_log_dir` instead." -)] -pub fn log_dir(config: &Config) -> Option { - app_log_dir(config) -} diff --git a/core/tauri/src/api/process/command.rs b/core/tauri/src/api/process/command.rs deleted file mode 100644 index fe4911e1a35e..000000000000 --- a/core/tauri/src/api/process/command.rs +++ /dev/null @@ -1,478 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::{ - collections::HashMap, - io::{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, Sender}; -pub use encoding_rs::Encoding; -use os_pipe::{pipe, PipeReader, 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() { - let commands = commands().lock().unwrap(); - let children = commands.values(); - for child in children { - 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 bytes until a newline (\n) or carriage return (\r) is found. - Stderr(String), - /// Stdout bytes until a newline (\n) or carriage return (\r) is found. - Stdout(String), - /// An error happened waiting for the command to finish or converting the stdout/stderr bytes to an UTF-8 string. - Error(String), - /// Command process terminated. - Terminated(TerminatedPayload), -} - -/// The type to spawn commands. -#[derive(Debug)] -pub struct Command { - program: String, - args: Vec, - env_clear: bool, - env: HashMap, - current_dir: Option, - encoding: Option<&'static Encoding>, -} - -/// 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!("{}\\{command}.exe", exe_dir.display())), - #[cfg(not(windows))] - Some(exe_dir) => Ok(format!("{}/{command}", exe_dir.display())), - None => Err(crate::api::Error::Command("Could not evaluate executable dir".to_string()).into()), - } -} - -impl From for StdCommand { - fn from(cmd: Command) -> StdCommand { - let mut command = StdCommand::new(cmd.program); - command.args(cmd.args); - command.stdout(Stdio::piped()); - command.stdin(Stdio::piped()); - command.stderr(Stdio::piped()); - if cmd.env_clear { - command.env_clear(); - } - command.envs(cmd.env); - if let Some(current_dir) = cmd.current_dir { - command.current_dir(current_dir); - } - #[cfg(windows)] - command.creation_flags(CREATE_NO_WINDOW); - command - } -} - -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, - encoding: 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 - } - - /// Sets the character encoding for stdout/stderr. - #[must_use] - pub fn encoding(mut self, encoding: &'static Encoding) -> Self { - self.encoding.replace(encoding); - self - } - - /// Spawns the command. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri::api::process::{Command, CommandEvent}; - /// tauri::async_runtime::spawn(async move { - /// let (mut rx, mut child) = Command::new("cargo") - /// .args(["tauri", "dev"]) - /// .spawn() - /// .expect("Failed to spawn cargo"); - /// - /// let mut i = 0; - /// while let Some(event) = rx.recv().await { - /// if let CommandEvent::Stdout(line) = event { - /// println!("got: {}", line); - /// i += 1; - /// if i == 4 { - /// child.write("message from Rust\n".as_bytes()).unwrap(); - /// i = 0; - /// } - /// } - /// } - /// }); - /// ``` - pub fn spawn(self) -> crate::api::Result<(Receiver, CommandChild)> { - let encoding = self.encoding; - let mut command: StdCommand = self.into(); - 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); - - spawn_pipe_reader( - tx.clone(), - guard.clone(), - stdout_reader, - CommandEvent::Stdout, - encoding, - ); - spawn_pipe_reader( - tx.clone(), - guard.clone(), - stderr_reader, - CommandEvent::Stderr, - encoding, - ); - - spawn(move || { - let _ = match child_.wait() { - Ok(status) => { - let _l = guard.write().unwrap(); - commands().lock().unwrap().remove(&child_.id()); - 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(); - 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. - /// - /// # Examples - /// ```rust,no_run - /// use tauri::api::process::Command; - /// let status = Command::new("which").args(["ls"]).status().unwrap(); - /// println!("`which` finished with status: {:?}", status.code()); - /// ``` - 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. - /// - /// # Examples - /// - /// ```rust,no_run - /// use tauri::api::process::Command; - /// let output = Command::new("echo").args(["TAURI"]).output().unwrap(); - /// assert!(output.status.success()); - /// assert_eq!(output.stdout, "TAURI"); - /// ``` - 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) - } -} - -fn spawn_pipe_reader CommandEvent + Send + Copy + 'static>( - tx: Sender, - guard: Arc>, - pipe_reader: PipeReader, - wrapper: F, - character_encoding: Option<&'static Encoding>, -) { - spawn(move || { - let _lock = guard.read().unwrap(); - let mut reader = BufReader::new(pipe_reader); - - let mut buf = Vec::new(); - loop { - buf.clear(); - match tauri_utils::io::read_line(&mut reader, &mut buf) { - Ok(n) => { - if n == 0 { - break; - } - let tx_ = tx.clone(); - let line = match character_encoding { - Some(encoding) => Ok(encoding.decode_with_bom_removal(&buf).0.into()), - None => String::from_utf8(buf.clone()), - }; - block_on_task(async move { - let _ = match line { - Ok(line) => tx_.send(wrapper(line)).await, - Err(e) => tx_.send(CommandEvent::Error(e.to_string())).await, - }; - }); - } - Err(e) => { - let tx_ = tx.clone(); - let _ = block_on_task(async move { tx_.send(CommandEvent::Error(e.to_string())).await }); - } - } - } - }); -} - -// 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 0a22dd67912c..000000000000 --- a/core/tauri/src/api/shell.rs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2019-2023 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 the program specified in `with`, or system default if `None`. -/// -/// The path will be matched against the shell open validation regex, defaulting to `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`. -/// A custom validation regex may be supplied in the config in `tauri > allowlist > scope > open`. -/// -/// # Examples -/// -/// ```rust,no_run -/// use tauri::{api::shell::open, Manager}; -/// tauri::Builder::default() -/// .setup(|app| { -/// // open the given URL on the system default browser -/// open(&app.shell_scope(), "https://github.com/tauri-apps/tauri", None)?; -/// Ok(()) -/// }); -/// ``` -pub fn open>( - scope: &ShellScope, - path: P, - with: Option, -) -> crate::api::Result<()> { - scope - .open(path.as_ref(), with) - .map_err(|err| crate::api::Error::Shell(format!("failed to open: {err}"))) -} diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index dcb965cdaed5..7128c8c0ca6d 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -19,16 +19,16 @@ use crate::{ window::{PendingWindow, WindowEvent as RuntimeWindowEvent}, ExitRequestedEventAction, RunEvent as RuntimeRunEvent, }, - scope::{FsScope, IpcScope}, + scope::IpcScope, sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::Config, - utils::{assets::Assets, resources::resource_relpath, Env}, - Context, DeviceEventFilter, EventLoopMessage, Invoke, InvokeError, InvokeResponse, Manager, + utils::{assets::Assets, Env}, + 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; @@ -40,7 +40,7 @@ use tauri_utils::PackageInfo; use std::{ collections::HashMap, - path::{Path, PathBuf}, + fmt, sync::{mpsc::Sender, Arc, Weak}, }; @@ -48,9 +48,6 @@ use crate::runtime::menu::{Menu, MenuId, MenuIdRef}; use crate::runtime::RuntimeHandle; -#[cfg(updater)] -use crate::updater; - #[cfg(target_os = "macos")] use crate::ActivationPolicy; @@ -180,18 +177,11 @@ 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, - /// Updater event. - #[cfg(updater)] - #[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))] - Updater(crate::UpdaterEvent), } impl From for RunEvent { fn from(event: EventLoopMessage) -> Self { - match event { - #[cfg(updater)] - EventLoopMessage::Updater(event) => RunEvent::Updater(event), - } + match event {} } } @@ -235,117 +225,6 @@ impl GlobalWindowEvent { } } -#[cfg(updater)] -#[derive(Debug, Clone, Default)] -pub(crate) struct UpdaterSettings { - pub(crate) target: Option, -} - -/// 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. - /// - /// Helper function for [`crate::api::path::resource_dir`]. - pub fn resource_dir(&self) -> Option { - crate::api::path::resource_dir(&self.package_info, &self.env) - } - - /// Resolves the path of the given resource. - /// Note that the path must be the same as provided in `tauri.conf.json`. - /// - /// This function is helpful when your resource path includes a root dir (`/`) or parent component (`..`), - /// because Tauri replaces them with a parent folder, so simply using [`Self::resource_dir`] and joining the path - /// won't work. - /// - /// # Examples - /// - /// `tauri.conf.json`: - /// ```json - /// { - /// "tauri": { - /// "bundle": { - /// "resources": ["../assets/*"] - /// } - /// } - /// } - /// ``` - /// - /// ```no_run - /// tauri::Builder::default() - /// .setup(|app| { - /// let resource_path = app.path_resolver() - /// .resolve_resource("../assets/logo.svg") - /// .expect("failed to resolve resource dir"); - /// Ok(()) - /// }); - /// ``` - pub fn resolve_resource>(&self, path: P) -> Option { - self - .resource_dir() - .map(|dir| dir.join(resource_relpath(path.as_ref()))) - } - - /// Returns the path to the suggested directory for your app's config files. - /// - /// Helper function for [`crate::api::path::app_config_dir`]. - pub fn app_config_dir(&self) -> Option { - crate::api::path::app_config_dir(&self.config) - } - - /// Returns the path to the suggested directory for your app's data files. - /// - /// Helper function for [`crate::api::path::app_data_dir`]. - pub fn app_data_dir(&self) -> Option { - crate::api::path::app_data_dir(&self.config) - } - - /// Returns the path to the suggested directory for your app's local data files. - /// - /// Helper function for [`crate::api::path::app_local_data_dir`]. - pub fn app_local_data_dir(&self) -> Option { - crate::api::path::app_local_data_dir(&self.config) - } - - /// Returns the path to the suggested directory for your app's cache files. - /// - /// Helper function for [`crate::api::path::app_cache_dir`]. - pub fn app_cache_dir(&self) -> Option { - crate::api::path::app_cache_dir(&self.config) - } - - /// Returns the path to the suggested directory for your app's log files. - /// - /// Helper function for [`crate::api::path::app_log_dir`]. - pub fn app_log_dir(&self) -> Option { - crate::api::path::app_log_dir(&self.config) - } - - /// Returns the path to the suggested directory for your app's config files. - #[deprecated( - since = "1.2.0", - note = "Will be removed in 2.0.0. Use `app_config_dir` or `app_data_dir` instead." - )] - pub fn app_dir(&self) -> Option { - self.app_config_dir() - } - - /// Returns the path to the suggested directory for your app's log files. - #[deprecated( - since = "1.2.0", - note = "Will be removed in 2.0.0. Use `app_log_dir` instead." - )] - pub fn log_dir(&self) -> Option { - self.app_log_dir() - } -} - /// The asset resolver is a helper to access the [`tauri_utils::assets::Assets`] interface. #[derive(Debug, Clone)] pub struct AssetResolver { @@ -365,23 +244,8 @@ impl AssetResolver { #[default_runtime(crate::Wry, wry)] #[derive(Debug)] pub struct AppHandle { - runtime_handle: R::Handle, + pub(crate) runtime_handle: R::Handle, pub(crate) manager: WindowManager, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: R::GlobalShortcutManager, - #[cfg(feature = "clipboard")] - clipboard_manager: R::ClipboardManager, - /// The updater configuration. - #[cfg(updater)] - pub(crate) updater_settings: UpdaterSettings, -} - -impl AppHandle { - // currently only used on the updater - #[allow(dead_code)] - pub(crate) fn create_proxy(&self) -> R::EventLoopProxy { - self.runtime_handle.create_proxy() - } } /// APIs specific to the wry runtime. @@ -418,12 +282,6 @@ impl Clone for AppHandle { Self { runtime_handle: self.runtime_handle.clone(), manager: self.manager.clone(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: self.global_shortcut_manager.clone(), - #[cfg(feature = "clipboard")] - clipboard_manager: self.clipboard_manager.clone(), - #[cfg(updater)] - updater_settings: self.updater_settings.clone(), } } } @@ -533,18 +391,14 @@ impl AppHandle { std::process::exit(exit_code); } - /// Restarts the app. This is the same as [`crate::api::process::restart`], but it performs cleanup on this application. + /// 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::api::process::restart(&self.env()); + crate::process::restart(&self.env()); } /// Runs necessary cleanup tasks before exiting the process fn cleanup_before_exit(&self) { - #[cfg(any(shell_execute, shell_sidecar))] - { - crate::api::process::kill_children(); - } #[cfg(all(windows, feature = "system-tray"))] { for tray in self.manager().trays().values() { @@ -573,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, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: R::GlobalShortcutManager, - #[cfg(feature = "clipboard")] - clipboard_manager: R::ClipboardManager, 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 { @@ -591,7 +452,11 @@ 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 managed_app_handle(&self) -> AppHandle { @@ -607,40 +472,19 @@ impl App { /// # Stability /// /// This API is unstable. - pub fn wry_plugin + 'static>( + pub fn wry_plugin + Send + 'static>( &mut self, plugin: P, - ) { - self.runtime.as_mut().unwrap().plugin(plugin); + ) where +

>::Plugin: Send, + { + self.handle.runtime_handle.plugin(plugin); } } macro_rules! shared_app_impl { ($app: ty) => { impl $app { - #[cfg(updater)] - #[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))] - /// Gets the updater builder to manually check if an update is available. - /// - /// # Examples - /// - /// ```no_run - /// tauri::Builder::default() - /// .setup(|app| { - /// let handle = app.handle(); - /// tauri::async_runtime::spawn(async move { - #[cfg_attr( - feature = "updater", - doc = r#" let response = handle.updater().check().await;"# - )] - /// }); - /// Ok(()) - /// }); - /// ``` - pub fn updater(&self) -> updater::UpdateBuilder { - updater::builder(self.app_handle()) - } - /// Gets a handle to the first system tray. /// /// Prefer [`Self::tray_handle_by_id`] when multiple system trays are created. @@ -709,29 +553,6 @@ macro_rules! shared_app_impl { .get_tray(id) } - /// 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(), - } - } - - /// Gets a copy of the global shortcut manager instance. - #[cfg(all(desktop, feature = "global-shortcut"))] - #[cfg_attr(doc_cfg, doc(cfg(feature = "global-shortcut")))] - pub fn global_shortcut_manager(&self) -> R::GlobalShortcutManager { - self.global_shortcut_manager.clone() - } - - /// Gets a copy of the clipboard manager instance. - #[cfg(feature = "clipboard")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "clipboard")))] - pub fn clipboard_manager(&self) -> R::ClipboardManager { - self.clipboard_manager.clone() - } - /// Gets the app's configuration, defined on the `tauri.conf.json` file. pub fn config(&self) -> Arc { self.manager.config() @@ -749,6 +570,11 @@ macro_rules! shared_app_impl { } } + /// 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<()> { @@ -778,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() @@ -834,26 +666,6 @@ impl App { .set_device_event_filter(filter); } - /// Gets the argument matches of the CLI definition configured in `tauri.conf.json`. - /// - /// # Examples - /// - /// ``` - /// tauri::Builder::default() - /// .setup(|app| { - /// let matches = app.get_cli_matches()?; - /// Ok(()) - /// }); - /// ``` - #[cfg(cli)] - pub fn get_cli_matches(&self) -> crate::Result { - if let Some(cli) = &self.manager.config().tauri.cli { - crate::api::cli::get_matches(cli, self.manager.package_info()).map_err(Into::into) - } else { - Ok(Default::default()) - } - } - /// Runs the application. /// /// # Examples @@ -873,6 +685,17 @@ 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 => { on_event_loop_event( &app_handle, @@ -891,7 +714,7 @@ 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). /// /// # Examples @@ -922,55 +745,6 @@ impl App { } } -#[cfg(updater)] -impl App { - /// Runs the updater hook with built-in dialog. - fn run_updater_dialog(&self) { - let handle = self.handle(); - - crate::async_runtime::spawn(async move { updater::check_update_with_dialog(handle).await }); - } - - fn run_updater(&self) { - let handle = self.handle(); - let handle_ = handle.clone(); - let updater_config = self.manager.config().tauri.updater.clone(); - // check if updater is active or not - if updater_config.active { - if updater_config.dialog { - #[cfg(not(target_os = "linux"))] - let updater_enabled = true; - #[cfg(target_os = "linux")] - let updater_enabled = cfg!(dev) || self.state::().appimage.is_some(); - if updater_enabled { - // if updater dialog is enabled spawn a new task - self.run_updater_dialog(); - // 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. - handle.listen_global(updater::EVENT_CHECK_UPDATE, move |_msg| { - let handle = handle_.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(handle).await }, - ); - }); - } - } else { - // 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 responsibilities to display dialog and ask if user want to install - // to install the update you need to invoke the Event `tauri://update-install` - updater::listener(handle); - } - } - } -} - /// Builds a Tauri application. /// /// # Examples @@ -1034,10 +808,6 @@ pub struct Builder { #[cfg(all(desktop, feature = "system-tray"))] system_tray_event_listeners: Vec>, - /// The updater configuration. - #[cfg(updater)] - updater_settings: UpdaterSettings, - /// The device event filter. device_event_filter: DeviceEventFilter, } @@ -1049,7 +819,7 @@ impl Builder { #[cfg(any(windows, target_os = "linux"))] runtime_any_thread: false, setup: Box::new(|_| Ok(())), - invoke_handler: Box::new(|invoke| invoke.resolver.reject("not implemented")), + invoke_handler: Box::new(|_| false), invoke_responder: Arc::new(window_invoke_responder), invoke_initialization_script: format!("Object.defineProperty(window, '__TAURI_POST_MESSAGE__', {{ value: (message) => window.ipc.postMessage({}(message)) }})", crate::manager::STRINGIFY_IPC_MESSAGE_FN), @@ -1066,8 +836,6 @@ impl Builder { system_tray: None, #[cfg(all(desktop, feature = "system-tray"))] system_tray_event_listeners: Vec::new(), - #[cfg(updater)] - updater_settings: Default::default(), device_event_filter: Default::default(), } } @@ -1102,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 @@ -1132,10 +900,7 @@ impl Builder { /// 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(()) /// }); /// ``` @@ -1176,7 +941,7 @@ impl Builder { /// } /// pub fn init() -> TauriPlugin { /// PluginBuilder::new("window") - /// .setup(|app| { + /// .setup(|app, api| { /// // initialize the plugin here /// Ok(()) /// }) @@ -1290,7 +1055,7 @@ impl Builder { let type_name = std::any::type_name::(); assert!( self.state.set(state), - "state for type '{type_name}' is already being managed" + "state for type '{type_name}' is already being managed", ); self } @@ -1367,19 +1132,15 @@ impl Builder { /// 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 @@ -1482,42 +1243,6 @@ impl Builder { self } - /// Sets the current platform's target name for the updater. - /// - /// See [`UpdateBuilder::target`](crate::updater::UpdateBuilder#method.target) for more information. - /// - /// # Examples - /// - /// - Use a macOS Universal binary target name: - /// - /// ``` - /// let mut builder = tauri::Builder::default(); - /// #[cfg(target_os = "macos")] - /// { - /// builder = builder.updater_target("darwin-universal"); - /// } - /// ``` - /// - /// - Append debug information to the target: - /// - /// ``` - /// let kind = if cfg!(debug_assertions) { "debug" } else { "release" }; - /// tauri::Builder::default() - /// .updater_target(format!("{}-{kind}", tauri::updater::target().unwrap())); - /// ``` - /// - /// - Use the platform's target triple: - /// - /// ``` - /// tauri::Builder::default() - /// .updater_target(tauri::utils::platform::target_triple().unwrap()); - /// ``` - #[cfg(updater)] - pub fn updater_target>(mut self, target: T) -> Self { - self.updater_settings.target.replace(target.into()); - self - } - /// Change the device event filter mode. /// /// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`] @@ -1548,9 +1273,6 @@ impl Builder { self.menu = Some(Menu::os_default(&context.package_info().name)); } - #[cfg(shell_scope)] - let shell_scope = context.shell_scope.clone(); - let manager = WindowManager::with_handlers( context, self.plugins, @@ -1567,7 +1289,6 @@ impl Builder { for config in manager.config().tauri.windows.clone() { let label = config.label.clone(); let webview_attributes = WebviewAttributes::from(&config); - self.pending_windows.push(PendingWindow::with_config( config, webview_attributes, @@ -1588,53 +1309,28 @@ impl Builder { let runtime_handle = runtime.handle(); - #[cfg(all(desktop, feature = "global-shortcut"))] - let global_shortcut_manager = runtime.global_shortcut_manager(); - - #[cfg(feature = "clipboard")] - 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(), - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: global_shortcut_manager.clone(), - #[cfg(feature = "clipboard")] - clipboard_manager: clipboard_manager.clone(), handle: AppHandle { runtime_handle, manager, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager, - #[cfg(feature = "clipboard")] - clipboard_manager, - #[cfg(updater)] - updater_settings: self.updater_settings, }, }; + app.register_core_plugins()?; + let env = Env::default(); + app.manage(env); + app.manage(Scopes { ipc: IpcScope::new(&app.config()), - 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(&app.manager.config(), app.package_info(), &env, shell_scope), + #[cfg(feature = "protocol-asset")] + asset_protocol: FsScope::for_fs_api(&app, &app.config().tauri.security.asset_protocol.scope)?, }); - app.manage(env); #[cfg(windows)] { @@ -1646,7 +1342,7 @@ impl Builder { .windows .webview_install_mode { - if let Some(resource_dir) = app.path_resolver().resource_dir() { + if let Ok(resource_dir) = app.path().resource_dir() { std::env::set_var( "WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", resource_dir.join(path), @@ -1686,25 +1382,6 @@ impl Builder { app.manager.initialize_plugins(&app.handle())?; - let window_labels = self - .pending_windows - .iter() - .map(|p| p.label.clone()) - .collect::>(); - - for pending in self.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); - } - - (self.setup)(&mut app).map_err(|e| crate::Error::Setup(e.into()))?; - - #[cfg(updater)] - app.run_updater(); - Ok(app) } @@ -1727,6 +1404,40 @@ unsafe impl HasRawDisplayHandle for App { } } +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::>(); + + for pending in pending_windows { + let pending = app + .manager + .prepare_window(app.handle.clone(), pending, &window_labels)?; + 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))?; + } + } + } + + 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, @@ -1812,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/asset_protocol.rs b/core/tauri/src/asset_protocol.rs index 6181a486d764..ec961a9a1169 100644 --- a/core/tauri/src/asset_protocol.rs +++ b/core/tauri/src/asset_protocol.rs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#![cfg(protocol_asset)] - -use crate::api::file::SafePathBuf; +use crate::path::SafePathBuf; use crate::scope::FsScope; use rand::RngCore; use std::io::SeekFrom; diff --git a/core/tauri/src/endpoints.rs b/core/tauri/src/endpoints.rs deleted file mode 100644 index 5a942a0e9565..000000000000 --- a/core/tauri/src/endpoints.rs +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::{ - hooks::{InvokeError, InvokeMessage, InvokeResolver}, - Config, Invoke, PackageInfo, Runtime, Window, -}; -pub use anyhow::Result; -use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; - -use std::sync::Arc; - -mod app; -#[cfg(cli)] -mod cli; -#[cfg(clipboard_any)] -mod clipboard; -#[cfg(dialog_any)] -mod dialog; -mod event; -#[cfg(fs_any)] -mod file_system; -#[cfg(global_shortcut_any)] -mod global_shortcut; -#[cfg(http_any)] -mod http; -mod notification; -#[cfg(os_any)] -mod operating_system; -#[cfg(path_any)] -mod path; -#[cfg(process_any)] -mod process; -#[cfg(shell_any)] -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: 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), - #[cfg(process_any)] - Process(process::Cmd), - #[cfg(fs_any)] - Fs(file_system::Cmd), - #[cfg(os_any)] - Os(operating_system::Cmd), - #[cfg(path_any)] - Path(path::Cmd), - Window(Box), - #[cfg(shell_any)] - Shell(shell::Cmd), - Event(event::Cmd), - #[cfg(dialog_any)] - Dialog(dialog::Cmd), - #[cfg(cli)] - Cli(cli::Cmd), - Notification(notification::Cmd), - #[cfg(http_any)] - Http(http::Cmd), - #[cfg(global_shortcut_any)] - GlobalShortcut(global_shortcut::Cmd), - #[cfg(clipboard_any)] - 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_anyhow) - }), - #[cfg(process_any)] - Self::Process(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(fs_any)] - Self::Fs(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(path_any)] - Self::Path(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(os_any)] - Self::Os(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - Self::Window(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .await - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(shell_any)] - Self::Shell(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - Self::Event(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(dialog_any)] - Self::Dialog(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(cli)] - Self::Cli(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - Self::Notification(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(http_any)] - Self::Http(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .await - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(global_shortcut_any)] - Self::GlobalShortcut(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - #[cfg(clipboard_any)] - Self::Clipboard(cmd) => resolver.respond_async(async move { - cmd - .run(context) - .and_then(|r| r.json) - .map_err(InvokeError::from_anyhow) - }), - } - } -} - -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.clone())); - } - - match serde_json::from_value::(payload) { - Ok(module) => module.run(window, resolver, config, package_info.clone()), - Err(e) => { - let message = e.to_string(); - if message.starts_with("unknown variant") { - let mut s = message.split('`'); - s.next(); - if let Some(unknown_variant_name) = s.next() { - if unknown_variant_name == module { - return resolver.reject(format!( - "The `{module}` module is not enabled. You must enable one of its APIs in the allowlist." - )); - } else if module == "Window" { - return resolver.reject(window::into_allowlist_error(unknown_variant_name).to_string()); - } - } - } - resolver.reject(message); - } - } -} diff --git a/core/tauri/src/endpoints/app.rs b/core/tauri/src/endpoints/app.rs deleted file mode 100644 index 32191eb6dd35..000000000000 --- a/core/tauri/src/endpoints/app.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019-2023 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::{command_enum, module_command_handler, CommandModule}; - -/// The API descriptor. -#[command_enum] -#[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, - /// Shows the application on macOS. - #[cmd(app_show, "app > show")] - Show, - /// Hides the application on macOS. - #[cmd(app_hide, "app > hide")] - Hide, -} - -impl Cmd { - fn get_app_version(context: InvokeContext) -> super::Result { - Ok(context.package_info.version.to_string()) - } - - fn get_app_name(context: InvokeContext) -> super::Result { - Ok(context.package_info.name) - } - - fn get_tauri_version(_context: InvokeContext) -> super::Result<&'static str> { - Ok(crate::VERSION) - } - - #[module_command_handler(app_show)] - #[allow(unused_variables)] - fn show(context: InvokeContext) -> super::Result<()> { - #[cfg(target_os = "macos")] - context.window.app_handle.show()?; - Ok(()) - } - - #[module_command_handler(app_hide)] - #[allow(unused_variables)] - fn hide(context: InvokeContext) -> super::Result<()> { - #[cfg(target_os = "macos")] - context.window.app_handle.hide()?; - Ok(()) - } -} diff --git a/core/tauri/src/endpoints/cli.rs b/core/tauri/src/endpoints/cli.rs deleted file mode 100644 index 1c7c490a9346..000000000000 --- a/core/tauri/src/endpoints/cli.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::{InvokeContext, InvokeResponse}; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{command_enum, module_command_handler, CommandModule}; - -/// The API descriptor. -#[command_enum] -#[derive(CommandModule, Deserialize)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// The get CLI matches API. - CliMatches, -} - -impl Cmd { - #[module_command_handler(cli)] - fn cli_matches(context: InvokeContext) -> super::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 { - Ok(crate::api::cli::Matches::default().into()) - } - } - - #[cfg(not(cli))] - fn cli_matches(_: InvokeContext) -> super::Result { - Err(crate::error::into_anyhow("CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.app/docs/api/config#tauri.cli)")) - } -} - -#[cfg(test)] -mod tests { - #[tauri_macros::module_command_test(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.app/docs/api/config#tauri.cli)", runtime)] - #[quickcheck_macros::quickcheck] - fn cli_matches() { - let res = super::Cmd::cli_matches(crate::test::mock_invoke_context()); - crate::test_utils::assert_not_allowlist_error(res); - } -} diff --git a/core/tauri/src/endpoints/clipboard.rs b/core/tauri/src/endpoints/clipboard.rs deleted file mode 100644 index 0eb31b71229a..000000000000 --- a/core/tauri/src/endpoints/clipboard.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::InvokeContext; -#[cfg(any(clipboard_write_text, clipboard_read_text))] -use crate::runtime::ClipboardManager; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{command_enum, module_command_handler, CommandModule}; - -/// The API descriptor. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", content = "data", rename_all = "camelCase")] -pub enum Cmd { - /// Write a text string to the clipboard. - #[cmd(clipboard_write_text, "clipboard > writeText")] - WriteText(String), - /// Read clipboard content as text. - ReadText, -} - -impl Cmd { - #[module_command_handler(clipboard_write_text)] - fn write_text(context: InvokeContext, text: String) -> super::Result<()> { - context - .window - .app_handle - .clipboard_manager() - .write_text(text) - .map_err(crate::error::into_anyhow) - } - - #[module_command_handler(clipboard_read_text)] - fn read_text(context: InvokeContext) -> super::Result> { - context - .window - .app_handle - .clipboard_manager() - .read_text() - .map_err(crate::error::into_anyhow) - } - - #[cfg(not(clipboard_read_text))] - fn read_text(_: InvokeContext) -> super::Result<()> { - Err(crate::Error::ApiNotAllowlisted("clipboard > readText".into()).into_anyhow()) - } -} - -#[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(); - #[cfg(clipboard_read_text)] - assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text)); - } - - #[tauri_macros::module_command_test(clipboard_read_text, "clipboard > readText", runtime)] - #[quickcheck_macros::quickcheck] - fn read_text() { - let ctx = crate::test::mock_invoke_context(); - assert_eq!(super::Cmd::read_text(ctx.clone()).unwrap(), None); - #[cfg(clipboard_write_text)] - { - 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 137c8434dd46..000000000000 --- a/core/tauri/src/endpoints/dialog.rs +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::{InvokeContext, InvokeResponse}; -use crate::Runtime; -#[cfg(any(dialog_open, dialog_save))] -use crate::{api::dialog::blocking::FileDialogBuilder, Manager, Scopes}; -use serde::{Deserialize, Deserializer}; -use tauri_macros::{command_enum, module_command_handler, CommandModule}; - -use std::path::PathBuf; - -macro_rules! message_dialog { - ($fn_name: ident, $allowlist: ident, $button_labels_type: ty, $buttons: expr) => { - #[module_command_handler($allowlist)] - fn $fn_name( - context: InvokeContext, - title: Option, - message: String, - level: Option, - button_labels: $button_labels_type, - ) -> super::Result { - let determine_button = $buttons; - let mut builder = crate::api::dialog::blocking::MessageDialogBuilder::new( - title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()), - message, - ) - .buttons(determine_button(button_labels)); - #[cfg(any(windows, target_os = "macos"))] - { - builder = builder.parent(&context.window); - } - if let Some(level) = level { - builder = builder.kind(level.into()); - } - Ok(builder.show()) - } - }; -} - -#[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, - /// If [`Self::directory`] is true, indicates that it will be read recursively later. - /// Defines whether subdirectories will be allowed on the scope or not. - #[serde(default)] - pub recursive: bool, -} - -/// 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, -} - -/// Types of message, ask and confirm dialogs. -#[non_exhaustive] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum MessageDialogType { - /// Information dialog. - Info, - /// Warning dialog. - Warning, - /// Error dialog. - Error, -} - -impl<'de> Deserialize<'de> for MessageDialogType { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Ok(match s.to_lowercase().as_str() { - "info" => MessageDialogType::Info, - "warning" => MessageDialogType::Warning, - "error" => MessageDialogType::Error, - _ => MessageDialogType::Info, - }) - } -} - -#[cfg(any(dialog_message, dialog_ask, dialog_confirm))] -impl From for crate::api::dialog::MessageDialogKind { - fn from(kind: MessageDialogType) -> Self { - match kind { - MessageDialogType::Info => Self::Info, - MessageDialogType::Warning => Self::Warning, - MessageDialogType::Error => Self::Error, - } - } -} - -/// The API descriptor. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -#[allow(clippy::enum_variant_names)] -pub enum Cmd { - /// The open dialog API. - #[cmd(dialog_open, "dialog > open")] - OpenDialog { options: OpenDialogOptions }, - /// The save dialog API. - #[cmd(dialog_save, "dialog > save")] - SaveDialog { options: SaveDialogOptions }, - #[cmd(dialog_message, "dialog > message")] - MessageDialog { - title: Option, - message: String, - #[serde(rename = "type")] - level: Option, - #[serde(rename = "buttonLabel")] - button_label: Option, - }, - #[cmd(dialog_ask, "dialog > ask")] - AskDialog { - title: Option, - message: String, - #[serde(rename = "type")] - level: Option, - #[serde(rename = "buttonLabels")] - button_label: Option<(String, String)>, - }, - #[cmd(dialog_confirm, "dialog > confirm")] - ConfirmDialog { - title: Option, - message: String, - #[serde(rename = "type")] - level: Option, - #[serde(rename = "buttonLabels")] - button_labels: Option<(String, String)>, - }, -} - -impl Cmd { - #[module_command_handler(dialog_open)] - #[allow(unused_variables)] - fn open_dialog( - context: InvokeContext, - options: OpenDialogOptions, - ) -> super::Result { - let mut dialog_builder = FileDialogBuilder::new(); - #[cfg(any(windows, target_os = "macos"))] - { - dialog_builder = dialog_builder.set_parent(&context.window); - } - if let Some(title) = options.title { - dialog_builder = dialog_builder.set_title(&title); - } - 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 scopes = context.window.state::(); - - let res = if options.directory { - if options.multiple { - let folders = dialog_builder.pick_folders(); - if let Some(folders) = &folders { - for folder in folders { - scopes - .allow_directory(folder, options.recursive) - .map_err(crate::error::into_anyhow)?; - } - } - folders.into() - } else { - let folder = dialog_builder.pick_folder(); - if let Some(path) = &folder { - scopes - .allow_directory(path, options.recursive) - .map_err(crate::error::into_anyhow)?; - } - folder.into() - } - } else if options.multiple { - let files = dialog_builder.pick_files(); - if let Some(files) = &files { - for file in files { - scopes.allow_file(file).map_err(crate::error::into_anyhow)?; - } - } - files.into() - } else { - let file = dialog_builder.pick_file(); - if let Some(file) = &file { - scopes.allow_file(file).map_err(crate::error::into_anyhow)?; - } - file.into() - }; - - Ok(res) - } - - #[module_command_handler(dialog_save)] - #[allow(unused_variables)] - fn save_dialog( - context: InvokeContext, - options: SaveDialogOptions, - ) -> super::Result> { - let mut dialog_builder = FileDialogBuilder::new(); - #[cfg(any(windows, target_os = "macos"))] - { - dialog_builder = dialog_builder.set_parent(&context.window); - } - if let Some(title) = options.title { - dialog_builder = dialog_builder.set_title(&title); - } - 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 scopes = context.window.state::(); - - let path = dialog_builder.save_file(); - if let Some(p) = &path { - scopes.allow_file(p).map_err(crate::error::into_anyhow)?; - } - - Ok(path) - } - - message_dialog!( - message_dialog, - dialog_message, - Option, - |label: Option| { - label - .map(crate::api::dialog::MessageDialogButtons::OkWithLabel) - .unwrap_or(crate::api::dialog::MessageDialogButtons::Ok) - } - ); - - message_dialog!( - ask_dialog, - dialog_ask, - Option<(String, String)>, - |labels: Option<(String, String)>| { - labels - .map(|(yes, no)| crate::api::dialog::MessageDialogButtons::OkCancelWithLabels(yes, no)) - .unwrap_or(crate::api::dialog::MessageDialogButtons::YesNo) - } - ); - - message_dialog!( - confirm_dialog, - dialog_confirm, - Option<(String, String)>, - |labels: Option<(String, String)>| { - labels - .map(|(ok, cancel)| { - crate::api::dialog::MessageDialogButtons::OkCancelWithLabels(ok, cancel) - }) - .unwrap_or(crate::api::dialog::MessageDialogButtons::OkCancel) - } - ); -} - -#[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()) { - if parent.components().count() > 0 { - dialog_builder = dialog_builder.set_directory(parent); - } - dialog_builder = dialog_builder.set_file_name(&file_name.to_string_lossy()); - } 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), - recursive: bool::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 471db9c3c089..000000000000 --- a/core/tauri/src/endpoints/event.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -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 serde_json::Value as JsonValue; -use tauri_macros::{command_enum, 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. -#[command_enum] -#[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: EventId, event_id: u32 }, - /// 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, - ) -> super::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), - )) - .map_err(crate::error::into_anyhow)?; - - context - .window - .register_js_listener(window_label, event.0, event_id); - - Ok(event_id) - } - - fn unlisten( - context: InvokeContext, - event: EventId, - event_id: u32, - ) -> super::Result<()> { - context - .window - .eval(&unlisten_js( - context.window.manager().event_listeners_object_name(), - event.0, - event_id, - )) - .map_err(crate::error::into_anyhow)?; - context.window.unregister_js_listener(event_id); - Ok(()) - } - - fn emit( - context: InvokeContext, - event: EventId, - window_label: Option, - payload: Option, - ) -> super::Result<()> { - // dispatch the event to Rust listeners - context.window.trigger( - &event.0, - // TODO: dispatch any serializable value instead of a string in v2 - payload.as_ref().and_then(|p| { - serde_json::to_string(&p) - .map_err(|e| { - #[cfg(debug_assertions)] - eprintln!("{e}"); - e - }) - .ok() - }), - ); - - if let Some(target) = window_label { - context - .window - .emit_to(&target.0, &event.0, payload) - .map_err(crate::error::into_anyhow)?; - } else { - context - .window - .emit_all(&event.0, payload) - .map_err(crate::error::into_anyhow)?; - } - Ok(()) - } -} diff --git a/core/tauri/src/endpoints/file_system.rs b/core/tauri/src/endpoints/file_system.rs deleted file mode 100644 index ccfabde77b96..000000000000 --- a/core/tauri/src/endpoints/file_system.rs +++ /dev/null @@ -1,506 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use crate::{ - api::{ - dir, - file::{self, SafePathBuf}, - path::BaseDirectory, - }, - scope::Scopes, - Config, Env, Manager, PackageInfo, Runtime, Window, -}; - -use super::InvokeContext; -#[allow(unused_imports)] -use anyhow::Context; -use serde::{ - de::{Deserializer, Error as DeError}, - Deserialize, Serialize, -}; -use tauri_macros::{command_enum, module_command_handler, CommandModule}; - -use std::fmt::{Debug, Formatter}; -use std::{ - fs, - fs::File, - io::Write, - path::{Component, Path}, - sync::Arc, -}; - -/// 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. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub(crate) enum Cmd { - /// The read binary file API. - #[cmd(fs_read_file, "fs > readFile")] - ReadFile { - path: SafePathBuf, - options: Option, - }, - /// The read binary file API. - #[cmd(fs_read_file, "fs > readFile")] - ReadTextFile { - path: SafePathBuf, - options: Option, - }, - /// The write file API. - #[cmd(fs_write_file, "fs > writeFile")] - WriteFile { - path: SafePathBuf, - contents: Vec, - options: Option, - }, - /// The read dir API. - #[cmd(fs_read_dir, "fs > readDir")] - ReadDir { - path: SafePathBuf, - options: Option, - }, - /// The copy file API. - #[cmd(fs_copy_file, "fs > copyFile")] - CopyFile { - source: SafePathBuf, - destination: SafePathBuf, - options: Option, - }, - /// The create dir API. - #[cmd(fs_create_dir, "fs > createDir")] - CreateDir { - path: SafePathBuf, - options: Option, - }, - /// The remove dir API. - #[cmd(fs_remove_dir, "fs > removeDir")] - RemoveDir { - path: SafePathBuf, - options: Option, - }, - /// The remove file API. - #[cmd(fs_remove_file, "fs > removeFile")] - RemoveFile { - path: SafePathBuf, - options: Option, - }, - /// The rename file API. - #[cmd(fs_rename_file, "fs > renameFile")] - #[serde(rename_all = "camelCase")] - RenameFile { - old_path: SafePathBuf, - new_path: SafePathBuf, - options: Option, - }, - /// The exists API. - #[cmd(fs_exists, "fs > exists")] - Exists { - path: SafePathBuf, - options: Option, - }, -} - -impl Cmd { - #[module_command_handler(fs_read_file)] - fn read_file( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> super::Result> { - let resolved_path = resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - options.and_then(|o| o.dir), - )?; - file::read_binary(&resolved_path) - .with_context(|| format!("path: {}", resolved_path.display())) - .map_err(Into::into) - } - - #[module_command_handler(fs_read_file)] - fn read_text_file( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> super::Result { - let resolved_path = resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - options.and_then(|o| o.dir), - )?; - file::read_string(&resolved_path) - .with_context(|| format!("path: {}", resolved_path.display())) - .map_err(Into::into) - } - - #[module_command_handler(fs_write_file)] - fn write_file( - context: InvokeContext, - path: SafePathBuf, - contents: Vec, - options: Option, - ) -> super::Result<()> { - let resolved_path = resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - options.and_then(|o| o.dir), - )?; - File::create(&resolved_path) - .with_context(|| format!("path: {}", resolved_path.display())) - .map_err(Into::into) - .and_then(|mut f| f.write_all(&contents).map_err(|err| err.into())) - } - - #[module_command_handler(fs_read_dir)] - fn read_dir( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> super::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, - )?; - dir::read_dir_with_options( - &resolved_path, - recursive, - dir::ReadDirOptions { - scope: Some(&context.window.state::().fs), - }, - ) - .with_context(|| format!("path: {}", resolved_path.display())) - .map_err(Into::into) - } - - #[module_command_handler(fs_copy_file)] - fn copy_file( - context: InvokeContext, - source: SafePathBuf, - destination: SafePathBuf, - options: Option, - ) -> super::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), - )?, - resolve_path( - &context.config, - &context.package_info, - &context.window, - destination, - Some(dir), - )?, - ), - None => (source, destination), - }; - fs::copy(src.clone(), dest.clone()) - .with_context(|| format!("source: {}, dest: {}", src.display(), dest.display()))?; - Ok(()) - } - - #[module_command_handler(fs_create_dir)] - fn create_dir( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> super::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) - .with_context(|| format!("path: {}", resolved_path.display()))?; - } else { - fs::create_dir(&resolved_path) - .with_context(|| format!("path: {} (non recursive)", resolved_path.display()))?; - } - - Ok(()) - } - - #[module_command_handler(fs_remove_dir)] - fn remove_dir( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> super::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) - .with_context(|| format!("path: {}", resolved_path.display()))?; - } else { - fs::remove_dir(&resolved_path) - .with_context(|| format!("path: {} (non recursive)", resolved_path.display()))?; - } - - Ok(()) - } - - #[module_command_handler(fs_remove_file)] - fn remove_file( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> super::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) - .with_context(|| format!("path: {}", resolved_path.display()))?; - Ok(()) - } - - #[module_command_handler(fs_rename_file)] - fn rename_file( - context: InvokeContext, - old_path: SafePathBuf, - new_path: SafePathBuf, - options: Option, - ) -> super::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), - )?, - resolve_path( - &context.config, - &context.package_info, - &context.window, - new_path, - Some(dir), - )?, - ), - None => (old_path, new_path), - }; - fs::rename(&old, &new) - .with_context(|| format!("old: {}, new: {}", old.display(), new.display())) - .map_err(Into::into) - } - - #[module_command_handler(fs_exists)] - fn exists( - context: InvokeContext, - path: SafePathBuf, - options: Option, - ) -> super::Result { - let resolved_path = resolve_path( - &context.config, - &context.package_info, - &context.window, - path, - options.and_then(|o| o.dir), - )?; - Ok(resolved_path.as_ref().exists()) - } -} - -#[allow(dead_code)] -fn resolve_path( - config: &Config, - package_info: &PackageInfo, - window: &Window, - path: SafePathBuf, - dir: Option, -) -> super::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( - // safety: the path is resolved by Tauri so it is safe - unsafe { SafePathBuf::new_unchecked(path) }, - ) - } else { - Err(anyhow::anyhow!( - crate::Error::PathNotAllowed(path).to_string() - )) - } - } - Err(e) => super::Result::::Err(e.into()) - .with_context(|| format!("path: {}, base dir: {dir:?}", path.display())), - } -} - -#[cfg(test)] -mod tests { - use super::{BaseDirectory, DirOperationOptions, FileOperationOptions, SafePathBuf}; - - use quickcheck::{Arbitrary, Gen}; - - impl Arbitrary for BaseDirectory { - fn arbitrary(g: &mut Gen) -> Self { - if bool::arbitrary(g) { - BaseDirectory::AppData - } 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); - crate::test_utils::assert_not_allowlist_error(res); - } - - #[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); - crate::test_utils::assert_not_allowlist_error(res); - } - - #[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); - crate::test_utils::assert_not_allowlist_error(res); - } - - #[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, - ); - crate::test_utils::assert_not_allowlist_error(res); - } - - #[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); - crate::test_utils::assert_not_allowlist_error(res); - } - - #[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); - crate::test_utils::assert_not_allowlist_error(res); - } - - #[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); - crate::test_utils::assert_not_allowlist_error(res); - } - - #[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, - ); - crate::test_utils::assert_not_allowlist_error(res); - } - - #[tauri_macros::module_command_test(fs_exists, "fs > exists")] - #[quickcheck_macros::quickcheck] - fn exists(path: SafePathBuf, options: Option) { - let res = super::Cmd::exists(crate::test::mock_invoke_context(), path, options); - crate::test_utils::assert_not_allowlist_error(res); - } -} diff --git a/core/tauri/src/endpoints/global_shortcut.rs b/core/tauri/src/endpoints/global_shortcut.rs deleted file mode 100644 index eefb34f4dba0..000000000000 --- a/core/tauri/src/endpoints/global_shortcut.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::InvokeContext; -use crate::{api::ipc::CallbackFn, Runtime}; -use serde::Deserialize; -use tauri_macros::{command_enum, module_command_handler, CommandModule}; - -#[cfg(global_shortcut_all)] -use crate::runtime::GlobalShortcutManager; - -/// The API descriptor. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// Register a global shortcut. - #[cmd(global_shortcut_all, "globalShortcut > all")] - Register { - shortcut: String, - handler: CallbackFn, - }, - /// Register a list of global shortcuts. - #[cmd(global_shortcut_all, "globalShortcut > all")] - RegisterAll { - shortcuts: Vec, - handler: CallbackFn, - }, - /// Unregister a global shortcut. - #[cmd(global_shortcut_all, "globalShortcut > all")] - Unregister { shortcut: String }, - /// Unregisters all registered shortcuts. - UnregisterAll, - /// Determines whether the given hotkey is registered or not. - #[cmd(global_shortcut_all, "globalShortcut > all")] - IsRegistered { shortcut: String }, -} - -impl Cmd { - #[module_command_handler(global_shortcut_all)] - fn register( - context: InvokeContext, - shortcut: String, - handler: CallbackFn, - ) -> super::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)] - fn register_all( - context: InvokeContext, - shortcuts: Vec, - handler: CallbackFn, - ) -> super::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)] - fn unregister(context: InvokeContext, shortcut: String) -> super::Result<()> { - context - .window - .app_handle - .global_shortcut_manager() - .unregister(&shortcut) - .map_err(crate::error::into_anyhow)?; - Ok(()) - } - - #[module_command_handler(global_shortcut_all)] - fn unregister_all(context: InvokeContext) -> super::Result<()> { - context - .window - .app_handle - .global_shortcut_manager() - .unregister_all() - .map_err(crate::error::into_anyhow)?; - Ok(()) - } - - #[cfg(not(global_shortcut_all))] - fn unregister_all(_: InvokeContext) -> super::Result<()> { - Err(crate::Error::ApiNotAllowlisted("globalShortcut > all".into()).into_anyhow()) - } - - #[module_command_handler(global_shortcut_all)] - fn is_registered(context: InvokeContext, shortcut: String) -> super::Result { - context - .window - .app_handle - .global_shortcut_manager() - .is_registered(&shortcut) - .map_err(crate::error::into_anyhow) - } -} - -#[cfg(global_shortcut_all)] -fn register_shortcut( - window: crate::Window, - manager: &mut R::GlobalShortcutManager, - shortcut: String, - handler: CallbackFn, -) -> super::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()); - }) - .map_err(crate::error::into_anyhow)?; - 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", runtime)] - #[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 4f6951791ea3..000000000000 --- a/core/tauri/src/endpoints/http.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::InvokeContext; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{command_enum, 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))] -#[allow(dead_code)] -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. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[cmd(async)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// Create a new HTTP client. - #[cmd(http_request, "http > request")] - CreateClient { options: Option }, - /// Drop a HTTP client. - #[cmd(http_request, "http > request")] - DropClient { client: ClientId }, - /// The HTTP request API. - #[cmd(http_request, "http > request")] - HttpRequest { - client: ClientId, - options: Box, - }, -} - -impl Cmd { - #[module_command_handler(http_request)] - async fn create_client( - _context: InvokeContext, - options: Option, - ) -> super::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)] - async fn drop_client( - _context: InvokeContext, - client: ClientId, - ) -> super::Result<()> { - let mut store = clients().lock().unwrap(); - store.remove(&client); - Ok(()) - } - - #[module_command_handler(http_request)] - async fn http_request( - context: InvokeContext, - client_id: ClientId, - options: Box, - ) -> super::Result { - use crate::Manager; - let scopes = context.window.state::(); - if scopes.http.is_allowed(&options.url) { - let client = clients() - .lock() - .unwrap() - .get(&client_id) - .ok_or_else(|| crate::Error::HttpClientNotInitialized.into_anyhow())? - .clone(); - let options = *options; - if let Some(crate::api::http::Body::Form(form)) = &options.body { - for value in form.0.values() { - if let crate::api::http::FormPart::File { - file: crate::api::http::FilePart::Path(path), - .. - } = value - { - if crate::api::file::SafePathBuf::new(path.clone()).is_err() - || !scopes.fs.is_allowed(path) - { - return Err(crate::Error::PathNotAllowed(path.clone()).into_anyhow()); - } - } - } - } - let response = client.send(options).await?; - Ok(response.read().await?) - } else { - Err(crate::Error::UrlNotAllowed(options.url).into_anyhow()) - } - } -} - -#[cfg(test)] -mod tests { - use super::{ClientBuilder, ClientId}; - - #[tauri_macros::module_command_test(http_request, "http > request")] - #[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")] - #[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 2399269a704a..000000000000 --- a/core/tauri/src/endpoints/notification.rs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::InvokeContext; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{command_enum, 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. -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. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// The show notification API. - #[cmd(notification_all, "notification > all")] - Notification { options: NotificationOptions }, - /// The request notification permission API. - RequestNotificationPermission, - /// The notification permission check API. - IsNotificationPermissionGranted, -} - -impl Cmd { - #[module_command_handler(notification_all)] - fn notification( - context: InvokeContext, - options: NotificationOptions, - ) -> super::Result<()> { - 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); - } - #[cfg(feature = "windows7-compat")] - { - notification.notify(&context.window.app_handle)?; - } - #[cfg(not(feature = "windows7-compat"))] - notification.show()?; - Ok(()) - } - - fn request_notification_permission( - _context: InvokeContext, - ) -> super::Result<&'static str> { - Ok(if cfg!(notification_all) { - PERMISSION_GRANTED - } else { - PERMISSION_DENIED - }) - } - - fn is_notification_permission_granted( - _context: InvokeContext, - ) -> super::Result { - Ok(cfg!(notification_all)) - } -} - -#[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(), - if cfg!(notification_all) { - super::PERMISSION_GRANTED - } else { - super::PERMISSION_DENIED - } - ) - } - - #[cfg(not(notification_all))] - #[test] - fn is_notification_permission_granted() { - let expected = cfg!(notification_all); - assert_eq!( - super::Cmd::is_notification_permission_granted(crate::test::mock_invoke_context()).unwrap(), - expected, - ); - } - - #[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 61b11d457232..000000000000 --- a/core/tauri/src/endpoints/operating_system.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::InvokeContext; -use crate::Runtime; -use serde::Deserialize; -use std::path::PathBuf; -use tauri_macros::{command_enum, module_command_handler, CommandModule}; - -/// The API descriptor. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - Platform, - Version, - OsType, - Arch, - Tempdir, - Locale, -} - -#[cfg(os_all)] -impl Cmd { - fn platform(_context: InvokeContext) -> super::Result<&'static str> { - Ok(os_platform()) - } - - fn version(_context: InvokeContext) -> super::Result { - Ok(os_info::get().version().to_string()) - } - - fn os_type(_context: InvokeContext) -> super::Result<&'static str> { - Ok(os_type()) - } - - fn arch(_context: InvokeContext) -> super::Result<&'static str> { - Ok(std::env::consts::ARCH) - } - - fn tempdir(_context: InvokeContext) -> super::Result { - Ok(std::env::temp_dir()) - } - - fn locale(_context: InvokeContext) -> super::Result> { - Ok(crate::api::os::locale()) - } -} - -#[cfg(not(os_all))] -impl Cmd { - fn platform(_context: InvokeContext) -> super::Result<&'static str> { - Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow()) - } - - fn version(_context: InvokeContext) -> super::Result { - Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow()) - } - - fn os_type(_context: InvokeContext) -> super::Result<&'static str> { - Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow()) - } - - fn arch(_context: InvokeContext) -> super::Result<&'static str> { - Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow()) - } - - fn tempdir(_context: InvokeContext) -> super::Result { - Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow()) - } - - fn locale(_context: InvokeContext) -> super::Result> { - Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow()) - } -} - -#[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(target_os = "ios")] - return "iOS"; -} - -#[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", runtime)] - #[quickcheck_macros::quickcheck] - fn platform() {} - - #[tauri_macros::module_command_test(os_all, "os > all", runtime)] - #[quickcheck_macros::quickcheck] - fn version() {} - - #[tauri_macros::module_command_test(os_all, "os > all", runtime)] - #[quickcheck_macros::quickcheck] - fn os_type() {} - - #[tauri_macros::module_command_test(os_all, "os > all", runtime)] - #[quickcheck_macros::quickcheck] - fn arch() {} - - #[tauri_macros::module_command_test(os_all, "os > all", runtime)] - #[quickcheck_macros::quickcheck] - fn tempdir() {} - - #[tauri_macros::module_command_test(os_all, "os > all", runtime)] - #[quickcheck_macros::quickcheck] - fn locale() {} -} diff --git a/core/tauri/src/endpoints/path.rs b/core/tauri/src/endpoints/path.rs deleted file mode 100644 index 66771c75529b..000000000000 --- a/core/tauri/src/endpoints/path.rs +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -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::{command_enum, module_command_handler, CommandModule}; - -/// The API descriptor. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - #[cmd(path_all, "path > all")] - ResolvePath { - path: String, - directory: Option, - }, - #[cmd(path_all, "path > all")] - Resolve { paths: Vec }, - #[cmd(path_all, "path > all")] - Normalize { path: String }, - #[cmd(path_all, "path > all")] - Join { paths: Vec }, - #[cmd(path_all, "path > all")] - Dirname { path: String }, - #[cmd(path_all, "path > all")] - Extname { path: String }, - #[cmd(path_all, "path > all")] - Basename { path: String, ext: Option }, - #[cmd(path_all, "path > all")] - IsAbsolute { path: String }, -} - -impl Cmd { - #[module_command_handler(path_all)] - fn resolve_path( - context: InvokeContext, - path: String, - directory: Option, - ) -> super::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)] - fn resolve(_context: InvokeContext, paths: Vec) -> super::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)] - fn normalize(_context: InvokeContext, path: String) -> super::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)] - fn join(_context: InvokeContext, mut paths: Vec) -> super::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)] - fn dirname(_context: InvokeContext, path: String) -> super::Result { - match Path::new(&path).parent() { - Some(p) => Ok(p.to_path_buf()), - None => Err(crate::error::into_anyhow(crate::api::Error::Path( - "Couldn't get the parent directory".into(), - ))), - } - } - - #[module_command_handler(path_all)] - fn extname(_context: InvokeContext, path: String) -> super::Result { - match Path::new(&path) - .extension() - .and_then(std::ffi::OsStr::to_str) - { - Some(p) => Ok(p.to_string()), - None => Err(crate::error::into_anyhow(crate::api::Error::Path( - "Couldn't get the extension of the file".into(), - ))), - } - } - - #[module_command_handler(path_all)] - fn basename( - _context: InvokeContext, - path: String, - ext: Option, - ) -> super::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::into_anyhow(crate::api::Error::Path( - "Couldn't get the basename".into(), - ))), - } - } - - #[module_command_handler(path_all)] - fn is_absolute(_context: InvokeContext, path: String) -> super::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 b9e8b5364e08..000000000000 --- a/core/tauri/src/endpoints/process.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::InvokeContext; -#[cfg(process_relaunch)] -use crate::Manager; -use crate::Runtime; -use serde::Deserialize; -use tauri_macros::{command_enum, module_command_handler, CommandModule}; - -/// The API descriptor. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// Relaunch application - Relaunch, - /// Close application with provided exit_code - #[cmd(process_exit, "process > exit")] - #[serde(rename_all = "camelCase")] - Exit { exit_code: i32 }, -} - -impl Cmd { - #[module_command_handler(process_relaunch)] - fn relaunch(context: InvokeContext) -> super::Result<()> { - context.window.app_handle().restart(); - Ok(()) - } - - #[cfg(not(process_relaunch))] - fn relaunch(_: InvokeContext) -> super::Result<()> { - Err(crate::Error::ApiNotAllowlisted("process > relaunch".into()).into_anyhow()) - } - - #[module_command_handler(process_exit)] - fn exit(context: InvokeContext, exit_code: i32) -> super::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 - context.window.app_handle.exit(exit_code); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - #[tauri_macros::module_command_test(process_relaunch, "process > relaunch", runtime)] - #[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 daf0d47b161e..000000000000 --- a/core/tauri/src/endpoints/shell.rs +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::InvokeContext; -use crate::{api::ipc::CallbackFn, Runtime}; -#[cfg(shell_scope)] -use crate::{Manager, Scopes}; -use serde::Deserialize; -use tauri_macros::{command_enum, 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_child_store() -> &'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>, - // Character encoding for stdout/stderr - encoding: Option, -} - -/// The API descriptor. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - /// The execute script API. - #[cmd(shell_script, "shell > execute or shell > sidecar")] - #[serde(rename_all = "camelCase")] - Execute { - program: String, - args: ExecuteArgs, - on_event_fn: CallbackFn, - #[serde(default)] - options: CommandOptions, - }, - #[cmd(shell_script, "shell > execute or shell > sidecar")] - StdinWrite { pid: ChildId, buffer: Buffer }, - #[cmd(shell_script, "shell > execute or shell > sidecar")] - KillChild { pid: ChildId }, - #[cmd(shell_open, "shell > open")] - Open { path: String, with: Option }, -} - -impl Cmd { - #[module_command_handler(shell_script)] - #[allow(unused_variables)] - fn execute( - context: InvokeContext, - program: String, - args: ExecuteArgs, - on_event_fn: CallbackFn, - options: CommandOptions, - ) -> super::Result { - let mut command = if options.sidecar { - #[cfg(not(shell_sidecar))] - return Err(crate::Error::ApiNotAllowlisted("shell > sidecar".to_string()).into_anyhow()); - #[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 configured_sidecar = context - .config - .tauri - .bundle - .external_bin - .as_ref() - .map(|bins| { - bins - .iter() - .find(|b| b == &&program_as_string || b == &&program_no_ext_as_string) - }) - .unwrap_or_default(); - if let Some(sidecar) = configured_sidecar { - context - .window - .state::() - .shell - .prepare_sidecar(&program.to_string_lossy(), sidecar, args) - .map_err(crate::error::into_anyhow)? - } else { - return Err(crate::Error::SidecarNotAllowed(program).into_anyhow()); - } - } - } else { - #[cfg(not(shell_execute))] - return Err(crate::Error::ApiNotAllowlisted("shell > execute".to_string()).into_anyhow()); - #[cfg(shell_execute)] - match context - .window - .state::() - .shell - .prepare(&program, args) - { - Ok(cmd) => cmd, - Err(e) => { - #[cfg(debug_assertions)] - eprintln!("{e}"); - return Err(crate::Error::ProgramNotAllowed(PathBuf::from(program)).into_anyhow()); - } - } - }; - #[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(); - } - if let Some(encoding) = options.encoding { - if let Some(encoding) = crate::api::process::Encoding::for_label(encoding.as_bytes()) { - command = command.encoding(encoding); - } else { - return Err(anyhow::anyhow!(format!("unknown encoding {encoding}"))); - } - } - let (mut rx, child) = command.spawn()?; - - let pid = child.pid(); - command_child_store().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_child_store().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) - } - } - - #[module_command_handler(shell_script)] - fn stdin_write( - _context: InvokeContext, - pid: ChildId, - buffer: Buffer, - ) -> super::Result<()> { - if let Some(child) = command_child_store().lock().unwrap().get_mut(&pid) { - match buffer { - Buffer::Text(t) => child.write(t.as_bytes())?, - Buffer::Raw(r) => child.write(&r)?, - } - } - Ok(()) - } - - #[module_command_handler(shell_script)] - fn kill_child(_context: InvokeContext, pid: ChildId) -> super::Result<()> { - if let Some(child) = command_child_store().lock().unwrap().remove(&pid) { - child.kill()?; - } - Ok(()) - } - - /// 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)] - fn open( - context: InvokeContext, - path: String, - with: Option, - ) -> super::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), - encoding: 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 5762c40e157d..000000000000 --- a/core/tauri/src/endpoints/window.rs +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(unused_imports)] - -use super::{InvokeContext, InvokeResponse}; -#[cfg(window_create)] -use crate::runtime::{webview::WindowBuilder, Dispatch}; -use crate::{ - runtime::{ - window::dpi::{Position, Size}, - UserAttentionType, - }, - utils::config::WindowConfig, - CursorIcon, Icon, Manager, Runtime, -}; -use serde::Deserialize; -use tauri_macros::{command_enum, module_command_handler, CommandModule}; - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum IconDto { - #[cfg(any(feature = "icon-png", feature = "icon-ico"))] - File(std::path::PathBuf), - #[cfg(any(feature = "icon-png", feature = "icon-ico"))] - Raw(Vec), - Rgba { - rgba: Vec, - width: u32, - height: u32, - }, -} - -impl From for Icon { - fn from(icon: IconDto) -> Self { - match icon { - #[cfg(any(feature = "icon-png", feature = "icon-ico"))] - IconDto::File(path) => Self::File(path), - #[cfg(any(feature = "icon-png", feature = "icon-ico"))] - IconDto::Raw(raw) => Self::Raw(raw), - IconDto::Rgba { - rgba, - width, - height, - } => Self::Rgba { - rgba, - width, - height, - }, - } - } -} - -/// Window management API descriptor. -#[derive(Deserialize)] -#[serde(tag = "type", content = "payload", rename_all = "camelCase")] -pub enum WindowManagerCmd { - // Getters - ScaleFactor, - InnerPosition, - OuterPosition, - InnerSize, - OuterSize, - IsFullscreen, - IsMinimized, - IsMaximized, - IsFocused, - IsDecorated, - IsResizable, - IsMaximizable, - IsMinimizable, - IsClosable, - IsVisible, - Title, - CurrentMonitor, - PrimaryMonitor, - AvailableMonitors, - Theme, - // Setters - #[cfg(window_center)] - Center, - #[cfg(window_request_user_attention)] - RequestUserAttention(Option), - #[cfg(window_set_resizable)] - SetResizable(bool), - #[cfg(window_set_maximizable)] - SetMaximizable(bool), - #[cfg(window_set_minimizable)] - SetMinimizable(bool), - #[cfg(window_set_closable)] - SetClosable(bool), - #[cfg(window_set_title)] - SetTitle(String), - #[cfg(window_maximize)] - Maximize, - #[cfg(window_unmaximize)] - Unmaximize, - #[cfg(all(window_maximize, window_unmaximize))] - ToggleMaximize, - #[cfg(window_minimize)] - Minimize, - #[cfg(window_unminimize)] - Unminimize, - #[cfg(window_show)] - Show, - #[cfg(window_hide)] - Hide, - #[cfg(window_close)] - Close, - #[cfg(window_set_decorations)] - SetDecorations(bool), - #[cfg(window_set_always_on_top)] - #[serde(rename_all = "camelCase")] - SetAlwaysOnTop(bool), - #[cfg(window_set_content_protected)] - SetContentProtected(bool), - #[cfg(window_set_size)] - SetSize(Size), - #[cfg(window_set_min_size)] - SetMinSize(Option), - #[cfg(window_set_max_size)] - SetMaxSize(Option), - #[cfg(window_set_position)] - SetPosition(Position), - #[cfg(window_set_fullscreen)] - SetFullscreen(bool), - #[cfg(window_set_focus)] - SetFocus, - #[cfg(window_set_icon)] - SetIcon { - icon: IconDto, - }, - #[cfg(window_set_skip_taskbar)] - SetSkipTaskbar(bool), - #[cfg(window_set_cursor_grab)] - SetCursorGrab(bool), - #[cfg(window_set_cursor_visible)] - SetCursorVisible(bool), - #[cfg(window_set_cursor_icon)] - SetCursorIcon(CursorIcon), - #[cfg(window_set_cursor_position)] - SetCursorPosition(Position), - #[cfg(window_set_ignore_cursor_events)] - SetIgnoreCursorEvents(bool), - #[cfg(window_start_dragging)] - StartDragging, - #[cfg(window_print)] - Print, - // internals - #[cfg(all(window_maximize, window_unmaximize))] - #[serde(rename = "__toggleMaximize")] - InternalToggleMaximize, - #[cfg(any(debug_assertions, feature = "devtools"))] - #[serde(rename = "__toggleDevtools")] - InternalToggleDevtools, -} - -pub fn into_allowlist_error(variant: &str) -> crate::Error { - match variant { - "center" => crate::Error::ApiNotAllowlisted("window > center".to_string()), - "requestUserAttention" => { - crate::Error::ApiNotAllowlisted("window > requestUserAttention".to_string()) - } - "setResizable" => crate::Error::ApiNotAllowlisted("window > setResizable".to_string()), - "setTitle" => crate::Error::ApiNotAllowlisted("window > setTitle".to_string()), - "maximize" => crate::Error::ApiNotAllowlisted("window > maximize".to_string()), - "unmaximize" => crate::Error::ApiNotAllowlisted("window > unmaximize".to_string()), - "toggleMaximize" => { - crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string()) - } - "minimize" => crate::Error::ApiNotAllowlisted("window > minimize".to_string()), - "unminimize" => crate::Error::ApiNotAllowlisted("window > unminimize".to_string()), - "show" => crate::Error::ApiNotAllowlisted("window > show".to_string()), - "hide" => crate::Error::ApiNotAllowlisted("window > hide".to_string()), - "close" => crate::Error::ApiNotAllowlisted("window > close".to_string()), - "setDecorations" => crate::Error::ApiNotAllowlisted("window > setDecorations".to_string()), - "setAlwaysOnTop" => crate::Error::ApiNotAllowlisted("window > setAlwaysOnTop".to_string()), - "setContentProtected" => { - crate::Error::ApiNotAllowlisted("window > setContentProtected".to_string()) - } - "setSize" => crate::Error::ApiNotAllowlisted("window > setSize".to_string()), - "setMinSize" => crate::Error::ApiNotAllowlisted("window > setMinSize".to_string()), - "setMaxSize" => crate::Error::ApiNotAllowlisted("window > setMaxSize".to_string()), - "setPosition" => crate::Error::ApiNotAllowlisted("window > setPosition".to_string()), - "setFullscreen" => crate::Error::ApiNotAllowlisted("window > setFullscreen".to_string()), - "setIcon" => crate::Error::ApiNotAllowlisted("window > setIcon".to_string()), - "setSkipTaskbar" => crate::Error::ApiNotAllowlisted("window > setSkipTaskbar".to_string()), - "setCursorGrab" => crate::Error::ApiNotAllowlisted("window > setCursorGrab".to_string()), - "setCursorVisible" => crate::Error::ApiNotAllowlisted("window > setCursorVisible".to_string()), - "setCursorIcon" => crate::Error::ApiNotAllowlisted("window > setCursorIcon".to_string()), - "setCursorPosition" => { - crate::Error::ApiNotAllowlisted("window > setCursorPosition".to_string()) - } - "setIgnoreCursorEvents" => { - crate::Error::ApiNotAllowlisted("window > setIgnoreCursorEvents".to_string()) - } - "startDragging" => crate::Error::ApiNotAllowlisted("window > startDragging".to_string()), - "print" => crate::Error::ApiNotAllowlisted("window > print".to_string()), - "__toggleMaximize" => { - crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string()) - } - "__toggleDevtools" => crate::Error::ApiNotAllowlisted("devtools".to_string()), - _ => crate::Error::ApiNotAllowlisted("window".to_string()), - } -} - -/// The API descriptor. -#[command_enum] -#[derive(Deserialize, CommandModule)] -#[cmd(async)] -#[serde(tag = "cmd", content = "data", rename_all = "camelCase")] -pub enum Cmd { - #[cmd(window_create, "window > create")] - CreateWebview { options: Box }, - Manage { - label: Option, - cmd: WindowManagerCmd, - }, -} - -impl Cmd { - #[module_command_handler(window_create)] - async fn create_webview( - context: InvokeContext, - mut options: Box, - ) -> super::Result<()> { - options.additional_browser_args = None; - crate::window::WindowBuilder::from_config(&context.window, *options) - .build() - .map_err(crate::error::into_anyhow)?; - Ok(()) - } - - async fn manage( - context: InvokeContext, - label: Option, - cmd: WindowManagerCmd, - ) -> super::Result { - Self::_manage(context, label, cmd) - .await - .map_err(crate::error::into_anyhow) - } - - 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::IsMinimized => return Ok(window.is_minimized()?.into()), - WindowManagerCmd::IsMaximized => return Ok(window.is_maximized()?.into()), - WindowManagerCmd::IsFocused => return Ok(window.is_focused()?.into()), - WindowManagerCmd::IsDecorated => return Ok(window.is_decorated()?.into()), - WindowManagerCmd::IsResizable => return Ok(window.is_resizable()?.into()), - WindowManagerCmd::IsMaximizable => return Ok(window.is_maximizable()?.into()), - WindowManagerCmd::IsMinimizable => return Ok(window.is_minimizable()?.into()), - WindowManagerCmd::IsClosable => return Ok(window.is_closable()?.into()), - WindowManagerCmd::IsVisible => return Ok(window.is_visible()?.into()), - WindowManagerCmd::Title => return Ok(window.title()?.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()), - WindowManagerCmd::Theme => return Ok(window.theme()?.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_maximizable)] - WindowManagerCmd::SetMaximizable(maximizable) => window.set_maximizable(maximizable)?, - #[cfg(window_set_minimizable)] - WindowManagerCmd::SetMinimizable(minimizable) => window.set_minimizable(minimizable)?, - #[cfg(window_set_closable)] - WindowManagerCmd::SetClosable(closable) => window.set_closable(closable)?, - #[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_content_protected)] - WindowManagerCmd::SetContentProtected(protected) => { - window.set_content_protected(protected)? - } - #[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_set_cursor_grab)] - WindowManagerCmd::SetCursorGrab(grab) => window.set_cursor_grab(grab)?, - #[cfg(window_set_cursor_visible)] - WindowManagerCmd::SetCursorVisible(visible) => window.set_cursor_visible(visible)?, - #[cfg(window_set_cursor_icon)] - WindowManagerCmd::SetCursorIcon(icon) => window.set_cursor_icon(icon)?, - #[cfg(window_set_cursor_position)] - WindowManagerCmd::SetCursorPosition(position) => window.set_cursor_position(position)?, - #[cfg(window_set_ignore_cursor_events)] - WindowManagerCmd::SetIgnoreCursorEvents(ignore_cursor) => { - window.set_ignore_cursor_events(ignore_cursor)? - } - #[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()?, - } - } - } - #[cfg(any(debug_assertions, feature = "devtools"))] - WindowManagerCmd::InternalToggleDevtools => { - if window.is_devtools_open() { - window.close_devtools(); - } else { - window.open_devtools(); - } - } - } - #[allow(unreachable_code)] - Ok(().into()) - } -} diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index f9c1b3e19427..03ad98f8900a 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::{fmt, path::PathBuf}; +use std::fmt; /// A generic boxed error. #[derive(Debug)] @@ -34,57 +34,30 @@ pub enum Error { /// Runtime error. #[error("runtime error: {0}")] Runtime(#[from] tauri_runtime::Error), - /// 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(std::io::Error), - /// Client with specified ID not found. - #[error("http client dropped or not initialized")] - HttpClientNotInitialized, - /// API not whitelisted on tauri.conf.json - #[error("'{0}' not in the allowlist (https://tauri.app/docs/api/config#tauri.allowlist)")] - ApiNotAllowlisted(String), /// Invalid args when running a command. #[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(SetupError), - /// Tauri updater error. - #[cfg(updater)] - #[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))] - #[error("Updater: {0}")] - TauriUpdater(#[from] crate::updater::Error), /// Error initializing plugin. #[error("failed to initialize plugin `{0}`: {1}")] PluginInitialization(String, String), @@ -95,25 +68,6 @@ pub enum Error { /// Task join error. #[error(transparent)] JoinError(#[from] tokio::task::JoinError), - /// 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), /// An error happened inside the isolation pattern. #[cfg(feature = "isolation")] #[error("isolation pattern error: {0}")] @@ -131,25 +85,8 @@ pub enum Error { /// The Window's raw handle is invalid for the platform. #[error("Unexpected `raw_window_handle` for the current platform")] InvalidWindowHandle, -} - -pub(crate) fn into_anyhow(err: T) -> anyhow::Error { - anyhow::anyhow!(err.to_string()) -} - -impl Error { - #[allow(dead_code)] - pub(crate) fn into_anyhow(self) -> anyhow::Error { - anyhow::anyhow!(self.to_string()) - } -} - -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) - } - } + /// JNI error. + #[cfg(target_os = "android")] + #[error("jni error: {0}")] + Jni(#[from] jni::errors::Error), } 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.rs b/core/tauri/src/event/listener.rs similarity index 73% rename from core/tauri/src/event.rs rename to core/tauri/src/event/listener.rs index 68d173f5a07b..db1263cf3da8 100644 --- a/core/tauri/src/event.rs +++ b/core/tauri/src/event/listener.rs @@ -2,59 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use super::{Event, EventHandler}; + use std::{ boxed::Box, cell::Cell, 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), @@ -77,7 +34,7 @@ struct InnerListeners { } /// A self-contained event manager. -pub(crate) struct Listeners { +pub struct Listeners { inner: Arc, } @@ -284,7 +241,6 @@ mod test { #[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. @@ -298,54 +254,3 @@ mod test { } } } - -pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: u32) -> 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: u32, - 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/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 fb9dea03bca0..4bbe77f0a9d6 100644 --- a/core/tauri/src/hooks.rs +++ b/core/tauri/src/hooks.rs @@ -19,7 +19,7 @@ pub type SetupHook = Box) -> Result<(), Box> + Send>; /// A closure that is run every time Tauri receives a message it doesn't explicitly handle. -pub type InvokeHandler = dyn Fn(Invoke) + Send + Sync + 'static; +pub type InvokeHandler = dyn Fn(Invoke) -> bool + Send + Sync + 'static; /// A closure that is responsible for respond a JS message. pub type InvokeResponder = @@ -61,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. @@ -166,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 { @@ -292,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 32c86cc9237e..43a5b5da82cf 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -17,27 +17,14 @@ //! - **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. -//! - **shell-open-api**: Enables the [`api::shell`] module. -//! - **http-api**: Enables the [`api::http`] module. -//! - **http-multipart**: Adds support to `multipart/form-data` requests. -//! - **reqwest-client**: Alias for the `http-api` feature flag. +//! - **native-tls**: Provides TLS support to connect over HTTPS. //! - **native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL. -//! - **reqwest-native-tls-vendored**: Alias for the `native-tls-vendored` feature flag. -//! - **os-api**: Enables the [`api::os`] module. -//! - **process-command-api**: Enables the [`api::process::Command`] APIs. -//! - **global-shortcut**: Enables the global shortcut APIs. -//! - **clipboard**: Enables the clipboard APIs. -//! - **process-relaunch-dangerous-allow-symlink-macos**: Allows the [`api::process::current_binary`] function to allow symlinks on macOS (this is dangerous, see the Security section in the documentation website). -//! - **dialog**: Enables the [`api::dialog`] module. -//! - **notification**: Enables the [`api::notification`] module. -//! - **fs-extract-api**: Enabled the `tauri::api::file::Extract` API. -//! - **cli**: Enables usage of `clap` for CLI argument parsing. Enabled by default if the `cli` config is defined on the `tauri.conf.json` file. +//! - **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. -//! - **windows7-compat**: Enables compatibility with Windows 7 for the notification API. //! - **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`. @@ -50,153 +37,62 @@ //! 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.app/en/docs/api/js/modules/clipboard/). -//! - **clipboard-read-text**: Enables the [`readText` API](https://tauri.app/en/docs/api/js/modules/clipboard/#readtext). -//! - **clipboard-write-text**: Enables the [`writeText` API](https://tauri.app/en/docs/api/js/modules/clipboard/#writetext). -//! -//! ### Dialog allowlist -//! -//! - **dialog-all**: Enables all [Dialog APIs](https://tauri.app/en/docs/api/js/modules/dialog). -//! - **dialog-ask**: Enables the [`ask` API](https://tauri.app/en/docs/api/js/modules/dialog#ask). -//! - **dialog-confirm**: Enables the [`confirm` API](https://tauri.app/en/docs/api/js/modules/dialog#confirm). -//! - **dialog-message**: Enables the [`message` API](https://tauri.app/en/docs/api/js/modules/dialog#message). -//! - **dialog-open**: Enables the [`open` API](https://tauri.app/en/docs/api/js/modules/dialog#open). -//! - **dialog-save**: Enables the [`save` API](https://tauri.app/en/docs/api/js/modules/dialog#save). -//! -//! ### Filesystem allowlist -//! -//! - **fs-all**: Enables all [Filesystem APIs](https://tauri.app/en/docs/api/js/modules/fs). -//! - **fs-copy-file**: Enables the [`copyFile` API](https://tauri.app/en/docs/api/js/modules/fs#copyfile). -//! - **fs-create-dir**: Enables the [`createDir` API](https://tauri.app/en/docs/api/js/modules/fs#createdir). -//! - **fs-exists**: Enables the [`exists` API](https://tauri.app/en/docs/api/js/modules/fs#exists). -//! - **fs-read-dir**: Enables the [`readDir` API](https://tauri.app/en/docs/api/js/modules/fs#readdir). -//! - **fs-read-file**: Enables the [`readTextFile` API](https://tauri.app/en/docs/api/js/modules/fs#readtextfile) and the [`readBinaryFile` API](https://tauri.app/en/docs/api/js/modules/fs#readbinaryfile). -//! - **fs-remove-dir**: Enables the [`removeDir` API](https://tauri.app/en/docs/api/js/modules/fs#removedir). -//! - **fs-remove-file**: Enables the [`removeFile` API](https://tauri.app/en/docs/api/js/modules/fs#removefile). -//! - **fs-rename-file**: Enables the [`renameFile` API](https://tauri.app/en/docs/api/js/modules/fs#renamefile). -//! - **fs-write-file**: Enables the [`writeFile` API](https://tauri.app/en/docs/api/js/modules/fs#writefile) and the [`writeBinaryFile` API](https://tauri.app/en/docs/api/js/modules/fs#writebinaryfile). -//! -//! ### Global shortcut allowlist -//! -//! - **global-shortcut-all**: Enables all [GlobalShortcut APIs](https://tauri.app/en/docs/api/js/modules/globalShortcut). -//! -//! ### HTTP allowlist -//! -//! - **http-all**: Enables all [HTTP APIs](https://tauri.app/en/docs/api/js/modules/http). -//! - **http-request**: Enables the [`request` APIs](https://tauri.app/en/docs/api/js/classes/http.client/). -//! -//! ### Notification allowlist -//! -//! - **notification-all**: Enables all [Notification APIs](https://tauri.app/en/docs/api/js/modules/notification). -//! -//! ### OS allowlist -//! -//! - **os-all**: Enables all [OS APIs](https://tauri.app/en/docs/api/js/modules/os). -//! -//! ### Path allowlist -//! -//! - **path-all**: Enables all [Path APIs](https://tauri.app/en/docs/api/js/modules/path). -//! -//! ### Process allowlist -//! -//! - **process-all**: Enables all [Process APIs](https://tauri.app/en/docs/api/js/modules/process). -//! - **process-exit**: Enables the [`exit` API](https://tauri.app/en/docs/api/js/modules/process#exit). -//! - **process-relaunch**: Enables the [`relaunch` API](https://tauri.app/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.app/en/docs/api/js/modules/shell). -//! - **shell-execute**: Enables [executing arbitrary programs](https://tauri.app/en/docs/api/js/classes/shell.Command#constructor). -//! - **shell-sidecar**: Enables [executing a `sidecar` program](https://tauri.app/en/docs/api/js/classes/shell.Command#sidecar). -//! - **shell-open**: Enables the [`open` API](https://tauri.app/en/docs/api/js/modules/shell#open). -//! -//! ### Window allowlist -//! -//! - **window-all**: Enables all [Window APIs](https://tauri.app/en/docs/api/js/modules/window). -//! - **window-create**: Enables the API used to [create new windows](https://tauri.app/en/docs/api/js/classes/window.webviewwindow/). -//! - **window-center**: Enables the [`center` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#center). -//! - **window-request-user-attention**: Enables the [`requestUserAttention` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#requestuserattention). -//! - **window-set-resizable**: Enables the [`setResizable` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setresizable). -//! - **window-set-maximizable**: Enables the [`setMaximizable` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setmaximizable). -//! - **window-set-minimizable**: Enables the [`setMinimizable` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setminimizable). -//! - **window-set-closable**: Enables the [`setClosable` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setclosable). -//! - **window-set-title**: Enables the [`setTitle` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#settitle). -//! - **window-maximize**: Enables the [`maximize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#maximize). -//! - **window-unmaximize**: Enables the [`unmaximize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#unmaximize). -//! - **window-minimize**: Enables the [`minimize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#minimize). -//! - **window-unminimize**: Enables the [`unminimize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#unminimize). -//! - **window-show**: Enables the [`show` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#show). -//! - **window-hide**: Enables the [`hide` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#hide). -//! - **window-close**: Enables the [`close` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#close). -//! - **window-set-decorations**: Enables the [`setDecorations` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setdecorations). -//! - **window-set-always-on-top**: Enables the [`setAlwaysOnTop` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setalwaysontop). -//! - **window-set-content-protected**: Enables the [`setContentProtected` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setcontentprotected). -//! - **window-set-size**: Enables the [`setSize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setsize). -//! - **window-set-min-size**: Enables the [`setMinSize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setminsize). -//! - **window-set-max-size**: Enables the [`setMaxSize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setmaxsize). -//! - **window-set-position**: Enables the [`setPosition` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setposition). -//! - **window-set-fullscreen**: Enables the [`setFullscreen` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setfullscreen). -//! - **window-set-focus**: Enables the [`setFocus` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setfocus). -//! - **window-set-icon**: Enables the [`setIcon` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#seticon). -//! - **window-set-skip-taskbar**: Enables the [`setSkipTaskbar` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setskiptaskbar). -//! - **window-set-cursor-grab**: Enables the [`setCursorGrab` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setcursorgrab). -//! - **window-set-cursor-visible**: Enables the [`setCursorVisible` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setcursorvisible). -//! - **window-set-cursor-icon**: Enables the [`setCursorIcon` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setcursoricon). -//! - **window-set-cursor-position**: Enables the [`setCursorPosition` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setcursorposition). -//! - **window-set-ignore-cursor-events**: Enables the [`setIgnoreCursorEvents` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setignorecursorevents). -//! - **window-start-dragging**: Enables the [`startDragging` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#startdragging). -//! - **window-print**: Enables the [`print` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#print). -//! -//! ### App allowlist -//! -//! - **app-all**: Enables all [App APIs](https://tauri.app/en/docs/api/js/modules/app). -//! - **app-show**: Enables the [`show` API](https://tauri.app/en/docs/api/js/modules/app#show). -//! - **app-hide**: Enables the [`hide` API](https://tauri.app/en/docs/api/js/modules/app#hide). #![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; use tauri_runtime as runtime; -#[cfg(protocol_asset)] -mod asset_protocol; +#[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; mod state; -#[cfg(updater)] -#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))] -pub mod updater; + pub use tauri_utils as utils; /// A Tauri [`Runtime`] wrapper around wry. @@ -204,6 +100,41 @@ pub use tauri_utils as utils; #[cfg_attr(doc_cfg, doc(cfg(feature = "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; @@ -240,8 +171,8 @@ pub use { }; pub use { self::app::{ - App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, PathResolver, - RunEvent, WindowEvent, + App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, RunEvent, + WindowEvent, }, self::hooks::{ Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokePayload, InvokeResolver, @@ -266,73 +197,49 @@ pub use { scope::*, }; -#[cfg(feature = "clipboard")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "clipboard")))] -pub use self::runtime::ClipboardManager; - -#[cfg(all(desktop, feature = "global-shortcut"))] -#[cfg_attr(doc_cfg, doc(cfg(feature = "global-shortcut")))] -pub use self::runtime::GlobalShortcutManager; - /// The Tauri version. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -/// Updater events. -#[cfg(updater)] -#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))] -#[derive(Debug, Clone)] -pub enum UpdaterEvent { - /// An update is available. - UpdateAvailable { - /// The update body. - body: String, - /// The update release date. - date: Option, - /// The update version. - version: String, - }, - /// The update is pending and about to be downloaded. - Pending, - /// The update download received a progress event. - DownloadProgress { - /// The amount that was downloaded on this iteration. - /// Does not accumulate with previous chunks. - chunk_length: usize, - /// The total - content_length: Option, - }, - /// The update has been downloaded and is now about to be installed. - Downloaded, - /// The update has been applied and the app is now up to date. - Updated, - /// The app is already up to date. - AlreadyUpToDate, - /// An error occurred while updating. - Error(String), -} +#[cfg(target_os = "ios")] +#[doc(hidden)] +pub fn log_stdout() { + use std::{ + ffi::CString, + fs::File, + io::{BufRead, BufReader}, + os::unix::prelude::*, + thread, + }; -#[cfg(updater)] -impl UpdaterEvent { - pub(crate) fn status_message(self) -> &'static str { - match self { - Self::Pending => updater::EVENT_STATUS_PENDING, - Self::Downloaded => updater::EVENT_STATUS_DOWNLOADED, - Self::Updated => updater::EVENT_STATUS_SUCCESS, - Self::AlreadyUpToDate => updater::EVENT_STATUS_UPTODATE, - Self::Error(_) => updater::EVENT_STATUS_ERROR, - _ => unreachable!(), + 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 { - /// Updater event. - #[cfg(updater)] - #[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))] - Updater(UpdaterEvent), -} +pub enum EventLoopMessage {} /// The webview runtime interface. A wrapper around [`runtime::Runtime`] with the proper user event type associated. pub trait Runtime: runtime::Runtime {} @@ -471,12 +378,11 @@ pub struct Context { pub(crate) assets: Arc, 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 { @@ -485,11 +391,12 @@ impl fmt::Debug for Context { d.field("config", &self.config) .field("default_window_icon", &self.default_window_icon) .field("app_icon", &self.app_icon) - .field("system_tray_icon", &self.system_tray_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() } } @@ -532,12 +439,14 @@ impl Context { } /// 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 @@ -561,13 +470,6 @@ 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)] @@ -576,25 +478,36 @@ impl Context { assets: Arc, default_window_icon: Option, app_icon: Option>, - system_tray_icon: Option, package_info: PackageInfo, info_plist: (), pattern: Pattern, - #[cfg(shell_scope)] shell_scope: scope::ShellScopeConfig, ) -> Self { Self { config, assets, default_window_icon, app_icon, - system_tray_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 @@ -610,6 +523,11 @@ pub trait Manager: sealed::ManagerBase { 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. @@ -883,26 +801,20 @@ 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() } } @@ -976,57 +888,6 @@ mod tests { } } } - - #[test] - fn all_allowlist_features_are_aliased() { - let manifest = get_manifest(); - let all_modules = manifest - .features - .iter() - .find(|(f, _)| f.as_str() == "api-all") - .map(|(_, enabled)| enabled) - .expect("api-all feature must exist"); - - let checked_features = CHECKED_FEATURES.split(',').collect::>(); - assert!( - checked_features.contains(&"api-all"), - "`api-all` is not aliased" - ); - - // features that look like an allowlist feature, but are not - let allowed = [ - "fs-extract-api", - "http-api", - "http-multipart", - "os-api", - "process-command-api", - "process-relaunch-dangerous-allow-symlink-macos", - "window-data-url", - ]; - - for module_all_feature in all_modules { - let module = module_all_feature.replace("-all", ""); - assert!( - checked_features.contains(&module_all_feature.as_str()), - "`{module}` is not aliased" - ); - - let module_prefix = format!("{module}-"); - // we assume that module features are the ones that start with `-` - // though it's not 100% accurate, we have an allowed list to fix it - let module_features = manifest - .features - .keys() - .filter(|f| f.starts_with(&module_prefix)); - for module_feature in module_features { - assert!( - allowed.contains(&module_feature.as_str()) - || checked_features.contains(&module_feature.as_str()), - "`{module_feature}` is not aliased" - ); - } - } - } } #[cfg(test)] diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index a57e1cc052d2..416692f552b8 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -53,7 +53,7 @@ use crate::{ }; #[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}; @@ -73,6 +73,12 @@ 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 { @@ -145,11 +151,6 @@ fn set_csp( Csp::DirectiveMap(csp).to_string() } -#[cfg(target_os = "linux")] -fn set_html_csp(html: &str, csp: &str) -> String { - html.replacen(tauri_utils::html::CSP_TOKEN, csp, 1) -} - // inspired by https://github.com/rust-lang/rust/blob/1be5c8f90912c446ecbdc405cbc4a89f9acd20fd/library/alloc/src/str.rs#L260-L297 fn replace_with_callback String>( original: &str, @@ -215,6 +216,7 @@ pub struct InnerWindowManager { assets: Arc, pub(crate) default_window_icon: Option, pub(crate) app_icon: Option>, + #[cfg(desktop)] pub(crate) tray_icon: Option, package_info: PackageInfo, @@ -236,17 +238,21 @@ pub struct InnerWindowManager { 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("tray_icon", &self.tray_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() } } @@ -318,6 +324,7 @@ impl WindowManager { 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, @@ -370,13 +377,17 @@ impl WindowManager { pub(crate) fn get_url(&self) -> Cow<'_, Url> { match self.base_path() { AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url), - #[cfg(windows)] - _ => Cow::Owned(Url::parse("https://tauri.localhost").unwrap()), - #[cfg(not(windows))] - _ => Cow::Owned(Url::parse("tauri://localhost").unwrap()), + _ => self.protocol_url(), } } + 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 { if cfg!(feature = "custom-protocol") { self.inner.config.tauri.security.csp.clone() @@ -442,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() { @@ -496,7 +506,7 @@ impl WindowManager { registered_scheme_protocols.push("tauri".into()); } - #[cfg(protocol_asset)] + #[cfg(feature = "protocol-asset")] if !registered_scheme_protocols.contains(&"asset".into()) { let asset_scope = self.state().get::().asset_protocol.clone(); pending.register_uri_scheme_protocol("asset", move |request| { @@ -557,43 +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()) - )); - } - } }) } @@ -689,46 +696,122 @@ impl WindowManager { >, ) -> 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() + request.uri().split(&['?', '#'][..]).next().unwrap() + }; + + 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 asset = manager.get_asset(path)?; - let mut builder = HttpResponseBuilder::new() - .header("Access-Control-Allow-Origin", &window_origin) - .mimetype(&asset.mime_type); - if let Some(csp) = &asset.csp_header { - builder = builder.header("Content-Security-Policy", csp); - } - let mut response = builder.body(asset.bytes)?; - 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(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 body = set_html_csp(&String::from_utf8_lossy(response.body()), &response_csp); - *response.body_mut() = body.as_bytes().to_vec(); - } - } else { - #[cfg(target_os = "linux")] + 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"))] { - if let Some(csp) = &asset.csp_header { - let body = set_html_csp(&String::from_utf8_lossy(response.body()), csp); - *response.body_mut() = body.as_bytes().to_vec(); + 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)? + }; + 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) }) @@ -761,8 +844,6 @@ impl WindowManager { plugin_initialization_script: &'a str, #[raw] freeze_prototype: &'a str, - #[raw] - hotkeys: &'a str, } let bundle_script = if with_global_tauri { @@ -777,34 +858,6 @@ impl WindowManager { "" }; - #[cfg(any(debug_assertions, feature = "devtools"))] - let hotkeys = &format!( - " - {}; - window.hotkeys('{}', () => {{ - window.__TAURI_INVOKE__('tauri', {{ - __tauriModule: 'Window', - message: {{ - cmd: 'manage', - data: {{ - cmd: {{ - type: '__toggleDevtools' - }} - }} - }} - }}); - }}); - ", - include_str!("../scripts/hotkey.js"), - if cfg!(target_os = "macos") { - "command+option+i" - } else { - "ctrl+shift+i" - } - ); - #[cfg(not(any(debug_assertions, feature = "devtools")))] - let hotkeys = ""; - InitJavascript { pattern_script, ipc_script, @@ -823,7 +876,6 @@ impl WindowManager { event_initialization_script: &self.event_initialization_script(), plugin_initialization_script, freeze_prototype, - hotkeys, } .render_default(&Default::default()) .map(|s| s.into_string()) @@ -865,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(), @@ -892,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) { @@ -906,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<()> { @@ -936,7 +988,11 @@ impl WindowManager { #[allow(unused_mut)] // mut url only for the data-url parsing let mut url = match &pending.webview_attributes.url { WindowUrl::App(path) => { - let url = self.get_url(); + 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 @@ -948,7 +1004,16 @@ impl WindowManager { url.into_owned() } } - WindowUrl::External(url) => url.clone(), + 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 + } _ => unimplemented!(), }; @@ -991,20 +1056,48 @@ impl WindowManager { } } + #[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, app_handle.clone())?; - pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle)); + 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); @@ -1093,6 +1186,15 @@ impl WindowManager { .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 } @@ -1241,7 +1343,7 @@ fn on_window_event( let windows = windows_map.values(); for window in windows { window.eval(&format!( - r#"(function () {{ const metadata = window.__TAURI_METADATA__; if (metadata != null) {{ 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}"); }} }})()"#, ))?; } } 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 8bf953871a24..2318e5972eaa 100644 --- a/core/tauri/src/pattern.rs +++ b/core/tauri/src/pattern.rs @@ -109,7 +109,7 @@ pub(crate) struct PatternJavascript { #[allow(dead_code)] pub(crate) fn format_real_schema(schema: &str) -> String { - if cfg!(windows) { + if cfg!(windows) || cfg!(target_os = "android") { format!("https://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}") } else { format!("{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}") diff --git a/core/tauri/src/plugin.rs b/core/tauri/src/plugin.rs index d27fa5f42c31..c7a67913cec4 100644 --- a/core/tauri/src/plugin.rs +++ b/core/tauri/src/plugin.rs @@ -12,10 +12,14 @@ 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 { @@ -54,16 +58,63 @@ 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) -> Result<()> + Send; -type SetupWithConfigHook = dyn FnOnce(&AppHandle, T) -> Result<()> + Send; +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 + } +} + +/// 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`]. /// /// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly. @@ -126,7 +177,7 @@ type OnDrop = dyn FnOnce(AppHandle) + Send; /// /// pub fn build(self) -> TauriPlugin { /// PluginBuilder::new("example") -/// .setup(move |app_handle| { +/// .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); /// @@ -139,8 +190,7 @@ type OnDrop = dyn FnOnce(AppHandle) + Send; pub struct Builder { name: &'static str, invoke_handler: Box>, - setup: Option>>, - setup_with_config: Option>>, + setup: Option>>, js_init_script: Option, on_page_load: Box>, on_webview_ready: Box>, @@ -154,9 +204,8 @@ impl Builder { Self { name, setup: None, - setup_with_config: 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(|_, _| ()), @@ -190,7 +239,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 @@ -234,10 +283,6 @@ impl Builder { /// Define a closure that runs when the plugin is registered. /// - /// This is a convenience function around [setup_with_config], without the need to specify a configuration object. - /// - /// The closure gets called before the [setup_with_config] closure. - /// /// # Examples /// /// ```rust @@ -251,61 +296,23 @@ impl Builder { /// /// fn init() -> TauriPlugin { /// Builder::new("example") - /// .setup(|app_handle| { - /// app_handle.manage(PluginState::default()); + /// .setup(|app, api| { + /// app.manage(PluginState::default()); /// /// Ok(()) /// }) /// .build() /// } /// ``` - /// - /// [setup_with_config]: struct.Builder.html#method.setup_with_config #[must_use] pub fn setup(mut self, setup: F) -> Self where - F: FnOnce(&AppHandle) -> Result<()> + Send + 'static, + F: FnOnce(&AppHandle, PluginApi) -> Result<()> + Send + 'static, { self.setup.replace(Box::new(setup)); self } - /// Define a closure that runs when the plugin is registered, accepting a configuration object set on `tauri.conf.json > plugins > yourPluginName`. - /// - /// If your plugin is not pulling a configuration object from `tauri.conf.json`, use [setup]. - /// - /// The closure gets called after the [setup] closure. - /// - /// # Examples - /// - /// ```rust,no_run - /// #[derive(serde::Deserialize)] - /// struct Config { - /// api_url: String, - /// } - /// - /// fn init() -> tauri::plugin::TauriPlugin { - /// tauri::plugin::Builder::::new("api") - /// .setup_with_config(|_app_handle, config| { - /// println!("config: {:?}", config.api_url); - /// Ok(()) - /// }) - /// .build() - /// } - /// - /// tauri::Builder::default().plugin(init()); - /// ``` - /// - /// [setup]: struct.Builder.html#method.setup - #[must_use] - pub fn setup_with_config(mut self, setup_with_config: F) -> Self - where - F: FnOnce(&AppHandle, C) -> Result<()> + Send + 'static, - { - self.setup_with_config.replace(Box::new(setup_with_config)); - self - } - /// Callback invoked when the webview performs a navigation to a page. /// /// # Examples @@ -418,7 +425,6 @@ impl Builder { app: None, invoke_handler: self.invoke_handler, setup: self.setup, - setup_with_config: self.setup_with_config, js_init_script: self.js_init_script, on_page_load: self.on_page_load, on_webview_ready: self.on_webview_ready, @@ -433,8 +439,7 @@ pub struct TauriPlugin { name: &'static str, app: Option>, invoke_handler: Box>, - setup: Option>>, - setup_with_config: Option>>, + setup: Option>>, js_init_script: Option, on_page_load: Box>, on_webview_ready: Box>, @@ -458,10 +463,15 @@ impl Plugin for TauriPlugin { fn initialize(&mut self, app: &AppHandle, config: JsonValue) -> Result<()> { self.app.replace(app.clone()); if let Some(s) = self.setup.take() { - (s)(app)?; - } - if let Some(s) = self.setup_with_config.take() { - (s)(app, serde_json::from_value(config)?)?; + (s)( + app, + PluginApi { + name: self.name, + handle: app.clone(), + raw_config: Arc::new(config.clone()), + config: serde_json::from_value(config)?, + }, + )?; } Ok(()) } @@ -482,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) } } @@ -573,20 +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 {target} not found")); + 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/api/process.rs b/core/tauri/src/process.rs similarity index 86% rename from core/tauri/src/api/process.rs rename to core/tauri/src/process.rs index 1cb4c0e08853..7eef25939acf 100644 --- a/core/tauri/src/api/process.rs +++ b/core/tauri/src/process.rs @@ -8,13 +8,6 @@ use crate::Env; use std::path::PathBuf; -#[cfg(feature = "process-command-api")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "process-command-api")))] -mod command; -#[cfg(feature = "process-command-api")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "process-command-api")))] -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 @@ -41,7 +34,7 @@ pub use command::*; /// # Examples /// /// ```rust,no_run -/// use tauri::{api::process::current_binary, Env, Manager}; +/// use tauri::{process::current_binary, Env, Manager}; /// let current_binary_path = current_binary(&Env::default()).unwrap(); /// /// tauri::Builder::default() @@ -70,7 +63,7 @@ pub fn current_binary(_env: &Env) -> std::io::Result { /// # Examples /// /// ```rust,no_run -/// use tauri::{api::process::restart, Env, Manager}; +/// use tauri::{process::restart, Env, Manager}; /// /// tauri::Builder::default() /// .setup(|app| { diff --git a/core/tauri/src/scope/fs.rs b/core/tauri/src/scope/fs.rs index 1deccf4ccbf9..754720e7d435 100644 --- a/core/tauri/src/scope/fs.rs +++ b/core/tauri/src/scope/fs.rs @@ -10,14 +10,9 @@ use std::{ }; pub use glob::Pattern; -use tauri_utils::{ - config::{Config, FsAllowlistScope}, - Env, PackageInfo, -}; +use tauri_utils::config::FsScope; use uuid::Uuid; -use crate::api::path::parse as parse_path; - /// Scope change event. #[derive(Debug, Clone)] pub enum Event { @@ -85,15 +80,14 @@ fn push_pattern, F: Fn(&str) -> Result>( + 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) = parse_path(config, package_info, env, path) { + if let Ok(path) = manager.path().parse(path) { push_pattern(&mut allowed_patterns, path, Pattern::new)?; } } @@ -101,14 +95,14 @@ impl Scope { let mut forbidden_patterns = HashSet::new(); if let Some(forbidden_paths) = scope.forbidden_paths() { for path in forbidden_paths { - if let Ok(path) = parse_path(config, package_info, env, path) { + if let Ok(path) = manager.path().parse(path) { push_pattern(&mut forbidden_patterns, path, Pattern::new)?; } } } let require_literal_leading_dot = match scope { - FsAllowlistScope::Scope { + FsScope::Scope { require_literal_leading_dot: Some(require), .. } => *require, diff --git a/core/tauri/src/scope/http.rs b/core/tauri/src/scope/http.rs deleted file mode 100644 index fe2bdb38b0ce..000000000000 --- a/core/tauri/src/scope/http.rs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use glob::Pattern; -use tauri_utils::config::HttpAllowlistScope; - -/// 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. - #[allow(dead_code)] - pub(crate) fn for_http_api(scope: &HttpAllowlistScope) -> Self { - Self { - allowed_urls: scope - .0 - .iter() - .flat_map(|url| { - [ - glob::Pattern::new(url.as_str()) - .unwrap_or_else(|_| panic!("scoped URL is not a valid glob pattern: `{url}`")), - glob::Pattern::new( - url - .as_str() - .strip_suffix('/') - .unwrap_or_else(|| url.as_str()), - ) - .unwrap_or_else(|_| panic!("scoped URL is not a valid glob pattern: `{url}`")), - ] - }) - .collect(), - } - } - - /// Determines if the given URL is allowed on this scope. - pub fn is_allowed(&self, url: &url::Url) -> bool { - self - .allowed_urls - .iter() - .any(|allowed| allowed.matches(url.as_str())) - } -} - -#[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())); - - let scope = super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://*".parse().unwrap()])); - - assert!(scope.is_allowed(&"http://something.else".parse().unwrap())); - assert!(scope.is_allowed(&"http://something.else/path/to/file".parse().unwrap())); - assert!(!scope.is_allowed(&"https://something.else".parse().unwrap())); - - let scope = super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://**".parse().unwrap()])); - - assert!(scope.is_allowed(&"http://something.else".parse().unwrap())); - assert!(scope.is_allowed(&"http://something.else/path/to/file".parse().unwrap())); - } -} diff --git a/core/tauri/src/scope/ipc.rs b/core/tauri/src/scope/ipc.rs index c127fce98e0e..5a0f6db27eff 100644 --- a/core/tauri/src/scope/ipc.rs +++ b/core/tauri/src/scope/ipc.rs @@ -14,7 +14,6 @@ pub struct RemoteDomainAccessScope { domain: String, windows: Vec, plugins: Vec, - enable_tauri_api: bool, } impl RemoteDomainAccessScope { @@ -25,7 +24,6 @@ impl RemoteDomainAccessScope { domain: domain.into(), windows: Vec::new(), plugins: Vec::new(), - enable_tauri_api: false, } } @@ -47,9 +45,13 @@ impl RemoteDomainAccessScope { self } - /// Enables access to the Tauri API. - pub fn enable_tauri_api(mut self) -> Self { - self.enable_tauri_api = true; + /// 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 } @@ -67,11 +69,6 @@ impl RemoteDomainAccessScope { pub fn plugins(&self) -> &Vec { &self.plugins } - - /// Whether this scope enables Tauri API access or not. - pub fn enables_tauri_api(&self) -> bool { - self.enable_tauri_api - } } pub(crate) struct RemoteAccessError { @@ -99,7 +96,6 @@ impl Scope { domain: s.domain, windows: s.windows, plugins: s.plugins, - enable_tauri_api: s.enable_tauri_api, }) .collect(); @@ -119,7 +115,7 @@ impl Scope { /// app.ipc_scope().configure_remote_access( /// RemoteDomainAccessScope::new("tauri.app") /// .add_window("main") - /// .enable_tauri_api() + /// .add_plugins(["path", "event"]) /// ); /// Ok(()) /// }); @@ -174,14 +170,16 @@ mod tests { use crate::{ api::ipc::CallbackFn, test::{assert_ipc_response, mock_app, MockRuntime}, - App, InvokePayload, Manager, Window, + App, InvokePayload, Manager, Window, WindowBuilder, }; const PLUGIN_NAME: &str = "test"; fn test_context(scopes: Vec) -> (App, Window) { let app = mock_app(); - let window = app.get_window("main").unwrap(); + let window = WindowBuilder::new(&app, "main", Default::default()) + .build() + .unwrap(); for scope in scopes { app.ipc_scope().configure_remote_access(scope); @@ -190,21 +188,18 @@ mod tests { (app, window) } - fn app_version_payload() -> InvokePayload { + fn path_is_absolute_payload() -> InvokePayload { let callback = CallbackFn(0); let error = CallbackFn(1); let mut payload = serde_json::Map::new(); - let mut msg = serde_json::Map::new(); - msg.insert( - "cmd".into(), - serde_json::Value::String("getAppVersion".into()), + payload.insert( + "path".into(), + serde_json::Value::String(std::env::current_dir().unwrap().display().to_string()), ); - payload.insert("message".into(), serde_json::Value::Object(msg)); InvokePayload { - cmd: "".into(), - tauri_module: Some("App".into()), + cmd: "plugin:path|is_absolute".into(), callback, error, inner: serde_json::Value::Object(payload), @@ -217,7 +212,6 @@ mod tests { InvokePayload { cmd: format!("plugin:{PLUGIN_NAME}|doSomething"), - tauri_module: None, callback, error, inner: Default::default(), @@ -228,12 +222,12 @@ mod tests { fn scope_not_defined() { let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("app.tauri.app") .add_window("other") - .enable_tauri_api()]); + .add_plugin("path")]); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + path_is_absolute_payload(), Err(&crate::window::ipc_scope_not_found_error_message( "main", "https://tauri.app/", @@ -245,12 +239,12 @@ mod tests { fn scope_not_defined_for_window() { let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") .add_window("second") - .enable_tauri_api()]); + .add_plugin("path")]); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + path_is_absolute_payload(), Err(&crate::window::ipc_scope_window_error_message("main")), ); } @@ -259,12 +253,12 @@ mod tests { fn scope_not_defined_for_url() { let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("github.com") .add_window("main") - .enable_tauri_api()]); + .add_plugin("path")]); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + path_is_absolute_payload(), Err(&crate::window::ipc_scope_domain_error_message( "https://tauri.app/", )), @@ -273,43 +267,35 @@ mod tests { #[test] fn subdomain_is_not_allowed() { - let (app, mut window) = test_context(vec![ + let (_app, mut window) = test_context(vec![ RemoteDomainAccessScope::new("tauri.app") .add_window("main") - .enable_tauri_api(), + .add_plugin("path"), RemoteDomainAccessScope::new("sub.tauri.app") .add_window("main") - .enable_tauri_api(), + .add_plugin("path"), ]); window.navigate("https://tauri.app".parse().unwrap()); - assert_ipc_response( - &window, - app_version_payload(), - Ok(app.package_info().version.to_string().as_str()), - ); + assert_ipc_response(&window, path_is_absolute_payload(), Ok(true)); window.navigate("https://blog.tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + 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, - app_version_payload(), - Ok(app.package_info().version.to_string().as_str()), - ); + 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, - app_version_payload(), + path_is_absolute_payload(), Err(&crate::window::ipc_scope_not_found_error_message( "test", "https://dev.tauri.app/", @@ -319,16 +305,12 @@ mod tests { #[test] fn subpath_is_allowed() { - let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") + let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") .add_window("main") - .enable_tauri_api()]); + .add_plugin("path")]); window.navigate("https://tauri.app/inner/path".parse().unwrap()); - assert_ipc_response( - &window, - app_version_payload(), - Ok(app.package_info().version.to_string().as_str()), - ); + assert_ipc_response(&window, path_is_absolute_payload(), Ok(true)); } #[test] @@ -340,7 +322,7 @@ mod tests { window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + path_is_absolute_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 72243c29e49e..3bb818d384b0 100644 --- a/core/tauri/src/scope/mod.rs +++ b/core/tauri/src/scope/mod.rs @@ -3,48 +3,42 @@ // SPDX-License-Identifier: MIT mod fs; -mod http; /// IPC scope. pub mod ipc; -#[cfg(shell_scope)] -mod shell; -pub use self::http::Scope as HttpScope; pub use self::ipc::Scope as IpcScope; pub use fs::{Event as FsScopeEvent, Pattern as GlobPattern, 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, -}; use std::path::Path; -pub(crate) struct Scopes { - pub ipc: IpcScope, - 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 { - #[allow(dead_code)] - pub(crate) fn allow_directory(&self, path: &Path, recursive: bool) -> crate::Result<()> { - self.fs.allow_directory(path, recursive)?; - #[cfg(protocol_asset)] + /// 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(()) } - #[allow(dead_code)] - pub(crate) fn allow_file(&self, path: &Path) -> crate::Result<()> { - self.fs.allow_file(path)?; - #[cfg(protocol_asset)] + /// 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 978214bfc313..000000000000 --- a/core/tauri/src/scope/shell.rs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2019-2023 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 tauri_utils::{config::Config, Env, PackageInfo}; - -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(crate) fn new( - config: &Config, - package_info: &PackageInfo, - env: &Env, - mut scope: ScopeConfig, - ) -> Self { - for cmd in scope.scopes.values_mut() { - if let Ok(path) = crate::api::path::parse(config, package_info, env, &cmd.command) { - cmd.command = path; - } - } - Self(scope) - } - - /// Validates argument inputs and creates a Tauri sidecar [`Command`]. - #[cfg(shell_sidecar)] - pub fn prepare_sidecar( - &self, - command_name: &str, - command_script: &str, - args: ExecuteArgs, - ) -> Result { - self._prepare(command_name, args, Some(command_script)) - } - - /// Validates argument inputs and creates a Tauri [`Command`]. - #[cfg(shell_execute)] - pub fn prepare(&self, command_name: &str, args: ExecuteArgs) -> Result { - self._prepare(command_name, args, None) - } - - /// Validates argument inputs and creates a Tauri [`Command`]. - #[cfg(any(shell_execute, shell_sidecar))] - pub fn _prepare( - &self, - command_name: &str, - args: ExecuteArgs, - sidecar: Option<&str>, - ) -> 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.is_some() { - 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 = sidecar - .map(|s| { - std::path::PathBuf::from(s) - .components() - .last() - .unwrap() - .as_os_str() - .to_string_lossy() - .into_owned() - }) - .unwrap_or_else(|| command.command.to_string_lossy().into_owned()); - 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 `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`. - #[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/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index 8fe083dfd49e..f007373256df 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -55,7 +55,6 @@ pub struct RuntimeContext { is_running: Arc, windows: Arc>>, shortcuts: Arc>, - clipboard: Arc>>, run_tx: SyncSender, } @@ -88,9 +87,7 @@ impl RuntimeContext { 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() } } @@ -122,7 +119,6 @@ impl RuntimeHandle for MockRuntimeHandle { url: pending.url, }, menu_ids: Default::default(), - js_event_listeners: Default::default(), }) } @@ -168,6 +164,26 @@ impl RuntimeHandle for MockRuntimeHandle { fn hide(&self) -> Result<()> { 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)] @@ -184,64 +200,6 @@ impl MockDispatcher { } } -#[cfg(all(desktop, feature = "global-shortcut"))] -#[derive(Debug, Clone)] -pub struct MockGlobalShortcutManager { - context: RuntimeContext, -} - -#[cfg(all(desktop, feature = "global-shortcut"))] -impl tauri_runtime::GlobalShortcutManager for MockGlobalShortcutManager { - fn is_registered(&self, accelerator: &str) -> Result { - Ok( - self - .context - .shortcuts - .lock() - .unwrap() - .contains_key(accelerator), - ) - } - - fn register(&mut self, accelerator: &str, handler: F) -> Result<()> { - self - .context - .shortcuts - .lock() - .unwrap() - .insert(accelerator.into(), Box::new(handler)); - Ok(()) - } - - fn unregister_all(&mut self) -> Result<()> { - *self.context.shortcuts.lock().unwrap() = Default::default(); - Ok(()) - } - - fn unregister(&mut self, accelerator: &str) -> Result<()> { - self.context.shortcuts.lock().unwrap().remove(accelerator); - Ok(()) - } -} - -#[cfg(feature = "clipboard")] -#[derive(Debug, Clone)] -pub struct MockClipboardManager { - context: RuntimeContext, -} - -#[cfg(feature = "clipboard")] -impl tauri_runtime::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()) - } -} - #[derive(Debug, Clone)] pub struct MockWindowBuilder {} @@ -345,6 +303,10 @@ impl WindowBuilder for MockWindowBuilder { self } + fn shadow(self, enable: bool) -> Self { + self + } + #[cfg(windows)] fn parent_window(self, parent: HWND) -> Self { self @@ -405,6 +367,10 @@ 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) {} @@ -565,7 +531,6 @@ impl Dispatch for MockDispatcher { url: pending.url, }, menu_ids: Default::default(), - js_event_listeners: Default::default(), }) } @@ -630,6 +595,10 @@ 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(()) } @@ -757,10 +726,6 @@ impl EventLoopProxy for EventProxy { pub struct MockRuntime { is_running: Arc, pub context: RuntimeContext, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: MockGlobalShortcutManager, - #[cfg(feature = "clipboard")] - clipboard_manager: MockClipboardManager, #[cfg(all(desktop, feature = "system-tray"))] tray_handler: MockTrayHandler, run_rx: Receiver, @@ -774,19 +739,10 @@ impl MockRuntime { is_running: is_running.clone(), windows: Default::default(), shortcuts: Default::default(), - clipboard: Default::default(), run_tx: tx, }; Self { is_running, - #[cfg(all(desktop, feature = "global-shortcut"))] - global_shortcut_manager: MockGlobalShortcutManager { - context: context.clone(), - }, - #[cfg(feature = "clipboard")] - clipboard_manager: MockClipboardManager { - context: context.clone(), - }, #[cfg(all(desktop, feature = "system-tray"))] tray_handler: MockTrayHandler { context: context.clone(), @@ -800,10 +756,6 @@ impl MockRuntime { impl Runtime for MockRuntime { type Dispatcher = MockDispatcher; type Handle = MockRuntimeHandle; - #[cfg(all(desktop, feature = "global-shortcut"))] - type GlobalShortcutManager = MockGlobalShortcutManager; - #[cfg(feature = "clipboard")] - type ClipboardManager = MockClipboardManager; #[cfg(all(desktop, feature = "system-tray"))] type TrayHandler = MockTrayHandler; type EventLoopProxy = EventProxy; @@ -827,16 +779,6 @@ impl Runtime for MockRuntime { } } - #[cfg(all(desktop, feature = "global-shortcut"))] - fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager { - self.global_shortcut_manager.clone() - } - - #[cfg(feature = "clipboard")] - fn clipboard_manager(&self) -> Self::ClipboardManager { - self.clipboard_manager.clone() - } - fn create_window(&self, pending: PendingWindow) -> Result> { let id = rand::random(); self.context.windows.borrow_mut().insert(id, Window); @@ -849,7 +791,6 @@ impl Runtime for MockRuntime { url: pending.url, }, menu_ids: Default::default(), - js_event_listeners: Default::default(), }) } diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index eaadae04f649..3884733d63ab 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -44,7 +44,6 @@ //! &window, //! tauri::InvokePayload { //! cmd: "my_cmd".into(), -//! tauri_module: None, //! callback: tauri::api::ipc::CallbackFn(0), //! error: tauri::api::ipc::CallbackFn(1), //! inner: serde_json::Value::Null, @@ -74,12 +73,10 @@ use std::{ }; use crate::hooks::window_invoke_responder; -#[cfg(shell_scope)] -use crate::ShellScopeConfig; 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)] @@ -127,19 +124,9 @@ pub fn mock_context(assets: A) -> crate::Context { 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, }, @@ -149,20 +136,17 @@ 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".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(), - }, } } @@ -240,7 +224,6 @@ pub fn mock_app() -> App { /// &window, /// tauri::InvokePayload { /// cmd: "ping".into(), -/// tauri_module: None, /// callback: tauri::api::ipc::CallbackFn(0), /// error: tauri::api::ipc::CallbackFn(1), /// inner: serde_json::Value::Null, @@ -272,19 +255,9 @@ pub fn assert_ipc_response( ); } -#[cfg(test)] -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(), - } -} - #[cfg(test)] mod tests { - use crate::Manager; + use crate::WindowBuilder; use std::time::Duration; use super::mock_app; @@ -292,7 +265,11 @@ mod tests { #[test] fn run_app() { let app = mock_app(); - let w = app.get_window("main").unwrap(); + + let w = WindowBuilder::new(&app, "main", Default::default()) + .build() + .unwrap(); + std::thread::spawn(move || { std::thread::sleep(Duration::from_secs(1)); w.close().unwrap(); diff --git a/core/tauri/src/updater/core.rs b/core/tauri/src/updater/core.rs deleted file mode 100644 index 4aa42e49fd3a..000000000000 --- a/core/tauri/src/updater/core.rs +++ /dev/null @@ -1,1650 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use super::error::{Error, Result}; -#[cfg(desktop)] -use crate::api::file::{ArchiveFormat, Extract, Move}; -use crate::{ - api::http::{ClientBuilder, HttpRequestBuilder}, - AppHandle, Manager, Runtime, -}; -use base64::Engine; -use http::{ - header::{HeaderName, HeaderValue}, - HeaderMap, StatusCode, -}; -use minisign_verify::{PublicKey, Signature}; -use semver::Version; -use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize}; -use tauri_utils::{platform::current_exe, Env}; -use time::OffsetDateTime; -use url::Url; - -#[cfg(desktop)] -use std::io::Seek; -use std::{ - collections::HashMap, - env, - fmt::{self}, - io::{Cursor, Read}, - path::{Path, PathBuf}, - str::{from_utf8, FromStr}, - time::Duration, -}; - -#[cfg(any(target_os = "linux", windows))] -use std::ffi::OsStr; - -#[cfg(all(desktop, not(target_os = "windows")))] -use crate::api::file::Compression; - -#[cfg(target_os = "windows")] -use std::{ - fs::read_dir, - process::{exit, Command}, -}; - -type ShouldInstall = dyn FnOnce(&Version, &RemoteRelease) -> bool + Send; - -#[derive(Debug, Deserialize, Serialize)] -#[serde(untagged)] -pub enum RemoteReleaseInner { - Dynamic(ReleaseManifestPlatform), - Static { - platforms: HashMap, - }, -} - -/// Information about a release returned by the remote update server. -/// -/// This type can have one of two shapes: Server Format (Dynamic Format) and Static Format. -#[derive(Debug)] -pub struct RemoteRelease { - /// Version to install. - version: Version, - /// Release notes. - notes: Option, - /// Release date. - pub_date: Option, - /// Release data. - data: RemoteReleaseInner, -} - -impl<'de> Deserialize<'de> for RemoteRelease { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct InnerRemoteRelease { - #[serde(alias = "name", deserialize_with = "parse_version")] - version: Version, - notes: Option, - pub_date: Option, - platforms: Option>, - // dynamic platform response - url: Option, - signature: Option, - #[cfg(target_os = "windows")] - #[serde(default)] - with_elevated_task: bool, - } - - let release = InnerRemoteRelease::deserialize(deserializer)?; - - let pub_date = if let Some(date) = release.pub_date { - Some( - OffsetDateTime::parse(&date, &time::format_description::well_known::Rfc3339) - .map_err(|e| DeError::custom(format!("invalid value for `pub_date`: {e}")))?, - ) - } else { - None - }; - - Ok(RemoteRelease { - version: release.version, - notes: release.notes, - pub_date, - data: if let Some(platforms) = release.platforms { - RemoteReleaseInner::Static { platforms } - } else { - RemoteReleaseInner::Dynamic(ReleaseManifestPlatform { - url: release.url.ok_or_else(|| { - DeError::custom("the `url` field was not set on the updater response") - })?, - signature: release.signature.ok_or_else(|| { - DeError::custom("the `signature` field was not set on the updater response") - })?, - #[cfg(target_os = "windows")] - with_elevated_task: release.with_elevated_task, - }) - }, - }) - } -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ReleaseManifestPlatform { - /// Download URL for the platform - pub url: Url, - /// Signature for the platform - pub signature: String, - #[cfg(target_os = "windows")] - #[serde(default)] - /// Optional: Windows only try to use elevated task - pub with_elevated_task: bool, -} - -fn parse_version<'de, D>(deserializer: D) -> std::result::Result -where - D: serde::Deserializer<'de>, -{ - let str = String::deserialize(deserializer)?; - - Version::from_str(str.trim_start_matches('v')).map_err(serde::de::Error::custom) -} - -impl RemoteRelease { - /// The release version. - pub fn version(&self) -> &Version { - &self.version - } - - /// The release notes. - pub fn notes(&self) -> Option<&String> { - self.notes.as_ref() - } - - /// The release date. - pub fn pub_date(&self) -> Option<&OffsetDateTime> { - self.pub_date.as_ref() - } - - /// The release's download URL for the given target. - pub fn download_url(&self, target: &str) -> Result<&Url> { - match self.data { - RemoteReleaseInner::Dynamic(ref platform) => Ok(&platform.url), - RemoteReleaseInner::Static { ref platforms } => platforms - .get(target) - .map_or(Err(Error::TargetNotFound(target.to_string())), |p| { - Ok(&p.url) - }), - } - } - - /// The release's signature for the given target. - pub fn signature(&self, target: &str) -> Result<&String> { - match self.data { - RemoteReleaseInner::Dynamic(ref platform) => Ok(&platform.signature), - RemoteReleaseInner::Static { ref platforms } => platforms - .get(target) - .map_or(Err(Error::TargetNotFound(target.to_string())), |platform| { - Ok(&platform.signature) - }), - } - } - - #[cfg(target_os = "windows")] - /// Optional: Windows only try to use elevated task - pub fn with_elevated_task(&self, target: &str) -> Result { - match self.data { - RemoteReleaseInner::Dynamic(ref platform) => Ok(platform.with_elevated_task), - RemoteReleaseInner::Static { ref platforms } => platforms - .get(target) - .map_or(Err(Error::TargetNotFound(target.to_string())), |platform| { - Ok(platform.with_elevated_task) - }), - } - } -} - -pub struct UpdateBuilder { - /// Application handle. - pub app: AppHandle, - /// Current version we are running to compare with announced version - pub current_version: Version, - /// 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, - should_install: Option>, - timeout: Option, - headers: HeaderMap, -} - -impl fmt::Debug for UpdateBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("UpdateBuilder") - .field("app", &self.app) - .field("current_version", &self.current_version) - .field("urls", &self.urls) - .field("target", &self.target) - .field("executable_path", &self.executable_path) - .field("timeout", &self.timeout) - .field("headers", &self.headers) - .finish() - } -} - -// Create new updater instance and return an Update -impl UpdateBuilder { - pub fn new(app: AppHandle) -> Self { - UpdateBuilder { - app, - urls: Vec::new(), - target: None, - executable_path: None, - // safe to unwrap: CARGO_PKG_VERSION is also a valid semver value - current_version: env!("CARGO_PKG_VERSION").parse().unwrap(), - should_install: None, - timeout: None, - headers: Default::default(), - } - } - - #[allow(dead_code)] - pub fn url(mut self, url: String) -> Self { - self.urls.push( - percent_encoding::percent_decode(url.as_bytes()) - .decode_utf8_lossy() - .to_string(), - ); - 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( - percent_encoding::percent_decode(url.as_bytes()) - .decode_utf8_lossy() - .to_string(), - ); - } - 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: Version) -> Self { - self.current_version = ver; - self - } - - /// Set the target name. Represents the string that is looked up on the updater API or response JSON. - pub fn target(mut self, target: impl Into) -> Self { - self.target.replace(target.into()); - 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 fn should_install bool + Send + 'static>( - mut self, - f: F, - ) -> Self { - self.should_install.replace(Box::new(f)); - self - } - - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout.replace(timeout); - self - } - - /// Add a `Header` to the request. - pub fn header(mut self, key: K, value: V) -> Result - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - let key: std::result::Result = key.try_into().map_err(Into::into); - let value: std::result::Result = value.try_into().map_err(Into::into); - self.headers.insert(key?, value?); - Ok(self) - } - - pub async fn build(mut 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(), - )); - }; - - // If no executable path provided, we use current_exe from tauri_utils - let executable_path = self.executable_path.unwrap_or(current_exe()?); - - let arch = get_updater_arch().ok_or(Error::UnsupportedArch)?; - // `target` is the `{{target}}` variable we replace in the endpoint - // `json_target` is the value we search if the updater server returns a JSON with the `platforms` object - let (target, json_target) = if let Some(target) = self.target { - (target.clone(), target) - } else { - let target = get_updater_target().ok_or(Error::UnsupportedOs)?; - (target.to_string(), format!("{target}-{arch}")) - }; - - // Get the extract_path from the provided executable_path - let extract_path = extract_path_from_executable(&self.app.state::(), &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"); - } - } - - // we want JSON only - let mut headers = self.headers; - headers.insert("Accept", HeaderValue::from_str("application/json").unwrap()); - - // Allow fallback if more than 1 urls is provided - let mut last_error: Option = None; - for url in &self.urls { - // replace {{current_version}}, {{target}} and {{arch}} in the provided URL - // this is useful if we need to query example - // https://releases.myapp.com/update/{{target}}/{{arch}}/{{current_version}} - // will be translated into -> - // https://releases.myapp.com/update/darwin/aarch64/1.0.0 - // The main objective is if the update URL is defined via the Cargo.toml - // the URL will be generated dynamically - let fixed_link = url - .replace("{{current_version}}", &self.current_version.to_string()) - .replace("{{target}}", &target) - .replace("{{arch}}", arch); - - let mut request = HttpRequestBuilder::new("GET", &fixed_link)?.headers(headers.clone()); - if let Some(timeout) = self.timeout { - request = request.timeout(timeout); - } - let resp = ClientBuilder::new().build()?.send(request).await; - - // If we got a success, we stop the loop - // and we set our remote_release variable - if let Ok(res) = resp { - let status = res.status(); - // got status code 2XX - if status.is_success() { - // if we got 204 - if status == StatusCode::NO_CONTENT { - // return with `UpToDate` error - // we should catch on the client - return Err(Error::UpToDate); - }; - let res = res.read().await?; - // Convert the remote result to our local struct - let built_release = serde_json::from_value(res.data).map_err(Into::into); - // 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); - } - - // Extracted remote metadata - let final_release = remote_release.ok_or(Error::ReleaseNotFound)?; - - // is the announced version greater than our current one? - let should_update = if let Some(comparator) = self.should_install.take() { - comparator(&self.current_version, &final_release) - } else { - final_release.version() > &self.current_version - }; - - headers.remove("Accept"); - - // create our new updater - Ok(Update { - app: self.app, - target, - extract_path, - should_update, - version: final_release.version().to_string(), - date: final_release.pub_date().cloned(), - current_version: self.current_version, - download_url: final_release.download_url(&json_target)?.to_owned(), - body: final_release.notes().cloned(), - signature: final_release.signature(&json_target)?.to_owned(), - #[cfg(target_os = "windows")] - with_elevated_task: final_release.with_elevated_task(&json_target)?, - timeout: self.timeout, - headers, - }) - } -} - -pub fn builder(app: AppHandle) -> UpdateBuilder { - UpdateBuilder::new(app) -} - -#[derive(Debug)] -pub struct Update { - /// Application handle. - pub app: AppHandle, - /// Update description - pub body: Option, - /// Should we update or not - pub should_update: bool, - /// Version announced - pub version: String, - /// Running version - pub current_version: Version, - /// Update publish date - pub date: Option, - /// Target - #[allow(dead_code)] - target: String, - /// Extract path - extract_path: PathBuf, - /// Download URL announced - download_url: Url, - /// Signature announced - signature: String, - #[cfg(target_os = "windows")] - /// Optional: Windows only try to use elevated task - /// Default to false - with_elevated_task: bool, - /// Request timeout - timeout: Option, - /// Request headers - headers: HeaderMap, -} - -impl Clone for Update { - fn clone(&self) -> Self { - Self { - app: self.app.clone(), - body: self.body.clone(), - should_update: self.should_update, - version: self.version.clone(), - current_version: self.current_version.clone(), - date: self.date, - target: self.target.clone(), - extract_path: self.extract_path.clone(), - download_url: self.download_url.clone(), - signature: self.signature.clone(), - #[cfg(target_os = "windows")] - with_elevated_task: self.with_elevated_task, - timeout: self.timeout, - headers: self.headers.clone(), - } - } -} - -impl Update { - // Download and install our update - // @todo(lemarier): Split into download and install (two step) but need to be thread safe - pub(crate) async fn download_and_install), D: FnOnce()>( - &self, - pub_key: String, - on_chunk: C, - on_download_finish: D, - ) -> Result { - // 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 - // anything with it yet - #[cfg(target_os = "linux")] - if self.app.state::().appimage.is_none() { - return Err(Error::UnsupportedLinuxPackage); - } - - // set our headers - let mut headers = self.headers.clone(); - headers.insert( - "Accept", - HeaderValue::from_str("application/octet-stream").unwrap(), - ); - headers.insert( - "User-Agent", - HeaderValue::from_str("tauri/updater").unwrap(), - ); - - let client = ClientBuilder::new().build()?; - // Create our request - let mut req = HttpRequestBuilder::new("GET", self.download_url.as_str())?.headers(headers); - if let Some(timeout) = self.timeout { - req = req.timeout(timeout); - } - - let response = client.send(req).await?; - - // make sure it's success - if !response.status().is_success() { - return Err(Error::Network(format!( - "Download request failed with status: {}", - response.status() - ))); - } - - let content_length: Option = response - .headers() - .get("Content-Length") - .and_then(|value| value.to_str().ok()) - .and_then(|value| value.parse().ok()); - - let mut buffer = Vec::new(); - { - use futures_util::StreamExt; - let mut stream = response.bytes_stream(); - while let Some(chunk) = stream.next().await { - let chunk = chunk?; - let bytes = chunk.as_ref().to_vec(); - on_chunk(bytes.len(), content_length); - buffer.extend(bytes); - } - } - - on_download_finish(); - - // create memory buffer from our archive (Seek + Read) - let mut archive_buffer = Cursor::new(buffer); - - // We need an announced signature by the server - // if there is no signature, bail out. - verify_signature(&mut archive_buffer, &self.signature, &pub_key)?; - - // TODO: implement updater in mobile - #[cfg(desktop)] - { - // 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, - &self.extract_path, - self.with_elevated_task, - &self.app.config(), - )?; - #[cfg(not(target_os = "windows"))] - copy_files_and_run(archive_buffer, &self.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 { - use std::os::unix::fs::{MetadataExt, PermissionsExt}; - - let extract_path_metadata = extract_path.metadata()?; - - let tmp_dir_locations = vec![ - Box::new(|| Some(env::temp_dir())) as Box Option>, - Box::new(dirs_next::cache_dir), - Box::new(|| Some(extract_path.parent().unwrap().to_path_buf())), - ]; - - for tmp_dir_location in tmp_dir_locations { - if let Some(tmp_dir_location) = tmp_dir_location() { - let tmp_dir = tempfile::Builder::new() - .prefix("tauri_current_app") - .tempdir_in(tmp_dir_location)?; - let tmp_dir_metadata = tmp_dir.path().metadata()?; - - if extract_path_metadata.dev() == tmp_dir_metadata.dev() { - let mut perms = tmp_dir_metadata.permissions(); - perms.set_mode(0o700); - std::fs::set_permissions(tmp_dir.path(), perms)?; - - 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))); - - return extractor - .with_files(|entry| { - let path = entry.path()?; - if path.extension() == Some(OsStr::new("AppImage")) { - // if something went wrong during the extraction, we should restore previous app - if let Err(err) = entry.extract(extract_path) { - Move::from_source(tmp_app_image).to_dest(extract_path)?; - return Err(crate::api::Error::Extract(err.to_string())); - } - // early finish we have everything we need here - return Ok(true); - } - Ok(false) - }) - .map_err(Into::into); - } - } - } - - Err(Error::TempDirNotOnSameMountPoint) -} - -// Windows -// -// ### Expected structure: -// ├── [AppName]_[version]_x64.msi.zip # ZIP generated by tauri-bundler -// │ └──[AppName]_[version]_x64.msi # Application MSI -// ├── [AppName]_[version]_x64-setup.exe.zip # ZIP generated by tauri-bundler -// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer -// └── ... -// -// ## 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, - config: &crate::Config, -) -> 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)?; - - 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) - .args(config.tauri.updater.windows.install_mode.nsis_args()) - .args(&config.tauri.updater.windows.installer_args) - .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 {product_name} - Skip UAC"); - if let Ok(output) = Command::new("schtasks") - .arg("/QUERY") - .arg("/TN") - .arg(update_task_name.clone()) - .output() - { - if output.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 - } - } - } - - // we need to wrap the current exe path in quotes for Start-Process - let mut current_exe_arg = std::ffi::OsString::new(); - current_exe_arg.push("\""); - current_exe_arg.push(current_exe()?); - current_exe_arg.push("\""); - - let mut msi_path_arg = std::ffi::OsString::new(); - msi_path_arg.push("\"\"\""); - msi_path_arg.push(&found_path); - msi_path_arg.push("\"\"\""); - - let mut msiexec_args = config - .tauri - .updater - .windows - .install_mode - .msiexec_args() - .iter() - .map(|p| p.to_string()) - .collect::>(); - msiexec_args.extend(config.tauri.updater.windows.installer_args.clone()); - - // run the installer and relaunch the application - let system_root = std::env::var("SYSTEMROOT"); - let powershell_path = system_root.as_ref().map_or_else( - |_| "powershell.exe".to_string(), - |p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"), - ); - let powershell_install_res = Command::new(powershell_path) - .args(["-NoProfile", "-windowstyle", "hidden"]) - .args([ - "Start-Process", - "-Wait", - "-FilePath", - "$env:SYSTEMROOT\\System32\\msiexec.exe", - "-ArgumentList", - ]) - .arg("/i,") - .arg(msi_path_arg) - .arg(format!(", {}, /promptrestart;", msiexec_args.join(", "))) - .arg("Start-Process") - .arg(current_exe_arg) - .spawn(); - if powershell_install_res.is_err() { - // fallback to running msiexec directly - relaunch won't be available - // we use this here in case powershell fails in an older machine somehow - let msiexec_path = system_root.as_ref().map_or_else( - |_| "msiexec.exe".to_string(), - |p| format!("{p}\\System32\\msiexec.exe"), - ); - let _ = Command::new(msiexec_path) - .arg("/i") - .arg(found_path) - .args(msiexec_args) - .arg("/promptrestart") - .spawn(); - } - - 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 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 - extractor.with_files(|entry| { - let path = entry.path()?; - // skip the first folder (should be the app name) - let collected_path: PathBuf = path.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) = entry.extract(&extraction_path) { - 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(crate::api::Error::Extract(err.to_string())); - } - - extracted_files.push(extraction_path); - - Ok(false) - })?; - - let _ = std::process::Command::new("touch") - .arg(extract_path) - .status(); - - Ok(()) -} - -pub(crate) fn get_updater_target() -> Option<&'static str> { - if cfg!(target_os = "linux") { - Some("linux") - } else if cfg!(target_os = "macos") { - Some("darwin") - } else if cfg!(target_os = "windows") { - Some("windows") - } else { - None - } -} - -pub(crate) fn get_updater_arch() -> Option<&'static str> { - if cfg!(target_arch = "x86") { - Some("i686") - } else if cfg!(target_arch = "x86_64") { - Some("x86_64") - } else if cfg!(target_arch = "arm") { - Some("armv7") - } else if cfg!(target_arch = "aarch64") { - Some("aarch64") - } 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 = &base64::engine::general_purpose::STANDARD.decode(base64_string)?; - let result = from_utf8(decoded_string) - .map_err(|_| Error::SignatureUtf8(base64_string.into()))? - .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-aarch64": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJZVGdpKzJmRWZ0SkRvWS9TdFpqTU9xcm1mUmJSSG5OWVlwSklrWkN1SFpWbmh4SDlBcTU3SXpjbm0xMmRjRkphbkpVeGhGcTdrdzlrWGpGVWZQSWdzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1MDU3CWZpbGU6L1VzZXJzL3J1bm5lci9ydW5uZXJzLzIuMjYzLjAvd29yay90YXVyaS90YXVyaS90YXVyaS9leGFtcGxlcy9jb21tdW5pY2F0aW9uL3NyYy10YXVyaS90YXJnZXQvZGVidWcvYnVuZGxlL29zeC9hcHAuYXBwLnRhci5negp4ZHFlUkJTVnpGUXdDdEhydTE5TGgvRlVPeVhjTnM5RHdmaGx3c0ZPWjZXWnFwVDRNWEFSbUJTZ1ZkU1IwckJGdmlwSzJPd00zZEZFN2hJOFUvL1FDZz09Cg==", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.app.tar.gz" - }, - "darwin-x86_64": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJZVGdpKzJmRWZ0SkRvWS9TdFpqTU9xcm1mUmJSSG5OWVlwSklrWkN1SFpWbmh4SDlBcTU3SXpjbm0xMmRjRkphbkpVeGhGcTdrdzlrWGpGVWZQSWdzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1MDU3CWZpbGU6L1VzZXJzL3J1bm5lci9ydW5uZXJzLzIuMjYzLjAvd29yay90YXVyaS90YXVyaS90YXVyaS9leGFtcGxlcy9jb21tdW5pY2F0aW9uL3NyYy10YXVyaS90YXJnZXQvZGVidWcvYnVuZGxlL29zeC9hcHAuYXBwLnRhci5negp4ZHFlUkJTVnpGUXdDdEhydTE5TGgvRlVPeVhjTnM5RHdmaGx3c0ZPWjZXWnFwVDRNWEFSbUJTZ1ZkU1IwckJGdmlwSzJPd00zZEZFN2hJOFUvL1FDZz09Cg==", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.app.tar.gz" - }, - "linux-x86_64": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOWZSM29hTFNmUEdXMHRoOC81WDFFVVFRaXdWOUdXUUdwT0NlMldqdXkyaWVieXpoUmdZeXBJaXRqSm1YVmczNXdRL1Brc0tHb1NOTzhrL1hadFcxdmdnPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE3MzQzCWZpbGU6L2hvbWUvcnVubmVyL3dvcmsvdGF1cmkvdGF1cmkvdGF1cmkvZXhhbXBsZXMvY29tbXVuaWNhdGlvbi9zcmMtdGF1cmkvdGFyZ2V0L2RlYnVnL2J1bmRsZS9hcHBpbWFnZS9hcHAuQXBwSW1hZ2UudGFyLmd6CmRUTUM2bWxnbEtTbUhOZGtERUtaZnpUMG5qbVo5TGhtZWE1SFNWMk5OOENaVEZHcnAvVW0zc1A2ajJEbWZUbU0yalRHT0FYYjJNVTVHOHdTQlYwQkF3PT0K", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.AppImage.tar.gz" - }, - "windows-x86_64": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K", - "url": "https://github.com/tauri-apps/updater-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{version}", - "notes": "This is the latest version! Once updated you shouldn't see this prompt.", - "pub_date": "2020-06-25T14:14:19Z", - "signature": "{public_signature}", - "url": "{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{version}", - "notes": "This is the latest version! Once updated you shouldn't see this prompt.", - "pub_date": "2020-06-25T14:14:19Z", - "signature": "{public_signature}", - "url": "{download_url}", - "with_elevated_task": {with_elevated_task} - }} - "# - ) - } - - #[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 app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("0.0.0".parse().unwrap()) - .url(mockito::server_url()) - .build()); - - 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 app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("0.0.0".parse().unwrap()) - .url(mockito::server_url()) - .build()); - - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - } - - #[test] - fn simple_http_updater_raw_json_windows_x86_64() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_raw_json()) - .create(); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("0.0.0".parse().unwrap()) - .target("windows-x86_64") - .url(mockito::server_url()) - .build()); - - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - assert_eq!(updater.version, "2.0.0"); - assert_eq!(updater.signature, "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K"); - assert_eq!( - updater.download_url.to_string(), - "https://github.com/tauri-apps/updater-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 app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("10.0.0".parse().unwrap()) - .url(mockito::server_url()) - .build()); - - 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-aarch64/1.0.0") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_platform_json( - "2.0.0", - "SampleTauriKey", - "https://tauri.app", - )) - .create(); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("1.0.0".parse().unwrap()) - .url(format!( - "{}/darwin-aarch64/{{{{current_version}}}}", - mockito::server_url() - )) - .build()); - - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - } - - #[test] - fn simple_http_updater_percent_decode() { - let _m = mockito::mock("GET", "/darwin-aarch64/1.0.0") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_platform_json( - "2.0.0", - "SampleTauriKey", - "https://tauri.app", - )) - .create(); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("1.0.0".parse().unwrap()) - .url( - url::Url::parse(&format!( - "{}/darwin-aarch64/{{{{current_version}}}}", - mockito::server_url() - )) - .unwrap() - .to_string() - ) - .build()); - - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("1.0.0".parse().unwrap()) - .urls(&[url::Url::parse(&format!( - "{}/darwin-aarch64/{{{{current_version}}}}", - mockito::server_url() - )) - .unwrap() - .to_string()]) - .build()); - - 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", "/windows-x86_64/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.app", - true, - )) - .create(); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("1.0.0".parse().unwrap()) - .url(format!( - "{}/windows-x86_64/{{{{current_version}}}}", - mockito::server_url() - )) - .build()); - - let updater = check_update.expect("Can't check update"); - - assert!(updater.should_update); - } - - #[test] - fn http_updater_uptodate() { - let _m = mockito::mock("GET", "/darwin-aarch64/10.0.0") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_platform_json( - "2.0.0", - "SampleTauriKey", - "https://tauri.app", - )) - .create(); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .current_version("10.0.0".parse().unwrap()) - .url(format!( - "{}/darwin-aarch64/{{{{current_version}}}}", - mockito::server_url() - )) - .build()); - - 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 app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .url("http://badurl.www.tld/1".into()) - .url(mockito::server_url()) - .current_version("0.0.1".parse().unwrap()) - .build()); - - let updater = check_update.expect("Can't check remote update"); - - assert!(updater.should_update); - } - - #[test] - fn http_updater_fallback_urls_with_array() { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(generate_sample_raw_json()) - .create(); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .urls(&["http://badurl.www.tld/1".into(), mockito::server_url(),]) - .current_version("0.0.1".parse().unwrap()) - .build()); - - let updater = check_update.expect("Can't check remote update"); - - assert!(updater.should_update); - } - - #[test] - fn http_updater_invalid_remote_data() { - let invalid_signature = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", - "signature": true - }"#; - let invalid_version = r#"{ - "version": 5, - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", - "signature": "x" - }"#; - let invalid_name = r#"{ - "name": false, - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", - "signature": "x" - }"#; - let invalid_date = r#"{ - "version": "1.0.0", - "notes": "Blablaa", - "pub_date": 345645646, - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", - "signature": "x" - }"#; - let invalid_notes = r#"{ - "version": "v0.0.3", - "notes": ["bla", "bla"], - "pub_date": "2020-02-20T15:41:00Z", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", - "signature": "x" - }"#; - let invalid_url = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "url": ["https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"], - "signature": "x" - }"#; - let invalid_platform_signature = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "platforms": { - "test-target": { - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", - "signature": { - "test-target": "x" - } - } - } - }"#; - let invalid_platform_url = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "platforms": { - "test-target": { - "url": { - "first": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz" - } - "signature": "x" - } - } - }"#; - - let test_cases = [ - ( - invalid_signature, - Box::new(|e| matches!(e, Error::InvalidResponseType("signature", "string", _))) - as Box bool>, - ), - ( - invalid_version, - Box::new(|e| matches!(e, Error::InvalidResponseType("version", "string", _))) - as Box bool>, - ), - ( - invalid_name, - Box::new(|e| matches!(e, Error::InvalidResponseType("name", "string", _))) - as Box bool>, - ), - ( - invalid_date, - Box::new(|e| matches!(e, Error::InvalidResponseType("pub_date", "string", _))) - as Box bool>, - ), - ( - invalid_notes, - Box::new(|e| matches!(e, Error::InvalidResponseType("notes", "string", _))) - as Box bool>, - ), - ( - invalid_url, - Box::new(|e| matches!(e, Error::InvalidResponseType("url", "string", _))) - as Box bool>, - ), - ( - invalid_platform_signature, - Box::new(|e| matches!(e, Error::InvalidResponseType("signature", "string", _))) - as Box bool>, - ), - ( - invalid_platform_url, - Box::new(|e| matches!(e, Error::InvalidResponseType("url", "string", _))) - as Box bool>, - ), - ]; - - for (response, validator) in test_cases { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(response) - .create(); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .url(mockito::server_url()) - .current_version("0.0.1".parse().unwrap()) - .target("test-target") - .build()); - if let Err(e) = check_update { - validator(e); - } else { - panic!("unexpected Ok response"); - } - } - } - - #[test] - fn http_updater_missing_remote_data() { - let missing_signature = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz" - }"#; - let missing_version = r#"{ - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", - "signature": "x" - }"#; - let missing_url = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "signature": "x" - }"#; - let missing_target = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "platforms": { - "unknown-target": { - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", - "signature": "x" - } - } - }"#; - let missing_platform_signature = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "platforms": { - "test-target": { - "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz" - } - } - }"#; - let missing_platform_url = r#"{ - "version": "v0.0.3", - "notes": "Blablaa", - "pub_date": "2020-02-20T15:41:00Z", - "platforms": { - "test-target": { - "signature": "x" - } - } - }"#; - - fn missing_field_error(field: &str) -> String { - format!("the `{field}` field was not set on the updater response") - } - - let test_cases = [ - (missing_signature, missing_field_error("signature")), - (missing_version, "missing field `version`".to_string()), - (missing_url, missing_field_error("url")), - ( - missing_target, - Error::TargetNotFound("test-target".into()).to_string(), - ), - ( - missing_platform_signature, - "missing field `signature`".to_string(), - ), - (missing_platform_url, "missing field `url`".to_string()), - ]; - - for (response, error) in test_cases { - let _m = mockito::mock("GET", "/") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(response) - .create(); - - let app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .url(mockito::server_url()) - .current_version("0.0.1".parse().unwrap()) - .target("test-target") - .build()); - if let Err(e) = check_update { - println!("ERROR: {e}, expected: {error}"); - assert!(e.to_string().contains(&error)); - } else { - panic!("unexpected Ok response"); - } - } - } - - // 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!("{}/{archive_file}", mockito::server_url()); - - let mut signature_file = File::open(format!( - "./test/updater/fixture/archives/{archive_file}.sig" - )) - .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 app = crate::test::mock_app(); - let check_update = block!(builder(app.handle()) - .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".parse().unwrap()) - .build()); - - #[cfg(target_os = "linux")] - { - env::set_var("APPIMAGE", my_executable); - } - - // 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 b94ad0dda425..000000000000 --- a/core/tauri/src/updater/error.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019-2023 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("The signature {0} could not be decoded, please check if it is a valid base64 string. The signature must be the contents of the `.sig` file generated by the Tauri bundler, as a string.")] - SignatureUtf8(String), - /// 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), - /// Could not fetch a valid response from the server. - #[error("Could not fetch a valid release JSON from the remote")] - ReleaseNotFound, - /// 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 cannot be executed on this Linux package. Currently the updater is enabled only on AppImages. - #[error("Cannot run updater on this Linux package. Currently only an AppImage can be updated.")] - UnsupportedLinuxPackage, - /// Operating system is not supported. - #[error("unsupported OS, expected one of `linux`, `darwin` or `windows`.")] - UnsupportedOs, - /// Unsupported app architecture. - #[error( - "Unsupported application architecture, expected one of `x86`, `x86_64`, `arm` or `aarch64`." - )] - UnsupportedArch, - /// The platform was not found on the updater JSON response. - #[error("the platform `{0}` was not found on the response `platforms` object")] - TargetNotFound(String), - /// 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, - /// The updater responded with an invalid signature type. - #[error("the updater response field `{0}` type is invalid, expected {1} but found {2}")] - InvalidResponseType(&'static str, &'static str, serde_json::Value), - /// HTTP error. - #[error(transparent)] - Http(#[from] http::Error), - /// Temp dir is not on same mount mount. This prevents our updater to rename the AppImage to a temp file. - #[cfg(target_os = "linux")] - #[error("temp directory is not on the same mount point as the AppImage")] - TempDirNotOnSameMountPoint, -} - -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 549eda47828f..000000000000 --- a/core/tauri/src/updater/mod.rs +++ /dev/null @@ -1,630 +0,0 @@ -// Copyright 2019-2023 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**. -//! -//! For a full guide on setting up the updater, see . -//! -//! Check [`UpdateBuilder`] to see how to manually trigger and customize the updater at runtime. -//! -//! ## Events -//! -//! To listen to the updater events, for example to check for error messages, you need to use [`RunEvent::Updater`](crate::RunEvent) in [`App::run`](crate::App#method.run). -//! -//! ```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")) -//! .expect("error while building tauri application"); -//! app.run(|_app_handle, event| match event { -//! tauri::RunEvent::Updater(updater_event) => { -//! match updater_event { -//! tauri::UpdaterEvent::UpdateAvailable { body, date, version } => { -//! println!("update available {} {:?} {}", body, date, version); -//! } -//! // Emitted when the download is about to be started. -//! tauri::UpdaterEvent::Pending => { -//! println!("update is pending!"); -//! } -//! tauri::UpdaterEvent::DownloadProgress { chunk_length, content_length } => { -//! println!("downloaded {} of {:?}", chunk_length, content_length); -//! } -//! // Emitted when the download has finished and the update is about to be installed. -//! tauri::UpdaterEvent::Downloaded => { -//! println!("update has been downloaded!"); -//! } -//! // Emitted when the update was installed. You can then ask to restart the app. -//! tauri::UpdaterEvent::Updated => { -//! println!("app has been updated"); -//! } -//! // Emitted when the app already has the latest version installed and an update is not needed. -//! tauri::UpdaterEvent::AlreadyUpToDate => { -//! println!("app is already up to date"); -//! } -//! // Emitted when there is an error with the updater. We suggest to listen to this event even if the default dialog is enabled. -//! tauri::UpdaterEvent::Error(error) => { -//! println!("failed to update: {}", error); -//! } -//! _ => (), -//! } -//! } -//! _ => {} -//! }); -//! ``` - -mod core; -mod error; - -use std::time::Duration; - -use http::header::{HeaderName, HeaderValue}; -use semver::Version; -use time::OffsetDateTime; - -pub use self::{core::RemoteRelease, error::Error}; -/// Alias for [`std::result::Result`] using our own [`Error`]. -pub type Result = std::result::Result; - -use crate::{runtime::EventLoopProxy, AppHandle, EventLoopMessage, Manager, Runtime, UpdaterEvent}; - -#[cfg(desktop)] -use crate::api::dialog::blocking::ask; - -#[cfg(mobile)] -fn ask( - _parent_window: Option<&crate::Window>, - _title: impl AsRef, - _message: impl AsRef, -) -> bool { - true -} - -/// 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"; -/// The name of the event that is emitted on download progress. -pub const EVENT_DOWNLOAD_PROGRESS: &str = "tauri://update-download-progress"; -/// 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"; -/// The update has been downloaded. -pub const EVENT_STATUS_DOWNLOADED: &str = "DOWNLOADED"; -/// 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"; - -/// Gets the target string used on the updater. -pub fn target() -> Option { - if let (Some(target), Some(arch)) = (core::get_updater_target(), core::get_updater_arch()) { - Some(format!("{target}-{arch}")) - } else { - None - } -} - -#[derive(Clone, serde::Serialize)] -struct StatusEvent { - status: String, - error: Option, -} - -#[derive(Clone, serde::Serialize)] -#[serde(rename_all = "camelCase")] -struct DownloadProgressEvent { - chunk_length: usize, - content_length: Option, -} - -#[derive(Clone, serde::Serialize)] -struct UpdateManifest { - version: String, - date: Option, - body: String, -} - -/// An update check builder. -#[derive(Debug)] -pub struct UpdateBuilder { - inner: core::UpdateBuilder, - events: bool, -} - -impl UpdateBuilder { - /// Do not use the event system to emit information or listen to install the update. - pub fn skip_events(mut self) -> Self { - self.events = false; - self - } - - /// Sets the current platform's target name for the updater. - /// - /// The target is injected in the endpoint URL by replacing `{{target}}`. - /// Note that this does not affect the `{{arch}}` variable. - /// - /// If the updater response JSON includes the `platforms` field, - /// that object must contain a value for the target key. - /// - /// By default Tauri uses `$OS_NAME` as the replacement for `{{target}}` - /// and `$OS_NAME-$ARCH` as the key in the `platforms` object, - /// where `$OS_NAME` is the current operating system name "linux", "windows" or "darwin") - /// and `$ARCH` is one of the supported architectures ("i686", "x86_64", "armv7" or "aarch64"). - /// - /// See [`Builder::updater_target`](crate::Builder#method.updater_target) for a way to set the target globally. - /// - /// # Examples - /// - /// ## Use a macOS Universal binary target name - /// - /// In this example, we set the updater target only on macOS. - /// On other platforms, we set the default target. - /// Note that `{{target}}` will be replaced with `darwin-universal`, - /// but `{{arch}}` is still the running platform's architecture. - /// - /// ```no_run - /// tauri::Builder::default() - /// .setup(|app| { - /// let handle = app.handle(); - /// tauri::async_runtime::spawn(async move { - /// let builder = tauri::updater::builder(handle).target(if cfg!(target_os = "macos") { - /// "darwin-universal".to_string() - /// } else { - /// tauri::updater::target().unwrap() - /// }); - /// match builder.check().await { - /// Ok(update) => {} - /// Err(error) => {} - /// } - /// }); - /// Ok(()) - /// }); - /// ``` - /// - /// ## Append debug information to the target - /// - /// This allows you to provide updates for both debug and release applications. - /// - /// ```no_run - /// tauri::Builder::default() - /// .setup(|app| { - /// let handle = app.handle(); - /// tauri::async_runtime::spawn(async move { - /// let kind = if cfg!(debug_assertions) { "debug" } else { "release" }; - /// let builder = tauri::updater::builder(handle).target(format!("{}-{kind}", tauri::updater::target().unwrap())); - /// match builder.check().await { - /// Ok(update) => {} - /// Err(error) => {} - /// } - /// }); - /// Ok(()) - /// }); - /// ``` - /// - /// ## Use the platform's target triple - /// - /// ```no_run - /// tauri::Builder::default() - /// .setup(|app| { - /// let handle = app.handle(); - /// tauri::async_runtime::spawn(async move { - /// let builder = tauri::updater::builder(handle).target(tauri::utils::platform::target_triple().unwrap()); - /// match builder.check().await { - /// Ok(update) => {} - /// Err(error) => {} - /// } - /// }); - /// Ok(()) - /// }); - /// ``` - pub fn target(mut self, target: impl Into) -> Self { - self.inner = self.inner.target(target); - self - } - - /// Sets a closure that is invoked to compare the current version and the latest version returned by the updater server. - /// The first argument is the current version, and the second one is the latest version. - /// - /// The closure must return `true` if the update should be installed. - /// - /// # Examples - /// - /// - Always install the version returned by the server: - /// - /// ```no_run - /// tauri::Builder::default() - /// .setup(|app| { - /// tauri::updater::builder(app.handle()).should_install(|_current, _latest| true); - /// Ok(()) - /// }); - /// ``` - pub fn should_install bool + Send + 'static>( - mut self, - f: F, - ) -> Self { - self.inner = self.inner.should_install(f); - self - } - - /// Sets the timeout for the requests to the updater endpoints. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.inner = self.inner.timeout(timeout); - self - } - - /// Add a `Header` to the request. - pub fn header(mut self, key: K, value: V) -> Result - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - self.inner = self.inner.header(key, value)?; - Ok(self) - } - - /// Check if an update is available. - /// - /// # Examples - /// - /// ```no_run - /// tauri::Builder::default() - /// .setup(|app| { - /// let handle = app.handle(); - /// tauri::async_runtime::spawn(async move { - /// match tauri::updater::builder(handle).check().await { - /// Ok(update) => { - /// if update.is_update_available() { - /// update.download_and_install().await.unwrap(); - /// } - /// } - /// Err(e) => { - /// println!("failed to get update: {}", e); - /// } - /// } - /// }); - /// Ok(()) - /// }); - /// ``` - /// - /// If ther server responds with status code `204`, this method will return [`Error::UpToDate`] - pub async fn check(self) -> Result> { - let handle = self.inner.app.clone(); - // check updates - match self.inner.build().await { - Ok(update) => { - if self.events { - // send notification if we need to update - if update.should_update { - let body = update.body.clone().unwrap_or_else(|| String::from("")); - - // Emit `tauri://update-available` - let _ = handle.emit_all( - EVENT_UPDATE_AVAILABLE, - UpdateManifest { - body: body.clone(), - date: update.date.map(|d| d.to_string()), - version: update.version.clone(), - }, - ); - let _ = handle.create_proxy().send_event(EventLoopMessage::Updater( - UpdaterEvent::UpdateAvailable { - body, - date: update.date, - version: update.version.clone(), - }, - )); - - // Listen for `tauri://update-install` - let update_ = update.clone(); - handle.once_global(EVENT_INSTALL_UPDATE, move |_msg| { - crate::async_runtime::spawn(async move { - let _ = download_and_install(update_).await; - }); - }); - } else { - send_status_update(&handle, UpdaterEvent::AlreadyUpToDate); - } - } - Ok(UpdateResponse { update }) - } - Err(e) => { - if self.events { - send_status_update( - &handle, - match e { - Error::UpToDate => UpdaterEvent::AlreadyUpToDate, - _ => UpdaterEvent::Error(e.to_string()), - }, - ); - } - Err(e) - } - } - } -} - -/// The response of an updater check. -pub struct UpdateResponse { - update: core::Update, -} - -impl Clone for UpdateResponse { - fn clone(&self) -> Self { - Self { - update: self.update.clone(), - } - } -} - -impl UpdateResponse { - /// Whether the updater found a newer release or not. - pub fn is_update_available(&self) -> bool { - self.update.should_update - } - - /// The current version of the application as read by the updater. - pub fn current_version(&self) -> &Version { - &self.update.current_version - } - - /// The latest version of the application found by the updater. - pub fn latest_version(&self) -> &str { - &self.update.version - } - - /// The update date. - pub fn date(&self) -> Option<&OffsetDateTime> { - self.update.date.as_ref() - } - - /// The update description. - pub fn body(&self) -> Option<&String> { - self.update.body.as_ref() - } - - /// Downloads and installs the update. - pub async fn download_and_install(self) -> Result<()> { - download_and_install(self.update).await - } -} - -/// Check if there is any new update with builtin dialog. -pub(crate) async fn check_update_with_dialog(handle: AppHandle) { - let updater_config = handle.config().tauri.updater.clone(); - let package_info = handle.package_info().clone(); - if let Some(endpoints) = updater_config.endpoints.clone() { - let endpoints = endpoints - .iter() - .map(|e| e.to_string()) - .collect::>(); - - let mut builder = self::core::builder(handle.clone()) - .urls(&endpoints[..]) - .current_version(package_info.version); - if let Some(target) = &handle.updater_settings.target { - builder = builder.target(target); - } - - // check updates - match builder.build().await { - Ok(updater) => { - let pubkey = updater_config.pubkey.clone(); - - if !updater.should_update { - send_status_update(&handle, UpdaterEvent::AlreadyUpToDate); - } else if updater_config.dialog { - // if dialog enabled only - let body = updater.body.clone().unwrap_or_else(|| String::from("")); - let dialog = - prompt_for_install(&updater.clone(), &package_info.name, &body.clone(), pubkey).await; - - if let Err(e) = dialog { - send_status_update(&handle, UpdaterEvent::Error(e.to_string())); - } - } - } - Err(e) => { - send_status_update( - &handle, - match e { - Error::UpToDate => UpdaterEvent::AlreadyUpToDate, - _ => UpdaterEvent::Error(e.to_string()), - }, - ); - } - } - } -} - -/// Updater listener -/// This function should be run on the main thread once. -pub(crate) fn listener(handle: AppHandle) { - // Wait to receive the event `"tauri://update"` - let handle_ = handle.clone(); - handle.listen_global(EVENT_CHECK_UPDATE, move |_msg| { - let handle_ = handle_.clone(); - crate::async_runtime::spawn(async move { - let _ = builder(handle_.clone()).check().await; - }); - }); -} - -pub(crate) async fn download_and_install(update: core::Update) -> Result<()> { - // Start installation - // emit {"status": "PENDING"} - send_status_update(&update.app, UpdaterEvent::Pending); - - let handle = update.app.clone(); - let handle_ = handle.clone(); - - // 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 = update - .download_and_install( - update.app.config().tauri.updater.pubkey.clone(), - move |chunk_length, content_length| { - send_download_progress_event(&handle, chunk_length, content_length); - }, - move || { - send_status_update(&handle_, UpdaterEvent::Downloaded); - }, - ) - .await; - - if let Err(err) = &update_result { - // emit {"status": "ERROR", "error": "The error message"} - send_status_update(&update.app, UpdaterEvent::Error(err.to_string())); - } else { - // emit {"status": "DONE"} - send_status_update(&update.app, UpdaterEvent::Updated); - } - update_result -} - -/// Initializes the [`UpdateBuilder`] using the app configuration. -pub fn builder(handle: AppHandle) -> UpdateBuilder { - let updater_config = &handle.config().tauri.updater; - let package_info = handle.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 mut builder = self::core::builder(handle.clone()) - .urls(&endpoints[..]) - .current_version(package_info.version); - if let Some(target) = &handle.updater_settings.target { - builder = builder.target(target); - } - UpdateBuilder { - inner: builder, - events: true, - } -} - -// Send a status update via `tauri://update-download-progress` event. -fn send_download_progress_event( - handle: &AppHandle, - chunk_length: usize, - content_length: Option, -) { - let _ = handle.emit_all( - EVENT_DOWNLOAD_PROGRESS, - DownloadProgressEvent { - chunk_length, - content_length, - }, - ); - let _ = - handle - .create_proxy() - .send_event(EventLoopMessage::Updater(UpdaterEvent::DownloadProgress { - chunk_length, - content_length, - })); -} - -// Send a status update via `tauri://update-status` event. -fn send_status_update(handle: &AppHandle, message: UpdaterEvent) { - let _ = handle.emit_all( - EVENT_STATUS_UPDATE, - if let UpdaterEvent::Error(error) = &message { - StatusEvent { - error: Some(error.clone()), - status: message.clone().status_message().into(), - } - } else { - StatusEvent { - error: None, - status: message.clone().status_message().into(), - } - }, - ); - let _ = handle - .create_proxy() - .send_event(EventLoopMessage::Updater(message)); -} - -// 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( - update: &self::core::Update, - app_name: &str, - body: &str, - pubkey: String, -) -> Result<()> { - let windows = update.app.windows(); - let parent_window = windows.values().next(); - - // todo(lemarier): We should review this and make sure we have - // something more conventional. - let should_install = ask( - parent_window, - format!(r#"A new version of {app_name} is available! "#), - format!( - r#"{app_name} {} is now available -- you have {}. - -Would you like to install it now? - -Release Notes: -{body}"#, - update.version, update.current_version - ), - ); - - if should_install { - let handle = update.app.clone(); - let handle_ = handle.clone(); - - // 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) - update - .download_and_install( - pubkey.clone(), - move |chunk_length, content_length| { - send_download_progress_event(&handle, chunk_length, content_length); - }, - move || { - send_status_update(&handle_, UpdaterEvent::Downloaded); - }, - ) - .await?; - - // emit {"status": "DONE"} - send_status_update(&update.app, UpdaterEvent::Updated); - - // Ask user if we need to restart the application - let should_exit = ask( - parent_window, - "Ready to Restart", - "The installation was successful, do you want to restart the application now?", - ); - if should_exit { - update.app.restart(); - } - } - - 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 974a2ba43323..788cdba8d6da 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -7,11 +7,13 @@ 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}, @@ -19,20 +21,28 @@ use crate::{ manager::WindowManager, runtime::{ http::{Request as HttpRequest, Response as HttpResponse}, - menu::Menu, monitor::Monitor as RuntimeMonitor, webview::{WebviewAttributes, WindowBuilder as _}, window::{ - dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - DetachedWindow, JsEventListenerKey, PendingWindow, + dpi::{PhysicalPosition, PhysicalSize}, + DetachedWindow, PendingWindow, }, - Dispatch, RuntimeHandle, UserAttentionType, + Dispatch, RuntimeHandle, }, sealed::ManagerBase, sealed::RuntimeOrDispatch, - utils::config::{WindowConfig, WindowUrl}, - CursorIcon, EventLoopMessage, Icon, Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager, - PageLoadPayload, Runtime, Theme, WindowEvent, + 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; @@ -42,10 +52,11 @@ use windows::Win32::Foundation::HWND; use tauri_macros::default_runtime; use std::{ + collections::{HashMap, HashSet}, fmt, hash::{Hash, Hasher}, path::PathBuf, - sync::Arc, + sync::{Arc, Mutex}, }; pub(crate) type WebResourceRequestHandler = dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync; @@ -314,7 +325,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { 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), @@ -322,6 +333,9 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { } .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())?, @@ -338,9 +352,11 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { Ok(window) } +} - // --------------------------------------------- Window builder --------------------------------------------- - +/// 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 { @@ -536,6 +552,21 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { 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] + pub fn shadow(mut self, enable: bool) -> Self { + self.window_builder = self.window_builder.shadow(enable); + 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. @@ -600,8 +631,29 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { self } - // ------------------------------------------- Webview attributes ------------------------------------------- + /// 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. + /// + /// 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 + pub fn effects(mut self, effects: WindowEffectsConfig) -> Self { + self.webview_attributes = self.webview_attributes.window_effects(effects); + self + } +} + +/// 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. /// @@ -690,14 +742,27 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { self } - /// Sets whether clicking an inactive window also clicks through to the webview. + /// Enable or disable incognito mode for the WebView.. + /// + /// ## Platform-specific: + /// + /// **Android**: Unsupported. #[must_use] - pub fn accept_first_mouse(mut self, accept: bool) -> Self { - self.webview_attributes.accept_first_mouse = accept; + pub fn incognito(mut self, incognito: bool) -> Self { + self.webview_attributes.incognito = incognito; self } } +/// 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, +} + // TODO: expand these docs since this is a pretty important type /// A webview window managed by Tauri. /// @@ -711,6 +776,7 @@ pub struct Window { /// 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, @@ -728,6 +794,7 @@ impl Clone for Window { 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(), } @@ -789,11 +856,11 @@ impl<'de, R: Runtime> CommandArg<'de, R> for Window { } /// The platform webview handle. Accessed with [`Window#method.with_webview`]; -#[cfg(all(desktop, feature = "wry"))] -#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "wry"))))] +#[cfg(feature = "wry")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))] pub struct PlatformWebview(tauri_runtime_wry::Webview); -#[cfg(all(desktop, feature = "wry"))] +#[cfg(feature = "wry")] impl PlatformWebview { /// Returns [`webkit2gtk::WebView`] handle. #[cfg(any( @@ -829,8 +896,8 @@ impl PlatformWebview { /// Returns the [WKWebView] handle. /// /// [WKWebView]: https://developer.apple.com/documentation/webkit/wkwebview - #[cfg(target_os = "macos")] - #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + #[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 } @@ -838,8 +905,8 @@ impl PlatformWebview { /// Returns WKWebView [controller] handle. /// /// [controller]: https://developer.apple.com/documentation/webkit/wkusercontentcontroller - #[cfg(target_os = "macos")] - #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))] + #[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 } @@ -852,65 +919,20 @@ impl PlatformWebview { pub fn ns_window(&self) -> cocoa::base::id { self.0.ns_window } -} -/// APIs specific to the wry runtime. -#[cfg(feature = "wry")] -impl Window { - /// 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.); - /// } + /// Returns [UIViewController] used by the WKWebView webview NSWindow. /// - /// #[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]; - /// } - /// }); - /// Ok(()) - /// }); - /// } - /// ``` - #[cfg(desktop)] - #[cfg_attr(doc_cfg, doc(cfg(all(feature = "wry", desktop))))] - pub fn with_webview( - &self, - f: F, - ) -> crate::Result<()> { - self - .window - .dispatcher - .with_webview(|w| f(PlatformWebview(w))) - .map_err(Into::into) + /// [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 } } @@ -926,6 +948,7 @@ impl Window { window, manager, app_handle, + js_event_listeners: Default::default(), #[cfg(test)] current_url: "http://tauri.app".parse().unwrap(), } @@ -986,6 +1009,70 @@ impl Window { 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 + .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap()))) + .map_err(Into::into) + } } /// Window getters. @@ -1186,7 +1273,8 @@ impl Window { } } -/// Window setters and actions. +/// Desktop window setters and actions. +#[cfg(desktop)] impl Window { /// Centers the window. pub fn center(&self) -> crate::Result<()> { @@ -1336,6 +1424,59 @@ impl Window { .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 @@ -1512,12 +1653,18 @@ impl Window { 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 config_url = manager.get_url(); - let is_local = config_url.make_relative(¤t_url).is_some(); + 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()); @@ -1548,26 +1695,16 @@ impl Window { payload.cmd.to_string(), payload.inner, ); - let resolver = InvokeResolver::new(self, payload.callback, payload.error); - let invoke = Invoke { message, resolver }; + #[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 let Some(module) = &payload.tauri_module { - if !is_local && scope.map(|s| !s.enables_tauri_api()).unwrap_or_default() { - invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); - return Ok(()); - } - crate::endpoints::handle( - module.to_string(), - invoke, - manager.config(), - manager.package_info(), - ); - } else if payload.cmd.starts_with("plugin:") { + 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(); @@ -1579,9 +1716,123 @@ impl Window { return Ok(()); } } - manager.extend_api(invoke); + 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 { - manager.run_invoke_handler(invoke); + 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")); + } } } } @@ -1594,9 +1845,24 @@ impl Window { self.window.dispatcher.eval_script(js).map_err(Into::into) } - pub(crate) fn register_js_listener(&self, window_label: Option, event: String, id: u32) { + /// 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 - .window .js_event_listeners .lock() .unwrap() @@ -1605,12 +1871,21 @@ impl Window { event, }) .or_insert_with(Default::default) - .insert(id); + .insert(event_id); + + Ok(event_id) } - pub(crate) fn unregister_js_listener(&self, id: u32) { + /// 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.window.js_event_listeners.lock().unwrap(); + 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) { @@ -1625,12 +1900,13 @@ impl Window { 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 - .window .js_event_listeners .lock() .unwrap() @@ -1893,6 +2169,61 @@ impl Window { } } +/// 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 { diff --git a/core/tauri/test/fixture/src-tauri/tauri.conf.json b/core/tauri/test/fixture/src-tauri/tauri.conf.json index 6b4473313d0e..147743e3c13c 100644 --- a/core/tauri/test/fixture/src-tauri/tauri.conf.json +++ b/core/tauri/test/fixture/src-tauri/tauri.conf.json @@ -9,9 +9,6 @@ "identifier": "studio.tauri.example", "active": true }, - "allowlist": { - "all": true - }, "windows": [ { "title": "Tauri App" @@ -19,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/tests/app-updater/.gitignore b/core/tests/app-updater/.gitignore deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/core/tests/app-updater/Cargo.toml b/core/tests/app-updater/Cargo.toml deleted file mode 100644 index 641afabe83aa..000000000000 --- a/core/tests/app-updater/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "app-updater" -version = "0.1.0" -edition = "2021" - -[build-dependencies] -tauri-build = { path = "../../tauri-build", features = [] } - -[dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" -tiny_http = "0.11" -tauri = { path = "../../tauri", features = ["updater"] } -time = { version = "0.3", features = ["formatting"] } - -[features] -default = ["custom-protocol"] -custom-protocol = ["tauri/custom-protocol"] diff --git a/core/tests/app-updater/src/main.rs b/core/tests/app-updater/src/main.rs deleted file mode 100644 index 94c27bcb4db4..000000000000 --- a/core/tests/app-updater/src/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -fn main() { - let mut context = tauri::generate_context!(); - if std::env::var("TARGET").unwrap_or_default() == "nsis" { - // /D sets the default installation directory ($INSTDIR), - // overriding InstallDir and InstallDirRegKey. - // It must be the last parameter used in the command line and must not contain any quotes, even if the path contains spaces. - // Only absolute paths are supported. - // NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder - context.config_mut().tauri.updater.windows.installer_args = vec![format!( - "/D={}", - tauri::utils::platform::current_exe() - .unwrap() - .parent() - .unwrap() - .display() - )]; - } - tauri::Builder::default() - .setup(|app| { - let handle = app.handle(); - tauri::async_runtime::spawn(async move { - match handle.updater().check().await { - Ok(update) => { - if update.is_update_available() { - if let Err(e) = update.download_and_install().await { - println!("{e}"); - std::process::exit(1); - } - } - std::process::exit(0); - } - Err(e) => { - println!("{e}"); - std::process::exit(1); - } - } - }); - Ok(()) - }) - .run(context) - .expect("error while running tauri application"); -} diff --git a/core/tests/app-updater/tauri.conf.json b/core/tests/app-updater/tauri.conf.json deleted file mode 100644 index 8c3351c63433..000000000000 --- a/core/tests/app-updater/tauri.conf.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "../../../core/tauri-config-schema/schema.json", - "build": { - "distDir": [], - "devPath": [] - }, - "tauri": { - "bundle": { - "active": true, - "targets": "all", - "identifier": "com.tauri.updater", - "icon": [ - "../../../examples/.icons/32x32.png", - "../../../examples/.icons/128x128.png", - "../../../examples/.icons/128x128@2x.png", - "../../../examples/.icons/icon.icns", - "../../../examples/.icons/icon.ico" - ], - "category": "DeveloperTool", - "windows": { - "wix": { - "skipWebviewInstall": true - } - } - }, - "allowlist": { - "all": false - }, - "updater": { - "active": true, - "dialog": false, - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK", - "endpoints": ["http://localhost:3007"], - "windows": { - "installMode": "quiet" - } - } - } -} diff --git a/core/tests/app-updater/tests/update.rs b/core/tests/app-updater/tests/update.rs deleted file mode 100644 index fc32c3dfec1f..000000000000 --- a/core/tests/app-updater/tests/update.rs +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![allow(dead_code, unused_imports)] - -use std::{ - collections::HashMap, - fs::File, - path::{Path, PathBuf}, - process::Command, -}; - -use serde::Serialize; - -const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg=="; - -#[derive(Serialize)] -struct PackageConfig { - version: &'static str, -} - -#[derive(Serialize)] -struct Config { - package: PackageConfig, -} - -#[derive(Serialize)] -struct PlatformUpdate { - signature: String, - url: &'static str, - with_elevated_task: bool, -} - -#[derive(Serialize)] -struct Update { - version: &'static str, - date: String, - platforms: HashMap, -} - -fn get_cli_bin_path(cli_dir: &Path, debug: bool) -> Option { - let mut cli_bin_path = cli_dir.join(format!( - "target/{}/cargo-tauri", - if debug { "debug" } else { "release" } - )); - if cfg!(windows) { - cli_bin_path.set_extension("exe"); - } - if cli_bin_path.exists() { - Some(cli_bin_path) - } else { - None - } -} - -fn build_app( - cli_bin_path: &Path, - cwd: &Path, - config: &Config, - bundle_updater: bool, - target: BundleTarget, -) { - let mut command = Command::new(cli_bin_path); - command - .args(["build", "--debug", "--verbose"]) - .arg("--config") - .arg(serde_json::to_string(config).unwrap()) - .current_dir(cwd); - - #[cfg(target_os = "linux")] - command.args(["--bundles", target.name()]); - #[cfg(target_os = "macos")] - command.args(["--bundles", target.name()]); - - if bundle_updater { - #[cfg(windows)] - command.args(["--bundles", "msi", "nsis"]); - - command - .env("TAURI_PRIVATE_KEY", UPDATER_PRIVATE_KEY) - .env("TAURI_KEY_PASSWORD", "") - .args(["--bundles", "updater"]); - } else { - #[cfg(windows)] - command.args(["--bundles", target.name()]); - } - - let status = command - .status() - .expect("failed to run Tauri CLI to bundle app"); - - if !status.code().map(|c| c == 0).unwrap_or(true) { - panic!("failed to bundle app {:?}", status.code()); - } -} - -#[derive(Copy, Clone)] -enum BundleTarget { - AppImage, - - App, - - Msi, - Nsis, -} - -impl BundleTarget { - fn name(self) -> &'static str { - match self { - Self::AppImage => "appimage", - Self::App => "app", - Self::Msi => "msi", - Self::Nsis => "nsis", - } - } -} - -impl Default for BundleTarget { - fn default() -> Self { - #[cfg(any(target_os = "macos", target_os = "ios"))] - return Self::App; - #[cfg(target_os = "linux")] - return Self::AppImage; - #[cfg(windows)] - return Self::Nsis; - } -} - -#[cfg(target_os = "linux")] -fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { - vec![( - BundleTarget::AppImage, - root_dir.join(format!( - "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" - )), - )] -} - -#[cfg(target_os = "macos")] -fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> { - vec![( - BundleTarget::App, - root_dir.join("target/debug/bundle/macos/app-updater.app"), - )] -} - -#[cfg(target_os = "ios")] -fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> { - vec![( - BundleTarget::App, - root_dir.join("target/debug/bundle/ios/app-updater.app"), - )] -} - -#[cfg(windows)] -fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { - vec![ - ( - BundleTarget::Nsis, - root_dir.join(format!( - "target/debug/bundle/nsis/app-updater_{version}_x64-setup.exe" - )), - ), - ( - BundleTarget::Msi, - root_dir.join(format!( - "target/debug/bundle/msi/app-updater_{version}_x64_en-US.msi" - )), - ), - ] -} - -#[test] -#[ignore] -fn update_app() { - let target = tauri::updater::target().expect("running updater test in an unsupported platform"); - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let root_dir = manifest_dir.join("../../.."); - let cli_dir = root_dir.join("tooling/cli"); - - let cli_bin_path = if let Some(p) = get_cli_bin_path(&cli_dir, false) { - p - } else if let Some(p) = get_cli_bin_path(&cli_dir, true) { - p - } else { - let status = Command::new("cargo") - .arg("build") - .current_dir(&cli_dir) - .status() - .expect("failed to run cargo"); - if !status.success() { - panic!("failed to build CLI"); - } - get_cli_bin_path(&cli_dir, true).expect("cargo did not build the Tauri CLI") - }; - - let mut config = Config { - package: PackageConfig { version: "1.0.0" }, - }; - - // bundle app update - build_app( - &cli_bin_path, - &manifest_dir, - &config, - true, - Default::default(), - ); - - let updater_zip_ext = if cfg!(windows) { "zip" } else { "tar.gz" }; - - for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { - let bundle_updater_ext = out_bundle_path - .extension() - .unwrap() - .to_str() - .unwrap() - .replace("exe", "nsis"); - let signature_path = - out_bundle_path.with_extension(format!("{bundle_updater_ext}.{updater_zip_ext}.sig")); - let signature = std::fs::read_to_string(&signature_path) - .unwrap_or_else(|_| panic!("failed to read signature file {}", signature_path.display())); - let out_updater_path = - out_bundle_path.with_extension(format!("{}.{}", bundle_updater_ext, updater_zip_ext)); - let updater_path = root_dir.join(format!( - "target/debug/{}", - out_updater_path.file_name().unwrap().to_str().unwrap() - )); - std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle"); - - let target = target.clone(); - std::thread::spawn(move || { - // start the updater server - let server = - tiny_http::Server::http("localhost:3007").expect("failed to start updater server"); - - loop { - if let Ok(request) = server.recv() { - match request.url() { - "/" => { - let mut platforms = HashMap::new(); - - platforms.insert( - target.clone(), - PlatformUpdate { - signature: signature.clone(), - url: "http://localhost:3007/download", - with_elevated_task: false, - }, - ); - let body = serde_json::to_vec(&Update { - version: "1.0.0", - date: time::OffsetDateTime::now_utc() - .format(&time::format_description::well_known::Rfc3339) - .unwrap(), - platforms, - }) - .unwrap(); - let len = body.len(); - let response = tiny_http::Response::new( - tiny_http::StatusCode(200), - Vec::new(), - std::io::Cursor::new(body), - Some(len), - None, - ); - let _ = request.respond(response); - } - "/download" => { - let _ = request.respond(tiny_http::Response::from_file( - File::open(&updater_path).unwrap_or_else(|_| { - panic!("failed to open updater bundle {}", updater_path.display()) - }), - )); - // close server - return; - } - _ => (), - } - } - } - }); - - config.package.version = "0.1.0"; - - // bundle initial app version - build_app(&cli_bin_path, &manifest_dir, &config, false, bundle_target); - - let mut binary_cmd = if cfg!(windows) { - Command::new(root_dir.join("target/debug/app-updater.exe")) - } else if cfg!(target_os = "macos") { - Command::new( - bundle_paths(&root_dir, "0.1.0") - .first() - .unwrap() - .1 - .join("Contents/MacOS/app-updater"), - ) - } else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() { - let mut c = Command::new("xvfb-run"); - c.arg("--auto-servernum") - .arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); - c - } else { - Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) - }; - - binary_cmd.env("TARGET", bundle_target.name()); - - let status = binary_cmd.status().expect("failed to run app"); - - if !status.success() { - panic!("failed to run app"); - } - } -} diff --git a/core/tests/restart/src/main.rs b/core/tests/restart/src/main.rs index 32a1641bdc49..6984ab20c5d3 100644 --- a/core/tests/restart/src/main.rs +++ b/core/tests/restart/src/main.rs @@ -13,8 +13,8 @@ fn main() { println!( "{}", - tauri::api::process::current_binary(&Default::default()) - .expect("tauri::api::process::current_binary could not resolve") + tauri::process::current_binary(&Default::default()) + .expect("tauri::process::current_binary could not resolve") .display() ); @@ -22,7 +22,7 @@ fn main() { Some("restart") => { let mut env = Env::default(); env.args.clear(); - tauri::api::process::restart(&env) + 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 index 6f5d75ab5626..a174f537a977 100644 --- a/core/tests/restart/tests/restart.rs +++ b/core/tests/restart/tests/restart.rs @@ -45,7 +45,7 @@ fn symlink_runner(create_symlinks: impl Fn(&Path) -> io::Result) -> Res // 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 + // add the restart parameter so that the invocation will call tauri::process::restart cmd.arg("restart"); let output = cmd.output()?; diff --git a/examples/api/dist/assets/index.css b/examples/api/dist/assets/index.css index 535be4489c30..1148ece6d18e 100644 --- a/examples/api/dist/assets/index.css +++ b/examples/api/dist/assets/index.css @@ -1 +1 @@ -*,::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-bell-dot{--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 fill-rule='evenodd' d='M12.994 7.875A4.008 4.008 0 0 1 12 8h-.01v.217c0 .909.143 1.818.442 2.691l.371 1.113h-9.63v-.012l.37-1.113a8.633 8.633 0 0 0 .443-2.691V6.004c0-.563.12-1.113.347-1.616c.227-.514.55-.969.969-1.34c.419-.382.91-.67 1.436-.837c.538-.18 1.1-.24 1.65-.18l.12.018a4 4 0 0 1 .673-.887a5.15 5.15 0 0 0-.697-.135c-.694-.072-1.4 0-2.07.227c-.67.215-1.28.574-1.794 1.053a4.923 4.923 0 0 0-1.208 1.675a5.067 5.067 0 0 0-.431 2.022v2.2a7.61 7.61 0 0 1-.383 2.37L2 12.343l.479.658h3.505c0 .526.215 1.04.586 1.412c.37.37.885.586 1.412.586c.526 0 1.04-.215 1.411-.586s.587-.886.587-1.412h3.505l.478-.658l-.586-1.77a7.63 7.63 0 0 1-.383-2.381v-.318ZM7.982 14.02a.997.997 0 0 0 .706-.3a.939.939 0 0 0 .287-.705H6.977c0 .263.107.514.299.706a.999.999 0 0 0 .706.299Z' clip-rule='evenodd'/%3E%3Cpath d='M12 7a3 3 0 1 0 0-6a3 3 0 0 0 0 6Z'/%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-chrome-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='m7.116 8l-4.558 4.558l.884.884L8 8.884l4.558 4.558l.884-.884L8.884 8l4.558-4.558l-.884-.884L8 7.116L3.442 2.558l-.884.884L7.116 8z' 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-chrome-maximize{--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='M3 3v10h10V3H3zm9 9H4V4h8v8z'/%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-chrome-minimize{--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='M14 8v1H3V8h11z'/%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-chrome-restore{--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='M3 5v9h9V5H3zm8 8H4V6h7v7z'/%3E%3Cpath fill-rule='evenodd' d='M5 5h1V4h7v7h-1v1h2V3H5v2z' clip-rule='evenodd'/%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-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-clippy{--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='M7 13.992H4v-9h8v2h1v-2.5l-.5-.5H11v-1h-1a2 2 0 0 0-4 0H4.94v1H3.5l-.5.5v10l.5.5H7v-1zm0-11.2a1 1 0 0 1 .8-.8a1 1 0 0 1 .58.06a.94.94 0 0 1 .45.36a1 1 0 1 1-1.75.94a1 1 0 0 1-.08-.56zm7.08 9.46L13 13.342v-5.35h-1v5.34l-1.08-1.08l-.71.71l1.94 1.93h.71l1.93-1.93l-.71-.71zm-5.92-4.16h.71l1.93 1.93l-.71.71l-1.08-1.08v5.34h-1v-5.35l-1.08 1.09l-.71-.71l1.94-1.93z' 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-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-cloud-download{--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='M11.957 6h.05a2.99 2.99 0 0 1 2.116.879a3.003 3.003 0 0 1 0 4.242a2.99 2.99 0 0 1-2.117.879v-1a2.002 2.002 0 0 0 0-4h-.914l-.123-.857a2.49 2.49 0 0 0-2.126-2.122A2.478 2.478 0 0 0 6.231 5.5l-.333.762l-.809-.189A2.49 2.49 0 0 0 4.523 6c-.662 0-1.297.263-1.764.732A2.503 2.503 0 0 0 4.523 11h.498v1h-.498a3.486 3.486 0 0 1-2.628-1.16a3.502 3.502 0 0 1 1.958-5.78a3.462 3.462 0 0 1 1.468.04a3.486 3.486 0 0 1 3.657-2.06A3.479 3.479 0 0 1 11.957 6zm-5.25 5.121l1.314 1.314V7h.994v5.4l1.278-1.279l.707.707l-2.146 2.147h-.708L6 11.829l.707-.708z' 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-files{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 24 24' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M17.5 0h-9L7 1.5V6H2.5L1 7.5v15.07L2.5 24h12.07L16 22.57V18h4.7l1.3-1.43V4.5L17.5 0zm0 2.12l2.38 2.38H17.5V2.12zm-3 20.38h-12v-15H7v9.07L8.5 18h6v4.5zm6-6h-12v-15H16V6h4.5v10.5z'/%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-hubot{--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.48 4h4l.5.5v2.03h.52l.5.5V8l-.5.5h-.52v3l-.5.5H9.36l-2.5 2.76L6 14.4V12H3.5l-.5-.64V8.5h-.5L2 8v-.97l.5-.5H3V4.36L3.53 4h4V2.86A1 1 0 0 1 7 2a1 1 0 0 1 2 0a1 1 0 0 1-.52.83V4zM12 8V5H4v5.86l2.5.14H7v2.19l1.8-2.04l.35-.15H12V8zm-2.12.51a2.71 2.71 0 0 1-1.37.74v-.01a2.71 2.71 0 0 1-2.42-.74l-.7.71c.34.34.745.608 1.19.79c.45.188.932.286 1.42.29a3.7 3.7 0 0 0 2.58-1.07l-.7-.71zM6.49 6.5h-1v1h1v-1zm3 0h1v1h-1v-1z' 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-multiple-windows{--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='m6 1.5l.5-.5h8l.5.5v7l-.5.5H12V8h2V4H7v1H6V1.5zM7 2v1h7V2H7zM1.5 7l-.5.5v7l.5.5h8l.5-.5v-7L9.5 7h-8zM2 9V8h7v1H2zm0 1h7v4H2v-4z' 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-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-codicon-record-keys{--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='M14 3H3a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm0 8H3V4h11v7zm-3-6h-1v1h1V5zm-1 2H9v1h1V7zm2-2h1v1h-1V5zm1 4h-1v1h1V9zM6 9h5v1H6V9zm7-2h-2v1h2V7zM8 5h1v1H8V5zm0 2H7v1h1V7zM4 9h1v1H4V9zm0-4h1v1H4V5zm3 0H6v1h1V5zM4 7h2v1H4V7z' 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-terminal{--un-icon:url("data:image/svg+xml;utf8,%3Csvg preserveAspectRatio='xMidYMid meet' viewBox='0 0 24 24' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M3 1.5L1.5 3v18L3 22.5h18l1.5-1.5V3L21 1.5H3zM3 21V3h18v18H3zm5.656-4.01l1.038 1.061l5.26-5.243v-.912l-5.26-5.26l-1.035 1.06l4.59 4.702l-4.593 4.592z' 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-terminal-bash{--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='M13.655 3.56L8.918.75a1.785 1.785 0 0 0-1.82 0L2.363 3.56a1.889 1.889 0 0 0-.921 1.628v5.624a1.889 1.889 0 0 0 .913 1.627l4.736 2.812a1.785 1.785 0 0 0 1.82 0l4.736-2.812a1.888 1.888 0 0 0 .913-1.627V5.188a1.889 1.889 0 0 0-.904-1.627zm-3.669 8.781v.404a.149.149 0 0 1-.07.124l-.239.137c-.038.02-.07 0-.07-.053v-.396a.78.78 0 0 1-.545.053a.073.073 0 0 1-.027-.09l.086-.365a.153.153 0 0 1 .071-.096a.048.048 0 0 1 .038 0a.662.662 0 0 0 .497-.063a.662.662 0 0 0 .37-.567c0-.206-.112-.292-.384-.293c-.344 0-.661-.066-.67-.574A1.47 1.47 0 0 1 9.6 9.437V9.03a.147.147 0 0 1 .07-.126l.231-.147c.038-.02.07 0 .07.054v.409a.754.754 0 0 1 .453-.055a.073.073 0 0 1 .03.095l-.081.362a.156.156 0 0 1-.065.09a.055.055 0 0 1-.035 0a.6.6 0 0 0-.436.072a.549.549 0 0 0-.331.486c0 .185.098.242.425.248c.438 0 .627.199.632.639a1.568 1.568 0 0 1-.576 1.185zm2.481-.68a.094.094 0 0 1-.036.092l-1.198.727a.034.034 0 0 1-.04.003a.035.035 0 0 1-.016-.037v-.31a.086.086 0 0 1 .055-.076l1.179-.706a.035.035 0 0 1 .056.035v.273zm.827-6.914L8.812 7.515c-.559.331-.97.693-.97 1.367v5.52c0 .404.165.662.413.741a1.465 1.465 0 0 1-.248.025c-.264 0-.522-.072-.748-.207L2.522 12.15a1.558 1.558 0 0 1-.75-1.338V5.188a1.558 1.558 0 0 1 .75-1.34l4.738-2.81a1.46 1.46 0 0 1 1.489 0l4.736 2.812a1.548 1.548 0 0 1 .728 1.083c-.154-.334-.508-.427-.92-.185h.002z'/%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-window{--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='M14.5 2h-13l-.5.5v11l.5.5h13l.5-.5v-11l-.5-.5zM14 13H2V6h12v7zm0-8H2V3h12v2z'/%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-globe-hemisphere-west{--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='M221.6 173.3A102.9 102.9 0 0 0 232 128a104.2 104.2 0 0 0-77.2-100.5h-.5A103.8 103.8 0 0 0 60.4 49l-1.3 1.2A103.9 103.9 0 0 0 128 232h2.4a104.3 104.3 0 0 0 90.6-57.4ZM216 128a89.3 89.3 0 0 1-5.5 30.7l-46.4-28.5a16.6 16.6 0 0 0-6.3-2.3l-22.8-3a16.1 16.1 0 0 0-15.3 6.8h-8.6l-3.8-7.9a15.9 15.9 0 0 0-11-8.7l-6.6-1.4l4.6-10.8h21.4a16.1 16.1 0 0 0 7.7-2l12.2-6.8a16.1 16.1 0 0 0 3-2.1l26.9-24.4a15.7 15.7 0 0 0 4.5-16.9a88 88 0 0 1 46 77.3Zm-68.8-85.9l7.6 13.7l-26.9 24.3l-12.2 6.8H94.3a15.9 15.9 0 0 0-14.7 9.8l-5.3 12.4l-10.9-29.2l8.1-19.3a88 88 0 0 1 75.7-18.5ZM40 128a87.1 87.1 0 0 1 9.5-39.7l10.4 27.9a16.1 16.1 0 0 0 11.6 10l5.5 1.2h.1l15.8 3.4l3.8 7.9a16.3 16.3 0 0 0 14.4 9h1.2l-7.7 17.2a15.9 15.9 0 0 0 2.8 17.4l18.8 20.4l-2.5 13.2A88.1 88.1 0 0 1 40 128Zm100.1 87.2l1.8-9.5a16 16 0 0 0-3.9-13.9l-18.8-20.3l12.7-28.7l1-2.1l22.8 3.1l47.8 29.4a88.5 88.5 0 0 1-63.4 42Z'/%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{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);padding:0.5rem;text-decoration:none;}.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{--un-border-opacity:1;border-color:rgba(103,214,237,var(--un-border-opacity));background-color:rgba(103,214,237,0.1);}.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;}.my-2{margin-top:0.5rem;margin-bottom:0.5rem;}.mb-2{margin-bottom:0.5rem;}.mr-2{margin-right:0.5rem;}.display-none,.hidden{display:none;}.children-h-10>*,.children\:h10>*{height:2.5rem;}.children\:h-100\%>*,.h-100\%{height:100%;}.children\:w-12>*{width:3rem;}.h-15rem{height:15rem;}.h-2px{height:2px;}.h-8{height:2rem;}.h-85\%{height:85%;}.h-auto{height:auto;}.h-screen{height:100vh;}.w-100\%{width:100%;}.w-8{width:2rem;}.w-screen{width:100vw;}.flex{display:flex;}.children\:inline-flex>*{display:inline-flex;}.flex-1{flex:1 1 0%;}.children-flex-none>*{flex:none;}.children\:grow>*,.grow{flex-grow:1;}.flex-row{flex-direction:row;}.flex-col{flex-direction:column;}.flex-wrap{flex-wrap:wrap;}@keyframes fade-in{from{opacity:0}to{opacity:1}}@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.animate-fade-in{animation:fade-in 1s linear 1;}.animate-spin{animation:spin 1s linear infinite;}.animate-duration-300ms{animation-duration:300ms;}.cursor-ns-resize{cursor:ns-resize;}.cursor-pointer{cursor:pointer;}.select-none{user-select:none;}.children\:items-center>*,.items-center{align-items:center;}.self-center{align-self:center;}.children\:justify-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));}.dark .dark\:hover\:bg-red-700:hover,.hover\:bg-red-700:hover{--un-bg-opacity:1;background-color:rgba(185,28,28,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);}.active\:bg-hoverOverlayDarker:active{--un-bg-opacity:.1;background-color:rgba(0,0,0,var(--un-bg-opacity));}.active\:bg-red-700\/90:active,.dark .dark\:active\:bg-red-700\/90:active{background-color:rgba(185,28,28,0.9);}.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);}.dark .dark\:active\:bg-darkHoverOverlayDarker:active{--un-bg-opacity:.1;background-color:hsla(0,0%,100%,var(--un-bg-opacity));}.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;}.pl-2{padding-left:0.5rem;}.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;}.text-sm{font-size:0.875rem;line-height:1.25rem;}.font-700{font-weight:700;}.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-darkAccentText,.text-primaryText{--un-text-opacity:1;color:rgba(28,30,33,var(--un-text-opacity));}.dark .dark\:text-darkPrimaryText,.hover\:text-darkPrimaryText:hover,.text-darkPrimaryText,.active\:text-darkPrimaryText:active{--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-accentText{--un-text-opacity:1;color:rgba(255,255,255,var(--un-text-opacity));}.filter{filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia);}.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\:pl-10{padding-left:2.5rem;}.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))}}ul.svelte-gbh3pt{list-style:none;margin:0;padding:0;padding-left:var(--nodePaddingLeft, 1rem);border-left:var(--nodeBorderLeft, 1px dotted #9ca3af);color:var(--nodeColor, #374151)}.hidden.svelte-gbh3pt{display:none}.bracket.svelte-gbh3pt{cursor:pointer}.bracket.svelte-gbh3pt:hover{background:var(--bracketHoverBackground, #d1d5db)}.comma.svelte-gbh3pt{color:var(--nodeColor, #374151)}.val.svelte-gbh3pt{color:var(--leafDefaultColor, #9ca3af)}.val.string.svelte-gbh3pt{color:var(--leafStringColor, #059669)}.val.number.svelte-gbh3pt{color:var(--leafNumberColor, #d97706)}.val.boolean.svelte-gbh3pt{color:var(--leafBooleanColor, #2563eb)}.spinner.svelte-4xesec{height:1.2rem;width:1.2rem;border-radius:50rem;color:currentColor;border:2px dashed currentColor} +*,::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 d1121b86296b..0ee0c82e2d75 100644 --- a/examples/api/dist/assets/index.js +++ b/examples/api/dist/assets/index.js @@ -1,50 +1,9 @@ -const vo=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))i(l);new MutationObserver(l=>{for(const o of l)if(o.type==="childList")for(const u of o.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&i(u)}).observe(document,{childList:!0,subtree:!0});function n(l){const o={};return l.integrity&&(o.integrity=l.integrity),l.referrerpolicy&&(o.referrerPolicy=l.referrerpolicy),l.crossorigin==="use-credentials"?o.credentials="include":l.crossorigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function i(l){if(l.ep)return;l.ep=!0;const o=n(l);fetch(l.href,o)}};vo();function V(){}function Ps(e){return e()}function ns(){return Object.create(null)}function oe(e){e.forEach(Ps)}function wo(e){return typeof e=="function"}function pe(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}let ti;function ko(e,t){return ti||(ti=document.createElement("a")),ti.href=t,e===ti.href}function Mo(e){return Object.keys(e).length===0}function Co(e,...t){if(e==null)return V;const n=e.subscribe(...t);return n.unsubscribe?()=>n.unsubscribe():n}function Os(e,t,n){e.$$.on_destroy.push(Co(t,n))}function s(e,t){e.appendChild(t)}function h(e,t,n){e.insertBefore(t,n||null)}function p(e){e.parentNode.removeChild(e)}function yt(e,t){for(let n=0;ne.removeEventListener(t,n,i)}function ai(e){return function(t){return t.preventDefault(),e.call(this,t)}}function r(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function se(e){return e===""?null:+e}function Ao(e){return Array.from(e.childNodes)}function Z(e,t){t=""+t,e.wholeText!==t&&(e.data=t)}function q(e,t){e.value=t==null?"":t}function Ht(e,t){for(let n=0;n{si.delete(e),i&&(n&&e.d(1),i())}),e.o(t)}else i&&i()}function ui(e){e&&e.c()}function xt(e,t,n,i){const{fragment:l,on_mount:o,on_destroy:u,after_update:d}=e.$$;l&&l.m(t,n),i||Ft(()=>{const c=o.map(Ps).filter(wo);u?u.push(...c):oe(c),e.$$.on_mount=[]}),d.forEach(Ft)}function $t(e,t){const n=e.$$;n.fragment!==null&&(oe(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function Wo(e,t){e.$$.dirty[0]===-1&&(Yt.push(e),zo(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const y=v.length?v[0]:_;return f.ctx&&l(f.ctx[k],f.ctx[k]=y)&&(!f.skip_bound&&f.bound[k]&&f.bound[k](y),g&&Wo(e,k)),_}):[],f.update(),g=!0,oe(f.before_update),f.fragment=i?i(f.ctx):!1,t.target){if(t.hydrate){const k=Ao(t.target);f.fragment&&f.fragment.l(k),k.forEach(p)}else f.fragment&&f.fragment.c();t.intro&&Te(e.$$.fragment),xt(e,t.target,t.anchor,t.customElement),Is()}Qt(c)}class ye{$destroy(){$t(this,1),this.$destroy=V}$on(t,n){const i=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return i.push(n),()=>{const l=i.indexOf(n);l!==-1&&i.splice(l,1)}}$set(t){this.$$set&&!Mo(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const It=[];function Hs(e,t=V){let n;const i=new Set;function l(d){if(pe(e,d)&&(e=d,n)){const c=!It.length;for(const f of i)f[1](),It.push(f,e);if(c){for(let f=0;f{i.delete(f),i.size===0&&(n(),n=null)}}return{set:l,update:o,subscribe:u}}var Do=Object.defineProperty,Me=(e,t)=>{for(var n in t)Do(e,n,{get:t[n],enumerable:!0})},Po={};Me(Po,{convertFileSrc:()=>Fs,invoke:()=>ci,transformCallback:()=>vt});function Oo(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function vt(e,t=!1){let n=Oo(),i=`_${n}`;return Object.defineProperty(window,i,{value:l=>(t&&Reflect.deleteProperty(window,i),e==null?void 0:e(l)),writable:!1,configurable:!0}),n}async function ci(e,t={}){return new Promise((n,i)=>{let l=vt(u=>{n(u),Reflect.deleteProperty(window,`_${o}`)},!0),o=vt(u=>{i(u),Reflect.deleteProperty(window,`_${l}`)},!0);window.__TAURI_IPC__({cmd:e,callback:l,error:o,...t})})}function Fs(e,t="asset"){let n=encodeURIComponent(e);return navigator.userAgent.includes("Windows")?`https://${t}.localhost/${n}`:`${t}://localhost/${n}`}async function L(e){return ci("tauri",e)}var Ro={};Me(Ro,{Child:()=>Ns,Command:()=>Yi,EventEmitter:()=>oi,open:()=>Ki});async function Io(e,t,n=[],i){return typeof n=="object"&&Object.freeze(n),L({__tauriModule:"Shell",message:{cmd:"execute",program:t,args:n,options:i,onEventFn:vt(e)}})}var oi=class{constructor(){this.eventListeners=Object.create(null)}addListener(e,t){return this.on(e,t)}removeListener(e,t){return this.off(e,t)}on(e,t){return e in this.eventListeners?this.eventListeners[e].push(t):this.eventListeners[e]=[t],this}once(e,t){let n=(...i)=>{this.removeListener(e,n),t(...i)};return this.addListener(e,n)}off(e,t){return e in this.eventListeners&&(this.eventListeners[e]=this.eventListeners[e].filter(n=>n!==t)),this}removeAllListeners(e){return e?delete this.eventListeners[e]:this.eventListeners=Object.create(null),this}emit(e,...t){if(e in this.eventListeners){let n=this.eventListeners[e];for(let i of n)i(...t);return!0}return!1}listenerCount(e){return e in this.eventListeners?this.eventListeners[e].length:0}prependListener(e,t){return e in this.eventListeners?this.eventListeners[e].unshift(t):this.eventListeners[e]=[t],this}prependOnceListener(e,t){let n=(...i)=>{this.removeListener(e,n),t(...i)};return this.prependListener(e,n)}},Ns=class{constructor(e){this.pid=e}async write(e){return L({__tauriModule:"Shell",message:{cmd:"stdinWrite",pid:this.pid,buffer:typeof e=="string"?e:Array.from(e)}})}async kill(){return L({__tauriModule:"Shell",message:{cmd:"killChild",pid:this.pid}})}},Yi=class extends oi{constructor(e,t=[],n){super(),this.stdout=new oi,this.stderr=new oi,this.program=e,this.args=typeof t=="string"?[t]:t,this.options=n!=null?n:{}}static sidecar(e,t=[],n){let i=new Yi(e,t,n);return i.options.sidecar=!0,i}async spawn(){return Io(e=>{switch(e.event){case"Error":this.emit("error",e.payload);break;case"Terminated":this.emit("close",e.payload);break;case"Stdout":this.stdout.emit("data",e.payload);break;case"Stderr":this.stderr.emit("data",e.payload);break}},this.program,this.args,this.options).then(e=>new Ns(e))}async execute(){return new Promise((e,t)=>{this.on("error",t);let n=[],i=[];this.stdout.on("data",l=>{n.push(l)}),this.stderr.on("data",l=>{i.push(l)}),this.on("close",l=>{e({code:l.code,signal:l.signal,stdout:n.join(` -`),stderr:i.join(` -`)})}),this.spawn().catch(t)})}};async function Ki(e,t){return L({__tauriModule:"Shell",message:{cmd:"open",path:e,with:t}})}var Ho={};Me(Ho,{TauriEvent:()=>Bs,emit:()=>_i,listen:()=>tn,once:()=>Vs});async function js(e,t){return L({__tauriModule:"Event",message:{cmd:"unlisten",event:e,eventId:t}})}async function Us(e,t,n){await L({__tauriModule:"Event",message:{cmd:"emit",event:e,windowLabel:t,payload:n}})}async function Qi(e,t,n){return L({__tauriModule:"Event",message:{cmd:"listen",event:e,windowLabel:t,handler:vt(n)}}).then(i=>async()=>js(e,i))}async function qs(e,t,n){return Qi(e,t,i=>{n(i),js(e,i.id).catch(()=>{})})}var Bs=(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.CHECK_UPDATE="tauri://update",e.UPDATE_AVAILABLE="tauri://update-available",e.INSTALL_UPDATE="tauri://update-install",e.STATUS_UPDATE="tauri://update-status",e.DOWNLOAD_PROGRESS="tauri://update-download-progress",e))(Bs||{});async function tn(e,t){return Qi(e,null,t)}async function Vs(e,t){return qs(e,null,t)}async function _i(e,t){return Us(e,void 0,t)}var Fo={};Me(Fo,{CloseRequestedEvent:()=>Ks,LogicalPosition:()=>Gs,LogicalSize:()=>di,PhysicalPosition:()=>ot,PhysicalSize:()=>gt,UserAttentionType:()=>Zi,WebviewWindow:()=>wt,WebviewWindowHandle:()=>Xs,WindowManager:()=>Ys,appWindow:()=>Ge,availableMonitors:()=>Uo,currentMonitor:()=>No,getAll:()=>Js,getCurrent:()=>Kt,primaryMonitor:()=>jo});var di=class{constructor(e,t){this.type="Logical",this.width=e,this.height=t}},gt=class{constructor(e,t){this.type="Physical",this.width=e,this.height=t}toLogical(e){return new di(this.width/e,this.height/e)}},Gs=class{constructor(e,t){this.type="Logical",this.x=e,this.y=t}},ot=class{constructor(e,t){this.type="Physical",this.x=e,this.y=t}toLogical(e){return new Gs(this.x/e,this.y/e)}},Zi=(e=>(e[e.Critical=1]="Critical",e[e.Informational=2]="Informational",e))(Zi||{});function Kt(){return new wt(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0})}function Js(){return window.__TAURI_METADATA__.__windows.map(e=>new wt(e.label,{skip:!0}))}var ls=["tauri://created","tauri://error"],Xs=class{constructor(e){this.label=e,this.listeners=Object.create(null)}async listen(e,t){return this._handleTauriEvent(e,t)?Promise.resolve(()=>{let n=this.listeners[e];n.splice(n.indexOf(t),1)}):Qi(e,this.label,t)}async once(e,t){return this._handleTauriEvent(e,t)?Promise.resolve(()=>{let n=this.listeners[e];n.splice(n.indexOf(t),1)}):qs(e,this.label,t)}async emit(e,t){if(ls.includes(e)){for(let n of this.listeners[e]||[])n({event:e,id:-1,windowLabel:this.label,payload:t});return Promise.resolve()}return Us(e,this.label,t)}_handleTauriEvent(e,t){return ls.includes(e)?(e in this.listeners?this.listeners[e].push(t):this.listeners[e]=[t],!0):!1}},Ys=class extends Xs{async scaleFactor(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"scaleFactor"}}}})}async innerPosition(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"innerPosition"}}}}).then(({x:e,y:t})=>new ot(e,t))}async outerPosition(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"outerPosition"}}}}).then(({x:e,y:t})=>new ot(e,t))}async innerSize(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"innerSize"}}}}).then(({width:e,height:t})=>new gt(e,t))}async outerSize(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"outerSize"}}}}).then(({width:e,height:t})=>new gt(e,t))}async isFullscreen(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isFullscreen"}}}})}async isMinimized(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMinimized"}}}})}async isMaximized(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMaximized"}}}})}async isDecorated(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isDecorated"}}}})}async isResizable(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isResizable"}}}})}async isMaximizable(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMaximizable"}}}})}async isMinimizable(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isMinimizable"}}}})}async isClosable(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isClosable"}}}})}async isVisible(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"isVisible"}}}})}async title(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"title"}}}})}async theme(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"theme"}}}})}async center(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"center"}}}})}async requestUserAttention(e){let t=null;return e&&(e===1?t={type:"Critical"}:t={type:"Informational"}),L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"requestUserAttention",payload:t}}}})}async setResizable(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setResizable",payload:e}}}})}async setMaximizable(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setMaximizable",payload:e}}}})}async setMinimizable(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setMinimizable",payload:e}}}})}async setClosable(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setClosable",payload:e}}}})}async setTitle(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setTitle",payload:e}}}})}async maximize(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"maximize"}}}})}async unmaximize(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"unmaximize"}}}})}async toggleMaximize(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"toggleMaximize"}}}})}async minimize(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"minimize"}}}})}async unminimize(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"unminimize"}}}})}async show(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"show"}}}})}async hide(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"hide"}}}})}async close(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"close"}}}})}async setDecorations(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setDecorations",payload:e}}}})}async setAlwaysOnTop(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setAlwaysOnTop",payload:e}}}})}async setContentProtected(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setContentProtected",payload:e}}}})}async setSize(e){if(!e||e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setSize",payload:{type:e.type,data:{width:e.width,height:e.height}}}}}})}async setMinSize(e){if(e&&e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setMinSize",payload:e?{type:e.type,data:{width:e.width,height:e.height}}:null}}}})}async setMaxSize(e){if(e&&e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setMaxSize",payload:e?{type:e.type,data:{width:e.width,height:e.height}}:null}}}})}async setPosition(e){if(!e||e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setPosition",payload:{type:e.type,data:{x:e.x,y:e.y}}}}}})}async setFullscreen(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setFullscreen",payload:e}}}})}async setFocus(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setFocus"}}}})}async setIcon(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setIcon",payload:{icon:typeof e=="string"?e:Array.from(e)}}}}})}async setSkipTaskbar(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setSkipTaskbar",payload:e}}}})}async setCursorGrab(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setCursorGrab",payload:e}}}})}async setCursorVisible(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setCursorVisible",payload:e}}}})}async setCursorIcon(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setCursorIcon",payload:e}}}})}async setCursorPosition(e){if(!e||e.type!=="Logical"&&e.type!=="Physical")throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setCursorPosition",payload:{type:e.type,data:{x:e.x,y:e.y}}}}}})}async setIgnoreCursorEvents(e){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"setIgnoreCursorEvents",payload:e}}}})}async startDragging(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{label:this.label,cmd:{type:"startDragging"}}}})}async onResized(e){return this.listen("tauri://resize",t=>{t.payload=Zs(t.payload),e(t)})}async onMoved(e){return this.listen("tauri://move",t=>{t.payload=Qs(t.payload),e(t)})}async onCloseRequested(e){return this.listen("tauri://close-requested",t=>{let n=new Ks(t);Promise.resolve(e(n)).then(()=>{if(!n.isPreventDefault())return this.close()})})}async onFocusChanged(e){let t=await this.listen("tauri://focus",i=>{e({...i,payload:!0})}),n=await this.listen("tauri://blur",i=>{e({...i,payload:!1})});return()=>{t(),n()}}async onScaleChanged(e){return this.listen("tauri://scale-change",e)}async onMenuClicked(e){return this.listen("tauri://menu",e)}async onFileDropEvent(e){let t=await this.listen("tauri://file-drop",l=>{e({...l,payload:{type:"drop",paths:l.payload}})}),n=await this.listen("tauri://file-drop-hover",l=>{e({...l,payload:{type:"hover",paths:l.payload}})}),i=await this.listen("tauri://file-drop-cancelled",l=>{e({...l,payload:{type:"cancel"}})});return()=>{t(),n(),i()}}async onThemeChanged(e){return this.listen("tauri://theme-changed",e)}},Ks=class{constructor(e){this._preventDefault=!1,this.event=e.event,this.windowLabel=e.windowLabel,this.id=e.id}preventDefault(){this._preventDefault=!0}isPreventDefault(){return this._preventDefault}},wt=class extends Ys{constructor(e,t={}){super(e),t!=null&&t.skip||L({__tauriModule:"Window",message:{cmd:"createWebview",data:{options:{label:e,...t}}}}).then(async()=>this.emit("tauri://created")).catch(async n=>this.emit("tauri://error",n))}static getByLabel(e){return Js().some(t=>t.label===e)?new wt(e,{skip:!0}):null}},Ge;"__TAURI_METADATA__"in window?Ge=new wt(window.__TAURI_METADATA__.__currentWindow.label,{skip:!0}):(console.warn(`Could not find "window.__TAURI_METADATA__". The "appWindow" value will reference the "main" window label. -Note that this is not an issue if running this frontend on a browser instead of a Tauri window.`),Ge=new wt("main",{skip:!0}));function xi(e){return e===null?null:{name:e.name,scaleFactor:e.scaleFactor,position:Qs(e.position),size:Zs(e.size)}}function Qs(e){return new ot(e.x,e.y)}function Zs(e){return new gt(e.width,e.height)}async function No(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"currentMonitor"}}}}).then(xi)}async function jo(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"primaryMonitor"}}}}).then(xi)}async function Uo(){return L({__tauriModule:"Window",message:{cmd:"manage",data:{cmd:{type:"availableMonitors"}}}}).then(e=>e.map(xi))}function qo(){return navigator.appVersion.includes("Win")}var Bo={};Me(Bo,{EOL:()=>Vo,arch:()=>Xo,locale:()=>Ko,platform:()=>xs,tempdir:()=>Yo,type:()=>Jo,version:()=>Go});var Vo=qo()?`\r -`:` -`;async function xs(){return L({__tauriModule:"Os",message:{cmd:"platform"}})}async function Go(){return L({__tauriModule:"Os",message:{cmd:"version"}})}async function Jo(){return L({__tauriModule:"Os",message:{cmd:"osType"}})}async function Xo(){return L({__tauriModule:"Os",message:{cmd:"arch"}})}async function Yo(){return L({__tauriModule:"Os",message:{cmd:"tempdir"}})}async function Ko(){return L({__tauriModule:"Os",message:{cmd:"locale"}})}var Qo={};Me(Qo,{getName:()=>eo,getTauriVersion:()=>to,getVersion:()=>$s,hide:()=>io,show:()=>no});async function $s(){return L({__tauriModule:"App",message:{cmd:"getAppVersion"}})}async function eo(){return L({__tauriModule:"App",message:{cmd:"getAppName"}})}async function to(){return L({__tauriModule:"App",message:{cmd:"getTauriVersion"}})}async function no(){return L({__tauriModule:"App",message:{cmd:"show"}})}async function io(){return L({__tauriModule:"App",message:{cmd:"hide"}})}var Zo={};Me(Zo,{exit:()=>lo,relaunch:()=>$i});async function lo(e=0){return L({__tauriModule:"Process",message:{cmd:"exit",exitCode:e}})}async function $i(){return L({__tauriModule:"Process",message:{cmd:"relaunch"}})}function xo(e){let t,n,i,l,o,u,d,c,f,g,k,_,v,y,b,A,P,I,O,j,W,C,T,E,M,N;return{c(){t=a("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 +(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.`,n=m(),i=a("br"),l=m(),o=a("br"),u=m(),d=a("pre"),c=z("App name: "),f=a("code"),g=z(e[2]),k=z(` -App version: `),_=a("code"),v=z(e[0]),y=z(` -Tauri version: `),b=a("code"),A=z(e[1]),P=z(` -`),I=m(),O=a("br"),j=m(),W=a("div"),C=a("button"),C.textContent="Close application",T=m(),E=a("button"),E.textContent="Relaunch application",r(C,"class","btn"),r(E,"class","btn"),r(W,"class","flex flex-wrap gap-1 shadow-")},m(U,J){h(U,t,J),h(U,n,J),h(U,i,J),h(U,l,J),h(U,o,J),h(U,u,J),h(U,d,J),s(d,c),s(d,f),s(f,g),s(d,k),s(d,_),s(_,v),s(d,y),s(d,b),s(b,A),s(d,P),h(U,I,J),h(U,O,J),h(U,j,J),h(U,W,J),s(W,C),s(W,T),s(W,E),M||(N=[S(C,"click",e[3]),S(E,"click",e[4])],M=!0)},p(U,[J]){J&4&&Z(g,U[2]),J&1&&Z(v,U[0]),J&2&&Z(A,U[1])},i:V,o:V,d(U){U&&p(t),U&&p(n),U&&p(i),U&&p(l),U&&p(o),U&&p(u),U&&p(d),U&&p(I),U&&p(O),U&&p(j),U&&p(W),M=!1,oe(N)}}}function $o(e,t,n){let i="0.0.0",l="0.0.0",o="Unknown";eo().then(c=>{n(2,o=c)}),$s().then(c=>{n(0,i=c)}),to().then(c=>{n(1,l=c)});async function u(){await lo()}async function d(){await $i()}return[i,l,o,u,d]}class ea extends ye{constructor(t){super(),ge(this,t,$o,xo,pe,{})}}var ta={};Me(ta,{getMatches:()=>so});async function so(){return L({__tauriModule:"Cli",message:{cmd:"cliMatches"}})}function na(e){let t,n,i,l,o,u,d,c,f,g,k,_,v;return{c(){t=a("p"),t.innerHTML=`This binary can be run from the terminal and takes the following arguments: -

  --config <PATH>
-  --theme <light|dark|system>
-  --verbose
- Additionally, it has a update --background subcommand.`,n=m(),i=a("br"),l=m(),o=a("div"),o.textContent="Note that the arguments are only parsed, not implemented.",u=m(),d=a("br"),c=m(),f=a("br"),g=m(),k=a("button"),k.textContent="Get matches",r(o,"class","note"),r(k,"class","btn"),r(k,"id","cli-matches")},m(y,b){h(y,t,b),h(y,n,b),h(y,i,b),h(y,l,b),h(y,o,b),h(y,u,b),h(y,d,b),h(y,c,b),h(y,f,b),h(y,g,b),h(y,k,b),_||(v=S(k,"click",e[0]),_=!0)},p:V,i:V,o:V,d(y){y&&p(t),y&&p(n),y&&p(i),y&&p(l),y&&p(o),y&&p(u),y&&p(d),y&&p(c),y&&p(f),y&&p(g),y&&p(k),_=!1,v()}}}function ia(e,t,n){let{onMessage:i}=t;function l(){so().then(i).catch(i)}return e.$$set=o=>{"onMessage"in o&&n(1,i=o.onMessage)},[l,i]}class la extends ye{constructor(t){super(),ge(this,t,ia,na,pe,{onMessage:1})}}function sa(e){let t,n,i,l,o,u,d,c;return{c(){t=a("div"),n=a("button"),n.textContent="Call Log API",i=m(),l=a("button"),l.textContent="Call Request (async) API",o=m(),u=a("button"),u.textContent="Send event to Rust",r(n,"class","btn"),r(n,"id","log"),r(l,"class","btn"),r(l,"id","request"),r(u,"class","btn"),r(u,"id","event")},m(f,g){h(f,t,g),s(t,n),s(t,i),s(t,l),s(t,o),s(t,u),d||(c=[S(n,"click",e[0]),S(l,"click",e[1]),S(u,"click",e[2])],d=!0)},p:V,i:V,o:V,d(f){f&&p(t),d=!1,oe(c)}}}function oa(e,t,n){let{onMessage:i}=t,l;_t(async()=>{l=await tn("rust-event",i)}),Xi(()=>{l&&l()});function o(){ci("log_operation",{event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}function u(){ci("perform_request",{endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(i).catch(i)}function d(){_i("js-event","this is the payload string")}return e.$$set=c=>{"onMessage"in c&&n(3,i=c.onMessage)},[o,u,d,i]}class aa extends ye{constructor(t){super(),ge(this,t,oa,sa,pe,{onMessage:3})}}var ra={};Me(ra,{ask:()=>ao,confirm:()=>ca,message:()=>ua,open:()=>el,save:()=>oo});async function el(e={}){return typeof e=="object"&&Object.freeze(e),L({__tauriModule:"Dialog",message:{cmd:"openDialog",options:e}})}async function oo(e={}){return typeof e=="object"&&Object.freeze(e),L({__tauriModule:"Dialog",message:{cmd:"saveDialog",options:e}})}async function ua(e,t){var i,l;let n=typeof t=="string"?{title:t}:t;return L({__tauriModule:"Dialog",message:{cmd:"messageDialog",message:e.toString(),title:(i=n==null?void 0:n.title)==null?void 0:i.toString(),type:n==null?void 0:n.type,buttonLabel:(l=n==null?void 0:n.okLabel)==null?void 0:l.toString()}})}async function ao(e,t){var i,l,o,u,d;let n=typeof t=="string"?{title:t}:t;return L({__tauriModule:"Dialog",message:{cmd:"askDialog",message:e.toString(),title:(i=n==null?void 0:n.title)==null?void 0:i.toString(),type:n==null?void 0:n.type,buttonLabels:[(o=(l=n==null?void 0:n.okLabel)==null?void 0:l.toString())!=null?o:"Yes",(d=(u=n==null?void 0:n.cancelLabel)==null?void 0:u.toString())!=null?d:"No"]}})}async function ca(e,t){var i,l,o,u,d;let n=typeof t=="string"?{title:t}:t;return L({__tauriModule:"Dialog",message:{cmd:"confirmDialog",message:e.toString(),title:(i=n==null?void 0:n.title)==null?void 0:i.toString(),type:n==null?void 0:n.type,buttonLabels:[(o=(l=n==null?void 0:n.okLabel)==null?void 0:l.toString())!=null?o:"Ok",(d=(u=n==null?void 0:n.cancelLabel)==null?void 0:u.toString())!=null?d:"Cancel"]}})}var da={};Me(da,{BaseDirectory:()=>en,Dir:()=>en,copyFile:()=>_a,createDir:()=>ma,exists:()=>ya,readBinaryFile:()=>tl,readDir:()=>ro,readTextFile:()=>fa,removeDir:()=>ha,removeFile:()=>ba,renameFile:()=>ga,writeBinaryFile:()=>pa,writeFile:()=>Ji,writeTextFile:()=>Ji});var en=(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",e[e.Temp=20]="Temp",e[e.AppConfig=21]="AppConfig",e[e.AppData=22]="AppData",e[e.AppLocalData=23]="AppLocalData",e[e.AppCache=24]="AppCache",e[e.AppLog=25]="AppLog",e))(en||{});async function fa(e,t={}){return L({__tauriModule:"Fs",message:{cmd:"readTextFile",path:e,options:t}})}async function tl(e,t={}){let n=await L({__tauriModule:"Fs",message:{cmd:"readFile",path:e,options:t}});return Uint8Array.from(n)}async function Ji(e,t,n){typeof n=="object"&&Object.freeze(n),typeof e=="object"&&Object.freeze(e);let i={path:"",contents:""},l=n;return typeof e=="string"?i.path=e:(i.path=e.path,i.contents=e.contents),typeof t=="string"?i.contents=t!=null?t:"":l=t,L({__tauriModule:"Fs",message:{cmd:"writeFile",path:i.path,contents:Array.from(new TextEncoder().encode(i.contents)),options:l}})}async function pa(e,t,n){typeof n=="object"&&Object.freeze(n),typeof e=="object"&&Object.freeze(e);let i={path:"",contents:[]},l=n;return typeof e=="string"?i.path=e:(i.path=e.path,i.contents=e.contents),t&&"dir"in t?l=t:typeof e=="string"&&(i.contents=t!=null?t:[]),L({__tauriModule:"Fs",message:{cmd:"writeFile",path:i.path,contents:Array.from(i.contents instanceof ArrayBuffer?new Uint8Array(i.contents):i.contents),options:l}})}async function ro(e,t={}){return L({__tauriModule:"Fs",message:{cmd:"readDir",path:e,options:t}})}async function ma(e,t={}){return L({__tauriModule:"Fs",message:{cmd:"createDir",path:e,options:t}})}async function ha(e,t={}){return L({__tauriModule:"Fs",message:{cmd:"removeDir",path:e,options:t}})}async function _a(e,t,n={}){return L({__tauriModule:"Fs",message:{cmd:"copyFile",source:e,destination:t,options:n}})}async function ba(e,t={}){return L({__tauriModule:"Fs",message:{cmd:"removeFile",path:e,options:t}})}async function ga(e,t,n={}){return L({__tauriModule:"Fs",message:{cmd:"renameFile",oldPath:e,newPath:t,options:n}})}async function ya(e,t={}){return L({__tauriModule:"Fs",message:{cmd:"exists",path:e,options:t}})}function va(e){let t,n,i,l,o,u,d,c,f,g,k,_,v,y,b,A,P,I,O,j,W,C,T,E;return{c(){t=a("div"),n=a("input"),i=m(),l=a("input"),o=m(),u=a("br"),d=m(),c=a("div"),f=a("input"),g=m(),k=a("label"),k.textContent="Multiple",_=m(),v=a("div"),y=a("input"),b=m(),A=a("label"),A.textContent="Directory",P=m(),I=a("br"),O=m(),j=a("button"),j.textContent="Open dialog",W=m(),C=a("button"),C.textContent="Open save dialog",r(n,"class","input"),r(n,"id","dialog-default-path"),r(n,"placeholder","Default path"),r(l,"class","input"),r(l,"id","dialog-filter"),r(l,"placeholder","Extensions filter, comma-separated"),r(t,"class","flex gap-2 children:grow"),r(f,"type","checkbox"),r(f,"id","dialog-multiple"),r(k,"for","dialog-multiple"),r(y,"type","checkbox"),r(y,"id","dialog-directory"),r(A,"for","dialog-directory"),r(j,"class","btn"),r(j,"id","open-dialog"),r(C,"class","btn"),r(C,"id","save-dialog")},m(M,N){h(M,t,N),s(t,n),q(n,e[0]),s(t,i),s(t,l),q(l,e[1]),h(M,o,N),h(M,u,N),h(M,d,N),h(M,c,N),s(c,f),f.checked=e[2],s(c,g),s(c,k),h(M,_,N),h(M,v,N),s(v,y),y.checked=e[3],s(v,b),s(v,A),h(M,P,N),h(M,I,N),h(M,O,N),h(M,j,N),h(M,W,N),h(M,C,N),T||(E=[S(n,"input",e[8]),S(l,"input",e[9]),S(f,"change",e[10]),S(y,"change",e[11]),S(j,"click",e[4]),S(C,"click",e[5])],T=!0)},p(M,[N]){N&1&&n.value!==M[0]&&q(n,M[0]),N&2&&l.value!==M[1]&&q(l,M[1]),N&4&&(f.checked=M[2]),N&8&&(y.checked=M[3])},i:V,o:V,d(M){M&&p(t),M&&p(o),M&&p(u),M&&p(d),M&&p(c),M&&p(_),M&&p(v),M&&p(P),M&&p(I),M&&p(O),M&&p(j),M&&p(W),M&&p(C),T=!1,oe(E)}}}function wa(e,t){var n=new Blob([e],{type:"application/octet-binary"}),i=new FileReader;i.onload=function(l){var o=l.target.result;t(o.substr(o.indexOf(",")+1))},i.readAsDataURL(n)}function ka(e,t,n){let{onMessage:i}=t,{insecureRenderHtml:l}=t,o=null,u=null,d=!1,c=!1;function f(){el({title:"My wonderful open dialog",defaultPath:o,filters:u?[{name:"Tauri Example",extensions:u.split(",").map(b=>b.trim())}]:[],multiple:d,directory:c}).then(function(b){if(Array.isArray(b))i(b);else{var A=b,P=A.match(/\S+\.\S+$/g);tl(A).then(function(I){P&&(A.includes(".png")||A.includes(".jpg"))?wa(new Uint8Array(I),function(O){var j="data:image/png;base64,"+O;l('')}):i(b)}).catch(i(b))}}).catch(i)}function g(){oo({title:"My wonderful save dialog",defaultPath:o,filters:u?[{name:"Tauri Example",extensions:u.split(",").map(b=>b.trim())}]:[]}).then(i).catch(i)}function k(){o=this.value,n(0,o)}function _(){u=this.value,n(1,u)}function v(){d=this.checked,n(2,d)}function y(){c=this.checked,n(3,c)}return e.$$set=b=>{"onMessage"in b&&n(6,i=b.onMessage),"insecureRenderHtml"in b&&n(7,l=b.insecureRenderHtml)},[o,u,d,c,f,g,i,l,k,_,v,y]}class Ma extends ye{constructor(t){super(),ge(this,t,ka,va,pe,{onMessage:6,insecureRenderHtml:7})}}function ss(e,t,n){const i=e.slice();return i[9]=t[n],i}function os(e){let t,n=e[9][0]+"",i,l;return{c(){t=a("option"),i=z(n),t.__value=l=e[9][1],t.value=t.__value},m(o,u){h(o,t,u),s(t,i)},p:V,d(o){o&&p(t)}}}function Ca(e){let t,n,i,l,o,u,d,c,f,g,k,_,v,y,b,A,P,I,O,j=e[2],W=[];for(let C=0;CisNaN(parseInt(_))).map(_=>[_,en[_]]);function c(){const _=o.match(/\S+\.\S+$/g),v={dir:as()};(_?tl(o,v):ro(o,v)).then(function(b){if(_)if(o.includes(".png")||o.includes(".jpg"))Ta(new Uint8Array(b),function(A){const P="data:image/png;base64,"+A;l('')});else{const A=String.fromCharCode.apply(null,b);l(''),setTimeout(()=>{const P=document.getElementById("file-response");P.value=A,document.getElementById("file-save").addEventListener("click",function(){Ji(o,P.value,{dir:as()}).catch(i)})})}else i(b)}).catch(i)}function f(){n(1,u.src=Fs(o),u)}function g(){o=this.value,n(0,o)}function k(_){ri[_?"unshift":"push"](()=>{u=_,n(1,u)})}return e.$$set=_=>{"onMessage"in _&&n(5,i=_.onMessage),"insecureRenderHtml"in _&&n(6,l=_.insecureRenderHtml)},[o,u,d,c,f,i,l,g,k]}class La extends ye{constructor(t){super(),ge(this,t,Aa,Ca,pe,{onMessage:5,insecureRenderHtml:6})}}var Sa={};Me(Sa,{Body:()=>at,Client:()=>co,Response:()=>uo,ResponseType:()=>nl,fetch:()=>za,getClient:()=>fi});var nl=(e=>(e[e.JSON=1]="JSON",e[e.Text=2]="Text",e[e.Binary=3]="Binary",e))(nl||{}),at=class{constructor(e,t){this.type=e,this.payload=t}static form(e){let t={},n=(i,l)=>{if(l!==null){let o;typeof l=="string"?o=l:l instanceof Uint8Array||Array.isArray(l)?o=Array.from(l):l instanceof File?o={file:l.name,mime:l.type,fileName:l.name}:typeof l.file=="string"?o={file:l.file,mime:l.mime,fileName:l.fileName}:o={file:Array.from(l.file),mime:l.mime,fileName:l.fileName},t[String(i)]=o}};if(e instanceof FormData)for(let[i,l]of e)n(i,l);else for(let[i,l]of Object.entries(e))n(i,l);return new at("Form",t)}static json(e){return new at("Json",e)}static text(e){return new at("Text",e)}static bytes(e){return new at("Bytes",Array.from(e instanceof ArrayBuffer?new Uint8Array(e):e))}},uo=class{constructor(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}},co=class{constructor(e){this.id=e}async drop(){return L({__tauriModule:"Http",message:{cmd:"dropClient",client:this.id}})}async request(e){let t=!e.responseType||e.responseType===1;return t&&(e.responseType=2),L({__tauriModule:"Http",message:{cmd:"httpRequest",client:this.id,options:e}}).then(n=>{let i=new uo(n);if(t){try{i.data=JSON.parse(i.data)}catch(l){if(i.ok&&i.data==="")i.data={};else if(i.ok)throw Error(`Failed to parse response \`${i.data}\` as JSON: ${l}; - try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.`)}return i}return i})}async get(e,t){return this.request({method:"GET",url:e,...t})}async post(e,t,n){return this.request({method:"POST",url:e,body:t,...n})}async put(e,t,n){return this.request({method:"PUT",url:e,body:t,...n})}async patch(e,t){return this.request({method:"PATCH",url:e,...t})}async delete(e,t){return this.request({method:"DELETE",url:e,...t})}};async function fi(e){return L({__tauriModule:"Http",message:{cmd:"createClient",options:e}}).then(t=>new co(t))}var Bi=null;async function za(e,t){var n;return Bi===null&&(Bi=await fi()),Bi.request({url:e,method:(n=t==null?void 0:t.method)!=null?n:"GET",...t})}function rs(e,t,n){const i=e.slice();return i[12]=t[n],i[14]=n,i}function us(e){let t,n,i,l,o,u,d,c,f,g,k,_,v,y,b,A,P,I=e[5],O=[];for(let T=0;TNe(O[T],1,1,()=>{O[T]=null});let W=!e[3]&&ps(),C=!e[3]&&e[8]&&ms();return{c(){t=a("span"),n=a("span"),i=z(e[6]),l=m(),o=a("ul");for(let T=0;T{g[y]=null}),hi(),o=g[l],o?o.p(_,v):(o=g[l]=f[l](_),o.c()),Te(o,1),o.m(t,u))},i(_){d||(Te(o),d=!0)},o(_){Ne(o),d=!1},d(_){_&&p(t),c&&c.d(),g[l].d()}}}function ps(e){let t;return{c(){t=a("span"),t.textContent=",",r(t,"class","comma svelte-gbh3pt")},m(n,i){h(n,t,i)},d(n){n&&p(t)}}}function ms(e){let t;return{c(){t=a("span"),t.textContent=",",r(t,"class","comma svelte-gbh3pt")},m(n,i){h(n,t,i)},d(n){n&&p(t)}}}function Da(e){let t,n,i=e[5].length&&us(e);return{c(){i&&i.c(),t=pi()},m(l,o){i&&i.m(l,o),h(l,t,o),n=!0},p(l,[o]){l[5].length?i?(i.p(l,o),o&32&&Te(i,1)):(i=us(l),i.c(),Te(i,1),i.m(t.parentNode,t)):i&&(mi(),Ne(i,1,1,()=>{i=null}),hi())},i(l){n||(Te(i),n=!0)},o(l){Ne(i),n=!1},d(l){i&&i.d(l),l&&p(t)}}}const Pa="...";function Oa(e,t,n){let{json:i}=t,{depth:l=1/0}=t,{_lvl:o=0}=t,{_last:u=!0}=t;const d=b=>b===null?"null":typeof b;let c,f,g,k,_;const v=b=>{switch(d(b)){case"string":return`"${b}"`;case"function":return"f () {...}";case"symbol":return b.toString();default:return b}},y=()=>{n(8,_=!_)};return e.$$set=b=>{"json"in b&&n(0,i=b.json),"depth"in b&&n(1,l=b.depth),"_lvl"in b&&n(2,o=b._lvl),"_last"in b&&n(3,u=b._last)},e.$$.update=()=>{e.$$.dirty&17&&(n(5,c=d(i)==="object"?Object.keys(i):[]),n(4,f=Array.isArray(i)),n(6,g=f?"[":"{"),n(7,k=f?"]":"}")),e.$$.dirty&6&&n(8,_=le[9].call(n)),r(k,"class","input h-auto w-100%"),r(k,"id","request-body"),r(k,"placeholder","Request body"),r(k,"rows","5"),r(b,"class","btn"),r(b,"id","make-request"),r(C,"class","input"),r(E,"class","input"),r(W,"class","flex gap-2 children:grow"),r($,"type","checkbox"),r(X,"class","btn"),r(X,"type","button")},m(D,G){h(D,t,G),s(t,n),s(n,i),s(n,l),s(n,o),s(n,u),s(n,d),Ht(n,e[0]),s(t,c),s(t,f),s(t,g),s(t,k),q(k,e[1]),s(t,_),s(t,v),s(t,y),s(t,b),h(D,A,G),h(D,P,G),h(D,I,G),h(D,O,G),h(D,j,G),h(D,W,G),s(W,C),q(C,e[2]),s(W,T),s(W,E),q(E,e[3]),h(D,M,G),h(D,N,G),h(D,U,G),h(D,J,G),s(J,$),$.checked=e[5],s(J,me),h(D,te,G),h(D,Y,G),h(D,de,G),h(D,x,G),h(D,H,G),h(D,X,G),h(D,K,G),h(D,ae,G),h(D,ne,G),h(D,he,G),h(D,_e,G),xt(fe,D,G),re=!0,Ae||(Le=[S(n,"change",e[9]),S(k,"input",e[10]),S(t,"submit",ai(e[6])),S(C,"input",e[11]),S(E,"input",e[12]),S($,"change",e[13]),S(X,"click",e[7])],Ae=!0)},p(D,[G]){G&1&&Ht(n,D[0]),G&2&&q(k,D[1]),G&4&&C.value!==D[2]&&q(C,D[2]),G&8&&E.value!==D[3]&&q(E,D[3]),G&32&&($.checked=D[5]);const Se={};G&16&&(Se.json=D[4]),fe.$set(Se)},i(D){re||(Te(fe.$$.fragment,D),re=!0)},o(D){Ne(fe.$$.fragment,D),re=!1},d(D){D&&p(t),D&&p(A),D&&p(P),D&&p(I),D&&p(O),D&&p(j),D&&p(W),D&&p(M),D&&p(N),D&&p(U),D&&p(J),D&&p(te),D&&p(Y),D&&p(de),D&&p(x),D&&p(H),D&&p(X),D&&p(K),D&&p(ae),D&&p(ne),D&&p(he),D&&p(_e),$t(fe,D),Ae=!1,oe(Le)}}}function Ia(e,t,n){let i="GET",l="",{onMessage:o}=t;async function u(){const P=await fi().catch(j=>{throw o(j),j}),O={url:"http://localhost:3003",method:i||"GET"||"GET"};l.startsWith("{")&&l.endsWith("}")||l.startsWith("[")&&l.endsWith("]")?O.body=at.json(JSON.parse(l)):l!==""&&(O.body=at.text(l)),P.request(O).then(o).catch(o)}let d="baz",c="qux",f=null,g=!0;async function k(){const P=await fi().catch(I=>{throw o(I),I});n(4,f=await P.request({url:"http://localhost:3003",method:"POST",body:at.form({foo:d,bar:c}),headers:g?{"Content-Type":"multipart/form-data"}:void 0,responseType:nl.Text}))}function _(){i=Vi(this),n(0,i)}function v(){l=this.value,n(1,l)}function y(){d=this.value,n(2,d)}function b(){c=this.value,n(3,c)}function A(){g=this.checked,n(5,g)}return e.$$set=P=>{"onMessage"in P&&n(8,o=P.onMessage)},[i,l,d,c,f,g,u,k,o,_,v,y,b,A]}class Ha extends ye{constructor(t){super(),ge(this,t,Ia,Ra,pe,{onMessage:8})}}function Fa(e){let t,n,i;return{c(){t=a("button"),t.textContent="Send test notification",r(t,"class","btn"),r(t,"id","notification")},m(l,o){h(l,t,o),n||(i=S(t,"click",Na),n=!0)},p:V,i:V,o:V,d(l){l&&p(t),n=!1,i()}}}function Na(){new Notification("Notification title",{body:"This is the notification body"})}function ja(e,t,n){let{onMessage:i}=t;return e.$$set=l=>{"onMessage"in l&&n(0,i=l.onMessage)},[i]}class Ua extends ye{constructor(t){super(),ge(this,t,ja,Fa,pe,{onMessage:0})}}function hs(e,t,n){const i=e.slice();return i[75]=t[n],i}function _s(e,t,n){const i=e.slice();return i[78]=t[n],i}function bs(e){let t,n,i,l,o,u,d=Object.keys(e[1]),c=[];for(let f=0;fe[43].call(i))},m(f,g){h(f,t,g),h(f,n,g),h(f,i,g),s(i,l);for(let k=0;ke[65].call(Ve)),r(nt,"class","input"),r(nt,"type","number"),r(it,"class","input"),r(it,"type","number"),r(Be,"class","flex gap-2"),r(lt,"class","input grow"),r(lt,"id","title"),r(Jt,"class","btn"),r(Jt,"type","submit"),r(mt,"class","flex gap-1"),r(st,"class","input grow"),r(st,"id","url"),r(Xt,"class","btn"),r(Xt,"id","open-url"),r(ht,"class","flex gap-1"),r(pt,"class","flex flex-col gap-1")},m(w,R){h(w,t,R),h(w,n,R),h(w,i,R),s(i,l),s(i,o),s(i,u),s(i,d),s(i,c),s(i,f),s(i,g),s(i,k),s(i,_),h(w,v,R),h(w,y,R),h(w,b,R),h(w,A,R),s(A,P),s(P,I),s(P,O),O.checked=e[6],s(A,j),s(A,W),s(W,C),s(W,T),T.checked=e[2],s(A,E),s(A,M),s(M,N),s(M,U),U.checked=e[3],s(A,J),s(A,$),s($,me),s($,te),te.checked=e[4],s(A,Y),s(A,de),s(de,x),s(de,H),H.checked=e[5],s(A,X),s(A,K),s(K,ae),s(K,ne),ne.checked=e[7],s(A,he),s(A,_e),s(_e,fe),s(_e,re),re.checked=e[8],s(A,Ae),s(A,Le),s(Le,D),s(Le,G),G.checked=e[9],s(A,Se),s(A,ve),s(ve,ue),s(ve,we),we.checked=e[10],h(w,ie,R),h(w,ze,R),h(w,Je,R),h(w,le,R),s(le,ee),s(ee,F),s(F,ce),s(F,B),q(B,e[17]),s(ee,He),s(ee,kt),s(kt,nn),s(kt,De),q(De,e[18]),s(le,ln),s(le,Xe),s(Xe,Mt),s(Mt,sn),s(Mt,Pe),q(Pe,e[11]),s(Xe,on),s(Xe,Ct),s(Ct,an),s(Ct,Oe),q(Oe,e[12]),s(le,rn),s(le,Ye),s(Ye,Tt),s(Tt,un),s(Tt,Fe),q(Fe,e[13]),s(Ye,Q),s(Ye,rt),s(rt,Nt),s(rt,Re),q(Re,e[14]),s(le,jt),s(le,je),s(je,ut),s(ut,Ut),s(ut,Ee),q(Ee,e[15]),s(je,qt),s(je,ct),s(ct,Bt),s(ct,We),q(We,e[16]),h(w,At,R),h(w,Lt,R),h(w,St,R),h(w,Ce,R),s(Ce,Ue),s(Ue,Ie),s(Ie,dt),s(Ie,Vt),s(Ie,ft),s(ft,cn),s(ft,bi),s(Ie,ll),s(Ie,fn),s(fn,sl),s(fn,gi),s(Ue,ol),s(Ue,Ke),s(Ke,mn),s(Ke,al),s(Ke,hn),s(hn,rl),s(hn,yi),s(Ke,ul),s(Ke,bn),s(bn,cl),s(bn,vi),s(Ce,dl),s(Ce,zt),s(zt,Qe),s(Qe,yn),s(Qe,fl),s(Qe,vn),s(vn,pl),s(vn,wi),s(Qe,ml),s(Qe,kn),s(kn,hl),s(kn,ki),s(zt,_l),s(zt,Ze),s(Ze,Cn),s(Ze,bl),s(Ze,Tn),s(Tn,gl),s(Tn,Mi),s(Ze,yl),s(Ze,Ln),s(Ln,vl),s(Ln,Ci),s(Ce,wl),s(Ce,Et),s(Et,xe),s(xe,zn),s(xe,kl),s(xe,En),s(En,Ml),s(En,Ti),s(xe,Cl),s(xe,Dn),s(Dn,Tl),s(Dn,Ai),s(Et,Al),s(Et,$e),s($e,On),s($e,Ll),s($e,Rn),s(Rn,Sl),s(Rn,Li),s($e,zl),s($e,Hn),s(Hn,El),s(Hn,Si),s(Ce,Wl),s(Ce,Wt),s(Wt,et),s(et,Nn),s(et,Dl),s(et,jn),s(jn,Pl),s(jn,zi),s(et,Ol),s(et,qn),s(qn,Rl),s(qn,Ei),s(Wt,Il),s(Wt,tt),s(tt,Vn),s(tt,Hl),s(tt,Gn),s(Gn,Fl),s(Gn,Wi),s(tt,Nl),s(tt,Xn),s(Xn,jl),s(Xn,Di),h(w,Pi,R),h(w,Oi,R),h(w,Ri,R),h(w,Gt,R),h(w,Ii,R),h(w,qe,R),s(qe,Kn),s(Kn,Dt),Dt.checked=e[19],s(Kn,Ul),s(qe,ql),s(qe,Qn),s(Qn,Pt),Pt.checked=e[20],s(Qn,Bl),s(qe,Vl),s(qe,Zn),s(Zn,Ot),Ot.checked=e[24],s(Zn,Gl),h(w,Hi,R),h(w,Be,R),s(Be,xn),s(xn,Jl),s(xn,Ve);for(let be=0;be=1,g,k,_,v=f&&bs(e),y=e[1][e[0]]&&ys(e);return{c(){t=a("div"),n=a("div"),i=a("input"),l=m(),o=a("button"),o.textContent="New window",u=m(),d=a("br"),c=m(),v&&v.c(),g=m(),y&&y.c(),r(i,"class","input grow"),r(i,"type","text"),r(i,"placeholder","New Window label.."),r(o,"class","btn"),r(n,"class","flex gap-1"),r(t,"class","flex flex-col children:grow gap-2")},m(b,A){h(b,t,A),s(t,n),s(n,i),q(i,e[25]),s(n,l),s(n,o),s(t,u),s(t,d),s(t,c),v&&v.m(t,null),s(t,g),y&&y.m(t,null),k||(_=[S(i,"input",e[42]),S(o,"click",e[39])],k=!0)},p(b,A){A[0]&33554432&&i.value!==b[25]&&q(i,b[25]),A[0]&2&&(f=Object.keys(b[1]).length>=1),f?v?v.p(b,A):(v=bs(b),v.c(),v.m(t,g)):v&&(v.d(1),v=null),b[1][b[0]]?y?y.p(b,A):(y=ys(b),y.c(),y.m(t,null)):y&&(y.d(1),y=null)},i:V,o:V,d(b){b&&p(t),v&&v.d(),y&&y.d(),k=!1,oe(_)}}}function Ba(e,t,n){let i=Ge.label;const l={[Ge.label]:Ge},o=["default","crosshair","hand","arrow","move","text","wait","help","progress","notAllowed","contextMenu","cell","verticalText","alias","copy","noDrop","grab","grabbing","allScroll","zoomIn","zoomOut","eResize","nResize","neResize","nwResize","sResize","seResize","swResize","wResize","ewResize","nsResize","neswResize","nwseResize","colResize","rowResize"];let{onMessage:u}=t,d,c="https://tauri.app",f=!0,g=!0,k=!0,_=!0,v=!1,y=!0,b=!1,A=!0,P=!1,I=null,O=null,j=null,W=null,C=null,T=null,E=null,M=null,N=1,U=new ot(E,M),J=new ot(E,M),$=new gt(I,O),me=new gt(I,O),te,Y,de=!1,x=!0,H=null,X=null,K="default",ae=!1,ne="Awesome Tauri Example!";function he(){Ki(c)}function _e(){l[i].setTitle(ne)}function fe(){l[i].hide(),setTimeout(l[i].show,2e3)}function re(){l[i].minimize(),setTimeout(l[i].unminimize,2e3)}function Ae(){el({multiple:!1}).then(Q=>{typeof Q=="string"&&l[i].setIcon(Q)})}function Le(){if(!d)return;const Q=new wt(d);n(1,l[d]=Q,l),Q.once("tauri://error",function(){u("Error creating new webview")})}function D(){l[i].innerSize().then(Q=>{n(30,$=Q),n(11,I=$.width),n(12,O=$.height)}),l[i].outerSize().then(Q=>{n(31,me=Q)})}function G(){l[i].innerPosition().then(Q=>{n(28,U=Q)}),l[i].outerPosition().then(Q=>{n(29,J=Q),n(17,E=J.x),n(18,M=J.y)})}async function Se(Q){!Q||(te&&te(),Y&&Y(),Y=await Q.listen("tauri://move",G),te=await Q.listen("tauri://resize",D))}async function ve(){await l[i].minimize(),await l[i].requestUserAttention(Zi.Critical),await new Promise(Q=>setTimeout(Q,3e3)),await l[i].requestUserAttention(null)}function ue(){d=this.value,n(25,d)}function we(){i=Vi(this),n(0,i),n(1,l)}const ie=()=>l[i].center();function ze(){v=this.checked,n(6,v)}function Je(){f=this.checked,n(2,f)}function le(){g=this.checked,n(3,g)}function ee(){k=this.checked,n(4,k)}function F(){_=this.checked,n(5,_)}function ce(){y=this.checked,n(7,y)}function B(){b=this.checked,n(8,b)}function He(){A=this.checked,n(9,A)}function kt(){P=this.checked,n(10,P)}function nn(){E=se(this.value),n(17,E)}function De(){M=se(this.value),n(18,M)}function ln(){I=se(this.value),n(11,I)}function Xe(){O=se(this.value),n(12,O)}function Mt(){j=se(this.value),n(13,j)}function sn(){W=se(this.value),n(14,W)}function Pe(){C=se(this.value),n(15,C)}function on(){T=se(this.value),n(16,T)}function Ct(){de=this.checked,n(19,de)}function an(){x=this.checked,n(20,x)}function Oe(){ae=this.checked,n(24,ae)}function rn(){K=Vi(this),n(23,K),n(33,o)}function Ye(){H=se(this.value),n(21,H)}function Tt(){X=se(this.value),n(22,X)}function un(){ne=this.value,n(32,ne)}function Fe(){c=this.value,n(26,c)}return e.$$set=Q=>{"onMessage"in Q&&n(41,u=Q.onMessage)},e.$$.update=()=>{var Q,rt,Nt,Re,jt,je,ut,Ut,Ee,qt,ct,Bt,We,At,Lt,St,Ce,Ue,Ie,dt,Vt,ft;e.$$.dirty[0]&3&&(l[i],G(),D()),e.$$.dirty[0]&7&&((Q=l[i])==null||Q.setResizable(f)),e.$$.dirty[0]&11&&((rt=l[i])==null||rt.setMaximizable(g)),e.$$.dirty[0]&19&&((Nt=l[i])==null||Nt.setMinimizable(k)),e.$$.dirty[0]&35&&((Re=l[i])==null||Re.setClosable(_)),e.$$.dirty[0]&67&&(v?(jt=l[i])==null||jt.maximize():(je=l[i])==null||je.unmaximize()),e.$$.dirty[0]&131&&((ut=l[i])==null||ut.setDecorations(y)),e.$$.dirty[0]&259&&((Ut=l[i])==null||Ut.setAlwaysOnTop(b)),e.$$.dirty[0]&515&&((Ee=l[i])==null||Ee.setContentProtected(A)),e.$$.dirty[0]&1027&&((qt=l[i])==null||qt.setFullscreen(P)),e.$$.dirty[0]&6147&&I&&O&&((ct=l[i])==null||ct.setSize(new gt(I,O))),e.$$.dirty[0]&24579&&(j&&W?(Bt=l[i])==null||Bt.setMinSize(new di(j,W)):(We=l[i])==null||We.setMinSize(null)),e.$$.dirty[0]&98307&&(C>800&&T>400?(At=l[i])==null||At.setMaxSize(new di(C,T)):(Lt=l[i])==null||Lt.setMaxSize(null)),e.$$.dirty[0]&393219&&E!==null&&M!==null&&((St=l[i])==null||St.setPosition(new ot(E,M))),e.$$.dirty[0]&3&&((Ce=l[i])==null||Ce.scaleFactor().then(cn=>n(27,N=cn))),e.$$.dirty[0]&3&&Se(l[i]),e.$$.dirty[0]&524291&&((Ue=l[i])==null||Ue.setCursorGrab(de)),e.$$.dirty[0]&1048579&&((Ie=l[i])==null||Ie.setCursorVisible(x)),e.$$.dirty[0]&8388611&&((dt=l[i])==null||dt.setCursorIcon(K)),e.$$.dirty[0]&6291459&&H!==null&&X!==null&&((Vt=l[i])==null||Vt.setCursorPosition(new ot(H,X))),e.$$.dirty[0]&16777219&&((ft=l[i])==null||ft.setIgnoreCursorEvents(ae))},[i,l,f,g,k,_,v,y,b,A,P,I,O,j,W,C,T,E,M,de,x,H,X,K,ae,d,c,N,U,J,$,me,ne,o,he,_e,fe,re,Ae,Le,ve,u,ue,we,ie,ze,Je,le,ee,F,ce,B,He,kt,nn,De,ln,Xe,Mt,sn,Pe,on,Ct,an,Oe,rn,Ye,Tt,un,Fe]}class Va extends ye{constructor(t){super(),ge(this,t,Ba,qa,pe,{onMessage:41},null,[-1,-1,-1])}}var Ga={};Me(Ga,{isRegistered:()=>Xa,register:()=>po,registerAll:()=>Ja,unregister:()=>mo,unregisterAll:()=>ho});async function po(e,t){return L({__tauriModule:"GlobalShortcut",message:{cmd:"register",shortcut:e,handler:vt(t)}})}async function Ja(e,t){return L({__tauriModule:"GlobalShortcut",message:{cmd:"registerAll",shortcuts:e,handler:vt(t)}})}async function Xa(e){return L({__tauriModule:"GlobalShortcut",message:{cmd:"isRegistered",shortcut:e}})}async function mo(e){return L({__tauriModule:"GlobalShortcut",message:{cmd:"unregister",shortcut:e}})}async function ho(){return L({__tauriModule:"GlobalShortcut",message:{cmd:"unregisterAll"}})}function ws(e,t,n){const i=e.slice();return i[9]=t[n],i}function ks(e){let t,n=e[9]+"",i,l,o,u,d;function c(){return e[8](e[9])}return{c(){t=a("div"),i=z(n),l=m(),o=a("button"),o.textContent="Unregister",r(o,"class","btn"),r(o,"type","button"),r(t,"class","flex justify-between")},m(f,g){h(f,t,g),s(t,i),s(t,l),s(t,o),u||(d=S(o,"click",c),u=!0)},p(f,g){e=f,g&2&&n!==(n=e[9]+"")&&Z(i,n)},d(f){f&&p(t),u=!1,d()}}}function Ms(e){let t,n,i,l,o;return{c(){t=a("br"),n=m(),i=a("button"),i.textContent="Unregister all",r(i,"class","btn"),r(i,"type","button")},m(u,d){h(u,t,d),h(u,n,d),h(u,i,d),l||(o=S(i,"click",e[5]),l=!0)},p:V,d(u){u&&p(t),u&&p(n),u&&p(i),l=!1,o()}}}function Ya(e){let t,n,i,l,o,u,d,c,f,g,k,_=e[1],v=[];for(let b=0;b<_.length;b+=1)v[b]=ks(ws(e,_,b));let y=e[1].length>1&&Ms(e);return{c(){t=a("div"),n=a("input"),i=m(),l=a("button"),l.textContent="Register",o=m(),u=a("br"),d=m(),c=a("div");for(let b=0;b1?y?y.p(b,A):(y=Ms(b),y.c(),y.m(c,null)):y&&(y.d(1),y=null)},i:V,o:V,d(b){b&&p(t),b&&p(o),b&&p(u),b&&p(d),b&&p(c),yt(v,b),y&&y.d(),g=!1,oe(k)}}}function Ka(e,t,n){let i,{onMessage:l}=t;const o=Hs([]);Os(e,o,_=>n(1,i=_));let u="CmdOrControl+X";function d(){const _=u;po(_,()=>{l(`Shortcut ${_} triggered`)}).then(()=>{o.update(v=>[...v,_]),l(`Shortcut ${_} registered successfully`)}).catch(l)}function c(_){const v=_;mo(v).then(()=>{o.update(y=>y.filter(b=>b!==v)),l(`Shortcut ${v} unregistered`)}).catch(l)}function f(){ho().then(()=>{o.update(()=>[]),l("Unregistered all shortcuts")}).catch(l)}function g(){u=this.value,n(0,u)}const k=_=>c(_);return e.$$set=_=>{"onMessage"in _&&n(6,l=_.onMessage)},[u,i,o,d,c,f,l,g,k]}class Qa extends ye{constructor(t){super(),ge(this,t,Ka,Ya,pe,{onMessage:6})}}function Cs(e){let t,n,i,l,o,u,d;return{c(){t=a("br"),n=m(),i=a("input"),l=m(),o=a("button"),o.textContent="Write",r(i,"class","input"),r(i,"placeholder","write to stdin"),r(o,"class","btn")},m(c,f){h(c,t,f),h(c,n,f),h(c,i,f),q(i,e[4]),h(c,l,f),h(c,o,f),u||(d=[S(i,"input",e[14]),S(o,"click",e[8])],u=!0)},p(c,f){f&16&&i.value!==c[4]&&q(i,c[4])},d(c){c&&p(t),c&&p(n),c&&p(i),c&&p(l),c&&p(o),u=!1,oe(d)}}}function Za(e){let t,n,i,l,o,u,d,c,f,g,k,_,v,y,b,A,P,I,O,j,W,C,T,E,M=e[5]&&Cs(e);return{c(){t=a("div"),n=a("div"),i=z(`Script: - `),l=a("input"),o=m(),u=a("div"),d=z(`Encoding: - `),c=a("input"),f=m(),g=a("div"),k=z(`Working directory: - `),_=a("input"),v=m(),y=a("div"),b=z(`Arguments: - `),A=a("input"),P=m(),I=a("div"),O=a("button"),O.textContent="Run",j=m(),W=a("button"),W.textContent="Kill",C=m(),M&&M.c(),r(l,"class","grow input"),r(n,"class","flex items-center gap-1"),r(c,"class","grow input"),r(u,"class","flex items-center gap-1"),r(_,"class","grow input"),r(_,"placeholder","Working directory"),r(g,"class","flex items-center gap-1"),r(A,"class","grow input"),r(A,"placeholder","Environment variables"),r(y,"class","flex items-center gap-1"),r(O,"class","btn"),r(W,"class","btn"),r(I,"class","flex children:grow gap-1"),r(t,"class","flex flex-col childre:grow gap-1")},m(N,U){h(N,t,U),s(t,n),s(n,i),s(n,l),q(l,e[0]),s(t,o),s(t,u),s(u,d),s(u,c),q(c,e[3]),s(t,f),s(t,g),s(g,k),s(g,_),q(_,e[1]),s(t,v),s(t,y),s(y,b),s(y,A),q(A,e[2]),s(t,P),s(t,I),s(I,O),s(I,j),s(I,W),s(t,C),M&&M.m(t,null),T||(E=[S(l,"input",e[10]),S(c,"input",e[11]),S(_,"input",e[12]),S(A,"input",e[13]),S(O,"click",e[6]),S(W,"click",e[7])],T=!0)},p(N,[U]){U&1&&l.value!==N[0]&&q(l,N[0]),U&8&&c.value!==N[3]&&q(c,N[3]),U&2&&_.value!==N[1]&&q(_,N[1]),U&4&&A.value!==N[2]&&q(A,N[2]),N[5]?M?M.p(N,U):(M=Cs(N),M.c(),M.m(t,null)):M&&(M.d(1),M=null)},i:V,o:V,d(N){N&&p(t),M&&M.d(),T=!1,oe(E)}}}function xa(e,t,n){const i=navigator.userAgent.includes("Windows");let l=i?"cmd":"sh",o=i?["/C"]:["-c"],{onMessage:u}=t,d='echo "hello world"',c=null,f="SOMETHING=value ANOTHER=2",g="",k="",_;function v(){return f.split(" ").reduce((C,T)=>{let[E,M]=T.split("=");return{...C,[E]:M}},{})}function y(){n(5,_=null);const C=new Yi(l,[...o,d],{cwd:c||null,env:v(),encoding:g});C.on("close",T=>{u(`command finished with code ${T.code} and signal ${T.signal}`),n(5,_=null)}),C.on("error",T=>u(`command error: "${T}"`)),C.stdout.on("data",T=>u(`command stdout: "${T}"`)),C.stderr.on("data",T=>u(`command stderr: "${T}"`)),C.spawn().then(T=>{n(5,_=T)}).catch(u)}function b(){_.kill().then(()=>u("killed child process")).catch(u)}function A(){_.write(k).catch(u)}function P(){d=this.value,n(0,d)}function I(){g=this.value,n(3,g)}function O(){c=this.value,n(1,c)}function j(){f=this.value,n(2,f)}function W(){k=this.value,n(4,k)}return e.$$set=C=>{"onMessage"in C&&n(9,u=C.onMessage)},[d,c,f,g,k,_,y,b,A,u,P,I,O,j,W]}class $a extends ye{constructor(t){super(),ge(this,t,xa,Za,pe,{onMessage:9})}}var er={};Me(er,{checkUpdate:()=>bo,installUpdate:()=>_o,onUpdaterEvent:()=>il});async function il(e){return tn("tauri://update-status",t=>{e(t==null?void 0:t.payload)})}async function _o(){let e;function t(){e&&e(),e=void 0}return new Promise((n,i)=>{function l(o){if(o.error){t(),i(o.error);return}o.status==="DONE"&&(t(),n())}il(l).then(o=>{e=o}).catch(o=>{throw t(),o}),_i("tauri://update-install").catch(o=>{throw t(),o})})}async function bo(){let e;function t(){e&&e(),e=void 0}return new Promise((n,i)=>{function l(u){t(),n({manifest:u,shouldUpdate:!0})}function o(u){if(u.error){t(),i(u.error);return}u.status==="UPTODATE"&&(t(),n({shouldUpdate:!1}))}Vs("tauri://update-available",u=>{l(u==null?void 0:u.payload)}).catch(u=>{throw t(),u}),il(o).then(u=>{e=u}).catch(u=>{throw t(),u}),_i("tauri://update").catch(u=>{throw t(),u})})}function tr(e){let t;return{c(){t=a("button"),t.innerHTML='
',r(t,"class","btn text-accentText dark:text-darkAccentText flex items-center justify-center")},m(n,i){h(n,t,i)},p:V,d(n){n&&p(t)}}}function nr(e){let t,n,i;return{c(){t=a("button"),t.textContent="Install update",r(t,"class","btn")},m(l,o){h(l,t,o),n||(i=S(t,"click",e[4]),n=!0)},p:V,d(l){l&&p(t),n=!1,i()}}}function ir(e){let t,n,i;return{c(){t=a("button"),t.textContent="Check update",r(t,"class","btn")},m(l,o){h(l,t,o),n||(i=S(t,"click",e[3]),n=!0)},p:V,d(l){l&&p(t),n=!1,i()}}}function lr(e){let t;function n(o,u){return!o[0]&&!o[2]?ir:!o[1]&&o[2]?nr:tr}let i=n(e),l=i(e);return{c(){t=a("div"),l.c(),r(t,"class","flex children:grow children:h10")},m(o,u){h(o,t,u),l.m(t,null)},p(o,[u]){i===(i=n(o))&&l?l.p(o,u):(l.d(1),l=i(o),l&&(l.c(),l.m(t,null)))},i:V,o:V,d(o){o&&p(t),l.d()}}}function sr(e,t,n){let{onMessage:i}=t,l;_t(async()=>{l=await tn("tauri://update-status",i)}),Xi(()=>{l&&l()});let o,u,d;async function c(){n(0,o=!0);try{const{shouldUpdate:g,manifest:k}=await bo();i(`Should update: ${g}`),i(k),n(2,d=g)}catch(g){i(g)}finally{n(0,o=!1)}}async function f(){n(1,u=!0);try{await _o(),i("Installation complete, restart required."),await $i()}catch(g){i(g)}finally{n(1,u=!1)}}return e.$$set=g=>{"onMessage"in g&&n(5,i=g.onMessage)},[o,u,d,c,f,i]}class or extends ye{constructor(t){super(),ge(this,t,sr,lr,pe,{onMessage:5})}}var ar={};Me(ar,{readText:()=>yo,writeText:()=>go});async function go(e){return L({__tauriModule:"Clipboard",message:{cmd:"writeText",data:e}})}async function yo(){return L({__tauriModule:"Clipboard",message:{cmd:"readText",data:null}})}function rr(e){let t,n,i,l,o,u,d,c;return{c(){t=a("div"),n=a("input"),i=m(),l=a("button"),l.textContent="Write",o=m(),u=a("button"),u.textContent="Read",r(n,"class","grow input"),r(n,"placeholder","Text to write to the clipboard"),r(l,"class","btn"),r(l,"type","button"),r(u,"class","btn"),r(u,"type","button"),r(t,"class","flex gap-1")},m(f,g){h(f,t,g),s(t,n),q(n,e[0]),s(t,i),s(t,l),s(t,o),s(t,u),d||(c=[S(n,"input",e[4]),S(l,"click",e[1]),S(u,"click",e[2])],d=!0)},p(f,[g]){g&1&&n.value!==f[0]&&q(n,f[0])},i:V,o:V,d(f){f&&p(t),d=!1,oe(c)}}}function ur(e,t,n){let{onMessage:i}=t,l="clipboard message";function o(){go(l).then(()=>{i("Wrote to the clipboard")}).catch(i)}function u(){yo().then(c=>{i(`Clipboard contents: ${c}`)}).catch(i)}function d(){l=this.value,n(0,l)}return e.$$set=c=>{"onMessage"in c&&n(3,i=c.onMessage)},[l,o,u,i,d]}class cr extends ye{constructor(t){super(),ge(this,t,ur,rr,pe,{onMessage:3})}}function dr(e){let t;return{c(){t=a("div"),t.innerHTML=`
Not available for Linux
- `,r(t,"class","flex flex-col gap-2")},m(n,i){h(n,t,i)},p:V,i:V,o:V,d(n){n&&p(t)}}}function fr(e,t,n){let{onMessage:i}=t;const l=window.constraints={audio:!0,video:!0};function o(d){const c=document.querySelector("video"),f=d.getVideoTracks();i("Got stream with constraints:",l),i(`Using video device: ${f[0].label}`),window.stream=d,c.srcObject=d}function u(d){if(d.name==="ConstraintNotSatisfiedError"){const c=l.video;i(`The resolution ${c.width.exact}x${c.height.exact} px is not supported by your device.`)}else d.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: ${d.name}`,d)}return _t(async()=>{try{const d=await navigator.mediaDevices.getUserMedia(l);o(d)}catch(d){u(d)}}),Xi(()=>{window.stream.getTracks().forEach(function(d){d.stop()})}),e.$$set=d=>{"onMessage"in d&&n(0,i=d.onMessage)},[i]}class pr extends ye{constructor(t){super(),ge(this,t,fr,dr,pe,{onMessage:0})}}function mr(e){let t,n,i,l,o,u;return{c(){t=a("div"),n=a("button"),n.textContent="Show",i=m(),l=a("button"),l.textContent="Hide",r(n,"class","btn"),r(n,"id","show"),r(n,"title","Hides and shows the app after 2 seconds"),r(l,"class","btn"),r(l,"id","hide")},m(d,c){h(d,t,c),s(t,n),s(t,i),s(t,l),o||(u=[S(n,"click",e[0]),S(l,"click",e[1])],o=!0)},p:V,i:V,o:V,d(d){d&&p(t),o=!1,oe(u)}}}function hr(e,t,n){let{onMessage:i}=t;function l(){o().then(()=>{setTimeout(()=>{no().then(()=>i("Shown app")).catch(i)},2e3)}).catch(i)}function o(){return io().then(()=>i("Hide app")).catch(i)}return e.$$set=u=>{"onMessage"in u&&n(2,i=u.onMessage)},[l,o,i]}class _r extends ye{constructor(t){super(),ge(this,t,hr,mr,pe,{onMessage:2})}}function Ts(e,t,n){const i=e.slice();return i[32]=t[n],i}function As(e,t,n){const i=e.slice();return i[35]=t[n],i}function Ls(e){let t,n,i,l,o,u,d,c,f,g,k,_,v,y,b;function A(C,T){return C[3]?gr:br}let P=A(e),I=P(e);function O(C,T){return C[2]?vr:yr}let j=O(e),W=j(e);return{c(){t=a("div"),n=a("span"),n.textContent="Tauri API Validation",i=m(),l=a("span"),o=a("span"),I.c(),d=m(),c=a("span"),c.innerHTML='
',f=m(),g=a("span"),W.c(),_=m(),v=a("span"),v.innerHTML='
',r(n,"class","lt-sm:pl-10 text-darkPrimaryText"),r(o,"title",u=e[3]?"Switch to Light mode":"Switch to Dark mode"),r(o,"class","hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"),r(c,"title","Minimize"),r(c,"class","hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"),r(g,"title",k=e[2]?"Restore":"Maximize"),r(g,"class","hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"),r(v,"title","Close"),r(v,"class","hover:bg-red-700 dark:hover:bg-red-700 hover:text-darkPrimaryText active:bg-red-700/90 dark:active:bg-red-700/90 active:text-darkPrimaryText "),r(l,"class","h-100% children:h-100% children:w-12 children:inline-flex children:items-center children:justify-center"),r(t,"class","w-screen select-none h-8 pl-2 flex justify-between items-center absolute text-primaryText dark:text-darkPrimaryText"),r(t,"data-tauri-drag-region","")},m(C,T){h(C,t,T),s(t,n),s(t,i),s(t,l),s(l,o),I.m(o,null),s(l,d),s(l,c),s(l,f),s(l,g),W.m(g,null),s(l,_),s(l,v),y||(b=[S(o,"click",e[12]),S(c,"click",e[9]),S(g,"click",e[10]),S(v,"click",e[11])],y=!0)},p(C,T){P!==(P=A(C))&&(I.d(1),I=P(C),I&&(I.c(),I.m(o,null))),T[0]&8&&u!==(u=C[3]?"Switch to Light mode":"Switch to Dark mode")&&r(o,"title",u),j!==(j=O(C))&&(W.d(1),W=j(C),W&&(W.c(),W.m(g,null))),T[0]&4&&k!==(k=C[2]?"Restore":"Maximize")&&r(g,"title",k)},d(C){C&&p(t),I.d(),W.d(),y=!1,oe(b)}}}function br(e){let t;return{c(){t=a("div"),r(t,"class","i-ph-moon")},m(n,i){h(n,t,i)},d(n){n&&p(t)}}}function gr(e){let t;return{c(){t=a("div"),r(t,"class","i-ph-sun")},m(n,i){h(n,t,i)},d(n){n&&p(t)}}}function yr(e){let t;return{c(){t=a("div"),r(t,"class","i-codicon-chrome-maximize")},m(n,i){h(n,t,i)},d(n){n&&p(t)}}}function vr(e){let t;return{c(){t=a("div"),r(t,"class","i-codicon-chrome-restore")},m(n,i){h(n,t,i)},d(n){n&&p(t)}}}function wr(e){let t;return{c(){t=a("span"),r(t,"class","i-codicon-menu animate-duration-300ms animate-fade-in")},m(n,i){h(n,t,i)},d(n){n&&p(t)}}}function kr(e){let t;return{c(){t=a("span"),r(t,"class","i-codicon-close animate-duration-300ms animate-fade-in")},m(n,i){h(n,t,i)},d(n){n&&p(t)}}}function Ss(e){let t,n,i,l,o,u,d,c,f;function g(v,y){return v[3]?Cr:Mr}let k=g(e),_=k(e);return{c(){t=a("a"),_.c(),n=m(),i=a("br"),l=m(),o=a("div"),u=m(),d=a("br"),r(t,"href","##"),r(t,"class","nv justify-between h-8"),r(o,"class","bg-white/5 h-2px")},m(v,y){h(v,t,y),_.m(t,null),h(v,n,y),h(v,i,y),h(v,l,y),h(v,o,y),h(v,u,y),h(v,d,y),c||(f=S(t,"click",e[12]),c=!0)},p(v,y){k!==(k=g(v))&&(_.d(1),_=k(v),_&&(_.c(),_.m(t,null)))},d(v){v&&p(t),_.d(),v&&p(n),v&&p(i),v&&p(l),v&&p(o),v&&p(u),v&&p(d),c=!1,f()}}}function Mr(e){let t,n;return{c(){t=z(`Switch to Dark mode - `),n=a("div"),r(n,"class","i-ph-moon")},m(i,l){h(i,t,l),h(i,n,l)},d(i){i&&p(t),i&&p(n)}}}function Cr(e){let t,n;return{c(){t=z(`Switch to Light mode - `),n=a("div"),r(n,"class","i-ph-sun")},m(i,l){h(i,t,l),h(i,n,l)},d(i){i&&p(t),i&&p(n)}}}function Tr(e){let t,n,i,l,o,u=e[35].label+"",d,c,f,g;function k(){return e[20](e[35])}return{c(){t=a("a"),n=a("div"),l=m(),o=a("p"),d=z(u),r(n,"class",i=e[35].icon+" mr-2"),r(t,"href","##"),r(t,"class",c="nv "+(e[1]===e[35]?"nv_selected":""))},m(_,v){h(_,t,v),s(t,n),s(t,l),s(t,o),s(o,d),f||(g=S(t,"click",k),f=!0)},p(_,v){e=_,v[0]&2&&c!==(c="nv "+(e[1]===e[35]?"nv_selected":""))&&r(t,"class",c)},d(_){_&&p(t),f=!1,g()}}}function zs(e){let t,n=e[35]&&Tr(e);return{c(){n&&n.c(),t=pi()},m(i,l){n&&n.m(i,l),h(i,t,l)},p(i,l){i[35]&&n.p(i,l)},d(i){n&&n.d(i),i&&p(t)}}}function Es(e){let t,n=e[32].html+"",i;return{c(){t=new Lo(!1),i=pi(),t.a=i},m(l,o){t.m(n,l,o),h(l,i,o)},p(l,o){o[0]&64&&n!==(n=l[32].html+"")&&t.p(n)},d(l){l&&p(i),l&&t.d()}}}function Ar(e){let t,n,i,l,o,u,d,c,f,g,k,_,v,y,b,A,P,I,O,j,W,C,T,E,M,N,U=e[1].label+"",J,$,me,te,Y,de,x,H,X,K,ae,ne,he,_e,fe,re,Ae,Le,D=e[5]&&Ls(e);function G(F,ce){return F[0]?kr:wr}let Se=G(e),ve=Se(e),ue=!e[5]&&Ss(e),we=e[7],ie=[];for(let F=0;F`,k=m(),_=a("a"),_.innerHTML=`GitHub - `,v=m(),y=a("a"),y.innerHTML=`Source - `,b=m(),A=a("br"),P=m(),I=a("div"),O=m(),j=a("br"),W=m(),C=a("div");for(let F=0;F',_e=m(),fe=a("div");for(let F=0;F{$t(B,1)}),hi()}ze?(Y=new ze(Je(F)),ui(Y.$$.fragment),Te(Y.$$.fragment,1),xt(Y,te,null)):Y=null}if(ce[0]&64){le=F[6];let B;for(B=0;B{await confirm("Are you sure?")||H.preventDefault()}),Ge.onFileDropEvent(H=>{P(`File drop: ${JSON.stringify(H.payload)}`)});const l=navigator.userAgent.toLowerCase(),o=l.includes("android")||l.includes("iphone"),u=[{label:"Welcome",component:ea,icon:"i-ph-hand-waving"},{label:"Communication",component:aa,icon:"i-codicon-radio-tower"},!o&&{label:"CLI",component:la,icon:"i-codicon-terminal"},!o&&{label:"Dialog",component:Ma,icon:"i-codicon-multiple-windows"},{label:"File system",component:La,icon:"i-codicon-files"},{label:"HTTP",component:Ha,icon:"i-ph-globe-hemisphere-west"},!o&&{label:"Notifications",component:Ua,icon:"i-codicon-bell-dot"},!o&&{label:"App",component:_r,icon:"i-codicon-hubot"},!o&&{label:"Window",component:Va,icon:"i-codicon-window"},!o&&{label:"Shortcuts",component:Qa,icon:"i-codicon-record-keys"},{label:"Shell",component:$a,icon:"i-codicon-terminal-bash"},!o&&{label:"Updater",component:or,icon:"i-codicon-cloud-download"},!o&&{label:"Clipboard",component:cr,icon:"i-codicon-clippy"},{label:"WebRTC",component:pr,icon:"i-ph-broadcast"}];let d=u[0];function c(H){n(1,d=H)}let f;_t(async()=>{const H=Kt();n(2,f=await H.isMaximized()),tn("tauri://resize",async()=>{n(2,f=await H.isMaximized())})});function g(){Kt().minimize()}async function k(){const H=Kt();await H.isMaximized()?H.unmaximize():H.maximize()}let _=!1;async function v(){_||(_=await ao("Are you sure that you want to close this window?",{title:"Tauri API"}),_&&Kt().close())}let y;_t(()=>{n(3,y=localStorage&&localStorage.getItem("theme")=="dark"),Ds(y)});function b(){n(3,y=!y),Ds(y)}let A=Hs([]);Os(e,A,H=>n(6,i=H));function P(H){A.update(X=>[{html:`
[${new Date().toLocaleTimeString()}]: `+(typeof H=="string"?H:JSON.stringify(H,null,1))+"
"},...X])}function I(H){A.update(X=>[{html:`
[${new Date().toLocaleTimeString()}]: `+H+"
"},...X])}function O(){A.update(()=>[])}let j,W,C;function T(H){C=H.clientY;const X=window.getComputedStyle(j);W=parseInt(X.height,10);const K=ne=>{const he=ne.clientY-C,_e=W-he;n(4,j.style.height=`${_e{document.removeEventListener("mouseup",ae),document.removeEventListener("mousemove",K)};document.addEventListener("mouseup",ae),document.addEventListener("mousemove",K)}let E;_t(async()=>{n(5,E=await xs()==="win32")});let M=!1,N,U,J=!1,$=0,me=0;const te=(H,X,K)=>Math.min(Math.max(X,H),K);_t(()=>{n(18,N=document.querySelector("#sidebar")),U=document.querySelector("#sidebarToggle"),document.addEventListener("click",H=>{U.contains(H.target)?n(0,M=!M):M&&!N.contains(H.target)&&n(0,M=!1)}),document.addEventListener("touchstart",H=>{if(U.contains(H.target))return;const X=H.touches[0].clientX;(0{if(J){const X=H.touches[0].clientX;me=X;const K=(X-$)/10;N.style.setProperty("--translate-x",`-${te(0,M?0-K:18.75-K,18.75)}rem`)}}),document.addEventListener("touchend",()=>{if(J){const H=(me-$)/10;n(0,M=M?H>-(18.75/2):H>18.75/2)}J=!1})});const Y=()=>Ki("https://tauri.app/"),de=H=>{c(H),n(0,M=!1)};function x(H){ri[H?"unshift":"push"](()=>{j=H,n(4,j)})}return e.$$.update=()=>{if(e.$$.dirty[0]&1){const H=document.querySelector("#sidebar");H&&Lr(H,M)}},[M,d,f,y,j,E,i,u,c,g,k,v,b,A,P,I,O,T,N,Y,de,x]}class zr extends ye{constructor(t){super(),ge(this,t,Sr,Ar,pe,{},null,[-1,-1])}}new zr({target:document.querySelector("#app")}); + 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/package.json b/examples/api/package.json index f39ee176d4b3..29cff8b61354 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "type": "module", "scripts": { - "dev": "vite --clearScreen false --port 5173", + "dev": "vite --clearScreen false", "build": "vite build", "serve": "vite preview", "tauri": "node ../../tooling/cli/node/tauri.js" @@ -15,9 +15,10 @@ "devDependencies": { "@iconify-json/codicon": "^1.1.10", "@iconify-json/ph": "^1.1.1", - "@sveltejs/vite-plugin-svelte": "^1.0.0-next.49", + "@sveltejs/vite-plugin-svelte": "^1.0.1", + "internal-ip": "^7.0.0", "svelte": "^3.49.0", "unocss": "^0.39.3", - "vite": "^2.9.16" + "vite": "^3.0.9" } } diff --git a/examples/api/src-tauri/.gitignore b/examples/api/src-tauri/.gitignore index aba21e242c95..e5f47c4d3d32 100644 --- a/examples/api/src-tauri/.gitignore +++ b/examples/api/src-tauri/.gitignore @@ -1,3 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ + +# cargo-mobile +.cargo/ +/gen 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 080a409e25d5..e4bcbf9ab0b3 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -10,9 +10,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", "generic-array", @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead", "aes", @@ -45,9 +45,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -67,6 +67,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_log-sys" version = "0.2.0" @@ -75,14 +81,14 @@ checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" [[package]] name = "android_logger" -version = "0.9.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ec2333c185d826313162cee39d3fcc6a84ba08114a839bebf53b961e7e75773" +checksum = "8619b80c242aa7bd638b5c7ddd952addeecb71f69c75e33f1d47b2804f8f883a" dependencies = [ "android_log-sys", - "env_logger 0.7.1", - "lazy_static", + "env_logger", "log", + "once_cell", ] [[package]] @@ -95,145 +101,87 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.68" +name = "anstream" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" - -[[package]] -name = "api" -version = "0.1.0" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ - "android_logger", - "env_logger 0.9.3", - "jni 0.19.0", - "log", - "mobile-entry-point", - "paste", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-runtime-wry", - "tiny_http", - "window-shadows", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", ] [[package]] -name = "ascii" -version = "1.1.0" +name = "anstyle" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] -name = "async-broadcast" -version = "0.5.1" +name = "anstyle-parse" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" dependencies = [ - "event-listener", - "futures-core", + "utf8parse", ] [[package]] -name = "async-channel" -version = "1.8.0" +name = "anstyle-query" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", + "windows-sys 0.48.0", ] [[package]] -name = "async-executor" -version = "1.5.0" +name = "anstyle-wincon" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", + "anstyle", + "windows-sys 0.48.0", ] [[package]] -name = "async-fs" -version = "1.6.0" +name = "anyhow" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock", - "autocfg", - "blocking", - "futures-lite", -] +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +name = "api" +version = "0.1.0" dependencies = [ - "async-lock", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite", "log", - "parking", - "polling", - "rustix", - "slab", - "socket2", - "waker-fn", -] - -[[package]] -name = "async-lock" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-recursion" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-cli", + "tauri-plugin-log", + "tauri-plugin-sample", + "tiny_http", + "window-shadows", ] [[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - -[[package]] -name = "async-trait" -version = "0.1.66" +name = "ascii" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", -] +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", @@ -243,31 +191,14 @@ dependencies = [ [[package]] name = "atk-sys" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", -] - -[[package]] -name = "atomic-waker" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", + "system-deps", ] [[package]] @@ -284,9 +215,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -302,27 +233,13 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", -] - [[package]] name = "brotli" version = "3.3.4" @@ -345,25 +262,26 @@ dependencies = [ ] [[package]] -name = "bstr" -version = "0.2.17" +name = "bumpalo" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "memchr", -] +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] -name = "bumpalo" -version = "3.12.0" +name = "byte-unit" +version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" +dependencies = [ + "serde", + "utf8-width", +] [[package]] name = "bytemuck" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" @@ -373,45 +291,46 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" dependencies = [ "serde", ] [[package]] name = "cairo-rs" -version = "0.15.12" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +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", "libc", - "system-deps 6.0.3", + "system-deps", ] [[package]] name = "cargo_toml" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f83bc2e401ed041b7057345ebc488c005efa0341d5541ce7004d30458d0090b" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" dependencies = [ "serde", - "toml 0.7.3", + "toml", ] [[package]] @@ -439,20 +358,12 @@ dependencies = [ [[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-expr" -version = "0.11.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" +checksum = "e70d3ad08698a0568b0562f22710fe6bfc1f4a61a367c77d0398c562eadd453a" dependencies = [ "smallvec", + "target-lexicon", ] [[package]] @@ -463,12 +374,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", - "num-integer", "num-traits", "serde", "winapi", @@ -482,9 +393,9 @@ checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" [[package]] name = "cipher" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", @@ -492,27 +403,31 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +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 = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" dependencies = [ - "atty", + "anstream", + "anstyle", "bitflags", "clap_lex", - "indexmap", "strsim", - "termcolor", - "textwrap", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "cocoa" @@ -532,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", @@ -561,6 +476,12 @@ 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 = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "combine" version = "4.6.6" @@ -571,15 +492,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "concurrent-queue" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -598,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" @@ -629,9 +541,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -647,9 +559,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -657,9 +569,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] @@ -689,17 +601,17 @@ dependencies = [ "proc-macro2", "quote", "smallvec", - "syn 1.0.107", + "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 1.0.107", + "syn 2.0.18", ] [[package]] @@ -709,7 +621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -721,17 +633,11 @@ dependencies = [ "cipher", ] -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - [[package]] name = "cxx" -version = "1.0.94" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" dependencies = [ "cc", "cxxbridge-flags", @@ -741,9 +647,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.94" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" dependencies = [ "cc", "codespan-reporting", @@ -751,24 +657,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.15", + "syn 1.0.109", ] [[package]] name = "cxxbridge-flags" -version = "1.0.94" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" +checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" [[package]] name = "cxxbridge-macro" -version = "1.0.94" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 1.0.109", ] [[package]] @@ -792,7 +698,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -803,18 +709,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 2.0.15", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", + "syn 2.0.18", ] [[package]] @@ -826,29 +721,20 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", - "syn 1.0.107", + "rustc_version", + "syn 1.0.109", ] [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -859,17 +745,6 @@ dependencies = [ "dirs-sys-next", ] -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -889,93 +764,72 @@ 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 = "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" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] -name = "enumflags2" -version = "0.7.7" +name = "embed-resource" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a" dependencies = [ - "enumflags2_derive", - "serde", + "cc", + "rustc_version", + "toml", + "vswhom", + "winreg 0.11.0", ] [[package]] -name = "enumflags2_derive" -version = "0.7.7" +name = "embed_plist" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] -name = "env_logger" -version = "0.7.1" +name = "encoding_rs" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ - "log", - "regex", + "cfg-if", ] [[package]] name = "env_logger" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "atty", - "humantime", "log", "regex", - "termcolor", ] [[package]] name = "errno" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -989,47 +843,47 @@ dependencies = [ ] [[package]] -name = "event-listener" -version = "2.5.3" +name = "fastrand" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] [[package]] -name = "fastrand" -version = "1.8.0" +name = "fdeflate" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" dependencies = [ - "instant", + "simd-adler32", ] [[package]] -name = "field-offset" -version = "0.3.4" +name = "fern" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" dependencies = [ - "memoffset 0.6.5", - "rustc_version 0.3.3", + "log", ] [[package]] -name = "filetime" -version = "0.2.19" +name = "field-offset" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "windows-sys 0.42.0", + "memoffset", + "rustc_version", ] [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", @@ -1058,9 +912,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -1077,24 +931,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1103,53 +957,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" - -[[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.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.18", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-io", @@ -1173,9 +1012,9 @@ dependencies = [ [[package]] name = "gdk" -version = "0.15.4" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1" dependencies = [ "bitflags", "cairo-rs", @@ -1189,9 +1028,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.15.11" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" dependencies = [ "bitflags", "gdk-pixbuf-sys", @@ -1202,22 +1041,22 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "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", @@ -1227,40 +1066,54 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.0.3", + "system-deps", ] [[package]] -name = "gdkx11-sys" -version = "0.15.1" +name = "gdkwayland-sys" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +checksum = "4511710212ed3020b61a8622a37aa6f0dd2a84516575da92e9b96928dcbe83ba" dependencies = [ "gdk-sys", "glib-sys", + "gobject-sys", "libc", - "system-deps 6.0.3", - "x11", + "pkg-config", + "system-deps", ] [[package]] -name = "generator" -version = "0.7.2" +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.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" +checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a" dependencies = [ "cc", "libc", "log", "rustversion", - "windows 0.39.0", + "windows 0.48.0", ] [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1279,9 +1132,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -1300,45 +1153,50 @@ dependencies = [ [[package]] name = "gio" -version = "0.15.12" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +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.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps", "winapi", ] [[package]] name = "glib" -version = "0.15.12" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +checksum = "16aa2475c9debed5a32832cb5ff2af5a3f9e1ab9e69df58eaadc1ab2004d6eba" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", + "futures-util", + "gio-sys", "glib-macros", "glib-sys", "gobject-sys", @@ -1350,27 +1208,27 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.15.11" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +checksum = "fb1a9325847aa46f1e96ffea37611b9d51fc4827e67f79e7de502a297560a67b" dependencies = [ "anyhow", - "heck 0.4.0", + "heck", "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "glib-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" dependencies = [ "libc", - "system-deps 6.0.3", + "system-deps", ] [[package]] @@ -1379,35 +1237,22 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "globset" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - [[package]] name = "gobject-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.3", + "system-deps", ] [[package]] name = "gtk" -version = "0.15.5" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6" dependencies = [ "atk", "bitflags", @@ -1428,9 +1273,9 @@ 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", @@ -1441,21 +1286,21 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.3", + "system-deps", ] [[package]] name = "gtk3-macros" -version = "0.15.4" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +checksum = "096eb63c6fedf03bafe65e5924595785eaf1bcb7200dac0f2cbe9c9738f05ad8" dependencies = [ "anyhow", "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1485,27 +1330,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[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.1.19" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1539,18 +1366,18 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.5", + "itoa 1.0.6", ] [[package]] @@ -1582,17 +1409,11 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -1603,7 +1424,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.5", + "itoa 1.0.6", "pin-project-lite", "socket2", "tokio", @@ -1612,19 +1433,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.56" @@ -1677,37 +1485,19 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 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" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" dependencies = [ "bytemuck", "byteorder", @@ -1718,9 +1508,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -1765,9 +1555,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", @@ -1776,9 +1566,21 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.1" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + +[[package]] +name = "is-terminal" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] [[package]] name = "itoa" @@ -1788,15 +1590,15 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +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", @@ -1805,28 +1607,14 @@ 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", "gobject-sys", "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "jni" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", + "system-deps", ] [[package]] @@ -1851,9 +1639,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1890,9 +1678,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libappindicator" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8" +checksum = "89e1edfdc9b0853358306c6dfb4b77c79c779174256fe93d80c0b5ebca451a2f" dependencies = [ "glib", "gtk", @@ -1903,9 +1691,9 @@ dependencies = [ [[package]] name = "libappindicator-sys" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa" +checksum = "08fcb2bea89cee9613982501ec83eaa2d09256b24540ae463c52a28906163918" dependencies = [ "gtk-sys", "libloading", @@ -1914,9 +1702,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.139" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libloading" @@ -1948,15 +1736,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1964,11 +1752,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" dependencies = [ - "cfg-if", + "value-bag", ] [[package]] @@ -1992,19 +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.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e72d50edb17756489e79d52eb146927bec8eba9dd48faadf9ef08bca3791ad5" -dependencies = [ - "cc", - "dirs-next", - "objc-foundation", - "objc_id", - "time", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -2039,9 +1814,9 @@ 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" @@ -2051,92 +1826,38 @@ 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 = "memoffset" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minisign-verify" -version = "0.2.1" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] name = "mio" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", -] - -[[package]] -name = "mobile-entry-point" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bef5a90018326583471cccca10424d7b3e770397b02f03276543cbb9b6a1a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "windows-sys 0.48.0", ] [[package]] @@ -2173,38 +1894,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" -[[package]] -name = "nix" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" -dependencies = [ - "bitflags", - "cfg-if", - "libc", - "memoffset 0.7.1", - "pin-utils", - "static_assertions", -] - [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "notify-rust" -version = "4.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce656bb6d22a93ae276a23de52d1aec5ba4db3ece3c0eb79dfd5add7384db6a" -dependencies = [ - "mac-notification-sys", - "serde", - "tauri-winrt-notification", - "zbus", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2257,23 +1952,23 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.7" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.7" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2295,17 +1990,6 @@ dependencies = [ "objc_exception", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc_exception" version = "0.1.2" @@ -2326,9 +2010,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -2337,215 +2021,96 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "open" -version = "3.2.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" -dependencies = [ - "pathdiff", - "windows-sys 0.42.0", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "openssl" -version = "0.10.48" +name = "pango" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" dependencies = [ "bitflags", - "cfg-if", - "foreign-types", + "gio", + "glib", "libc", "once_cell", - "openssl-macros", - "openssl-sys", + "pango-sys", ] [[package]] -name = "openssl-macros" -version = "0.1.0" +name = "pango-sys" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", ] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "parking_lot" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] [[package]] -name = "openssl-sys" -version = "0.9.83" +name = "parking_lot_core" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ - "autocfg", - "cc", + "cfg-if", "libc", - "pkg-config", - "vcpkg", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets 0.48.0", ] [[package]] -name = "ordered-stream" -version = "0.2.0" +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "phf" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "futures-core", - "pin-project-lite", + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", ] [[package]] -name = "os_info" -version = "3.5.0" +name = "phf" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5209b2162b2c140df493a93689e04f8deab3a67634f5bc7a553c0a98e5b8d399" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ - "log", - "serde", - "winapi", + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] -name = "os_pipe" -version = "1.1.2" +name = "phf_codegen" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a252f1f8c11e84b3ab59d7a488e48e4478a93937e027076638c49536204639" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" dependencies = [ - "libc", - "windows-sys 0.42.0", -] - -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "pango" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" -dependencies = [ - "bitflags", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.0.3", -] - -[[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.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 0.42.0", -] - -[[package]] -name = "paste" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" - -[[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.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", + "phf_generator 0.8.0", + "phf_shared 0.8.0", ] [[package]] @@ -2579,7 +2144,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2593,7 +2158,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2628,52 +2193,37 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5329b8f106a176ab0dce4aae5da86bfcb139bb74fb00882859e03745011f3635" +checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ - "base64 0.13.1", + "base64 0.21.2", "indexmap", "line-wrap", - "quick-xml 0.26.0", + "quick-xml", "serde", "time", ] [[package]] name = "png" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" dependencies = [ "bitflags", "crc32fast", + "fdeflate", "flate2", "miniz_oxide", ] -[[package]] -name = "polling" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.45.0", -] - [[package]] name = "polyval" version = "0.6.0" @@ -2700,13 +2250,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "thiserror", - "toml 0.5.10", + "toml_edit", ] [[package]] @@ -2718,7 +2267,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", "version_check", ] @@ -2741,36 +2290,27 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.26.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -2835,7 +2375,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -2858,12 +2398,9 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" -dependencies = [ - "cty", -] +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "redox_syscall" @@ -2874,26 +2411,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +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", + "getrandom 0.2.10", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] [[package]] @@ -2902,31 +2448,28 @@ 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.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +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 = "reqwest" -version = "0.11.13" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.13.1", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -2935,13 +2478,10 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "mime_guess", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -2949,47 +2489,14 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "winreg", -] - -[[package]] -name = "rfd" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" -dependencies = [ - "block", - "dispatch", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "lazy_static", - "log", - "objc", - "objc-foundation", - "objc_id", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows 0.37.0", -] - -[[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", + "winreg 0.10.1", ] [[package]] @@ -2998,34 +2505,34 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.16", + "semver", ] [[package]] name = "rustix" -version = "0.37.3" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "rustversion" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safemem" @@ -3042,15 +2549,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -3069,29 +2567,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" -[[package]] -name = "security-framework" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "selectors" version = "0.22.0" @@ -3114,49 +2589,31 @@ dependencies = [ [[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" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" 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.160" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -3165,27 +2622,27 @@ version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ - "itoa 1.0.5", + "itoa 1.0.6", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.18", ] [[package]] name = "serde_spanned" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" dependencies = [ "serde", ] @@ -3197,7 +2654,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.5", + "itoa 1.0.6", "ryu", "serde", ] @@ -3208,7 +2665,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "chrono", "hex", "indexmap", @@ -3227,7 +2684,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -3249,7 +2706,7 @@ checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3262,17 +2719,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha2" version = "0.10.6" @@ -3294,14 +2740,10 @@ 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" @@ -3311,9 +2753,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -3326,40 +2768,40 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] [[package]] -name = "soup2" -version = "0.2.1" +name = "soup3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +checksum = "82bc46048125fefd69d30b32b9d263d6556c9ffe82a7a7df181a86d912da5616" dependencies = [ "bitflags", + "futures-channel", "gio", "glib", "libc", "once_cell", - "soup2-sys", + "soup3-sys", ] [[package]] -name = "soup2-sys" -version = "0.2.0" +name = "soup3-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +checksum = "014bbeb1c4cdb30739dc181e8d98b7908f124d9555843afa89b5570aaf4ec62b" dependencies = [ - "bitflags", "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 5.0.0", + "system-deps", ] [[package]] @@ -3377,17 +2819,11 @@ 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.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", @@ -3416,98 +2852,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "strum" -version = "0.22.0" +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "swift-rs" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e" +checksum = "05e51d6f2b5fff4808614f429f8a7655ac8bcfe218185413f3a60c508482c2d6" dependencies = [ - "strum_macros", + "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 1.0.107", -] - -[[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.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "unicode-ident", ] [[package]] name = "syn" -version = "2.0.15" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "sys-locale" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee" -dependencies = [ - "js-sys", - "libc", - "wasm-bindgen", - "web-sys", - "windows-sys 0.45.0", -] - [[package]] name = "system-deps" -version = "5.0.0" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" dependencies = [ - "cfg-expr 0.9.1", - "heck 0.3.3", + "cfg-expr", + "heck", "pkg-config", - "toml 0.5.10", - "version-compare 0.0.11", -] - -[[package]] -name = "system-deps" -version = "6.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" -dependencies = [ - "cfg-expr 0.11.0", - "heck 0.4.0", - "pkg-config", - "toml 0.5.10", - "version-compare 0.1.1", + "toml", + "version-compare", ] [[package]] name = "tao" -version = "0.16.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704522803dda895767f69198af8351b0a3f4fe2e293c3ca54cce0ecba05a97f2" +checksum = "746ae5d0ca57ae275a792f109f6e992e0b41a443abdf3f5c6eff179ef5b3443a" dependencies = [ "bitflags", "cairo-rs", @@ -3521,6 +2921,7 @@ dependencies = [ "gdk", "gdk-pixbuf", "gdk-sys", + "gdkwayland-sys", "gdkx11-sys", "gio", "glib", @@ -3528,7 +2929,7 @@ dependencies = [ "gtk", "image", "instant", - "jni 0.20.0", + "jni", "lazy_static", "libappindicator", "libc", @@ -3546,117 +2947,101 @@ dependencies = [ "tao-macros", "unicode-segmentation", "uuid", - "windows 0.39.0", + "windows 0.44.0", "windows-implement", "x11-dl", ] [[package]] name = "tao-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b6fcd8245d45a39ffc8715183d92ae242750eb57b285eb3bcd63dfd512afd09" +checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "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.3.0" +version = "2.0.0-alpha.9" dependencies = [ "anyhow", - "base64 0.21.0", "bytes", - "clap", "cocoa", "dirs-next", "embed_plist", - "encoding_rs", - "flate2", "futures-util", "glib", "glob", "gtk", - "heck 0.4.0", + "heck", "http", "ico 0.2.0", - "ignore", "infer 0.9.0", - "minisign-verify", - "notify-rust", + "jni", + "libc", + "log", "objc", "once_cell", - "open", - "os_info", - "os_pipe", "percent-encoding", "png", "rand 0.8.5", "raw-window-handle", - "regex", "reqwest", - "rfd", - "semver 1.0.16", + "semver", "serde", "serde_json", "serde_repr", "serialize-to-javascript", - "shared_child", "state", - "sys-locale", - "tar", + "swift-rs", + "tauri-build", "tauri-macros", "tauri-runtime", "tauri-runtime-wry", "tauri-utils", "tempfile", "thiserror", - "time", "tokio", "url", "uuid", "webkit2gtk", "webview2-com", - "win7-notifications", - "windows 0.39.0", - "zip", + "windows 0.44.0", ] [[package]] name = "tauri-build" -version = "1.3.0" +version = "2.0.0-alpha.5" dependencies = [ "anyhow", "cargo_toml", - "heck 0.4.0", + "heck", "json-patch", "quote", - "semver 1.0.16", + "semver", "serde", "serde_json", + "swift-rs", "tauri-codegen", "tauri-utils", "tauri-winres", + "walkdir", ] [[package]] name = "tauri-codegen" -version = "1.3.0" +version = "2.0.0-alpha.5" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "brotli", "ico 0.3.0", "json-patch", @@ -3664,37 +3049,82 @@ dependencies = [ "png", "proc-macro2", "quote", - "regex", - "semver 1.0.16", + "semver", "serde", "serde_json", "sha2", "tauri-utils", "thiserror", "time", + "url", "uuid", "walkdir", ] [[package]] name = "tauri-macros" -version = "1.3.0" +version = "2.0.0-alpha.5" dependencies = [ - "heck 0.4.0", + "heck", "proc-macro2", "quote", - "syn 1.0.107", + "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.13.0" +version = "0.13.0-alpha.5" dependencies = [ "gtk", "http", "http-range", + "jni", "rand 0.8.5", "raw-window-handle", "serde", @@ -3703,16 +3133,16 @@ dependencies = [ "thiserror", "url", "uuid", - "webview2-com", - "windows 0.39.0", + "windows 0.44.0", ] [[package]] name = "tauri-runtime-wry" -version = "0.13.0" +version = "0.13.0-alpha.5" dependencies = [ "cocoa", "gtk", + "jni", "percent-encoding", "rand 0.8.5", "raw-window-handle", @@ -3721,21 +3151,21 @@ dependencies = [ "uuid", "webkit2gtk", "webview2-com", - "windows 0.39.0", + "windows 0.44.0", "wry", ] [[package]] name = "tauri-utils" -version = "1.3.0" +version = "2.0.0-alpha.5" dependencies = [ "aes-gcm", "brotli", "ctor", "dunce", - "getrandom 0.2.8", + "getrandom 0.2.10", "glob", - "heck 0.4.0", + "heck", "html5ever", "infer 0.12.0", "json-patch", @@ -3744,7 +3174,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", - "semver 1.0.16", + "semver", "serde", "serde_json", "serde_with", @@ -3752,42 +3182,31 @@ dependencies = [ "thiserror", "url", "walkdir", - "windows 0.39.0", + "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 0.5.10", - "version_check", -] - -[[package]] -name = "tauri-winrt-notification" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" dependencies = [ - "quick-xml 0.23.1", - "strum", - "windows 0.39.0", + "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", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -3810,12 +3229,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thin-slice" version = "0.1.1" @@ -3839,36 +3252,47 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "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.3.15" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ - "itoa 1.0.5", + "itoa 1.0.6", "libc", "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.4" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] [[package]] name = "tiny_http" @@ -3894,42 +3318,31 @@ dependencies = [ [[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.24.2" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "pin-project-lite", "socket2", - "windows-sys 0.42.0", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", + "windows-sys 0.48.0", ] [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -3939,15 +3352,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" -dependencies = [ - "serde", -] - [[package]] name = "toml" version = "0.7.3" @@ -4002,20 +3406,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -4034,9 +3438,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -4071,42 +3475,17 @@ 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 = "uds_windows" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" -dependencies = [ - "tempfile", - "winapi", -] - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -4119,9 +3498,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" @@ -4131,9 +3510,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle", @@ -4141,9 +3520,9 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -4157,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 = "1.2.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -4173,16 +3564,10 @@ 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" @@ -4197,19 +3582,32 @@ 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 = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +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 = "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", ] @@ -4237,9 +3635,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4247,24 +3645,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -4274,9 +3672,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4284,28 +3682,41 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" + +[[package]] +name = "wasm-streams" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -4313,9 +3724,9 @@ dependencies = [ [[package]] name = "webkit2gtk" -version = "0.18.2" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" +checksum = "d8eea819afe15eb8dcdff4f19d8bfda540bae84d874c10e6f4b8faf2d6704bd1" dependencies = [ "bitflags", "cairo-rs", @@ -4331,20 +3742,18 @@ dependencies = [ "javascriptcore-rs", "libc", "once_cell", - "soup2", + "soup3", "webkit2gtk-sys", ] [[package]] name = "webkit2gtk-sys" -version = "0.18.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +checksum = "d0ac7a95ddd3fdfcaf83d8e513b4b1ad101b95b413b6aa6662ed95f284fc3d5b" dependencies = [ - "atk-sys", "bitflags", "cairo-sys-rs", - "gdk-pixbuf-sys", "gdk-sys", "gio-sys", "glib-sys", @@ -4352,21 +3761,20 @@ dependencies = [ "gtk-sys", "javascriptcore-rs-sys", "libc", - "pango-sys", "pkg-config", - "soup2-sys", - "system-deps 6.0.3", + "soup3-sys", + "system-deps", ] [[package]] name = "webview2-com" -version = "0.19.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" +checksum = "11296e5daf3a653b79bf47d66c380e4143d5b9c975818871179a3bda79499562" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.39.0", + "windows 0.44.0", "windows-implement", ] @@ -4378,34 +3786,24 @@ checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "webview2-com-sys" -version = "0.19.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" +checksum = "cde542bed28058a5b028d459689ee57f1d06685bb6c266da3b91b1be6703952f" dependencies = [ "regex", "serde", "serde_json", "thiserror", - "windows 0.39.0", + "windows 0.44.0", "windows-bindgen", "windows-metadata", ] -[[package]] -name = "win7-notifications" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210952d7163b9ed83a6fd9754ab2a101d14480f8491b5f1d6292771d88dbee70" -dependencies = [ - "once_cell", - "windows-sys 0.36.1", -] - [[package]] name = "winapi" version = "0.3.9" @@ -4451,29 +3849,13 @@ dependencies = [ [[package]] name = "windows" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" -dependencies = [ - "windows_aarch64_msvc 0.37.0", - "windows_i686_gnu 0.37.0", - "windows_i686_msvc 0.37.0", - "windows_x86_64_gnu 0.37.0", - "windows_x86_64_msvc 0.37.0", -] - -[[package]] -name = "windows" -version = "0.39.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ "windows-implement", - "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", + "windows-interface", + "windows-targets 0.42.2", ] [[package]] @@ -4487,9 +3869,9 @@ dependencies = [ [[package]] name = "windows-bindgen" -version = "0.39.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" +checksum = "222204ecf46521382a4d88b4a1bbefca9f8855697b4ab7d20803901425e061a3" dependencies = [ "windows-metadata", "windows-tokens", @@ -4497,55 +3879,45 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.39.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" +checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6" dependencies = [ - "syn 1.0.107", - "windows-tokens", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "windows-metadata" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" - -[[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows-interface" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-metadata" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "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 0.42.1", - "windows_x86_64_msvc 0.42.1", -] +checksum = "ee78911e3f4ce32c1ad9d3c7b0bd95389662ad8d8f1a3155688fed70bd96e2b6" [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows-targets 0.42.1", + "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]] @@ -4559,17 +3931,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "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 0.42.1", - "windows_x86_64_msvc 0.42.1", + "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]] @@ -4589,15 +3961,15 @@ dependencies = [ [[package]] name = "windows-tokens" -version = "0.39.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" +checksum = "fa4251900975a0d10841c5d4bde79c56681543367ef811f3fabb8d1803b0959b" [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -4607,27 +3979,9 @@ checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" - -[[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" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -4637,27 +3991,9 @@ checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" - -[[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" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -4667,27 +4003,9 @@ checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" - -[[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" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -4697,27 +4015,9 @@ checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.37.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" - -[[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" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -4727,9 +4027,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -4739,27 +4039,9 @@ checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.37.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" - -[[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" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -4769,9 +4051,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.1" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ "memchr", ] @@ -4785,11 +4067,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "wry" -version = "0.24.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c846dc4dda988e959869dd0802cd27417c9696e584593e49178aeee28890d25" +checksum = "7d15f9f827d537cefe6d047be3930f5d89b238dfb85e08ba6a319153217635aa" dependencies = [ "base64 0.13.1", "block", @@ -4803,6 +4095,7 @@ dependencies = [ "gtk", "html5ever", "http", + "javascriptcore-rs", "kuchiki", "libc", "log", @@ -4812,22 +4105,22 @@ dependencies = [ "serde", "serde_json", "sha2", - "soup2", + "soup3", "tao", "thiserror", "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.39.0", + "windows 0.44.0", "windows-implement", ] [[package]] name = "x11" -version = "2.20.1" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2638d5b9c17ac40575fb54bb461a4b1d2a8d1b4ffcc4ff237d254ec59ddeb82" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" dependencies = [ "libc", "pkg-config", @@ -4835,133 +4128,11 @@ dependencies = [ [[package]] name = "x11-dl" -version = "2.20.1" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1536d6965a5d4e573c7ef73a2c15ebcd0b2de3347bdf526c34c297c00ac40f0" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "lazy_static", "libc", - "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 = "zbus" -version = "3.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc29e76f558b2cb94190e8605ecfe77dd40f5df8c072951714b4b71a97f5848" -dependencies = [ - "async-broadcast", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-recursion", - "async-task", - "async-trait", - "byteorder", - "derivative", - "dirs", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix", "once_cell", - "ordered-stream", - "rand 0.8.5", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "winapi", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "3.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62a80fd82c011cd08459eaaf1fd83d3090c1b61e6d5284360074a7475af3a85d" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "regex", - "syn 1.0.107", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" -dependencies = [ - "serde", - "static_assertions", - "zvariant", -] - -[[package]] -name = "zip" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" -dependencies = [ - "byteorder", - "crc32fast", - "crossbeam-utils", -] - -[[package]] -name = "zvariant" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe4914a985446d6fd287019b5fceccce38303d71407d9e6e711d44954a05d8" -dependencies = [ - "byteorder", - "enumflags2", - "libc", - "serde", - "static_assertions", - "zvariant_derive", -] - -[[package]] -name = "zvariant_derive" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34c20260af4b28b3275d6676c7e2a6be0d4332e8e0aba4616d34007fd84e462a" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.107", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b22993dbc4d128a17a3b6c92f1c63872dd67198537ee728d8b5d7c40640a8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", + "pkg-config", ] diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index 3de7ea83062d..8bd1e11634e7 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -3,11 +3,12 @@ name = "api" version = "0.1.0" description = "An example Tauri Application showcasing the api" edition = "2021" -rust-version = "1.60" +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 = ["codegen", "isolation"] } @@ -16,21 +17,30 @@ tauri-build = { path = "../../../core/tauri-build", features = ["codegen", "isol serde_json = "1.0" serde = { version = "1.0", features = [ "derive" ] } 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 = [ - "api-all", - "cli", - "global-shortcut", - "http-multipart", + "protocol-asset", "icon-ico", "icon-png", "isolation", "macos-private-api", - "windows7-compat", - "system-tray", - "updater" + "system-tray" ] [dev-dependencies.tauri] @@ -40,19 +50,6 @@ features = ["test"] [target."cfg(target_os = \"windows\")".dependencies] window-shadows= "0.2" -[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] -log = "0.4" -tauri-runtime-wry = { path = "../../../core/tauri-runtime-wry/" } - -[target.'cfg(target_os = "android")'.dependencies] -android_logger = "0.9.0" -jni = "0.19.0" -paste = "1.0" - -[target.'cfg(target_os = "ios")'.dependencies] -mobile-entry-point = "0.1.0" -env_logger = "0.9.0" - [features] custom-protocol = [ "tauri/custom-protocol" ] diff --git a/examples/api/src-tauri/build.rs b/examples/api/src-tauri/build.rs index b8c24f76e182..2154ff35f5e3 100644 --- a/examples/api/src-tauri/build.rs +++ b/examples/api/src-tauri/build.rs @@ -8,5 +8,5 @@ fn main() { codegen = codegen.dev(); } codegen.build(); - tauri_build::build() + tauri_build::build(); } diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs index 791a621385ba..221881fc1b3b 100644 --- a/examples/api/src-tauri/src/cmd.rs +++ b/examples/api/src-tauri/src/cmd.rs @@ -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 index 4be07f36be26..13e428037e37 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -2,20 +2,27 @@ // 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::{ - api::dialog::{ask, message}, - App, GlobalShortcutManager, Manager, RunEvent, Runtime, WindowBuilder, WindowEvent, WindowUrl, -}; +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| {}) @@ -27,32 +34,45 @@ pub fn run_app) + Send + 'static>( ) { #[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| { - tray::create_tray(app)?; + #[cfg(desktop)] + { + tray::create_tray(app)?; - #[allow(unused_mut)] - let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()) - .user_agent("Tauri API") - .title("Tauri API Validation") - .inner_size(1000., 800.) - .min_inner_size(600., 400.) - .content_protected(true); + app.handle().plugin(tauri_plugin_cli::init())?; + } - #[cfg(target_os = "windows")] + let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()); + #[cfg(desktop)] { - window_builder = window_builder.transparent(true).decorations(false); + 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(target_os = "windows")] - { - let _ = window_shadows::set_shadow(&window, true); - } - #[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, @@ -112,61 +132,12 @@ pub fn run_app) + Send + 'static>( #[cfg(target_os = "macos")] app.set_activation_policy(tauri::ActivationPolicy::Regular); - app.run(move |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(); - if let Some(window) = app_handle.get_window("main") { - message( - Some(&window), - "Tauri API", - "CmdOrCtrl+1 global shortcut triggered", - ); - } - }) - .unwrap(); - } - - // Triggered when a window is trying to close - RunEvent::WindowEvent { - label, - event: WindowEvent::CloseRequested { api, .. }, - .. - } => { - // for other windows, we handle it in JS - if label == "main" { - 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(); - }); - } - }, - ); - } - } - #[cfg(not(test))] - RunEvent::ExitRequested { api, .. } => { - // 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(); - } - _ => (), + 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(); } }) } diff --git a/examples/api/src-tauri/src/main.rs b/examples/api/src-tauri/src/main.rs index e9885338f1b7..33ab0aa16d1c 100644 --- a/examples/api/src-tauri/src/main.rs +++ b/examples/api/src-tauri/src/main.rs @@ -6,6 +6,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - #[cfg(desktop)] api_lib::run(); } diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs index e69f99b3486b..96a8bbd9aef8 100644 --- a/examples/api/src-tauri/src/tray.rs +++ b/examples/api/src-tauri/src/tray.rs @@ -4,10 +4,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use tauri::{ - api::{ - dialog::{MessageDialogBuilder, MessageDialogButtons}, - shell, - }, CustomMenuItem, Manager, Runtime, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowUrl, }; @@ -26,7 +22,6 @@ pub fn create_tray(app: &tauri::App) -> tauri::Result<()> { tray_menu1 = tray_menu1 .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) - .add_item(CustomMenuItem::new("about", "About")) .add_item(CustomMenuItem::new("exit_app", "Quit")) .add_item(CustomMenuItem::new("destroy", "Destroy")); @@ -34,7 +29,6 @@ pub fn create_tray(app: &tauri::App) -> tauri::Result<()> { .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("about", "About")) .add_item(CustomMenuItem::new("exit_app", "Quit")) .add_item(CustomMenuItem::new("destroy", "Destroy")); let is_menu1 = AtomicBool::new(true); @@ -119,20 +113,6 @@ pub fn create_tray(app: &tauri::App) -> tauri::Result<()> { tray_handle.set_tooltip(tooltip).unwrap(); is_menu1.store(!flag, Ordering::Relaxed); } - "about" => { - let window = handle.get_window("main").unwrap(); - MessageDialogBuilder::new("About app", "Tauri demo app") - .parent(&window) - .buttons(MessageDialogButtons::OkCancelWithLabels( - "Homepage".into(), - "know it".into(), - )) - .show(move |ok| { - if ok { - shell::open(&window.shell_scope(), "https://tauri.app/", None).unwrap(); - } - }); - } _ => {} } } 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/sidecar/src-tauri/Cargo.lock b/examples/api/src-tauri/tauri-plugin-sample/Cargo.lock similarity index 90% rename from examples/sidecar/src-tauri/Cargo.lock rename to examples/api/src-tauri/tauri-plugin-sample/Cargo.lock index 10b074a6a770..cb47713eba71 100644 --- a/examples/sidecar/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/tauri-plugin-sample/Cargo.lock @@ -40,9 +40,9 @@ checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[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", @@ -52,14 +52,29 @@ dependencies = [ [[package]] name = "atk-sys" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "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]] @@ -76,9 +91,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "bitflags" @@ -114,9 +129,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -152,33 +167,34 @@ checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cairo-rs" -version = "0.15.12" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +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", "libc", - "system-deps 6.0.3", + "system-deps", ] [[package]] name = "cargo_toml" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1204fe51a1e56042b8ec31d6407547ecd18f596b66f470dadb9abd9be9c843" +checksum = "2bfbc36312494041e2cdd5f06697b7e89d4b76f42773a0b5556ac290ff22acc2" dependencies = [ "serde", "toml", @@ -207,15 +223,6 @@ dependencies = [ "uuid", ] -[[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-expr" version = "0.11.0" @@ -691,9 +698,9 @@ dependencies = [ [[package]] name = "gdk" -version = "0.15.4" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1" dependencies = [ "bitflags", "cairo-rs", @@ -707,9 +714,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.15.11" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" dependencies = [ "bitflags", "gdk-pixbuf-sys", @@ -720,22 +727,22 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "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", @@ -745,19 +752,33 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.0.3", + "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", "libc", - "system-deps 6.0.3", + "system-deps", "x11", ] @@ -771,7 +792,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows", + "windows 0.39.0", ] [[package]] @@ -808,45 +829,50 @@ dependencies = [ [[package]] name = "gio" -version = "0.15.12" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +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.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps", "winapi", ] [[package]] name = "glib" -version = "0.15.12" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", + "futures-util", + "gio-sys", "glib-macros", "glib-sys", "gobject-sys", @@ -858,12 +884,12 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf" dependencies = [ "anyhow", - "heck 0.4.0", + "heck", "proc-macro-crate", "proc-macro-error", "proc-macro2", @@ -873,12 +899,12 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" dependencies = [ "libc", - "system-deps 6.0.3", + "system-deps", ] [[package]] @@ -902,20 +928,20 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.3", + "system-deps", ] [[package]] name = "gtk" -version = "0.15.5" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6" dependencies = [ "atk", "bitflags", @@ -936,9 +962,9 @@ 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", @@ -949,14 +975,14 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.3", + "system-deps", ] [[package]] name = "gtk3-macros" -version = "0.15.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +checksum = "8cfd6557b1018b773e43c8de9d0d13581d6b36190d0501916cbec4731db5ccff" dependencies = [ "anyhow", "proc-macro-crate", @@ -972,15 +998,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.0" @@ -1126,9 +1143,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[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", @@ -1137,14 +1154,14 @@ 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", "gobject-sys", "libc", - "system-deps 5.0.0", + "system-deps", ] [[package]] @@ -1459,16 +1476,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" -[[package]] -name = "os_pipe" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a252f1f8c11e84b3ab59d7a488e48e4478a93937e027076638c49536204639" -dependencies = [ - "libc", - "windows-sys", -] - [[package]] name = "overload" version = "0.1.1" @@ -1477,11 +1484,12 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pango" -version = "0.15.10" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" dependencies = [ "bitflags", + "gio", "glib", "libc", "once_cell", @@ -1490,14 +1498,14 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps", ] [[package]] @@ -1657,16 +1665,16 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plist" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5329b8f106a176ab0dce4aae5da86bfcb139bb74fb00882859e03745011f3635" +checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" dependencies = [ "base64 0.13.1", "indexmap", "line-wrap", - "quick-xml", "serde", "time", + "xml-rs", ] [[package]] @@ -1736,22 +1744,13 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-xml" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "1.0.23" @@ -2052,6 +2051,18 @@ dependencies = [ "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" @@ -2126,26 +2137,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shared_child" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "sidecar" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "tauri", - "tauri-build", -] - [[package]] name = "siphasher" version = "0.3.10" @@ -2168,31 +2159,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] -name = "soup2" -version = "0.2.1" +name = "soup3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +checksum = "82bc46048125fefd69d30b32b9d263d6556c9ffe82a7a7df181a86d912da5616" dependencies = [ "bitflags", + "futures-channel", "gio", "glib", "libc", "once_cell", - "soup2-sys", + "soup3-sys", ] [[package]] -name = "soup2-sys" -version = "0.2.0" +name = "soup3-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +checksum = "014bbeb1c4cdb30739dc181e8d98b7908f124d9555843afa89b5570aaf4ec62b" dependencies = [ - "bitflags", "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 5.0.0", + "system-deps", ] [[package]] @@ -2242,6 +2233,16 @@ 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" @@ -2253,37 +2254,23 @@ dependencies = [ "unicode-ident", ] -[[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", -] - [[package]] name = "system-deps" version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" dependencies = [ - "cfg-expr 0.11.0", - "heck 0.4.0", + "cfg-expr", + "heck", "pkg-config", "toml", - "version-compare 0.1.1", + "version-compare", ] [[package]] name = "tao" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704522803dda895767f69198af8351b0a3f4fe2e293c3ca54cce0ecba05a97f2" +version = "0.18.0" +source = "git+https://github.com/tauri-apps/tao?branch=dev#382ea30300b8e909cae5896b57c7c623c9825655" dependencies = [ "bitflags", "cairo-rs", @@ -2296,6 +2283,7 @@ dependencies = [ "gdk", "gdk-pixbuf", "gdk-sys", + "gdkwayland-sys", "gdkx11-sys", "gio", "glib", @@ -2320,16 +2308,15 @@ dependencies = [ "tao-macros", "unicode-segmentation", "uuid", - "windows", + "windows 0.44.0", "windows-implement", "x11-dl", ] [[package]] name = "tao-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b6fcd8245d45a39ffc8715183d92ae242750eb57b285eb3bcd63dfd512afd09" +version = "0.1.1" +source = "git+https://github.com/tauri-apps/tao?branch=dev#382ea30300b8e909cae5896b57c7c623c9825655" dependencies = [ "proc-macro2", "quote", @@ -2349,9 +2336,10 @@ dependencies = [ [[package]] name = "tauri" -version = "1.2.3" +version = "2.0.0-alpha.3" dependencies = [ "anyhow", + "attohttpc", "cocoa", "dirs-next", "embed_plist", @@ -2361,24 +2349,26 @@ dependencies = [ "glib", "glob", "gtk", - "heck 0.4.0", + "heck", "http", "ignore", + "jni", + "libc", + "log", "objc", "once_cell", - "os_pipe", "percent-encoding", "rand 0.8.5", "raw-window-handle", - "regex", "semver 1.0.16", "serde", "serde_json", "serde_repr", "serialize-to-javascript", - "shared_child", "state", + "swift-rs", "tar", + "tauri-build", "tauri-macros", "tauri-runtime", "tauri-runtime-wry", @@ -2390,30 +2380,32 @@ dependencies = [ "uuid", "webkit2gtk", "webview2-com", - "windows", + "windows 0.44.0", ] [[package]] name = "tauri-build" -version = "1.2.1" +version = "2.0.0-alpha.1" dependencies = [ "anyhow", "cargo_toml", - "heck 0.4.0", + "filetime", + "heck", "json-patch", - "quote", "semver 1.0.16", + "serde", "serde_json", - "tauri-codegen", + "swift-rs", "tauri-utils", - "winres", + "tauri-winres", + "walkdir", ] [[package]] name = "tauri-codegen" -version = "1.2.1" +version = "2.0.0-alpha.1" dependencies = [ - "base64 0.20.0", + "base64 0.21.0", "brotli", "ico", "json-patch", @@ -2421,7 +2413,6 @@ dependencies = [ "png", "proc-macro2", "quote", - "regex", "semver 1.0.16", "serde", "serde_json", @@ -2429,15 +2420,16 @@ dependencies = [ "tauri-utils", "thiserror", "time", + "url", "uuid", "walkdir", ] [[package]] name = "tauri-macros" -version = "1.2.1" +version = "2.0.0-alpha.1" dependencies = [ - "heck 0.4.0", + "heck", "proc-macro2", "quote", "syn", @@ -2445,13 +2437,23 @@ dependencies = [ "tauri-utils", ] +[[package]] +name = "tauri-plugin-sample" +version = "0.1.0" +dependencies = [ + "log", + "tauri", + "tauri-build", +] + [[package]] name = "tauri-runtime" -version = "0.12.1" +version = "0.13.0-alpha.1" dependencies = [ "gtk", "http", "http-range", + "jni", "rand 0.8.5", "raw-window-handle", "serde", @@ -2461,15 +2463,16 @@ dependencies = [ "url", "uuid", "webview2-com", - "windows", + "windows 0.44.0", ] [[package]] name = "tauri-runtime-wry" -version = "0.12.2" +version = "0.13.0-alpha.1" dependencies = [ "cocoa", "gtk", + "jni", "percent-encoding", "rand 0.8.5", "raw-window-handle", @@ -2478,18 +2481,18 @@ dependencies = [ "uuid", "webkit2gtk", "webview2-com", - "windows", + "windows 0.44.0", "wry", ] [[package]] name = "tauri-utils" -version = "1.2.1" +version = "2.0.0-alpha.1" dependencies = [ "brotli", "ctor", "glob", - "heck 0.4.0", + "heck", "html5ever", "infer", "json-patch", @@ -2505,7 +2508,17 @@ dependencies = [ "thiserror", "url", "walkdir", - "windows", + "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]] @@ -2612,9 +2625,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.24.2" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" dependencies = [ "autocfg", "bytes", @@ -2763,9 +2776,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ "getrandom 0.2.8", ] @@ -2776,12 +2789,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "version-compare" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" - [[package]] name = "version-compare" version = "0.1.1" @@ -2819,9 +2826,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "webkit2gtk" -version = "0.18.2" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" +checksum = "d8eea819afe15eb8dcdff4f19d8bfda540bae84d874c10e6f4b8faf2d6704bd1" dependencies = [ "bitflags", "cairo-rs", @@ -2837,20 +2844,18 @@ dependencies = [ "javascriptcore-rs", "libc", "once_cell", - "soup2", + "soup3", "webkit2gtk-sys", ] [[package]] name = "webkit2gtk-sys" -version = "0.18.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +checksum = "d0ac7a95ddd3fdfcaf83d8e513b4b1ad101b95b413b6aa6662ed95f284fc3d5b" dependencies = [ - "atk-sys", "bitflags", "cairo-sys-rs", - "gdk-pixbuf-sys", "gdk-sys", "gio-sys", "glib-sys", @@ -2858,21 +2863,20 @@ dependencies = [ "gtk-sys", "javascriptcore-rs-sys", "libc", - "pango-sys", "pkg-config", - "soup2-sys", - "system-deps 6.0.3", + "soup3-sys", + "system-deps", ] [[package]] name = "webview2-com" -version = "0.19.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" +checksum = "03411e89ec447e29c08b3c086edeb88c5f8fd782cbdd4d6d316bea439be7a244" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.44.0", "windows-implement", ] @@ -2889,15 +2893,15 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.19.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" +checksum = "1c0f5ce43e9611c5b2983a33156d6abe31abf39185bad84a6766c80ba1dbf1ab" dependencies = [ "regex", "serde", "serde_json", "thiserror", - "windows", + "windows 0.44.0", "windows-bindgen", "windows-metadata", ] @@ -2939,7 +2943,6 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" dependencies = [ - "windows-implement", "windows_aarch64_msvc 0.39.0", "windows_i686_gnu 0.39.0", "windows_i686_msvc 0.39.0", @@ -2947,11 +2950,22 @@ dependencies = [ "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.39.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" +checksum = "222204ecf46521382a4d88b4a1bbefca9f8855697b4ab7d20803901425e061a3" dependencies = [ "windows-metadata", "windows-tokens", @@ -2959,19 +2973,31 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.39.0" +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 = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" +checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f" dependencies = [ + "proc-macro2", + "quote", "syn", - "windows-tokens", ] [[package]] name = "windows-metadata" -version = "0.39.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" +checksum = "ee78911e3f4ce32c1ad9d3c7b0bd95389662ad8d8f1a3155688fed70bd96e2b6" [[package]] name = "windows-sys" @@ -2988,11 +3014,26 @@ dependencies = [ "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.39.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" +checksum = "fa4251900975a0d10841c5d4bde79c56681543367ef811f3fabb8d1803b0959b" [[package]] name = "windows_aarch64_gnullvm" @@ -3066,20 +3107,10 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" -[[package]] -name = "winres" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" -dependencies = [ - "toml", -] - [[package]] name = "wry" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c846dc4dda988e959869dd0802cd27417c9696e584593e49178aeee28890d25" +version = "0.26.0" +source = "git+https://github.com/tauri-apps/wry?branch=dev#077eb3a7ca520d07e73f899da60ce23eef941e6f" dependencies = [ "base64 0.13.1", "block", @@ -3102,14 +3133,14 @@ dependencies = [ "serde", "serde_json", "sha2", - "soup2", + "soup3", "tao", "thiserror", "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.44.0", "windows-implement", ] @@ -3125,12 +3156,12 @@ dependencies = [ [[package]] name = "x11-dl" -version = "2.20.1" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1536d6965a5d4e573c7ef73a2c15ebcd0b2de3347bdf526c34c297c00ac40f0" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "lazy_static", "libc", + "once_cell", "pkg-config", ] @@ -3142,3 +3173,9 @@ 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 422f2e6ed009..fc7eef0ed0fb 100644 --- a/examples/api/src-tauri/tauri.conf.json +++ b/examples/api/src-tauri/tauri.conf.json @@ -4,20 +4,14 @@ "distDir": "../dist", "devPath": "http://localhost:5173", "beforeDevCommand": "yarn dev", - "beforeBuildCommand": "yarn build" + "beforeBuildCommand": "yarn build", + "withGlobalTauri": true }, "package": { "productName": "Tauri API", "version": "1.0.0" }, - "tauri": { - "pattern": { - "use": "isolation", - "options": { - "dir": "../isolation-dist/" - } - }, - "macOSPrivateApi": true, + "plugins": { "cli": { "description": "Tauri API example", "args": [ @@ -37,7 +31,6 @@ { "short": "v", "name": "verbose", - "multipleOccurrences": true, "description": "Verbosity level" } ], @@ -53,7 +46,16 @@ ] } } + } + }, + "tauri": { + "pattern": { + "use": "isolation", + "options": { + "dir": "../isolation-dist/" + } }, + "macOSPrivateApi": true, "bundle": { "active": true, "identifier": "com.tauri.api", @@ -73,48 +75,13 @@ } } } - } - }, - "updater": { - "active": true, - "dialog": false, - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK", - "endpoints": [ - "https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}" - ] - }, - "allowlist": { - "all": true, - "fs": { - "scope": { - "allow": ["$APPDATA/db/**", "$DOWNLOAD/**", "$RESOURCE/**"], - "deny": ["$APPDATA/db/*.stronghold"] - } - }, - "shell": { - "open": true, - "scope": [ - { - "name": "sh", - "cmd": "sh", - "args": ["-c", { "validator": "\\S+" }] - }, - { - "name": "cmd", - "cmd": "cmd", - "args": ["/C", { "validator": "\\S+" }] - } - ] }, - "protocol": { - "asset": true, - "assetScope": { - "allow": ["$APPDATA/db/**", "$RESOURCE/**"], - "deny": ["$APPDATA/db/*.stronghold"] + "updater": { + "active": true, + "pubkey": "asdasd", + "windows": { + "installMode": "passive" } - }, - "http": { - "scope": ["http://localhost:3003"] } }, "windows": [], @@ -125,7 +92,14 @@ "img-src": "'self' asset: https://asset.localhost blob: data:", "style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com" }, - "freezePrototype": true + "freezePrototype": true, + "assetProtocol": { + "enable": true, + "scope": { + "allow": ["$APPDATA/db/**", "$RESOURCE/**"], + "deny": ["$APPDATA/db/*.stronghold"] + } + } }, "systemTray": { "iconPath": "../../.icons/tray_icon_with_transparency.png", diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte index d736e05c9248..264e9a0bed2b 100644 --- a/examples/api/src/App.svelte +++ b/examples/api/src/App.svelte @@ -1,41 +1,10 @@ - -{#if isWindows} -
- Tauri API Validation - - - {#if isDark} -
- {:else} -
- {/if} - - -
- - - {#if isWindowMaximized} -
- {:else} -
- {/if} - - -
- - -
-{/if} -
open('https://tauri.app/')} class="self-center p-7 cursor-pointer" src="tauri_logo.png" alt="Tauri logo" /> - {#if !isWindows} - - {#if isDark} - Switch to Light mode -
- {:else} - Switch to Dark mode -
- {/if} - -
-
-
- {/if} + + {#if isDark} + Switch to Light mode +
+ {:else} + Switch to Dark mode +
+ {/if} + +
+
+
- import { show, hide } from '@tauri-apps/api/app' - - export let onMessage - - function showApp() { - hideApp() - .then(() => { - setTimeout(() => { - show() - .then(() => onMessage('Shown app')) - .catch(onMessage) - }, 2000) - }) - .catch(onMessage) - } - - function hideApp() { - return hide() - .then(() => onMessage('Hide app')) - .catch(onMessage) - } - - -
- - -
diff --git a/examples/api/src/views/Cli.svelte b/examples/api/src/views/Cli.svelte deleted file mode 100644 index 9da4aa17effc..000000000000 --- a/examples/api/src/views/Cli.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -

- This binary can be run from 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/views/Clipboard.svelte b/examples/api/src/views/Clipboard.svelte deleted file mode 100644 index da1f1d7e4a59..000000000000 --- a/examples/api/src/views/Clipboard.svelte +++ /dev/null @@ -1,32 +0,0 @@ - - -
- - - -
diff --git a/examples/api/src/views/Dialog.svelte b/examples/api/src/views/Dialog.svelte deleted file mode 100644 index bdeca61ac80e..000000000000 --- a/examples/api/src/views/Dialog.svelte +++ /dev/null @@ -1,118 +0,0 @@ - - -
- - -
- -
-
- - -
-
- - -
-
- - diff --git a/examples/api/src/views/FileSystem.svelte b/examples/api/src/views/FileSystem.svelte deleted file mode 100644 index d8465cb37b4b..000000000000 --- a/examples/api/src/views/FileSystem.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - -
-
- - -
-
-
- - -
-
- -
- - diff --git a/examples/api/src/views/Http.svelte b/examples/api/src/views/Http.svelte deleted file mode 100644 index 16e6d47c4b94..000000000000 --- a/examples/api/src/views/Http.svelte +++ /dev/null @@ -1,99 +0,0 @@ - - -
- -
-