diff --git a/Cargo.lock b/Cargo.lock
index 6300bb8b..2814dfd6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4939,11 +4939,15 @@ version = "0.1.0"
dependencies = [
"log",
"notify-rust",
+ "rand 0.8.5",
"serde",
"serde_json",
+ "serde_repr",
"tauri",
"tauri-build",
"thiserror",
+ "time 0.3.20",
+ "url",
"win7-notifications",
]
diff --git a/Cargo.toml b/Cargo.toml
index 24649cd1..44306836 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[workspace]
-members = ["plugins/*"]
-exclude = ["plugins/fs", "plugins/http"]
+members = ["plugins/*", "examples/*/src-tauri"]
+exclude = ["plugins/fs", "plugins/http", "examples/api/src-tauri"]
resolver = "2"
[workspace.dependencies]
diff --git a/examples/api/.gitignore b/examples/api/.gitignore
new file mode 100644
index 00000000..ac9643a2
--- /dev/null
+++ b/examples/api/.gitignore
@@ -0,0 +1,4 @@
+/node_modules/
+/.vscode/
+.DS_Store
+.cargo
diff --git a/examples/api/.setup-cross.sh b/examples/api/.setup-cross.sh
new file mode 100644
index 00000000..cd563694
--- /dev/null
+++ b/examples/api/.setup-cross.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+export ICONS_VOLUME="$(realpath icons)"
+export DIST_VOLUME="$(realpath dist)"
+export ISOLATION_VOLUME="$(realpath isolation-dist)"
diff --git a/examples/api/.taurignore b/examples/api/.taurignore
new file mode 100644
index 00000000..848a0f2a
--- /dev/null
+++ b/examples/api/.taurignore
@@ -0,0 +1,3 @@
+src-tauri/locales/
+src-tauri/Cross.toml
+src-tauri/.gitignore
diff --git a/examples/api/README.md b/examples/api/README.md
new file mode 100644
index 00000000..60de2fca
--- /dev/null
+++ b/examples/api/README.md
@@ -0,0 +1,28 @@
+# API example
+
+This example demonstrates Tauri's API capabilities using the plugins from this repository. It's used as the main validation app, serving as the testbed of our development process.
+In the future, this app will be used on Tauri's integration tests.
+
+
+
+## Running the example
+
+- Install dependencies and build packages (Run inside of the repository root)
+
+```bash
+$ pnpm install
+$ pnpm build
+```
+
+- Run the app in development mode (Run inside of this folder `examples/api/`)
+
+```bash
+$ pnpm tauri dev
+```
+
+- Build an run the release app (Run inside of this folder `examples/api/`)
+
+```bash
+$ pnpm tauri build
+$ ./src-tauri/target/release/app
+```
diff --git a/examples/api/index.html b/examples/api/index.html
new file mode 100644
index 00000000..3b39b0f6
--- /dev/null
+++ b/examples/api/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Svelte + Vite App
+
+
+
+
+
+
+
diff --git a/examples/api/isolation-dist/index.html b/examples/api/isolation-dist/index.html
new file mode 100644
index 00000000..27c8d3f3
--- /dev/null
+++ b/examples/api/isolation-dist/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Isolation Secure Script
+
+
+
+
+
diff --git a/examples/api/isolation-dist/index.js b/examples/api/isolation-dist/index.js
new file mode 100644
index 00000000..83f35e0d
--- /dev/null
+++ b/examples/api/isolation-dist/index.js
@@ -0,0 +1,7 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+window.__TAURI_ISOLATION_HOOK__ = (payload) => {
+ return payload;
+};
diff --git a/examples/api/jsconfig.json b/examples/api/jsconfig.json
new file mode 100644
index 00000000..42585941
--- /dev/null
+++ b/examples/api/jsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "moduleResolution": "node",
+ "target": "esnext",
+ "module": "esnext",
+ /**
+ * svelte-preprocess cannot figure out whether you have
+ * a value or a type, so tell TypeScript to enforce using
+ * `import type` instead of `import` for Types.
+ */
+ "importsNotUsedAsValues": "error",
+ "isolatedModules": true,
+ "resolveJsonModule": true,
+ /**
+ * To have warnings / errors of the Svelte compiler at the
+ * correct position, enable source maps by default.
+ */
+ "sourceMap": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable this if you'd like to use dynamic types.
+ */
+ "checkJs": true
+ },
+ /**
+ * Use global.d.ts instead of compilerOptions.types
+ * to avoid limiting type declarations.
+ */
+ "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
+}
diff --git a/examples/api/package.json b/examples/api/package.json
new file mode 100644
index 00000000..830146ce
--- /dev/null
+++ b/examples/api/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "svelte-app",
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --clearScreen false",
+ "build": "vite build",
+ "serve": "vite preview"
+ },
+ "dependencies": {
+ "@tauri-apps/api": "2.0.0-alpha.3",
+ "@tauri-apps/cli": "2.0.0-alpha.8",
+ "@zerodevx/svelte-json-view": "0.2.1",
+ "tauri-plugin-cli-api": "0.0.0",
+ "tauri-plugin-clipboard-api": "0.0.0",
+ "tauri-plugin-dialog-api": "0.0.0",
+ "tauri-plugin-fs-api": "0.0.0",
+ "tauri-plugin-global-shortcut-api": "0.0.0",
+ "tauri-plugin-http-api": "0.0.0",
+ "tauri-plugin-notification-api": "0.0.0",
+ "tauri-plugin-shell-api": "0.0.0"
+ },
+ "devDependencies": {
+ "@iconify-json/codicon": "^1.1.10",
+ "@iconify-json/ph": "^1.1.1",
+ "@sveltejs/vite-plugin-svelte": "^1.0.1",
+ "internal-ip": "^7.0.0",
+ "svelte": "^3.49.0",
+ "unocss": "^0.39.3",
+ "vite": "^3.0.9"
+ }
+}
diff --git a/examples/api/public/tauri_logo.png b/examples/api/public/tauri_logo.png
new file mode 100644
index 00000000..2c53b8c4
Binary files /dev/null and b/examples/api/public/tauri_logo.png differ
diff --git a/examples/api/screenshot.png b/examples/api/screenshot.png
new file mode 100644
index 00000000..c51a4699
Binary files /dev/null and b/examples/api/screenshot.png differ
diff --git a/examples/api/src-tauri/.gitignore b/examples/api/src-tauri/.gitignore
new file mode 100644
index 00000000..211f2466
--- /dev/null
+++ b/examples/api/src-tauri/.gitignore
@@ -0,0 +1,6 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
+# cargo-mobile
+.cargo/
diff --git a/examples/api/src-tauri/.taurignore b/examples/api/src-tauri/.taurignore
new file mode 100644
index 00000000..cbff5293
--- /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
new file mode 100644
index 00000000..3e52a758
--- /dev/null
+++ b/examples/api/src-tauri/Cargo.lock
@@ -0,0 +1,4976 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aead"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "android_log-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
+
+[[package]]
+name = "android_logger"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8619b80c242aa7bd638b5c7ddd952addeecb71f69c75e33f1d47b2804f8f883a"
+dependencies = [
+ "android_log-sys",
+ "env_logger",
+ "log",
+ "once_cell",
+]
+
+[[package]]
+name = "anstream"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is-terminal",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
+
+[[package]]
+name = "api"
+version = "0.1.0"
+dependencies = [
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "tauri-plugin-cli",
+ "tauri-plugin-clipboard",
+ "tauri-plugin-dialog",
+ "tauri-plugin-fs",
+ "tauri-plugin-global-shortcut",
+ "tauri-plugin-http",
+ "tauri-plugin-log",
+ "tauri-plugin-notification",
+ "tauri-plugin-shell",
+ "tiny_http",
+ "window-shadows",
+]
+
+[[package]]
+name = "arboard"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854"
+dependencies = [
+ "clipboard-win",
+ "core-graphics",
+ "image",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "once_cell",
+ "parking_lot",
+ "thiserror",
+ "winapi",
+ "x11rb",
+]
+
+[[package]]
+name = "ascii"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
+
+[[package]]
+name = "async-broadcast"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
+dependencies = [
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-channel"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b"
+dependencies = [
+ "async-lock",
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
+dependencies = [
+ "async-lock",
+ "autocfg",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+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",
+]
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atk"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39991bc421ddf72f70159011b323ff49b0f783cc676a7287c59453da2e2531cf"
+dependencies = [
+ "atk-sys",
+ "bitflags",
+ "glib",
+ "libc",
+]
+
+[[package]]
+name = "atk-sys"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bstr"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+
+[[package]]
+name = "byte-unit"
+version = "4.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c"
+dependencies = [
+ "serde",
+ "utf8-width",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cairo-rs"
+version = "0.16.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d"
+dependencies = [
+ "bitflags",
+ "cairo-sys-rs",
+ "glib",
+ "libc",
+ "once_cell",
+ "thiserror",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "cargo_toml"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bfbc36312494041e2cdd5f06697b7e89d4b76f42773a0b5556ac290ff22acc2"
+dependencies = [
+ "serde",
+ "toml",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfb"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
+dependencies = [
+ "byteorder",
+ "fnv",
+ "uuid",
+]
+
+[[package]]
+name = "cfg-expr"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chunked_transfer"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a"
+
+[[package]]
+name = "cipher"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "bitflags",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
+
+[[package]]
+name = "clipboard-win"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
+dependencies = [
+ "error-code",
+ "str-buf",
+ "winapi",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
+dependencies = [
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318"
+dependencies = [
+ "bitflags",
+ "block",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "core-graphics"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
+]
+
+[[package]]
+name = "cssparser"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa 0.4.8",
+ "matches",
+ "phf 0.8.0",
+ "proc-macro2",
+ "quote",
+ "smallvec",
+ "syn",
+]
+
+[[package]]
+name = "cssparser-macros"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "darling"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version 0.4.0",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs"
+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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
+[[package]]
+name = "dtoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6"
+dependencies = [
+ "dtoa",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
+
+[[package]]
+name = "embed_plist"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enumflags2"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "error-code"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
+dependencies = [
+ "libc",
+ "str-buf",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "fastrand"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fern"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "field-offset"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92"
+dependencies = [
+ "memoffset 0.6.5",
+ "rustc_version 0.3.3",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
+dependencies = [
+ "mac",
+ "new_debug_unreachable",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
+
+[[package]]
+name = "futures-lite"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
+
+[[package]]
+name = "futures-task"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
+
+[[package]]
+name = "futures-util"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "gdk"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk-sys",
+ "gio",
+ "glib",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk-pixbuf"
+version = "0.16.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05"
+dependencies = [
+ "bitflags",
+ "gdk-pixbuf-sys",
+ "gio",
+ "glib",
+ "libc",
+]
+
+[[package]]
+name = "gdk-pixbuf-sys"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016"
+dependencies = [
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gdk-sys"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76354f97a913e55b984759a997b693aa7dc71068c9e98bcce51aa167a0a5c5a"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gdkwayland-sys"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4511710212ed3020b61a8622a37aa6f0dd2a84516575da92e9b96928dcbe83ba"
+dependencies = [
+ "gdk-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gdkx11-sys"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa2bf8b5b8c414bc5d05e48b271896d0fd3ddb57464a3108438082da61de6af"
+dependencies = [
+ "gdk-sys",
+ "glib-sys",
+ "libc",
+ "system-deps",
+ "x11",
+]
+
+[[package]]
+name = "generator"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustversion",
+ "windows 0.39.0",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "gethostname"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "gio"
+version = "0.16.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092"
+dependencies = [
+ "bitflags",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "gio-sys",
+ "glib",
+ "libc",
+ "once_cell",
+ "pin-project-lite",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+ "winapi",
+]
+
+[[package]]
+name = "glib"
+version = "0.16.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f"
+dependencies = [
+ "bitflags",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys",
+ "glib-macros",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "once_cell",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf"
+dependencies = [
+ "anyhow",
+ "heck 0.4.1",
+ "proc-macro-crate",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65"
+dependencies = [
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "global-hotkey"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92007db5c632751655a4c0ef122832fd283e6651621d34613b17fd1edfbfd2ed"
+dependencies = [
+ "crossbeam-channel",
+ "keyboard-types",
+ "once_cell",
+ "thiserror",
+ "windows-sys 0.48.0",
+ "x11-dl",
+]
+
+[[package]]
+name = "globset"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gtk"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6"
+dependencies = [
+ "atk",
+ "bitflags",
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk",
+ "gdk-pixbuf",
+ "gio",
+ "glib",
+ "gtk-sys",
+ "gtk3-macros",
+ "libc",
+ "once_cell",
+ "pango",
+ "pkg-config",
+]
+
+[[package]]
+name = "gtk-sys"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b5f8946685d5fe44497007786600c2f368ff6b1e61a16251c89f72a97520a3"
+dependencies = [
+ "atk-sys",
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "gtk3-macros"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cfd6557b1018b773e43c8de9d0d13581d6b36190d0501916cbec4731db5ccff"
+dependencies = [
+ "anyhow",
+ "proc-macro-crate",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "html5ever"
+version = "0.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "http"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa 1.0.5",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-range"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "hyper"
+version = "0.14.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa 1.0.5",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "ico"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd"
+dependencies = [
+ "byteorder",
+ "png",
+]
+
+[[package]]
+name = "ico"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae"
+dependencies = [
+ "byteorder",
+ "png",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
+dependencies = [
+ "globset",
+ "lazy_static",
+ "log",
+ "memchr",
+ "regex",
+ "same-file",
+ "thread_local",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "image"
+version = "0.24.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "num-rational",
+ "num-traits",
+ "png",
+ "tiff",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "infer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f178e61cdbfe084aa75a2f4f7a25a5bb09701a47ae1753608f194b15783c937a"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "infer"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
+
+[[package]]
+name = "is-docker"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "is-wsl"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
+dependencies = [
+ "is-docker",
+ "once_cell",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "itoa"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
+
+[[package]]
+name = "javascriptcore-rs"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "110b9902c80c12bf113c432d0b71c7a94490b294a8234f326fd0abca2fac0b00"
+dependencies = [
+ "bitflags",
+ "glib",
+ "javascriptcore-rs-sys",
+]
+
+[[package]]
+name = "javascriptcore-rs-sys"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98a216519a52cd941a733a0ad3f1023cfdb1cd47f3955e8e863ed56f558f916c"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "jni"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
+
+[[package]]
+name = "js-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-patch"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e712e62827c382a77b87f590532febb1f8b2fdbc3eefa1ee37fe7281687075ef"
+dependencies = [
+ "serde",
+ "serde_json",
+ "thiserror",
+ "treediff",
+]
+
+[[package]]
+name = "keyboard-types"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68"
+dependencies = [
+ "bitflags",
+ "serde",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "kuchiki"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
+dependencies = [
+ "cssparser",
+ "html5ever",
+ "matches",
+ "selectors",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libappindicator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89e1edfdc9b0853358306c6dfb4b77c79c779174256fe93d80c0b5ebca451a2f"
+dependencies = [
+ "glib",
+ "gtk",
+ "gtk-sys",
+ "libappindicator-sys",
+ "log",
+]
+
+[[package]]
+name = "libappindicator-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08fcb2bea89cee9613982501ec83eaa2d09256b24540ae463c52a28906163918"
+dependencies = [
+ "gtk-sys",
+ "libloading",
+ "once_cell",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "line-wrap"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
+dependencies = [
+ "safemem",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+ "value-bag",
+]
+
+[[package]]
+name = "loom"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
+dependencies = [
+ "cfg-if",
+ "generator",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+
+[[package]]
+name = "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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "markup5ever"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
+dependencies = [
+ "log",
+ "phf 0.8.0",
+ "phf_codegen",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
+dependencies = [
+ "libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "ndk"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4"
+dependencies = [
+ "bitflags",
+ "jni-sys",
+ "ndk-sys",
+ "num_enum",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+
+[[package]]
+name = "nix"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "libc",
+ "memoffset 0.6.5",
+]
+
+[[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 = "nom8"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
+dependencies = [
+ "memchr",
+]
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi 0.2.6",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+ "objc_exception",
+]
+
+[[package]]
+name = "objc-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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "open"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16814a067484415fda653868c9be0ac5f2abd2ef5d951082a5f2fe1b3662944"
+dependencies = [
+ "is-wsl",
+ "pathdiff",
+]
+
+[[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "os_info"
+version = "3.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c424bc68d15e0778838ac013b5b3449544d8133633d8016319e7e05a820b8c0"
+dependencies = [
+ "log",
+ "serde",
+ "winapi",
+]
+
+[[package]]
+name = "os_pipe"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a53dbb20faf34b16087a931834cba2d7a73cc74af2b7ef345a4c8324e2409a12"
+dependencies = [
+ "libc",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "pango"
+version = "0.16.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94"
+dependencies = [
+ "bitflags",
+ "gio",
+ "glib",
+ "libc",
+ "once_cell",
+ "pango-sys",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "parking"
+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.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
+[[package]]
+name = "pest"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ab62d2fa33726dbe6321cc97ef96d8cde531e3eeaf858a058de53a8a6d40d8f"
+dependencies = [
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_macros 0.8.0",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_macros 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+dependencies = [
+ "phf_generator 0.8.0",
+ "phf_shared 0.8.0",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+dependencies = [
+ "phf_shared 0.8.0",
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared 0.10.0",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
+dependencies = [
+ "phf_generator 0.8.0",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
+name = "plist"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5329b8f106a176ab0dce4aae5da86bfcb139bb74fb00882859e03745011f3635"
+dependencies = [
+ "base64 0.13.1",
+ "indexmap",
+ "line-wrap",
+ "quick-xml 0.26.0",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "png"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
+dependencies = [
+ "once_cell",
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.20+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
+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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.8",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f851a03551ceefd30132e447f07f96cb7011d6b658374f3aed847333adb5559"
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom 0.2.8",
+ "redox_syscall",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.11.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
+dependencies = [
+ "base64 0.21.0",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "mime_guess",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-util",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+ "winreg",
+]
+
+[[package]]
+name = "rfd"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2583255eadc4e0d816cb7648371cc91e49cbac85b0748b6ab417093bff4040"
+dependencies = [
+ "block",
+ "dispatch",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "js-sys",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "raw-window-handle",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows 0.44.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",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver 1.0.16",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
+
+[[package]]
+name = "ryu"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "selectors"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe"
+dependencies = [
+ "bitflags",
+ "cssparser",
+ "derive_more",
+ "fxhash",
+ "log",
+ "matches",
+ "phf 0.8.0",
+ "phf_codegen",
+ "precomputed-hash",
+ "servo_arc",
+ "smallvec",
+ "thin-slice",
+]
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a"
+dependencies = [
+ "itoa 1.0.5",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa 1.0.5",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
+dependencies = [
+ "serde",
+ "serde_with_macros",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serialize-to-javascript"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb"
+dependencies = [
+ "serde",
+ "serde_json",
+ "serialize-to-javascript-impl",
+]
+
+[[package]]
+name = "serialize-to-javascript-impl"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "servo_arc"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432"
+dependencies = [
+ "nodrop",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shared_child"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "slab"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "soup3"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82bc46048125fefd69d30b32b9d263d6556c9ffe82a7a7df181a86d912da5616"
+dependencies = [
+ "bitflags",
+ "futures-channel",
+ "gio",
+ "glib",
+ "libc",
+ "once_cell",
+ "soup3-sys",
+]
+
+[[package]]
+name = "soup3-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "014bbeb1c4cdb30739dc181e8d98b7908f124d9555843afa89b5570aaf4ec62b"
+dependencies = [
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "state"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b"
+dependencies = [
+ "loom",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "str-buf"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
+
+[[package]]
+name = "string_cache"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08"
+dependencies = [
+ "new_debug_unreachable",
+ "once_cell",
+ "parking_lot",
+ "phf_shared 0.10.0",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "strum"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb"
+dependencies = [
+ "heck 0.3.3",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "swift-rs"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fa67d647176dfa7bdc5775430a1cb339e0ea48fe24707424023a4b17eb9688e"
+dependencies = [
+ "base64 0.21.0",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff"
+dependencies = [
+ "cfg-expr",
+ "heck 0.4.1",
+ "pkg-config",
+ "toml",
+ "version-compare",
+]
+
+[[package]]
+name = "tao"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389820c5cd5279ffdde7729baa9cf4db20e9936e11b1e171a20fb74babe3a6d3"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "cc",
+ "cocoa",
+ "core-foundation",
+ "core-graphics",
+ "crossbeam-channel",
+ "dirs-next",
+ "dispatch",
+ "gdk",
+ "gdk-pixbuf",
+ "gdk-sys",
+ "gdkwayland-sys",
+ "gdkx11-sys",
+ "gio",
+ "glib",
+ "glib-sys",
+ "gtk",
+ "image",
+ "instant",
+ "jni",
+ "lazy_static",
+ "libappindicator",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "objc",
+ "once_cell",
+ "parking_lot",
+ "png",
+ "raw-window-handle",
+ "scopeguard",
+ "serde",
+ "tao-macros",
+ "unicode-segmentation",
+ "uuid",
+ "windows 0.44.0",
+ "windows-implement",
+ "x11-dl",
+]
+
+[[package]]
+name = "tao-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "tauri"
+version = "2.0.0-alpha.8"
+source = "git+https://github.com/tauri-apps/tauri?branch=next#bb2a8ccf1356e59b98947d827d61e4e99533f2bc"
+dependencies = [
+ "anyhow",
+ "base64 0.21.0",
+ "bytes",
+ "cocoa",
+ "dirs-next",
+ "embed_plist",
+ "encoding_rs",
+ "flate2",
+ "futures-util",
+ "glib",
+ "glob",
+ "gtk",
+ "heck 0.4.1",
+ "http",
+ "ico 0.2.0",
+ "ignore",
+ "infer 0.9.0",
+ "jni",
+ "libc",
+ "log",
+ "minisign-verify",
+ "objc",
+ "once_cell",
+ "os_info",
+ "percent-encoding",
+ "png",
+ "rand 0.8.5",
+ "raw-window-handle",
+ "reqwest",
+ "semver 1.0.16",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "serialize-to-javascript",
+ "state",
+ "swift-rs",
+ "tar",
+ "tauri-build",
+ "tauri-macros",
+ "tauri-runtime",
+ "tauri-runtime-wry",
+ "tauri-utils",
+ "tempfile",
+ "thiserror",
+ "time",
+ "tokio",
+ "url",
+ "uuid",
+ "webkit2gtk",
+ "webview2-com",
+ "windows 0.44.0",
+ "zip",
+]
+
+[[package]]
+name = "tauri-build"
+version = "2.0.0-alpha.4"
+source = "git+https://github.com/tauri-apps/tauri?branch=next#bb2a8ccf1356e59b98947d827d61e4e99533f2bc"
+dependencies = [
+ "anyhow",
+ "cargo_toml",
+ "filetime",
+ "heck 0.4.1",
+ "json-patch",
+ "quote",
+ "semver 1.0.16",
+ "serde",
+ "serde_json",
+ "swift-rs",
+ "tauri-codegen",
+ "tauri-utils",
+ "tauri-winres",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-codegen"
+version = "2.0.0-alpha.4"
+source = "git+https://github.com/tauri-apps/tauri?branch=next#bb2a8ccf1356e59b98947d827d61e4e99533f2bc"
+dependencies = [
+ "base64 0.21.0",
+ "brotli",
+ "ico 0.3.0",
+ "json-patch",
+ "plist",
+ "png",
+ "proc-macro2",
+ "quote",
+ "semver 1.0.16",
+ "serde",
+ "serde_json",
+ "sha2",
+ "tauri-utils",
+ "thiserror",
+ "time",
+ "url",
+ "uuid",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-macros"
+version = "2.0.0-alpha.4"
+source = "git+https://github.com/tauri-apps/tauri?branch=next#bb2a8ccf1356e59b98947d827d61e4e99533f2bc"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "tauri-codegen",
+ "tauri-utils",
+]
+
+[[package]]
+name = "tauri-plugin-cli"
+version = "0.0.0"
+dependencies = [
+ "clap",
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-clipboard"
+version = "0.0.0"
+dependencies = [
+ "arboard",
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-dialog"
+version = "0.0.0"
+dependencies = [
+ "glib",
+ "log",
+ "raw-window-handle",
+ "rfd",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-fs"
+version = "0.0.0"
+dependencies = [
+ "anyhow",
+ "serde",
+ "tauri",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-global-shortcut"
+version = "0.0.0"
+dependencies = [
+ "global-hotkey",
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-http"
+version = "0.0.0"
+dependencies = [
+ "bytes",
+ "glob",
+ "http",
+ "rand 0.8.5",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "tauri",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-log"
+version = "0.0.0"
+dependencies = [
+ "android_logger",
+ "byte-unit",
+ "cocoa",
+ "fern",
+ "log",
+ "objc",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "swift-rs",
+ "tauri",
+ "tauri-build",
+ "time",
+]
+
+[[package]]
+name = "tauri-plugin-notification"
+version = "0.1.0"
+dependencies = [
+ "log",
+ "notify-rust",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "thiserror",
+ "win7-notifications",
+]
+
+[[package]]
+name = "tauri-plugin-shell"
+version = "0.0.0"
+dependencies = [
+ "encoding_rs",
+ "log",
+ "open",
+ "os_pipe",
+ "regex",
+ "serde",
+ "serde_json",
+ "shared_child",
+ "tauri",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-runtime"
+version = "0.13.0-alpha.4"
+source = "git+https://github.com/tauri-apps/tauri?branch=next#bb2a8ccf1356e59b98947d827d61e4e99533f2bc"
+dependencies = [
+ "gtk",
+ "http",
+ "http-range",
+ "jni",
+ "rand 0.8.5",
+ "raw-window-handle",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "thiserror",
+ "url",
+ "uuid",
+ "webview2-com",
+ "windows 0.44.0",
+]
+
+[[package]]
+name = "tauri-runtime-wry"
+version = "0.13.0-alpha.4"
+source = "git+https://github.com/tauri-apps/tauri?branch=next#bb2a8ccf1356e59b98947d827d61e4e99533f2bc"
+dependencies = [
+ "cocoa",
+ "gtk",
+ "jni",
+ "percent-encoding",
+ "rand 0.8.5",
+ "raw-window-handle",
+ "tauri-runtime",
+ "tauri-utils",
+ "uuid",
+ "webkit2gtk",
+ "webview2-com",
+ "windows 0.44.0",
+ "wry",
+]
+
+[[package]]
+name = "tauri-utils"
+version = "2.0.0-alpha.4"
+source = "git+https://github.com/tauri-apps/tauri?branch=next#bb2a8ccf1356e59b98947d827d61e4e99533f2bc"
+dependencies = [
+ "aes-gcm",
+ "brotli",
+ "ctor",
+ "getrandom 0.2.8",
+ "glob",
+ "heck 0.4.1",
+ "html5ever",
+ "infer 0.12.0",
+ "json-patch",
+ "kuchiki",
+ "memchr",
+ "phf 0.10.1",
+ "proc-macro2",
+ "quote",
+ "semver 1.0.16",
+ "serde",
+ "serde_json",
+ "serde_with",
+ "serialize-to-javascript",
+ "thiserror",
+ "url",
+ "walkdir",
+ "windows 0.44.0",
+]
+
+[[package]]
+name = "tauri-winres"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b7a78dc04f75fb5ab815e66ac561c81e92a968a40f29e7c21afd152d694fad8"
+dependencies = [
+ "toml",
+ "version_check",
+]
+
+[[package]]
+name = "tauri-winrt-notification"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b"
+dependencies = [
+ "quick-xml 0.23.1",
+ "strum",
+ "windows 0.39.0",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "tendril"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
+dependencies = [
+ "futf",
+ "mac",
+ "utf-8",
+]
+
+[[package]]
+name = "thin-slice"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
+
+[[package]]
+name = "thiserror"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tiff"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471"
+dependencies = [
+ "flate2",
+ "jpeg-decoder",
+ "weezl",
+]
+
+[[package]]
+name = "time"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
+dependencies = [
+ "itoa 1.0.5",
+ "libc",
+ "num_threads",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
+name = "tiny_http"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0d6ef4e10d23c1efb862eecad25c5054429a71958b4eeef85eb5e7170b477ca"
+dependencies = [
+ "ascii",
+ "chunked_transfer",
+ "log",
+ "time",
+ "url",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
+
+[[package]]
+name = "toml_edit"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
+dependencies = [
+ "indexmap",
+ "nom8",
+ "toml_datetime",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "treediff"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303"
+dependencies = [
+ "serde_json",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+
+[[package]]
+name = "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.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "url"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "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.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
+dependencies = [
+ "getrandom 0.2.8",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "value-bag"
+version = "1.0.0-alpha.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55"
+dependencies = [
+ "ctor",
+ "version_check",
+]
+
+[[package]]
+name = "version-compare"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+
+[[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.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webkit2gtk"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8eea819afe15eb8dcdff4f19d8bfda540bae84d874c10e6f4b8faf2d6704bd1"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "gdk",
+ "gdk-sys",
+ "gio",
+ "gio-sys",
+ "glib",
+ "glib-sys",
+ "gobject-sys",
+ "gtk",
+ "gtk-sys",
+ "javascriptcore-rs",
+ "libc",
+ "once_cell",
+ "soup3",
+ "webkit2gtk-sys",
+]
+
+[[package]]
+name = "webkit2gtk-sys"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0ac7a95ddd3fdfcaf83d8e513b4b1ad101b95b413b6aa6662ed95f284fc3d5b"
+dependencies = [
+ "bitflags",
+ "cairo-sys-rs",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "javascriptcore-rs-sys",
+ "libc",
+ "pkg-config",
+ "soup3-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "webview2-com"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03411e89ec447e29c08b3c086edeb88c5f8fd782cbdd4d6d316bea439be7a244"
+dependencies = [
+ "webview2-com-macros",
+ "webview2-com-sys",
+ "windows 0.44.0",
+ "windows-implement",
+]
+
+[[package]]
+name = "webview2-com-macros"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "webview2-com-sys"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c0f5ce43e9611c5b2983a33156d6abe31abf39185bad84a6766c80ba1dbf1ab"
+dependencies = [
+ "regex",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "windows 0.44.0",
+ "windows-bindgen",
+ "windows-metadata",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-wsapoll"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "window-shadows"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29d30320647cfc3dc45554c8ad825b84831def81f967a2f7589931328ff9b16d"
+dependencies = [
+ "cocoa",
+ "objc",
+ "raw-window-handle",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a"
+dependencies = [
+ "windows_aarch64_msvc 0.39.0",
+ "windows_i686_gnu 0.39.0",
+ "windows_i686_msvc 0.39.0",
+ "windows_x86_64_gnu 0.39.0",
+ "windows_x86_64_msvc 0.39.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-targets 0.42.1",
+]
+
+[[package]]
+name = "windows-bindgen"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222204ecf46521382a4d88b4a1bbefca9f8855697b4ab7d20803901425e061a3"
+dependencies = [
+ "windows-metadata",
+ "windows-tokens",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-metadata"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee78911e3f4ce32c1ad9d3c7b0bd95389662ad8d8f1a3155688fed70bd96e2b6"
+
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+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",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.42.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",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+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",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows-tokens"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4251900975a0d10841c5d4bde79c56681543367ef811f3fabb8d1803b0959b"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "winreg"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "wry"
+version = "0.28.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d15f9f827d537cefe6d047be3930f5d89b238dfb85e08ba6a319153217635aa"
+dependencies = [
+ "base64 0.13.1",
+ "block",
+ "cocoa",
+ "core-graphics",
+ "crossbeam-channel",
+ "dunce",
+ "gdk",
+ "gio",
+ "glib",
+ "gtk",
+ "html5ever",
+ "http",
+ "javascriptcore-rs",
+ "kuchiki",
+ "libc",
+ "log",
+ "objc",
+ "objc_id",
+ "once_cell",
+ "serde",
+ "serde_json",
+ "sha2",
+ "soup3",
+ "tao",
+ "thiserror",
+ "url",
+ "webkit2gtk",
+ "webkit2gtk-sys",
+ "webview2-com",
+ "windows 0.44.0",
+ "windows-implement",
+]
+
+[[package]]
+name = "x11"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11-dl"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
+dependencies = [
+ "libc",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507"
+dependencies = [
+ "gethostname",
+ "nix 0.24.3",
+ "winapi",
+ "winapi-wsapoll",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67"
+dependencies = [
+ "nix 0.24.3",
+]
+
+[[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 0.26.2",
+ "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",
+ "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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef"
+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",
+ "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",
+]
diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml
new file mode 100644
index 00000000..775067cd
--- /dev/null
+++ b/examples/api/src-tauri/Cargo.toml
@@ -0,0 +1,60 @@
+[package]
+name = "api"
+version = "0.1.0"
+description = "An example Tauri Application showcasing the api"
+edition = "2021"
+rust-version = "1.64"
+license = "Apache-2.0 OR MIT"
+
+[lib]
+crate-type = ["staticlib", "cdylib", "rlib"]
+
+[build-dependencies]
+tauri-build = { version = "2.0.0-alpha.4", features = ["codegen", "isolation"] }
+
+[dependencies]
+serde_json = "1.0"
+serde = { version = "1.0", features = [ "derive" ] }
+tiny_http = "0.11"
+log = "0.4"
+tauri-plugin-log = { path = "../../../plugins/log" }
+tauri-plugin-fs = { path = "../../../plugins/fs" }
+tauri-plugin-clipboard = { path = "../../../plugins/clipboard" }
+tauri-plugin-dialog = { path = "../../../plugins/dialog" }
+tauri-plugin-http = { path = "../../../plugins/http", features = [ "http-multipart" ] }
+tauri-plugin-notification = { path = "../../../plugins/notification", features = [ "windows7-compat" ] }
+tauri-plugin-shell = { path = "../../../plugins/shell" }
+
+[patch.crates-io]
+tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next" }
+tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next" }
+
+[dependencies.tauri]
+version = "2.0.0-alpha.8"
+features = [
+ "api-all",
+ "icon-ico",
+ "icon-png",
+ "isolation",
+ "macos-private-api",
+ "system-tray",
+ "updater"
+]
+
+[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
+tauri-plugin-cli = { path = "../../../plugins/cli" }
+tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut" }
+
+[target."cfg(target_os = \"windows\")".dependencies]
+window-shadows = "0.2"
+
+[features]
+custom-protocol = [ "tauri/custom-protocol" ]
+
+# default to small, optimized release binaries
+[profile.release]
+panic = "abort"
+codegen-units = 1
+lto = true
+incremental = false
+opt-level = "s"
diff --git a/examples/api/src-tauri/Cross.toml b/examples/api/src-tauri/Cross.toml
new file mode 100644
index 00000000..23ac27b6
--- /dev/null
+++ b/examples/api/src-tauri/Cross.toml
@@ -0,0 +1,11 @@
+[build.env]
+# must set ICONS_VOLUME, DIST_VOLUME and ISOLATION_VOLUME environment variables
+# ICONS_VOLUME: absolute path to the icons folder
+# DIST_VOLUME: absolute path to the dist folder
+# ISOLATION_VOLUME: absolute path to the isolation dist folder
+# this can be done running `$ . .setup-cross.sh` in the examples/api folder
+volumes = ["ICONS_VOLUME", "DIST_VOLUME", "ISOLATION_VOLUME"]
+
+[target.aarch64-unknown-linux-gnu]
+image = "aarch64-unknown-linux-gnu:latest"
+#image = "ghcr.io/tauri-apps/tauri/aarch64-unknown-linux-gnu:latest"
diff --git a/examples/api/src-tauri/Info.plist b/examples/api/src-tauri/Info.plist
new file mode 100644
index 00000000..fe253ec7
--- /dev/null
+++ b/examples/api/src-tauri/Info.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ NSCameraUsageDescription
+ Request camera access for WebRTC
+ NSMicrophoneUsageDescription
+ Request microphone access for WebRTC
+
+
diff --git a/examples/api/src-tauri/build.rs b/examples/api/src-tauri/build.rs
new file mode 100644
index 00000000..2154ff35
--- /dev/null
+++ b/examples/api/src-tauri/build.rs
@@ -0,0 +1,12 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+fn main() {
+ let mut codegen = tauri_build::CodegenContext::new();
+ if !cfg!(feature = "custom-protocol") {
+ codegen = codegen.dev();
+ }
+ codegen.build();
+ tauri_build::build();
+}
diff --git a/examples/api/src-tauri/gen/android/api/.editorconfig b/examples/api/src-tauri/gen/android/api/.editorconfig
new file mode 100644
index 00000000..ebe51d3b
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/.editorconfig
@@ -0,0 +1,12 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = false
+insert_final_newline = false
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/.gitignore b/examples/api/src-tauri/gen/android/api/.gitignore
new file mode 100644
index 00000000..6bb2f5ee
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/.gitignore
@@ -0,0 +1,18 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+/.tauri
+/tauri.settings.gradle
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/app/.gitignore b/examples/api/src-tauri/gen/android/api/app/.gitignore
new file mode 100644
index 00000000..3b9e0a22
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/.gitignore
@@ -0,0 +1,4 @@
+/src/main/java/com/tauri/api/generated
+/src/main/jniLibs/**/*.so
+/tauri.build.gradle.kts
+/proguard-tauri.pro
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/app/build.gradle.kts b/examples/api/src-tauri/gen/android/api/app/build.gradle.kts
new file mode 100644
index 00000000..7a18b94b
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/build.gradle.kts
@@ -0,0 +1,113 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+ id("rustPlugin")
+}
+
+android {
+ compileSdk = 33
+ defaultConfig {
+ manifestPlaceholders["usesCleartextTraffic"] = "false"
+ applicationId = "com.tauri.api"
+ minSdk = 24
+ targetSdk = 33
+ versionCode = 1
+ versionName = "1.0"
+ }
+ sourceSets.getByName("main") {
+ // Vulkan validation layers
+ val ndkHome = System.getenv("NDK_HOME")
+ jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
+ }
+ buildTypes {
+ getByName("debug") {
+ manifestPlaceholders["usesCleartextTraffic"] = "true"
+ isDebuggable = true
+ isJniDebuggable = true
+ isMinifyEnabled = false
+ packagingOptions { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
+ jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
+ jniLibs.keepDebugSymbols.add("*/x86/*.so")
+ jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
+ }
+ }
+ getByName("release") {
+ isMinifyEnabled = true
+ val proguards = fileTree(".") {
+ include("*.pro")
+ }
+ proguardFiles(*proguards.toList().toTypedArray())
+ }
+ }
+ flavorDimensions.add("abi")
+ productFlavors {
+ create("universal") {
+ dimension = "abi"
+ ndk {
+ abiFilters += (findProperty("abiList") as? String)?.split(",") ?: listOf( "arm64-v8a", "armeabi-v7a", "x86", "x86_64",
+ )
+ }
+ }
+ create("arm64") {
+ dimension = "abi"
+ ndk {
+ abiFilters += listOf("arm64-v8a")
+ }
+ }
+
+ create("arm") {
+ dimension = "abi"
+ ndk {
+ abiFilters += listOf("armeabi-v7a")
+ }
+ }
+
+ create("x86") {
+ dimension = "abi"
+ ndk {
+ abiFilters += listOf("x86")
+ }
+ }
+
+ create("x86_64") {
+ dimension = "abi"
+ ndk {
+ abiFilters += listOf("x86_64")
+ }
+ }
+ }
+
+ assetPacks += mutableSetOf()
+ namespace = "com.tauri.api"
+}
+
+rust {
+ rootDirRel = "../../../../"
+ targets = (findProperty("targetList") as? String)?.split(",") ?: listOf("aarch64", "armv7", "i686", "x86_64")
+ arches = (findProperty("archList") as? String)?.split(",") ?: listOf("arm64", "arm", "x86", "x86_64")
+}
+
+dependencies {
+ implementation("androidx.webkit:webkit:1.5.0")
+ implementation("androidx.appcompat:appcompat:1.5.1")
+ implementation("com.google.android.material:material:1.7.0")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.4")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
+ implementation(project(":tauri-android"))
+}
+
+apply(from = "tauri.build.gradle.kts")
+
+afterEvaluate {
+ android.applicationVariants.all {
+ tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"])
+ tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"])
+ if (findProperty("targetList") == null) {
+ productFlavors.filter{ it.name != "universal" }.forEach { _ ->
+ val archAndBuildType = name.capitalize()
+ tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
+ }
+ }
+ }
+}
diff --git a/examples/api/src-tauri/gen/android/api/app/proguard-rules.pro b/examples/api/src-tauri/gen/android/api/app/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/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/gen/android/api/app/src/main/AndroidManifest.xml b/examples/api/src-tauri/gen/android/api/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..13dcbd14
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/java/com/tauri/api/MainActivity.kt b/examples/api/src-tauri/gen/android/api/app/src/main/java/com/tauri/api/MainActivity.kt
new file mode 100644
index 00000000..46c4bc6e
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/java/com/tauri/api/MainActivity.kt
@@ -0,0 +1,7 @@
+package com.tauri.api
+
+import app.tauri.plugin.PluginManager
+
+class MainActivity : TauriActivity() {
+ var pluginManager: PluginManager = PluginManager(this)
+}
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/examples/api/src-tauri/gen/android/api/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..2b068d11
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/drawable/ic_launcher_background.xml b/examples/api/src-tauri/gen/android/api/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..07d5da9c
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/layout/activity_main.xml b/examples/api/src-tauri/gen/android/api/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..4fc24441
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..28f1aa11
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..85d0c88a
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..28f1aa11
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..73e48dbf
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..13dd2147
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..73e48dbf
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..1d98044f
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..a888b336
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..1d98044f
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..08183246
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..a2a838e7
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..08183246
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..b18bceb6
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..3f8a57f3
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..b18bceb6
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/values-night/themes.xml b/examples/api/src-tauri/gen/android/api/app/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..dc82b8d8
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/values/colors.xml b/examples/api/src-tauri/gen/android/api/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..f8c6127d
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/values/strings.xml b/examples/api/src-tauri/gen/android/api/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..6f4305e0
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+ Tauri API
+ Tauri API
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/values/themes.xml b/examples/api/src-tauri/gen/android/api/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..0f71fd44
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/examples/api/src-tauri/gen/android/api/app/src/main/res/xml/file_paths.xml b/examples/api/src-tauri/gen/android/api/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 00000000..782d63b9
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/examples/api/src-tauri/gen/android/api/build.gradle.kts b/examples/api/src-tauri/gen/android/api/build.gradle.kts
new file mode 100644
index 00000000..8c6fe584
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/build.gradle.kts
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath("com.android.tools.build:gradle:7.3.1")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+tasks.register("clean").configure {
+ delete("build")
+}
+
diff --git a/examples/api/src-tauri/gen/android/api/buildSrc/build.gradle.kts b/examples/api/src-tauri/gen/android/api/buildSrc/build.gradle.kts
new file mode 100644
index 00000000..73058dd7
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/buildSrc/build.gradle.kts
@@ -0,0 +1,23 @@
+plugins {
+ `kotlin-dsl`
+}
+
+gradlePlugin {
+ plugins {
+ create("pluginsForCoolKids") {
+ id = "rustPlugin"
+ implementationClass = "com.tauri.RustPlugin"
+ }
+ }
+}
+
+repositories {
+ google()
+ mavenCentral()
+}
+
+dependencies {
+ compileOnly(gradleApi())
+ implementation("com.android.tools.build:gradle:7.3.1")
+}
+
diff --git a/examples/api/src-tauri/gen/android/api/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt b/examples/api/src-tauri/gen/android/api/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt
new file mode 100644
index 00000000..dfc8507e
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt
@@ -0,0 +1,58 @@
+package com.tauri
+
+import java.io.File
+import org.apache.tools.ant.taskdefs.condition.Os
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+
+open class BuildTask : DefaultTask() {
+ @InputDirectory
+ @PathSensitive(PathSensitivity.RELATIVE)
+ var rootDirRel: File? = null
+ @Input
+ var target: String? = null
+ @Input
+ var release: Boolean? = null
+
+ @TaskAction
+ fun build() {
+ val executable = """yarn""";
+ try {
+ runTauriCli(executable)
+ } catch (e: Exception) {
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+ runTauriCli("$executable.cmd")
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ fun runTauriCli(executable: String) {
+ val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null")
+ val target = target ?: throw GradleException("target cannot be null")
+ val release = release ?: throw GradleException("release cannot be null")
+ val args = listOf("tauri", "android", "android-studio-script");
+
+ project.exec {
+ workingDir(File(project.projectDir, rootDirRel.path))
+ executable(executable)
+ args(args)
+ if (project.logger.isEnabled(LogLevel.DEBUG)) {
+ args("-vv")
+ } else if (project.logger.isEnabled(LogLevel.INFO)) {
+ args("-v")
+ }
+ if (release) {
+ args("--release")
+ }
+ args(listOf("--target", target))
+ }.assertNormalExitValue()
+ }
+}
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/buildSrc/src/main/java/com/tauri/api/kotlin/RustPlugin.kt b/examples/api/src-tauri/gen/android/api/buildSrc/src/main/java/com/tauri/api/kotlin/RustPlugin.kt
new file mode 100644
index 00000000..064fc632
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/buildSrc/src/main/java/com/tauri/api/kotlin/RustPlugin.kt
@@ -0,0 +1,59 @@
+package com.tauri
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import java.io.File
+import java.util.*
+
+const val TASK_GROUP = "rust"
+
+open class Config {
+ var rootDirRel: String? = null
+ var targets: List? = null
+ var arches: List? = null
+}
+
+open class RustPlugin : Plugin {
+ private lateinit var config: Config
+
+ override fun apply(project: Project) {
+ config = project.extensions.create("rust", Config::class.java)
+ project.afterEvaluate {
+ if (config.targets == null) {
+ throw GradleException("targets cannot be null")
+ }
+ if (config.arches == null) {
+ throw GradleException("arches cannot be null")
+ }
+ for (profile in listOf("debug", "release")) {
+ val profileCapitalized = profile.capitalize(Locale.ROOT)
+ val buildTask = project.tasks.maybeCreate(
+ "rustBuild$profileCapitalized",
+ DefaultTask::class.java
+ ).apply {
+ group = TASK_GROUP
+ description = "Build dynamic library in $profile mode for all targets"
+ }
+ for (targetPair in config.targets!!.withIndex()) {
+ val targetName = targetPair.value
+ val targetArch = config.arches!![targetPair.index]
+ val targetArchCapitalized = targetArch.capitalize(Locale.ROOT)
+ val targetBuildTask = project.tasks.maybeCreate(
+ "rustBuild$targetArchCapitalized$profileCapitalized",
+ BuildTask::class.java
+ ).apply {
+ group = TASK_GROUP
+ description = "Build dynamic library in $profile mode for $targetArch"
+ rootDirRel = config.rootDirRel?.let { File(it) }
+ target = targetName
+ release = profile == "release"
+ }
+ buildTask.dependsOn(targetBuildTask)
+ project.tasks.findByName("preBuild")?.mustRunAfter(targetBuildTask)
+ }
+ }
+ }
+ }
+}
diff --git a/examples/api/src-tauri/gen/android/api/gradle.properties b/examples/api/src-tauri/gen/android/api/gradle.properties
new file mode 100644
index 00000000..cd0519bb
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/api/gradle/wrapper/gradle-wrapper.jar b/examples/api/src-tauri/gen/android/api/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e708b1c0
Binary files /dev/null and b/examples/api/src-tauri/gen/android/api/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/examples/api/src-tauri/gen/android/api/gradle/wrapper/gradle-wrapper.properties b/examples/api/src-tauri/gen/android/api/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..de8c362b
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue May 10 19:22:52 CST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/examples/api/src-tauri/gen/android/api/gradlew b/examples/api/src-tauri/gen/android/api/gradlew
new file mode 100755
index 00000000..4f906e0c
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/examples/api/src-tauri/gen/android/api/gradlew.bat b/examples/api/src-tauri/gen/android/api/gradlew.bat
new file mode 100644
index 00000000..ac1b06f9
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/examples/api/src-tauri/gen/android/api/settings.gradle b/examples/api/src-tauri/gen/android/api/settings.gradle
new file mode 100644
index 00000000..b179eac8
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/api/settings.gradle
@@ -0,0 +1,6 @@
+include ':app'
+
+include ':tauri-android'
+project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
+
+apply from: 'tauri.settings.gradle'
diff --git a/examples/api/src-tauri/icons/128x128.png b/examples/api/src-tauri/icons/128x128.png
new file mode 100644
index 00000000..77e7d233
Binary files /dev/null and b/examples/api/src-tauri/icons/128x128.png differ
diff --git a/examples/api/src-tauri/icons/128x128@2x.png b/examples/api/src-tauri/icons/128x128@2x.png
new file mode 100644
index 00000000..0f7976f1
Binary files /dev/null and b/examples/api/src-tauri/icons/128x128@2x.png differ
diff --git a/examples/api/src-tauri/icons/32x32.png b/examples/api/src-tauri/icons/32x32.png
new file mode 100644
index 00000000..98fda06f
Binary files /dev/null and b/examples/api/src-tauri/icons/32x32.png differ
diff --git a/examples/api/src-tauri/icons/icon.icns b/examples/api/src-tauri/icons/icon.icns
new file mode 100644
index 00000000..5594104c
Binary files /dev/null and b/examples/api/src-tauri/icons/icon.icns differ
diff --git a/examples/api/src-tauri/icons/icon.ico b/examples/api/src-tauri/icons/icon.ico
new file mode 100644
index 00000000..06c23c82
Binary files /dev/null and b/examples/api/src-tauri/icons/icon.ico differ
diff --git a/examples/api/src-tauri/icons/icon.png b/examples/api/src-tauri/icons/icon.png
new file mode 100644
index 00000000..d1756ce4
Binary files /dev/null and b/examples/api/src-tauri/icons/icon.png differ
diff --git a/examples/api/src-tauri/icons/tray_icon.png b/examples/api/src-tauri/icons/tray_icon.png
new file mode 100644
index 00000000..e037c514
Binary files /dev/null and b/examples/api/src-tauri/icons/tray_icon.png differ
diff --git a/examples/api/src-tauri/icons/tray_icon_with_transparency.ico b/examples/api/src-tauri/icons/tray_icon_with_transparency.ico
new file mode 100644
index 00000000..b3990240
Binary files /dev/null and b/examples/api/src-tauri/icons/tray_icon_with_transparency.ico differ
diff --git a/examples/api/src-tauri/icons/tray_icon_with_transparency.png b/examples/api/src-tauri/icons/tray_icon_with_transparency.png
new file mode 100644
index 00000000..99d8ff2b
Binary files /dev/null and b/examples/api/src-tauri/icons/tray_icon_with_transparency.png differ
diff --git a/examples/api/src-tauri/locales/pt-BR.wxl b/examples/api/src-tauri/locales/pt-BR.wxl
new file mode 100644
index 00000000..8f58c074
--- /dev/null
+++ b/examples/api/src-tauri/locales/pt-BR.wxl
@@ -0,0 +1,6 @@
+
+ Executar Tauri API
+ Uma versão mais recente de Tauri API está instalada.
+ Adiciona o caminho do executável de Tauri API para a variável de ambiente PATH. Isso permite Tauri API ser executado pela linha de comando.
+ Instala Tauri API.
+
diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs
new file mode 100644
index 00000000..734552c6
--- /dev/null
+++ b/examples/api/src-tauri/src/cmd.rs
@@ -0,0 +1,24 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::Deserialize;
+use tauri::command;
+
+#[derive(Debug, Deserialize)]
+#[allow(unused)]
+pub struct RequestBody {
+ id: i32,
+ name: String,
+}
+
+#[command]
+pub fn log_operation(event: String, payload: Option) {
+ log::info!("{} {:?}", event, payload);
+}
+
+#[command]
+pub fn perform_request(endpoint: String, body: RequestBody) -> String {
+ println!("{} {:?}", endpoint, body);
+ "message response".into()
+}
diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs
new file mode 100644
index 00000000..ba75c55e
--- /dev/null
+++ b/examples/api/src-tauri/src/lib.rs
@@ -0,0 +1,139 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#![cfg_attr(
+ all(not(debug_assertions), target_os = "windows"),
+ windows_subsystem = "windows"
+)]
+
+mod cmd;
+#[cfg(desktop)]
+mod tray;
+
+use serde::Serialize;
+use tauri::{window::WindowBuilder, App, AppHandle, RunEvent, WindowUrl};
+
+#[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() {
+ #[allow(unused_mut)]
+ let mut builder = tauri::Builder::default()
+ .plugin(
+ tauri_plugin_log::Builder::default()
+ .level(log::LevelFilter::Info)
+ .build(),
+ )
+ .plugin(tauri_plugin_fs::init())
+ .plugin(tauri_plugin_clipboard::init())
+ .plugin(tauri_plugin_dialog::init())
+ .plugin(tauri_plugin_http::init())
+ .plugin(tauri_plugin_notification::init())
+ .plugin(tauri_plugin_shell::init())
+ .setup(move |app| {
+ #[cfg(desktop)]
+ {
+ tray::create_tray(app)?;
+ app.handle().plugin(tauri_plugin_cli::init())?;
+ app.handle()
+ .plugin(tauri_plugin_global_shortcut::Builder::new().build())?;
+ }
+
+ let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default());
+ #[cfg(desktop)]
+ {
+ window_builder = window_builder
+ .user_agent("Tauri API")
+ .title("Tauri API Validation")
+ .inner_size(1000., 800.)
+ .min_inner_size(600., 400.)
+ .content_protected(true);
+ }
+
+ #[cfg(target_os = "windows")]
+ {
+ window_builder = window_builder
+ .transparent(true)
+ .shadow(true)
+ .decorations(false);
+ }
+
+ let window = window_builder.build().unwrap();
+
+ #[cfg(debug_assertions)]
+ window.open_devtools();
+
+ #[cfg(desktop)]
+ std::thread::spawn(|| {
+ let server = match tiny_http::Server::http("localhost:3003") {
+ Ok(s) => s,
+ Err(e) => {
+ eprintln!("{}", e);
+ std::process::exit(1);
+ }
+ };
+ loop {
+ if let Ok(mut request) = server.recv() {
+ let mut body = Vec::new();
+ let _ = request.as_reader().read_to_end(&mut body);
+ let response = tiny_http::Response::new(
+ tiny_http::StatusCode(200),
+ request.headers().to_vec(),
+ std::io::Cursor::new(body),
+ request.body_length(),
+ None,
+ );
+ let _ = request.respond(response);
+ }
+ }
+ });
+
+ Ok(())
+ })
+ .on_page_load(|window, _| {
+ let window_ = window.clone();
+ window.listen("js-event", move |event| {
+ println!("got js-event with message '{:?}'", event.payload());
+ let reply = Reply {
+ data: "something else".to_string(),
+ };
+
+ window_
+ .emit("rust-event", Some(reply))
+ .expect("failed to emit");
+ });
+ });
+
+ #[cfg(target_os = "macos")]
+ {
+ builder = builder.menu(tauri::Menu::os_default("Tauri API Validation"));
+ }
+
+ #[allow(unused_mut)]
+ let mut app = builder
+ .invoke_handler(tauri::generate_handler![
+ cmd::log_operation,
+ cmd::perform_request,
+ ])
+ .build(tauri::tauri_build_context!())
+ .expect("error while building tauri application");
+
+ #[cfg(target_os = "macos")]
+ app.set_activation_policy(tauri::ActivationPolicy::Regular);
+
+ app.run(move |_app_handle, _event| {
+ #[cfg(desktop)]
+ 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
new file mode 100644
index 00000000..2eb029af
--- /dev/null
+++ b/examples/api/src-tauri/src/main.rs
@@ -0,0 +1,11 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+// Prevents additional console window on Windows in release, DO NOT REMOVE!!
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+fn main() {
+ #[cfg(desktop)]
+ api::run();
+}
diff --git a/examples/api/src-tauri/src/tray.rs b/examples/api/src-tauri/src/tray.rs
new file mode 100644
index 00000000..fdd815d0
--- /dev/null
+++ b/examples/api/src-tauri/src/tray.rs
@@ -0,0 +1,143 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::sync::atomic::{AtomicBool, Ordering};
+use tauri::{
+ CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowUrl,
+};
+use tauri_plugin_dialog::DialogExt;
+use tauri_plugin_shell::ShellExt;
+
+pub fn create_tray(app: &tauri::App) -> tauri::Result<()> {
+ let mut tray_menu1 = SystemTrayMenu::new()
+ .add_item(CustomMenuItem::new("toggle", "Toggle"))
+ .add_item(CustomMenuItem::new("new", "New window"))
+ .add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
+ .add_item(CustomMenuItem::new("icon_2", "Tray Icon 2"));
+
+ #[cfg(target_os = "macos")]
+ {
+ tray_menu1 = tray_menu1.add_item(CustomMenuItem::new("set_title", "Set Title"));
+ }
+
+ tray_menu1 = tray_menu1
+ .add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
+ .add_item(CustomMenuItem::new("about", "About"))
+ .add_item(CustomMenuItem::new("exit_app", "Quit"))
+ .add_item(CustomMenuItem::new("destroy", "Destroy"));
+
+ let tray_menu2 = SystemTrayMenu::new()
+ .add_item(CustomMenuItem::new("toggle", "Toggle"))
+ .add_item(CustomMenuItem::new("new", "New window"))
+ .add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
+ .add_item(CustomMenuItem::new("about", "About"))
+ .add_item(CustomMenuItem::new("exit_app", "Quit"))
+ .add_item(CustomMenuItem::new("destroy", "Destroy"));
+ let is_menu1 = AtomicBool::new(true);
+
+ let handle = app.handle();
+ let tray_id = "my-tray".to_string();
+ SystemTray::new()
+ .with_id(&tray_id)
+ .with_menu(tray_menu1.clone())
+ .with_tooltip("Tauri")
+ .on_event(move |event| {
+ let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap();
+ match event {
+ SystemTrayEvent::LeftClick {
+ position: _,
+ size: _,
+ ..
+ } => {
+ let window = handle.get_window("main").unwrap();
+ window.show().unwrap();
+ window.set_focus().unwrap();
+ }
+ SystemTrayEvent::MenuItemClick { id, .. } => {
+ let item_handle = tray_handle.get_item(&id);
+ match id.as_str() {
+ "exit_app" => {
+ // exit the app
+ handle.exit(0);
+ }
+ "destroy" => {
+ tray_handle.destroy().unwrap();
+ }
+ "toggle" => {
+ let window = handle.get_window("main").unwrap();
+ let new_title = if window.is_visible().unwrap() {
+ window.hide().unwrap();
+ "Show"
+ } else {
+ window.show().unwrap();
+ "Hide"
+ };
+ item_handle.set_title(new_title).unwrap();
+ }
+ "new" => {
+ WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into()))
+ .title("Tauri")
+ .build()
+ .unwrap();
+ }
+ "set_title" => {
+ #[cfg(target_os = "macos")]
+ tray_handle.set_title("Tauri").unwrap();
+ }
+ "icon_1" => {
+ #[cfg(target_os = "macos")]
+ tray_handle.set_icon_as_template(true).unwrap();
+
+ tray_handle
+ .set_icon(tauri::Icon::Raw(
+ include_bytes!("../icons/tray_icon_with_transparency.png")
+ .to_vec(),
+ ))
+ .unwrap();
+ }
+ "icon_2" => {
+ #[cfg(target_os = "macos")]
+ tray_handle.set_icon_as_template(true).unwrap();
+
+ tray_handle
+ .set_icon(tauri::Icon::Raw(
+ include_bytes!("../icons/icon.ico").to_vec(),
+ ))
+ .unwrap();
+ }
+ "switch_menu" => {
+ let flag = is_menu1.load(Ordering::Relaxed);
+ let (menu, tooltip) = if flag {
+ (tray_menu2.clone(), "Menu 2")
+ } else {
+ (tray_menu1.clone(), "Tauri")
+ };
+ tray_handle.set_menu(menu).unwrap();
+ tray_handle.set_tooltip(tooltip).unwrap();
+ is_menu1.store(!flag, Ordering::Relaxed);
+ }
+ "about" => {
+ let window = handle.get_window("main").unwrap();
+ window
+ .dialog()
+ .message("Tauri demo app")
+ .title("About app")
+ .parent(&window)
+ .ok_button_label("Homepage")
+ .cancel_button_label("Cancel")
+ .show(move |ok| {
+ if ok {
+ window.shell().open("https://tauri.app/", None).unwrap();
+ }
+ });
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ })
+ .build(app)
+ .map(|_| ())
+}
diff --git a/examples/api/src-tauri/tauri.conf.json b/examples/api/src-tauri/tauri.conf.json
new file mode 100644
index 00000000..741a7e1f
--- /dev/null
+++ b/examples/api/src-tauri/tauri.conf.json
@@ -0,0 +1,147 @@
+{
+ "$schema": "../node_modules/@tauri-apps/cli/schema.json",
+ "build": {
+ "distDir": "../dist",
+ "devPath": "http://localhost:5173",
+ "beforeDevCommand": "yarn dev",
+ "beforeBuildCommand": "yarn build",
+ "withGlobalTauri": true
+ },
+ "package": {
+ "productName": "Tauri API",
+ "version": "2.0.0"
+ },
+ "plugins": {
+ "cli": {
+ "description": "Tauri API example",
+ "args": [
+ {
+ "short": "c",
+ "name": "config",
+ "takesValue": true,
+ "description": "Config path"
+ },
+ {
+ "short": "t",
+ "name": "theme",
+ "takesValue": true,
+ "description": "App theme",
+ "possibleValues": ["light", "dark", "system"]
+ },
+ {
+ "short": "v",
+ "name": "verbose",
+ "description": "Verbosity level"
+ }
+ ],
+ "subcommands": {
+ "update": {
+ "description": "Updates the app",
+ "args": [
+ {
+ "short": "b",
+ "name": "background",
+ "description": "Update in background"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "tauri": {
+ "pattern": {
+ "use": "isolation",
+ "options": {
+ "dir": "../isolation-dist/"
+ }
+ },
+ "macOSPrivateApi": true,
+ "bundle": {
+ "active": true,
+ "identifier": "com.tauri.api",
+ "icon": [
+ "icons/32x32.png",
+ "icons/128x128.png",
+ "icons/128x128@2x.png",
+ "icons/icon.icns",
+ "icons/icon.ico"
+ ],
+ "windows": {
+ "wix": {
+ "language": {
+ "en-US": {},
+ "pt-BR": {
+ "localePath": "locales/pt-BR.wxl"
+ }
+ }
+ }
+ }
+ },
+ "updater": {
+ "active": true,
+ "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"]
+ }
+ },
+ "http": {
+ "scope": ["http://localhost:3003"]
+ }
+ },
+ "windows": [],
+ "security": {
+ "csp": {
+ "default-src": "'self' customprotocol: asset:",
+ "font-src": ["https://fonts.gstatic.com"],
+ "img-src": "'self' asset: https://asset.localhost blob: data:",
+ "style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com"
+ },
+ "freezePrototype": true
+ },
+ "systemTray": {
+ "iconPath": "icons/tray_icon_with_transparency.png",
+ "iconAsTemplate": true,
+ "menuOnLeftClick": false
+ }
+ }
+}
diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte
new file mode 100644
index 00000000..b955ee61
--- /dev/null
+++ b/examples/api/src/App.svelte
@@ -0,0 +1,485 @@
+
+
+
+{#if isWindows}
+
+
Tauri API Validation
+
+
+ {#if isDark}
+
+ {:else}
+
+ {/if}
+
+
+
+
+
+ {#if isWindowMaximized}
+
+ {:else}
+
+ {/if}
+
+
+
+
+
+
+{/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#each $messages as r}
+ {@html r.html}
+ {/each}
+
+
+
+
diff --git a/examples/api/src/app.css b/examples/api/src/app.css
new file mode 100644
index 00000000..77704c36
--- /dev/null
+++ b/examples/api/src/app.css
@@ -0,0 +1,41 @@
+*:not(h1, h2, h3, h4, h5, h6) {
+ margin: 0;
+ padding: 0;
+}
+
+* {
+ box-sizing: border-box;
+ font-family: "Rubik", sans-serif;
+}
+
+::-webkit-scrollbar {
+ width: 0.25rem;
+ height: 3px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ border-radius: 0.25rem;
+}
+
+code {
+ padding: 0.05rem 0.25rem;
+}
+
+code.code-block {
+ padding: 0.5rem;
+}
+
+#sidebar {
+ width: 18.75rem;
+}
+
+@media screen and (max-width: 640px) {
+ #sidebar {
+ --translate-x: -18.75rem;
+ transform: translateX(var(--translate-x));
+ }
+}
diff --git a/examples/api/src/main.js b/examples/api/src/main.js
new file mode 100644
index 00000000..f9785b74
--- /dev/null
+++ b/examples/api/src/main.js
@@ -0,0 +1,13 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import "uno.css";
+import "./app.css";
+import App from "./App.svelte";
+
+const app = new App({
+ target: document.querySelector("#app"),
+});
+
+export default app;
diff --git a/examples/api/src/views/App.svelte b/examples/api/src/views/App.svelte
new file mode 100644
index 00000000..760590f0
--- /dev/null
+++ b/examples/api/src/views/App.svelte
@@ -0,0 +1,33 @@
+
+
+
+ Show
+ Hide
+
diff --git a/examples/api/src/views/Cli.svelte b/examples/api/src/views/Cli.svelte
new file mode 100644
index 00000000..828c054f
--- /dev/null
+++ b/examples/api/src/views/Cli.svelte
@@ -0,0 +1,29 @@
+
+
+
+ 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.
+
+
+
+
+ Get matches
+
diff --git a/examples/api/src/views/Clipboard.svelte b/examples/api/src/views/Clipboard.svelte
new file mode 100644
index 00000000..f796e861
--- /dev/null
+++ b/examples/api/src/views/Clipboard.svelte
@@ -0,0 +1,32 @@
+
+
+
+
+ Write
+ Read
+
diff --git a/examples/api/src/views/Communication.svelte b/examples/api/src/views/Communication.svelte
new file mode 100644
index 00000000..e5b47f01
--- /dev/null
+++ b/examples/api/src/views/Communication.svelte
@@ -0,0 +1,50 @@
+
+
+
+ Call Log API
+
+ Call Request (async) API
+
+
+ Send event to Rust
+
+
diff --git a/examples/api/src/views/Dialog.svelte b/examples/api/src/views/Dialog.svelte
new file mode 100644
index 00000000..f6cabc22
--- /dev/null
+++ b/examples/api/src/views/Dialog.svelte
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+ Multiple
+
+
+
+ Directory
+
+
+Open dialog
+Open save dialog
diff --git a/examples/api/src/views/FileSystem.svelte b/examples/api/src/views/FileSystem.svelte
new file mode 100644
index 00000000..ca0e277e
--- /dev/null
+++ b/examples/api/src/views/FileSystem.svelte
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
diff --git a/examples/api/src/views/Http.svelte b/examples/api/src/views/Http.svelte
new file mode 100644
index 00000000..5b34126e
--- /dev/null
+++ b/examples/api/src/views/Http.svelte
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+HTTP Form
+
+
+
+
+
+
+
+
+ Multipart
+
+
+
+ Post it
+
+
+
diff --git a/examples/api/src/views/Notifications.svelte b/examples/api/src/views/Notifications.svelte
new file mode 100644
index 00000000..624561df
--- /dev/null
+++ b/examples/api/src/views/Notifications.svelte
@@ -0,0 +1,34 @@
+
+
+
+ Send test notification
+
diff --git a/examples/api/src/views/Shell.svelte b/examples/api/src/views/Shell.svelte
new file mode 100644
index 00000000..e7afb9a7
--- /dev/null
+++ b/examples/api/src/views/Shell.svelte
@@ -0,0 +1,100 @@
+
+
+
diff --git a/examples/api/src/views/Shortcuts.svelte b/examples/api/src/views/Shortcuts.svelte
new file mode 100644
index 00000000..cdc2a065
--- /dev/null
+++ b/examples/api/src/views/Shortcuts.svelte
@@ -0,0 +1,73 @@
+
+
+
+
+ Register
+
+
+
+ {#each $shortcuts as savedShortcut}
+
+ {savedShortcut}
+ unregister(savedShortcut)}>Unregister
+
+ {/each}
+ {#if $shortcuts.length > 1}
+
+
Unregister all
+ {/if}
+
diff --git a/examples/api/src/views/Updater.svelte b/examples/api/src/views/Updater.svelte
new file mode 100644
index 00000000..3b219cfe
--- /dev/null
+++ b/examples/api/src/views/Updater.svelte
@@ -0,0 +1,76 @@
+
+
+
+ {#if !isChecking && !newUpdate}
+
Check update
+ {:else if !isInstalling && newUpdate}
+
Install update
+ {:else}
+
+ {/if}
+
+
+
diff --git a/examples/api/src/views/WebRTC.svelte b/examples/api/src/views/WebRTC.svelte
new file mode 100644
index 00000000..e0f9862a
--- /dev/null
+++ b/examples/api/src/views/WebRTC.svelte
@@ -0,0 +1,56 @@
+
+
+
+
Not available for Linux
+
+
+
+
diff --git a/examples/api/src/views/Welcome.svelte b/examples/api/src/views/Welcome.svelte
new file mode 100644
index 00000000..7906d298
--- /dev/null
+++ b/examples/api/src/views/Welcome.svelte
@@ -0,0 +1,47 @@
+
+
+
+ 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.
+
+
+
+
+
+App name: {appName}
+App version: {version}
+Tauri version: {tauriVersion}
+
+
+
+ Close application
+ Relaunch application
+
diff --git a/examples/api/src/views/Window.svelte b/examples/api/src/views/Window.svelte
new file mode 100644
index 00000000..df77d9b0
--- /dev/null
+++ b/examples/api/src/views/Window.svelte
@@ -0,0 +1,459 @@
+
+
+
+
+
+ New window
+
+
+ {#if Object.keys(windowMap).length >= 1}
+
Selected window:
+
+ Choose a window...
+ {#each Object.keys(windowMap) as label}
+ {label}
+ {/each}
+
+ {/if}
+ {#if windowMap[selectedWindow]}
+
+
+ windowMap[selectedWindow].center()}
+ >
+ Center
+
+
+ Minimize
+
+
+ Hide
+
+ Change icon
+ Request attention
+
+
+
+
+ Maximized
+
+
+
+ Resizable
+
+
+
+ Has decorations
+
+
+
+ Always on top
+
+
+
+ Content protected
+
+
+
+ Fullscreen
+
+
+
+
+
+
+
+
+
+
+ Inner Size
+
+
Width: {innerSize.width}
+
Height: {innerSize.height}
+
+
+
+ Outer Size
+
+
Width: {outerSize.width}
+
Height: {outerSize.height}
+
+
+
+
+
+ Inner Logical Size
+
+
Width: {innerSize.toLogical(scaleFactor).width}
+
Height: {innerSize.toLogical(scaleFactor).height}
+
+
+
+ Outer Logical Size
+
+
Width: {outerSize.toLogical(scaleFactor).width}
+
Height: {outerSize.toLogical(scaleFactor).height}
+
+
+
+
+
+ Inner Position
+
+
x: {innerPosition.x}
+
y: {innerPosition.y}
+
+
+
+ Outer Position
+
+
x: {outerPosition.x}
+
y: {outerPosition.y}
+
+
+
+
+
+ Inner Logical Position
+
+
x: {innerPosition.toLogical(scaleFactor).x}
+
y: {innerPosition.toLogical(scaleFactor).y}
+
+
+
+ Outer Logical Position
+
+
x: {outerPosition.toLogical(scaleFactor).x}
+
y: {outerPosition.toLogical(scaleFactor).y}
+
+
+
+
+
Cursor
+
+
+
+ Grab
+
+
+
+ Visible
+
+
+
+ Ignore events
+
+
+
+
+ Icon
+
+ {#each cursorIconOptions as kind}
+ {kind}
+ {/each}
+
+
+
+ X position
+
+
+
+ Y position
+
+
+
+
+
+
+
+ Set title
+
+
+
+ Open URL
+
+
+ {/if}
+
diff --git a/examples/api/unocss.config.js b/examples/api/unocss.config.js
new file mode 100644
index 00000000..7c6dc105
--- /dev/null
+++ b/examples/api/unocss.config.js
@@ -0,0 +1,105 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import {
+ defineConfig,
+ presetIcons,
+ presetUno,
+ extractorSvelte,
+ presetWebFonts,
+} from "unocss";
+
+export default defineConfig({
+ theme: {
+ colors: {
+ primary: "#FFFFFF",
+ primaryLighter: "#e9ecef",
+ darkPrimary: "#1B1B1D",
+ darkPrimaryLighter: "#242526",
+ primaryText: "#1C1E21",
+ darkPrimaryText: "#E3E3E3",
+ secondaryText: "#858A91",
+ darkSecondaryText: "#C2C5CA",
+ accent: "#3578E5",
+ accentDark: "#306cce",
+ accentDarker: "#2d66c3",
+ accentDarkest: "#2554a0",
+ accentLight: "#538ce9",
+ accentLighter: "#72a1ed",
+ accentLightest: "#9abcf2",
+ accentText: "#FFFFFF",
+ darkAccent: "#67d6ed",
+ darkAccentDark: "#49cee9",
+ darkAccentDarker: "#39cae8",
+ darkAccentDarkest: "#19b5d5",
+ darkAccentLight: "#85def1",
+ darkAccentLighter: "#95e2f2",
+ darkAccentLightest: "#c2eff8",
+ darkAccentText: "#1C1E21",
+ code: "#d6d8da",
+ codeDark: "#282a2e",
+ hoverOverlay: "rgba(0,0,0,.05)",
+ hoverOverlayDarker: "rgba(0,0,0,.1)",
+ darkHoverOverlay: "hsla(0,0%,100%,.05)",
+ darkHoverOverlayDarker: "hsla(0,0%,100%,.1)",
+ },
+ },
+ preflights: [
+ {
+ getCSS: ({ theme }) => `
+ ::-webkit-scrollbar-thumb {
+ background-color: ${theme.colors.accent};
+ }
+
+ .dark ::-webkit-scrollbar-thumb {
+ background-color: ${theme.colors.darkAccent};
+ }
+
+ code {
+ font-size: ${theme.fontSize.xs[0]};
+ font-family: ${theme.fontFamily.mono};
+ border-radius: ${theme.borderRadius["DEFAULT"]};
+ background-color: ${theme.colors.code};
+ }
+
+ .code-block {
+ font-family: ${theme.fontFamily.mono};
+ font-size: ${theme.fontSize.sm[0]};
+ }
+
+ .dark code {
+ background-color: ${theme.colors.codeDark};
+ }
+ `,
+ },
+ ],
+ shortcuts: {
+ btn: `select-none outline-none shadow-md p-2 rd-1 text-primaryText border-none font-400 dark:font-600
+ bg-accent hover:bg-accentDarker active:bg-accentDarkest text-accentText
+ dark:bg-darkAccent dark:hover:bg-darkAccentDarker dark:active:bg-darkAccentDarkest dark:text-darkAccentText`,
+ nv: `decoration-none flex items-center relative p-2 rd-1 transition-all-125 ease
+ text-darkSecondaryText
+ hover:text-accent dark:hover:text-darkAccent
+ hover:bg-darkHoverOverlay hover:border-l-4`,
+ nv_selected: `nv bg-darkHoverOverlay text-accent dark:text-darkAccent border-l-4`,
+ note: `decoration-none flex-inline items-center relative p-2 rd-1
+ border-l-4 border-accent dark:border-darkAccent
+ bg-accent/10 dark:bg-darkAccent/10`,
+ "note-red":
+ "note bg-red-700/10 dark:bg-red-700/10 after:bg-red-700 dark:after:bg-red-700",
+ input:
+ "h-10 flex items-center outline-none border-none p-2 rd-1 shadow-md bg-primaryLighter dark:bg-darkPrimaryLighter text-primaryText dark:text-darkPrimaryText",
+ },
+ presets: [
+ presetUno(),
+ presetIcons(),
+ presetWebFonts({
+ fonts: {
+ sans: "Rubik",
+ mono: ["Fira Code", "Fira Mono:400,700"],
+ },
+ }),
+ ],
+ extractors: [extractorSvelte],
+});
diff --git a/examples/api/vite.config.js b/examples/api/vite.config.js
new file mode 100644
index 00000000..df8c64f2
--- /dev/null
+++ b/examples/api/vite.config.js
@@ -0,0 +1,43 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import { defineConfig } from "vite";
+import Unocss from "unocss/vite";
+import { svelte } from "@sveltejs/vite-plugin-svelte";
+import { internalIpV4 } from "internal-ip";
+import process from "process";
+
+// https://vitejs.dev/config/
+export default defineConfig(async () => {
+ const host =
+ process.env.TAURI_PLATFORM === "android" ||
+ process.env.TAURI_PLATFORM === "ios"
+ ? await internalIpV4()
+ : "localhost";
+ return {
+ plugins: [Unocss(), svelte()],
+ build: {
+ rollupOptions: {
+ output: {
+ entryFileNames: `assets/[name].js`,
+ chunkFileNames: `assets/[name].js`,
+ assetFileNames: `assets/[name].[ext]`,
+ },
+ },
+ },
+ server: {
+ host: "0.0.0.0",
+ port: 5173,
+ strictPort: true,
+ hmr: {
+ protocol: "ws",
+ host,
+ port: 5183,
+ },
+ fs: {
+ allow: [".", "../../tooling/api/dist"],
+ },
+ },
+ };
+});
diff --git a/examples/api/yarn-error.log b/examples/api/yarn-error.log
new file mode 100644
index 00000000..59baf8f8
--- /dev/null
+++ b/examples/api/yarn-error.log
@@ -0,0 +1,1094 @@
+Arguments:
+ /home/lucas/.nvm/versions/node/v16.14.0/bin/node /usr/bin/yarn
+
+PATH:
+ /home/lucas/.local/bin:/opt/ums:/home/lucas/NDK/x86/bin:/home/lucas/Android/Sdk/platform-tools:/home/lucas/.yarn/bin:/home/lucas/.cargo/bin:/home/lucas/.nvm/versions/node/v16.14.0/bin:/home/lucas/.config/composer/vendor/bin:/home/lucasfernog/perl5/bin:/usr/bin:/home/lucas/.local/bin:/opt/ums:/home/lucas/NDK/x86/bin:/home/lucas/Android/Sdk/platform-tools:/home/lucas/.yarn/bin:/home/lucas/.cargo/bin:/home/lucas/.nvm/versions/node/v16.14.0/bin:/home/lucas/.config/composer/vendor/bin:/home/lucasfernog/perl5/bin:/usr/condabin:/usr/local/sbin:/usr/local/bin:/usr/bin:/home/lucas/.dotnet/tools:/home/lucas/.local/share/flatpak/exports/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/home/lucas/.rvm/bin:/home/lucas/.rvm/bin
+
+Yarn version:
+ 1.22.19
+
+Node version:
+ 16.14.0
+
+Platform:
+ linux x64
+
+Trace:
+ Error: https://gitpkg.now.sh/tauri-apps/plugins-workspace/plugins/fs?feat/fs-plugin&scripts.build=build: Request failed "500 Internal Server Error"
+ at ResponseError.ExtendableBuiltin (/usr/lib/node_modules/yarn/lib/cli.js:696:66)
+ at new ResponseError (/usr/lib/node_modules/yarn/lib/cli.js:802:124)
+ at Request. (/usr/lib/node_modules/yarn/lib/cli.js:66215:16)
+ at Request.emit (node:events:520:28)
+ at Request.module.exports.Request.onRequestResponse (/usr/lib/node_modules/yarn/lib/cli.js:141767:10)
+ at ClientRequest.emit (node:events:520:28)
+ at HTTPParser.parserOnIncomingClient (node:_http_client:618:27)
+ at HTTPParser.parserOnHeadersComplete (node:_http_common:128:17)
+ at TLSSocket.socketOnData (node:_http_client:482:22)
+ at TLSSocket.emit (node:events:520:28)
+
+npm manifest:
+ {
+ "name": "svelte-app",
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --clearScreen false",
+ "build": "vite build",
+ "serve": "vite preview",
+ "tauri": "node ../../tooling/cli/node/tauri.js"
+ },
+ "dependencies": {
+ "@tauri-apps/api": "../../tooling/api/dist",
+ "@zerodevx/svelte-json-view": "0.2.1",
+ "tauri-plugin-fs-api": "https://gitpkg.now.sh/tauri-apps/plugins-workspace/plugins/fs?feat/fs-plugin&scripts.build=build"
+ },
+ "devDependencies": {
+ "@iconify-json/codicon": "^1.1.10",
+ "@iconify-json/ph": "^1.1.1",
+ "@sveltejs/vite-plugin-svelte": "^1.0.1",
+ "internal-ip": "^7.0.0",
+ "svelte": "^3.49.0",
+ "unocss": "^0.39.3",
+ "vite": "^3.0.9"
+ }
+ }
+
+yarn manifest:
+ No manifest
+
+Lockfile:
+ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+ # yarn lockfile v1
+
+
+ "@antfu/install-pkg@^0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@antfu/install-pkg/-/install-pkg-0.1.0.tgz#8d8c61820cbc32e5c37d82d515485ad3ee9bd052"
+ integrity sha512-VaIJd3d1o7irZfK1U0nvBsHMyjkuyMP3HKYVV53z8DKyulkHKmjhhtccXO51WSPeeSHIeoJEoNOKavYpS7jkZw==
+ dependencies:
+ execa "^5.1.1"
+ find-up "^5.0.0"
+
+ "@antfu/utils@^0.5.0", "@antfu/utils@^0.5.1":
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.5.2.tgz#8c2d931ff927be0ebe740169874a3d4004ab414b"
+ integrity sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==
+
+ "@esbuild/linux-loong64@0.14.54":
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
+ integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
+
+ "@iconify-json/codicon@^1.1.10":
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/@iconify-json/codicon/-/codicon-1.1.10.tgz#22fee909be51afebfbcc6cd57209064b5363f202"
+ integrity sha512-xx3nX5k4UeDQnpX9D1T6L1RCEwyLtqu3Lqk9plYK+SoBSQ/kR73bPy9WbYyDVOw2MDw50JCSpZZYlBC718k7Sg==
+ dependencies:
+ "@iconify/types" "^1.1.0"
+
+ "@iconify-json/ph@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@iconify-json/ph/-/ph-1.1.1.tgz#17b3dee91a47055bac93d65c32b9ee33c654d56f"
+ integrity sha512-sIHTY+c1F8x29BM49IqoccJ3T8mvVXPcrE4WOpJ3GsBaip2YqFJRYU60rw64UL6GEI13vWSD7NsZKq8ytTO87g==
+ dependencies:
+ "@iconify/types" "^1.0.12"
+
+ "@iconify/types@^1.0.12", "@iconify/types@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@iconify/types/-/types-1.1.0.tgz#dc15fc988b1b3fd558dd140a24ede7e0aac11280"
+ integrity sha512-Jh0llaK2LRXQoYsorIH8maClebsnzTcve+7U3rQUSnC11X4jtPnFuyatqFLvMxZ8MLG8dB4zfHsbPfuvxluONw==
+
+ "@iconify/utils@^1.0.33":
+ version "1.0.33"
+ resolved "https://registry.yarnpkg.com/@iconify/utils/-/utils-1.0.33.tgz#9952ecae79e3b1685b83c58159c1d48959f6105a"
+ integrity sha512-vGeAqo7aGPxOQmGdVoXFUOuyN+0V7Lcrx2EvaiRjxUD1x6Om0Tvq2bdm7E24l2Pz++4S0mWMCVFXe/17EtKImQ==
+ dependencies:
+ "@antfu/install-pkg" "^0.1.0"
+ "@antfu/utils" "^0.5.0"
+ "@iconify/types" "^1.1.0"
+ debug "^4.3.4"
+ kolorist "^1.5.1"
+ local-pkg "^0.4.1"
+
+ "@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+ "@nodelib/fs.walk@^1.2.3":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+ "@polka/url@^1.0.0-next.20":
+ version "1.0.0-next.21"
+ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
+ integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
+
+ "@rollup/pluginutils@^4.2.1":
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
+ integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
+ dependencies:
+ estree-walker "^2.0.1"
+ picomatch "^2.2.2"
+
+ "@sveltejs/vite-plugin-svelte@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.1.tgz#7f468f03c933fcdfc60d4773671c73f33b9ef4d6"
+ integrity sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==
+ dependencies:
+ "@rollup/pluginutils" "^4.2.1"
+ debug "^4.3.4"
+ deepmerge "^4.2.2"
+ kleur "^4.1.5"
+ magic-string "^0.26.2"
+ svelte-hmr "^0.14.12"
+
+ "@tauri-apps/api@../../tooling/api/dist":
+ version "2.0.0-alpha.3"
+
+ "@tauri-apps/api@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.2.0.tgz#1f196b3e012971227f41b98214c846430a4eb477"
+ integrity sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==
+
+ "@unocss/cli@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/cli/-/cli-0.39.3.tgz#a7e956a4fffb9586579f09727069f83d34c5e358"
+ integrity sha512-h+qq76CJTkV7GYBSQ3vSJCn/jewFzBVh8owMYH3B1ROe5D1mCev2INYvHlsQsVVoyxnccBeuZ6st6OK56VyDjA==
+ dependencies:
+ "@unocss/config" "0.39.3"
+ "@unocss/core" "0.39.3"
+ "@unocss/preset-uno" "0.39.3"
+ cac "^6.7.12"
+ chokidar "^3.5.3"
+ colorette "^2.0.16"
+ consola "^2.15.3"
+ fast-glob "^3.2.11"
+ pathe "^0.3.0"
+ perfect-debounce "^0.1.3"
+
+ "@unocss/config@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/config/-/config-0.39.3.tgz#0404c29dc76b87833fef331a628652be7cb1f9ee"
+ integrity sha512-qyxjUUdi+D/vS4Snhoj0uW8ErKlfZCKdjJ+ntwnJK3c8dxAp/IuicE+6ukcLfHxT0kAw1xaRlNwamtL3MgcX/A==
+ dependencies:
+ "@unocss/core" "0.39.3"
+ unconfig "^0.3.4"
+
+ "@unocss/core@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/core/-/core-0.39.3.tgz#fec1e71b2f89e14629c47be72f6c894dd88933ac"
+ integrity sha512-8MnXKHNtp6xgsFIaFtWctnbsT60c8JSlxXA7XbGxEztOmSEhpZmLeLGe5AgmEGPH6MssqJtI0DCeTbzbbtOjfw==
+
+ "@unocss/inspector@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/inspector/-/inspector-0.39.3.tgz#0e22826e1c9c017be2d5e0db00728d48f2e39a1d"
+ integrity sha512-j7U04I07sqK63+3cA7oju/hoGOkdN+/hAwGYkCgWGNj+HwxiU7TTEVg0qZ1FAUU/GyyI9G/c4RIpwei9dLVz9w==
+ dependencies:
+ gzip-size "^6.0.0"
+ sirv "^2.0.2"
+
+ "@unocss/preset-attributify@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/preset-attributify/-/preset-attributify-0.39.3.tgz#b2bf4f645edd7f9890e85fcdfd770e19298d611d"
+ integrity sha512-SZWWUfTTKyHHqlF9x6aZ+BFMIiwOsUTP4NXS3/rIroqzfvVDZtGS6/a7RVBl+M74wjqSWB/DDeS9kQiH2L/CIg==
+ dependencies:
+ "@unocss/core" "0.39.3"
+
+ "@unocss/preset-icons@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/preset-icons/-/preset-icons-0.39.3.tgz#d6dcbd09bce41c11370a1d652967584f9eb7043f"
+ integrity sha512-zMTfP3pVaN2WREWY36adsY62gEm51R0CZd7v0gHOlltEG6kT1UCeyIQwOtn48wHRCesy92f70R6RIR3rwSVaCQ==
+ dependencies:
+ "@iconify/utils" "^1.0.33"
+ "@unocss/core" "0.39.3"
+ ohmyfetch "^0.4.18"
+
+ "@unocss/preset-mini@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/preset-mini/-/preset-mini-0.39.3.tgz#3996da482ddc9ba3de5e5656d1808939befd5812"
+ integrity sha512-XCxp3mwWsEpCo0cIJA3tLrWqdAL09gP3wv9iGh4H9o0fIPlYXjVTC1WtUHkv3C09LdZ+MH/9Ja/KqnVf3bNROA==
+ dependencies:
+ "@unocss/core" "0.39.3"
+
+ "@unocss/preset-tagify@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/preset-tagify/-/preset-tagify-0.39.3.tgz#a0268c8aae0e0c5825079a87312c93ac8b05a43c"
+ integrity sha512-OXE47cS/tiL92ZThgLOpbSFy7MPZ4upE4ZX1m9pnCaWzX7LBzp8Gw0DM+dF3IYdIfJpmU4R6b53ME8SchofuHA==
+ dependencies:
+ "@unocss/core" "0.39.3"
+
+ "@unocss/preset-typography@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/preset-typography/-/preset-typography-0.39.3.tgz#fd151885aa9d83a9ea9577aa26737f9f96dcf661"
+ integrity sha512-jTJOA87bEkU0RGMPSFZK3Zobr2fgkqKCYDczTjPbCiZ8UzlMJnWrpsNTN9f4UI0b6Ck8sXtMtW8sRrJsEll9jg==
+ dependencies:
+ "@unocss/core" "0.39.3"
+
+ "@unocss/preset-uno@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/preset-uno/-/preset-uno-0.39.3.tgz#bf8d05ee9e92006a788d56c95a52f8a6676af4e1"
+ integrity sha512-EADVFqx5x4te/teqwjHb025FIy/T0QXafcVDRwUijS6OOqm5rZ7fXd/hu41XYYn3B802r/g4bDC2wO+7foNVwA==
+ dependencies:
+ "@unocss/core" "0.39.3"
+ "@unocss/preset-mini" "0.39.3"
+ "@unocss/preset-wind" "0.39.3"
+
+ "@unocss/preset-web-fonts@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/preset-web-fonts/-/preset-web-fonts-0.39.3.tgz#a7af50bda616d9e980e45157a59079f90d463e01"
+ integrity sha512-b23nmEGHbfvC/PCv0m0BGqFt2zX8J9ekwjfmEL1Bk1C0KL2voYGSdbSm0I8iO6sKb1CLy6qy71N/CuGtIE3FJA==
+ dependencies:
+ "@unocss/core" "0.39.3"
+ ohmyfetch "^0.4.18"
+
+ "@unocss/preset-wind@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/preset-wind/-/preset-wind-0.39.3.tgz#014d5da2cae63489c363ff057a500f26d15455a4"
+ integrity sha512-kjMgPxt4xfmiliodKTbotJDSAqAOCy25f1jdIj9CjjFjwYsUAuiYi8UgPsEi550Bj5BlBEHFn/RhcMGvinzY8A==
+ dependencies:
+ "@unocss/core" "0.39.3"
+ "@unocss/preset-mini" "0.39.3"
+
+ "@unocss/reset@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/reset/-/reset-0.39.3.tgz#71fbbac03c96617e039b46f98e372271413a5d3d"
+ integrity sha512-hW3gZ3lsu6N58XEG7m1dprt15fN0xkYjAo7vSp8eT3/p7h5HE7wNgU2v9ttGBC3B2z4AWHGdspfmaH3sR8lCJw==
+
+ "@unocss/scope@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/scope/-/scope-0.39.3.tgz#8f1b5fd02f6d935bb1b83f4100c06b23dbb38c2f"
+ integrity sha512-ex2QDRgBQ5mTwBcXtCWdTDPl6/HrBv0asDWVXXv7ezjxcByJjMrHj64gMvUbAcGAoX2ic7hIEUT3Ju5i6knKFw==
+
+ "@unocss/transformer-compile-class@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/transformer-compile-class/-/transformer-compile-class-0.39.3.tgz#6e8b7ca6eee0a689588d19a8ba3b5953e03d086b"
+ integrity sha512-OmYP0uk+DGR5kc2T+teL6CLNj/sRxbY3SmlPx2kDbsRLc5gFccQryjj4bBk6QNOKxP5OGJpAqcw1y1JctvRgog==
+ dependencies:
+ "@unocss/core" "0.39.3"
+
+ "@unocss/transformer-directives@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/transformer-directives/-/transformer-directives-0.39.3.tgz#e490a479582a94503fc02f91ce167347e822cff5"
+ integrity sha512-E1wzZaR6rIBQNemgDi9LoljtkYcOSiKGMUTz6kRGoxVBzaYE6Ji/YKbb22lKd6vLOFnRyCxzPHdzY9qvvl5D6w==
+ dependencies:
+ "@unocss/core" "0.39.3"
+ css-tree "^2.1.0"
+
+ "@unocss/transformer-variant-group@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/transformer-variant-group/-/transformer-variant-group-0.39.3.tgz#31730bd414a06a676e753f69839652eaf6c0be9c"
+ integrity sha512-YoYz87qSSEvXXUkgHbO2kz/M03dbZuedjDvvWXsBAvj20MQFpkZpbNHYf2DJ+EkO/WXd+KEF2HBwlgoANcZlaw==
+ dependencies:
+ "@unocss/core" "0.39.3"
+
+ "@unocss/vite@0.39.3":
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/@unocss/vite/-/vite-0.39.3.tgz#a65cfa72732a6f3bc9c18640c74716d2f56c9f6b"
+ integrity sha512-JT21v6ZwLCHPGVfjoWsOdSkMhFNiW2robhQke33WLlRGyT5U4K1SWLxNk+XPDbFdP+WZdcVJi5W5yG8Mm27WBw==
+ dependencies:
+ "@rollup/pluginutils" "^4.2.1"
+ "@unocss/config" "0.39.3"
+ "@unocss/core" "0.39.3"
+ "@unocss/inspector" "0.39.3"
+ "@unocss/scope" "0.39.3"
+ "@unocss/transformer-directives" "0.39.3"
+ magic-string "^0.26.2"
+
+ "@zerodevx/svelte-json-view@0.2.1":
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/@zerodevx/svelte-json-view/-/svelte-json-view-0.2.1.tgz#12d1497f49d120bfb74e098dbe46ce5f16240d1c"
+ integrity sha512-yaLojLYTi08vccUKRg/XSRCCPoyzCZqrG+W8mVhJEGiOfFKAmWqNH6b+/il1gG3V1UaEe7amj2mzmo1mo4q1iA==
+
+ anymatch@~3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
+ integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+ binary-extensions@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+ integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+ braces@^3.0.2, braces@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+ busboy@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
+ integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
+ dependencies:
+ streamsearch "^1.1.0"
+
+ cac@^6.7.12:
+ version "6.7.12"
+ resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.12.tgz#6fb5ea2ff50bd01490dbda497f4ae75a99415193"
+ integrity sha512-rM7E2ygtMkJqD9c7WnFU6fruFcN3xe4FM5yUmgxhZzIKJk4uHl9U/fhwdajGFQbQuv43FAUo1Fe8gX/oIKDeSA==
+
+ chokidar@^3.5.3:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+ colorette@^2.0.16:
+ version "2.0.19"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
+ integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
+
+ consola@^2.15.3:
+ version "2.15.3"
+ resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550"
+ integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==
+
+ cross-spawn@^7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+ css-tree@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.1.0.tgz#170e27ccf94e7c5facb183765c25898be843d1d2"
+ integrity sha512-PcysZRzToBbrpoUrZ9qfblRIRf8zbEAkU0AIpQFtgkFK0vSbzOmBCvdSAx2Zg7Xx5wiYJKUKk0NMP7kxevie/A==
+ dependencies:
+ mdn-data "2.0.27"
+ source-map-js "^1.0.1"
+
+ debug@^4.3.4:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+ deepmerge@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
+ integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+
+ default-gateway@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71"
+ integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==
+ dependencies:
+ execa "^5.0.0"
+
+ defu@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/defu/-/defu-6.0.0.tgz#b397a6709a2f3202747a3d9daf9446e41ad0c5fc"
+ integrity sha512-t2MZGLf1V2rV4VBZbWIaXKdX/mUcYW0n2znQZoADBkGGxYL8EWqCuCZBmJPJ/Yy9fofJkyuuSuo5GSwo0XdEgw==
+
+ destr@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/destr/-/destr-1.1.1.tgz#910457d10a2f2f247add4ca4fdb4a03adcc49079"
+ integrity sha512-QqkneF8LrYmwATMdnuD2MLI3GHQIcBnG6qFC2q9bSH430VTCDAVjcspPmUaKhPGtAtPAftIUFqY1obQYQuwmbg==
+
+ duplexer@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
+ integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
+
+ esbuild-android-64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
+ integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
+
+ esbuild-android-arm64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
+ integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
+
+ esbuild-darwin-64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
+ integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
+
+ esbuild-darwin-arm64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
+ integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
+
+ esbuild-freebsd-64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
+ integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
+
+ esbuild-freebsd-arm64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
+ integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
+
+ esbuild-linux-32@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
+ integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
+
+ esbuild-linux-64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
+ integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
+
+ esbuild-linux-arm64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
+ integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
+
+ esbuild-linux-arm@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
+ integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
+
+ esbuild-linux-mips64le@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
+ integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
+
+ esbuild-linux-ppc64le@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
+ integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
+
+ esbuild-linux-riscv64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
+ integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
+
+ esbuild-linux-s390x@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
+ integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
+
+ esbuild-netbsd-64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
+ integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
+
+ esbuild-openbsd-64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
+ integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
+
+ esbuild-sunos-64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
+ integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
+
+ esbuild-windows-32@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
+ integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
+
+ esbuild-windows-64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
+ integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
+
+ esbuild-windows-arm64@0.14.54:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
+ integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
+
+ esbuild@^0.14.47:
+ version "0.14.54"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
+ integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
+ optionalDependencies:
+ "@esbuild/linux-loong64" "0.14.54"
+ esbuild-android-64 "0.14.54"
+ esbuild-android-arm64 "0.14.54"
+ esbuild-darwin-64 "0.14.54"
+ esbuild-darwin-arm64 "0.14.54"
+ esbuild-freebsd-64 "0.14.54"
+ esbuild-freebsd-arm64 "0.14.54"
+ esbuild-linux-32 "0.14.54"
+ esbuild-linux-64 "0.14.54"
+ esbuild-linux-arm "0.14.54"
+ esbuild-linux-arm64 "0.14.54"
+ esbuild-linux-mips64le "0.14.54"
+ esbuild-linux-ppc64le "0.14.54"
+ esbuild-linux-riscv64 "0.14.54"
+ esbuild-linux-s390x "0.14.54"
+ esbuild-netbsd-64 "0.14.54"
+ esbuild-openbsd-64 "0.14.54"
+ esbuild-sunos-64 "0.14.54"
+ esbuild-windows-32 "0.14.54"
+ esbuild-windows-64 "0.14.54"
+ esbuild-windows-arm64 "0.14.54"
+
+ estree-walker@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+ execa@^5.0.0, execa@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
+ integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^6.0.0"
+ human-signals "^2.1.0"
+ is-stream "^2.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^4.0.1"
+ onetime "^5.1.2"
+ signal-exit "^3.0.3"
+ strip-final-newline "^2.0.0"
+
+ fast-glob@^3.2.11:
+ version "3.2.11"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
+ integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
+ fastq@^1.6.0:
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
+ integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
+ dependencies:
+ reusify "^1.0.4"
+
+ fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+ find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+ fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+ function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+ get-stream@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+ integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
+ glob-parent@^5.1.2, glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+ gzip-size@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
+ integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==
+ dependencies:
+ duplexer "^0.1.2"
+
+ has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+ human-signals@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
+ integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+
+ internal-ip@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-7.0.0.tgz#5b1c6a9d7e188aa73a1b69717daf50c8d8ed774f"
+ integrity sha512-qE4TeD4brqC45Vq/+VASeMiS1KRyfBkR6HT2sh9pZVVCzSjPkaCEfKFU+dL0PRv7NHJtvoKN2r82G6wTfzorkw==
+ dependencies:
+ default-gateway "^6.0.3"
+ ipaddr.js "^2.0.1"
+ is-ip "^3.1.0"
+ p-event "^4.2.0"
+
+ ip-regex@^4.0.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5"
+ integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==
+
+ ipaddr.js@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
+ integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
+
+ is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+ is-core-module@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
+ integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
+ dependencies:
+ has "^1.0.3"
+
+ is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+ is-glob@^4.0.1, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+ is-ip@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-3.1.0.tgz#2ae5ddfafaf05cb8008a62093cf29734f657c5d8"
+ integrity sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==
+ dependencies:
+ ip-regex "^4.0.0"
+
+ is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+ is-stream@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+ integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+
+ isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+ jiti@^1.13.0:
+ version "1.14.0"
+ resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.14.0.tgz#5350fff532a4d891ca4bcd700c47c1f40e6ee326"
+ integrity sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A==
+
+ kleur@^4.1.5:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
+ integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
+
+ kolorist@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.5.1.tgz#c3d66dc4fabde4f6b7faa6efda84c00491f9e52b"
+ integrity sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==
+
+ local-pkg@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.1.tgz#e7b0d7aa0b9c498a1110a5ac5b00ba66ef38cfff"
+ integrity sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==
+
+ locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+ magic-string@^0.26.2:
+ version "0.26.2"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.2.tgz#5331700e4158cd6befda738bb6b0c7b93c0d4432"
+ integrity sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==
+ dependencies:
+ sourcemap-codec "^1.4.8"
+
+ mdn-data@2.0.27:
+ version "2.0.27"
+ resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.27.tgz#1710baa7b0db8176d3b3d565ccb7915fc69525ab"
+ integrity sha512-kwqO0I0jtWr25KcfLm9pia8vLZ8qoAKhWZuZMbneJq3jjBD3gl5nZs8l8Tu3ZBlBAHVQtDur9rdDGyvtfVraHQ==
+
+ merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+ merge2@^1.3.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+ micromatch@^4.0.4:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
+ integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+ dependencies:
+ braces "^3.0.2"
+ picomatch "^2.3.1"
+
+ mimic-fn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+ mrmime@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27"
+ integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==
+
+ ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+ nanoid@^3.3.4:
+ version "3.3.4"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
+ integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
+
+ node-fetch-native@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-0.1.4.tgz#09b06754f9e298bac23848050da2352125634f89"
+ integrity sha512-10EKpOCQPXwZVFh3U1ptOMWBgKTbsN7Vvo6WVKt5pw4hp8zbv6ZVBZPlXw+5M6Tyi1oc1iD4/sNPd71KYA16tQ==
+
+ normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+ npm-run-path@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
+ integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+ dependencies:
+ path-key "^3.0.0"
+
+ ohmyfetch@^0.4.18:
+ version "0.4.18"
+ resolved "https://registry.yarnpkg.com/ohmyfetch/-/ohmyfetch-0.4.18.tgz#2952e04bd52662d0618d3d2f344db0250c3eeac2"
+ integrity sha512-MslzNrQzBLtZHmiZBI8QMOcMpdNFlK61OJ34nFNFynZ4v+4BonfCQ7VIN4EGXvGGq5zhDzgdJoY3o9S1l2T7KQ==
+ dependencies:
+ destr "^1.1.1"
+ node-fetch-native "^0.1.3"
+ ufo "^0.8.4"
+ undici "^5.2.0"
+
+ onetime@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+ integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
+ dependencies:
+ mimic-fn "^2.1.0"
+
+ p-event@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5"
+ integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==
+ dependencies:
+ p-timeout "^3.1.0"
+
+ p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+ integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
+
+ p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+ p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+ p-timeout@^3.1.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
+ integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
+ dependencies:
+ p-finally "^1.0.0"
+
+ path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+ path-key@^3.0.0, path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+ path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+ pathe@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.3.0.tgz#fd95bc16208263fa6dc1c78c07b3907a528de6eb"
+ integrity sha512-3vUjp552BJzCw9vqKsO5sttHkbYqqsZtH0x1PNtItgqx8BXEXzoY1SYRKcL6BTyVh4lGJGLj0tM42elUDMvcYA==
+
+ perfect-debounce@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/perfect-debounce/-/perfect-debounce-0.1.3.tgz#ff6798ea543a3ba1f0efeeaf97c0340f5c8871ce"
+ integrity sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==
+
+ picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+ picomatch@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
+ integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
+
+ postcss@^8.4.16:
+ version "8.4.16"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
+ integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
+ dependencies:
+ nanoid "^3.3.4"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
+ queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+ readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+ resolve@^1.22.1:
+ version "1.22.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
+ integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
+ dependencies:
+ is-core-module "^2.9.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+ reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+ "rollup@>=2.75.6 <2.77.0 || ~2.77.0":
+ version "2.77.3"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12"
+ integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+ run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+ shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+ shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+ signal-exit@^3.0.3:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+ integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
+ sirv@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.2.tgz#128b9a628d77568139cff85703ad5497c46a4760"
+ integrity sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==
+ dependencies:
+ "@polka/url" "^1.0.0-next.20"
+ mrmime "^1.0.0"
+ totalist "^3.0.0"
+
+ source-map-js@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
+ integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
+
+ source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+ sourcemap-codec@^1.4.8:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+ integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
+ streamsearch@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
+ integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+
+ strip-final-newline@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
+ integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
+ supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+ svelte-hmr@^0.14.12:
+ version "0.14.12"
+ resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.12.tgz#a127aec02f1896500b10148b2d4d21ddde39973f"
+ integrity sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==
+
+ svelte@^3.49.0:
+ version "3.49.0"
+ resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.49.0.tgz#5baee3c672306de1070c3b7888fc2204e36a4029"
+ integrity sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==
+
+ "tauri-plugin-fs-api@https://gitpkg.now.sh/tauri-apps/plugins-workspace/plugins/fs?feat/fs-plugin":
+ version "0.0.0"
+ resolved "https://gitpkg.now.sh/tauri-apps/plugins-workspace/plugins/fs?feat/fs-plugin#a4b37d92c5fd3e638ad21d4ccf0132e07acc0136"
+ dependencies:
+ "@tauri-apps/api" "^1.2.0"
+
+ to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+ totalist@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
+ integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==
+
+ ufo@^0.8.4:
+ version "0.8.4"
+ resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.8.4.tgz#23e9ed82398d2116dcb378e8fba5ced8eca2ee40"
+ integrity sha512-/+BmBDe8GvlB2nIflWasLLAInjYG0bC9HRnfEpNi4sw77J2AJNnEVnTDReVrehoh825+Q/evF3THXTAweyam2g==
+
+ unconfig@^0.3.4:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/unconfig/-/unconfig-0.3.4.tgz#f0c85584a088a434dde2215d8a3b272427d6056c"
+ integrity sha512-cf39F1brwQuLSuMLTYXOdWJH0O1CJee6a4QW1nYtO7SoBUfVvQCvEel6ssTNXtPfi17kop1ADmVtmC49NlFkIQ==
+ dependencies:
+ "@antfu/utils" "^0.5.1"
+ defu "^6.0.0"
+ jiti "^1.13.0"
+
+ undici@^5.2.0:
+ version "5.19.1"
+ resolved "https://registry.yarnpkg.com/undici/-/undici-5.19.1.tgz#92b1fd3ab2c089b5a6bd3e579dcda8f1934ebf6d"
+ integrity sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A==
+ dependencies:
+ busboy "^1.6.0"
+
+ unocss@^0.39.3:
+ version "0.39.3"
+ resolved "https://registry.yarnpkg.com/unocss/-/unocss-0.39.3.tgz#3b92d689a86c97a5cb2e57ebfab04219a0de1c01"
+ integrity sha512-+BZazovI1A+jlW0+GuSSABHQjBLpu2sQkLXriBTdZiPYZAqJJdiWHuQ6VPzF4Al5WM4VPpOgX5mUYWusJ813qw==
+ dependencies:
+ "@unocss/cli" "0.39.3"
+ "@unocss/core" "0.39.3"
+ "@unocss/preset-attributify" "0.39.3"
+ "@unocss/preset-icons" "0.39.3"
+ "@unocss/preset-mini" "0.39.3"
+ "@unocss/preset-tagify" "0.39.3"
+ "@unocss/preset-typography" "0.39.3"
+ "@unocss/preset-uno" "0.39.3"
+ "@unocss/preset-web-fonts" "0.39.3"
+ "@unocss/preset-wind" "0.39.3"
+ "@unocss/reset" "0.39.3"
+ "@unocss/transformer-compile-class" "0.39.3"
+ "@unocss/transformer-directives" "0.39.3"
+ "@unocss/transformer-variant-group" "0.39.3"
+ "@unocss/vite" "0.39.3"
+
+ vite@^3.0.9:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30"
+ integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==
+ dependencies:
+ esbuild "^0.14.47"
+ postcss "^8.4.16"
+ resolve "^1.22.1"
+ rollup ">=2.75.6 <2.77.0 || ~2.77.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+ which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+ yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/plugins/notification/Cargo.toml b/plugins/notification/Cargo.toml
index 57d5c149..f136b2e5 100644
--- a/plugins/notification/Cargo.toml
+++ b/plugins/notification/Cargo.toml
@@ -16,6 +16,10 @@ serde_json.workspace = true
tauri.workspace = true
log.workspace = true
thiserror.workspace = true
+rand = "0.8"
+time = { version = "0.3", features = ["serde", "parsing", "formatting"] }
+url = { version = "2", features = ["serde"] }
+serde_repr = "0.1"
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
notify-rust = "4.5"
diff --git a/plugins/notification/android/build.gradle.kts b/plugins/notification/android/build.gradle.kts
index 5fdedcf4..7d961104 100644
--- a/plugins/notification/android/build.gradle.kts
+++ b/plugins/notification/android/build.gradle.kts
@@ -5,11 +5,11 @@ plugins {
android {
namespace = "app.tauri.notification"
- compileSdk = 32
+ compileSdk = 33
defaultConfig {
minSdk = 24
- targetSdk = 32
+ targetSdk = 33
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
diff --git a/plugins/notification/android/src/main/AndroidManifest.xml b/plugins/notification/android/src/main/AndroidManifest.xml
index 9a40236b..986d5f85 100644
--- a/plugins/notification/android/src/main/AndroidManifest.xml
+++ b/plugins/notification/android/src/main/AndroidManifest.xml
@@ -1,3 +1,20 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/notification/android/src/main/java/AssetUtils.kt b/plugins/notification/android/src/main/java/AssetUtils.kt
new file mode 100644
index 00000000..c97cd528
--- /dev/null
+++ b/plugins/notification/android/src/main/java/AssetUtils.kt
@@ -0,0 +1,25 @@
+package app.tauri.notification
+
+import android.annotation.SuppressLint
+import android.content.Context
+
+class AssetUtils {
+ companion object {
+ const val RESOURCE_ID_ZERO_VALUE = 0
+
+ @SuppressLint("DiscouragedApi")
+ fun getResourceID(context: Context, resourceName: String?, dir: String?): Int {
+ return context.resources.getIdentifier(resourceName, dir, context.packageName)
+ }
+
+ fun getResourceBaseName(resPath: String?): String? {
+ if (resPath == null) return null
+ if (resPath.contains("/")) {
+ return resPath.substring(resPath.lastIndexOf('/') + 1)
+ }
+ return if (resPath.contains(".")) {
+ resPath.substring(0, resPath.lastIndexOf('.'))
+ } else resPath
+ }
+ }
+}
diff --git a/plugins/notification/android/src/main/java/ChannelManager.kt b/plugins/notification/android/src/main/java/ChannelManager.kt
new file mode 100644
index 00000000..cf68e666
--- /dev/null
+++ b/plugins/notification/android/src/main/java/ChannelManager.kt
@@ -0,0 +1,150 @@
+package app.tauri.notification
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.ContentResolver
+import android.content.Context
+import android.graphics.Color
+import android.media.AudioAttributes
+import android.net.Uri
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import app.tauri.Logger
+import app.tauri.plugin.Invoke
+import app.tauri.plugin.JSArray
+import app.tauri.plugin.JSObject
+
+private const val CHANNEL_ID = "id"
+private const val CHANNEL_NAME = "name"
+private const val CHANNEL_DESCRIPTION = "description"
+private const val CHANNEL_IMPORTANCE = "importance"
+private const val CHANNEL_VISIBILITY = "visibility"
+private const val CHANNEL_SOUND = "sound"
+private const val CHANNEL_VIBRATE = "vibration"
+private const val CHANNEL_USE_LIGHTS = "lights"
+private const val CHANNEL_LIGHT_COLOR = "lightColor"
+
+class ChannelManager(private var context: Context) {
+ private var notificationManager: NotificationManager? = null
+
+ init {
+ notificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
+ }
+
+ fun createChannel(invoke: Invoke) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = JSObject()
+ if (invoke.getString(CHANNEL_ID) != null) {
+ channel.put(CHANNEL_ID, invoke.getString(CHANNEL_ID))
+ } else {
+ invoke.reject("Channel missing identifier")
+ return
+ }
+ if (invoke.getString(CHANNEL_NAME) != null) {
+ channel.put(CHANNEL_NAME, invoke.getString(CHANNEL_NAME))
+ } else {
+ invoke.reject("Channel missing name")
+ return
+ }
+ channel.put(
+ CHANNEL_IMPORTANCE,
+ invoke.getInt(CHANNEL_IMPORTANCE, NotificationManager.IMPORTANCE_DEFAULT)
+ )
+ channel.put(CHANNEL_DESCRIPTION, invoke.getString(CHANNEL_DESCRIPTION, ""))
+ channel.put(
+ CHANNEL_VISIBILITY,
+ invoke.getInt(CHANNEL_VISIBILITY, NotificationCompat.VISIBILITY_PUBLIC)
+ )
+ channel.put(CHANNEL_SOUND, invoke.getString(CHANNEL_SOUND))
+ channel.put(CHANNEL_VIBRATE, invoke.getBoolean(CHANNEL_VIBRATE, false))
+ channel.put(CHANNEL_USE_LIGHTS, invoke.getBoolean(CHANNEL_USE_LIGHTS, false))
+ channel.put(CHANNEL_LIGHT_COLOR, invoke.getString(CHANNEL_LIGHT_COLOR))
+ createChannel(channel)
+ invoke.resolve()
+ } else {
+ invoke.reject("channel not available")
+ }
+ }
+
+ private fun createChannel(channel: JSObject) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val notificationChannel = NotificationChannel(
+ channel.getString(CHANNEL_ID),
+ channel.getString(CHANNEL_NAME),
+ channel.getInteger(CHANNEL_IMPORTANCE)!!
+ )
+ notificationChannel.description = channel.getString(CHANNEL_DESCRIPTION)
+ notificationChannel.lockscreenVisibility = channel.getInteger(CHANNEL_VISIBILITY, android.app.Notification.VISIBILITY_PRIVATE)
+ notificationChannel.enableVibration(channel.getBoolean(CHANNEL_VIBRATE, false))
+ notificationChannel.enableLights(channel.getBoolean(CHANNEL_USE_LIGHTS, false))
+ val lightColor = channel.getString(CHANNEL_LIGHT_COLOR)
+ if (lightColor.isNotEmpty()) {
+ try {
+ notificationChannel.lightColor = Color.parseColor(lightColor)
+ } catch (ex: IllegalArgumentException) {
+ Logger.error(
+ Logger.tags("NotificationChannel"),
+ "Invalid color provided for light color.",
+ null
+ )
+ }
+ }
+ var sound = channel.getString(CHANNEL_SOUND)
+ if (sound.isNotEmpty()) {
+ if (sound.contains(".")) {
+ sound = sound.substring(0, sound.lastIndexOf('.'))
+ }
+ val audioAttributes = AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .build()
+ val soundUri =
+ Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/raw/" + sound)
+ notificationChannel.setSound(soundUri, audioAttributes)
+ }
+ notificationManager?.createNotificationChannel(notificationChannel)
+ }
+ }
+
+ fun deleteChannel(invoke: Invoke) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channelId = invoke.getString("id")
+ notificationManager?.deleteNotificationChannel(channelId)
+ invoke.resolve()
+ } else {
+ invoke.reject("channel not available")
+ }
+ }
+
+ fun listChannels(invoke: Invoke) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val notificationChannels: List =
+ notificationManager?.notificationChannels ?: listOf()
+ val channels = JSArray()
+ for (notificationChannel in notificationChannels) {
+ val channel = JSObject()
+ channel.put(CHANNEL_ID, notificationChannel.id)
+ channel.put(CHANNEL_NAME, notificationChannel.name)
+ channel.put(CHANNEL_DESCRIPTION, notificationChannel.description)
+ channel.put(CHANNEL_IMPORTANCE, notificationChannel.importance)
+ channel.put(CHANNEL_VISIBILITY, notificationChannel.lockscreenVisibility)
+ channel.put(CHANNEL_SOUND, notificationChannel.sound)
+ channel.put(CHANNEL_VIBRATE, notificationChannel.shouldVibrate())
+ channel.put(CHANNEL_USE_LIGHTS, notificationChannel.shouldShowLights())
+ channel.put(
+ CHANNEL_LIGHT_COLOR, String.format(
+ "#%06X",
+ 0xFFFFFF and notificationChannel.lightColor
+ )
+ )
+ channels.put(channel)
+ }
+ val result = JSObject()
+ result.put("channels", channels)
+ invoke.resolve(result)
+ } else {
+ invoke.reject("channel not available")
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/notification/android/src/main/java/Notification.kt b/plugins/notification/android/src/main/java/Notification.kt
new file mode 100644
index 00000000..3839807b
--- /dev/null
+++ b/plugins/notification/android/src/main/java/Notification.kt
@@ -0,0 +1,165 @@
+package app.tauri.notification
+
+import android.content.ContentResolver
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import app.tauri.plugin.JSArray
+import app.tauri.plugin.JSObject
+import org.json.JSONException
+import org.json.JSONObject
+
+class Notification {
+ var title: String? = null
+ var body: String? = null
+ var largeBody: String? = null
+ var summary: String? = null
+ var id: Int = 0
+ private var sound: String? = null
+ private var smallIcon: String? = null
+ private var largeIcon: String? = null
+ var iconColor: String? = null
+ var actionTypeId: String? = null
+ var group: String? = null
+ var inboxLines: List? = null
+ var isGroupSummary = false
+ var isOngoing = false
+ var isAutoCancel = false
+ var extra: JSObject? = null
+ var attachments: List? = null
+ var schedule: NotificationSchedule? = null
+ var channelId: String? = null
+ var source: JSObject? = null
+ var visibility: Int? = null
+ var number: Int? = null
+
+ fun getSound(context: Context, defaultSound: Int): String? {
+ var soundPath: String? = null
+ var resId: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE
+ val name = AssetUtils.getResourceBaseName(sound)
+ if (name != null) {
+ resId = AssetUtils.getResourceID(context, name, "raw")
+ }
+ if (resId == AssetUtils.RESOURCE_ID_ZERO_VALUE) {
+ resId = defaultSound
+ }
+ if (resId != AssetUtils.RESOURCE_ID_ZERO_VALUE) {
+ soundPath =
+ ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + resId
+ }
+ return soundPath
+ }
+
+ fun setSound(sound: String?) {
+ this.sound = sound
+ }
+
+ fun setSmallIcon(smallIcon: String?) {
+ this.smallIcon = AssetUtils.getResourceBaseName(smallIcon)
+ }
+
+ fun setLargeIcon(largeIcon: String?) {
+ this.largeIcon = AssetUtils.getResourceBaseName(largeIcon)
+ }
+
+ fun getIconColor(globalColor: String): String {
+ // use the one defined local before trying for a globally defined color
+ return iconColor ?: globalColor
+ }
+
+ fun getSmallIcon(context: Context, defaultIcon: Int): Int {
+ var resId: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE
+ if (smallIcon != null) {
+ resId = AssetUtils.getResourceID(context, smallIcon, "drawable")
+ }
+ if (resId == AssetUtils.RESOURCE_ID_ZERO_VALUE) {
+ resId = defaultIcon
+ }
+ return resId
+ }
+
+ fun getLargeIcon(context: Context): Bitmap? {
+ if (largeIcon != null) {
+ val resId: Int = AssetUtils.getResourceID(context, largeIcon, "drawable")
+ return BitmapFactory.decodeResource(context.resources, resId)
+ }
+ return null
+ }
+
+ val isScheduled = schedule != null
+
+ companion object {
+ fun fromJson(jsonNotification: JSONObject): Notification {
+ val notification: JSObject = try {
+ val identifier = jsonNotification.getLong("id")
+ if (identifier > Int.MAX_VALUE || identifier < Int.MIN_VALUE) {
+ throw Exception("The notification identifier should be a 32-bit integer")
+ }
+ JSObject.fromJSONObject(jsonNotification)
+ } catch (e: JSONException) {
+ throw Exception("Invalid notification JSON object", e)
+ }
+ return fromJSObject(notification)
+ }
+
+ fun fromJSObject(jsonObject: JSObject): Notification {
+ val notification = Notification()
+ notification.source = jsonObject
+ notification.id = jsonObject.getInteger("id") ?: throw Exception("Missing notification identifier")
+ notification.body = jsonObject.getString("body", null)
+ notification.largeBody = jsonObject.getString("largeBody", null)
+ notification.summary = jsonObject.getString("summary", null)
+ notification.actionTypeId = jsonObject.getString("actionTypeId", null)
+ notification.group = jsonObject.getString("group", null)
+ notification.setSound(jsonObject.getString("sound", null))
+ notification.title = jsonObject.getString("title", null)
+ notification.setSmallIcon(jsonObject.getString("icon", null))
+ notification.setLargeIcon(jsonObject.getString("largeIcon", null))
+ notification.iconColor = jsonObject.getString("iconColor", null)
+ notification.attachments = NotificationAttachment.getAttachments(jsonObject)
+ notification.isGroupSummary = jsonObject.getBoolean("groupSummary", false)
+ notification.channelId = jsonObject.getString("channelId", null)
+ val schedule = jsonObject.getJSObject("schedule")
+ if (schedule != null) {
+ notification.schedule = NotificationSchedule(schedule)
+ }
+ notification.extra = jsonObject.getJSObject("extra")
+ notification.isOngoing = jsonObject.getBoolean("ongoing", false)
+ notification.isAutoCancel = jsonObject.getBoolean("autoCancel", true)
+ notification.visibility = jsonObject.getInteger("visibility")
+ notification.number = jsonObject.getInteger("number")
+ try {
+ val inboxLines = jsonObject.getJSONArray("inboxLines")
+ val inboxStringList: MutableList = ArrayList()
+ for (i in 0 until inboxLines.length()) {
+ inboxStringList.add(inboxLines.getString(i))
+ }
+ notification.inboxLines = inboxStringList
+ } catch (_: Exception) {
+ }
+ return notification
+ }
+
+ fun buildNotificationPendingList(notifications: List): JSObject {
+ val result = JSObject()
+ val jsArray = JSArray()
+ for (notification in notifications) {
+ val jsNotification = JSObject()
+ jsNotification.put("id", notification.id)
+ jsNotification.put("title", notification.title)
+ jsNotification.put("body", notification.body)
+ val schedule = notification.schedule
+ if (schedule != null) {
+ val jsSchedule = JSObject()
+ jsSchedule.put("kind", schedule.scheduleObj.getString("kind", null))
+ jsSchedule.put("data", schedule.scheduleObj.getJSObject("data"))
+ jsNotification.put("schedule", jsSchedule)
+ }
+ jsNotification.put("extra", notification.extra)
+ jsArray.put(jsNotification)
+ }
+ result.put("notifications", jsArray)
+ return result
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/notification/android/src/main/java/NotificationAction.kt b/plugins/notification/android/src/main/java/NotificationAction.kt
new file mode 100644
index 00000000..c1a964b4
--- /dev/null
+++ b/plugins/notification/android/src/main/java/NotificationAction.kt
@@ -0,0 +1,47 @@
+package app.tauri.notification
+
+import app.tauri.Logger
+import app.tauri.plugin.JSArray
+import app.tauri.plugin.JSObject
+import org.json.JSONObject
+
+class NotificationAction() {
+ var id: String? = null
+ var title: String? = null
+ var input = false
+
+ constructor(id: String?, title: String?, input: Boolean): this() {
+ this.id = id
+ this.title = title
+ this.input = input
+ }
+
+ companion object {
+ fun buildTypes(types: JSArray): Map> {
+ val actionTypeMap: MutableMap> = HashMap()
+ try {
+ val objects: List = types.toList()
+ for (obj in objects) {
+ val jsObject = JSObject.fromJSONObject(
+ obj
+ )
+ val actionGroupId = jsObject.getString("id")
+ val actions = jsObject.getJSONArray("actions")
+ val typesArray = mutableListOf()
+ for (i in 0 until actions.length()) {
+ val notificationAction = NotificationAction()
+ val action = JSObject.fromJSONObject(actions.getJSONObject(i))
+ notificationAction.id = action.getString("id")
+ notificationAction.title = action.getString("title")
+ notificationAction.input = action.getBoolean("input")
+ typesArray.add(notificationAction)
+ }
+ actionTypeMap[actionGroupId] = typesArray.toList()
+ }
+ } catch (e: Exception) {
+ Logger.error(Logger.tags("Notification"), "Error when building action types", e)
+ }
+ return actionTypeMap
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/notification/android/src/main/java/NotificationAttachment.kt b/plugins/notification/android/src/main/java/NotificationAttachment.kt
new file mode 100644
index 00000000..1cc35e89
--- /dev/null
+++ b/plugins/notification/android/src/main/java/NotificationAttachment.kt
@@ -0,0 +1,48 @@
+package app.tauri.notification
+
+import app.tauri.plugin.JSObject
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+
+class NotificationAttachment {
+ var id: String? = null
+ var url: String? = null
+ var options: JSONObject? = null
+
+ companion object {
+ fun getAttachments(notification: JSObject): List {
+ val attachmentsList: MutableList = ArrayList()
+ var attachments: JSONArray? = null
+ try {
+ attachments = notification.getJSONArray("attachments")
+ } catch (_: Exception) {
+ }
+ if (attachments != null) {
+ for (i in 0 until attachments.length()) {
+ val newAttachment = NotificationAttachment()
+ var jsonObject: JSONObject? = null
+ try {
+ jsonObject = attachments.getJSONObject(i)
+ } catch (e: JSONException) {
+ }
+ if (jsonObject != null) {
+ var jsObject: JSObject? = null
+ try {
+ jsObject = JSObject.fromJSONObject(jsonObject)
+ } catch (_: JSONException) {
+ }
+ newAttachment.id = jsObject!!.getString("id")
+ newAttachment.url = jsObject.getString("url")
+ try {
+ newAttachment.options = jsObject.getJSONObject("options")
+ } catch (_: JSONException) {
+ }
+ attachmentsList.add(newAttachment)
+ }
+ }
+ }
+ return attachmentsList
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/notification/android/src/main/java/NotificationPlugin.kt b/plugins/notification/android/src/main/java/NotificationPlugin.kt
index ab6c9df7..f87bcf17 100644
--- a/plugins/notification/android/src/main/java/NotificationPlugin.kt
+++ b/plugins/notification/android/src/main/java/NotificationPlugin.kt
@@ -1,31 +1,263 @@
package app.tauri.notification
+import android.Manifest
+import android.annotation.SuppressLint
import android.app.Activity
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.webkit.WebView
+import app.tauri.PermissionState
import app.tauri.annotation.Command
+import app.tauri.annotation.Permission
+import app.tauri.annotation.PermissionCallback
import app.tauri.annotation.TauriPlugin
+import app.tauri.plugin.Invoke
+import app.tauri.plugin.JSArray
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
-import app.tauri.plugin.Invoke
+import org.json.JSONException
+import org.json.JSONObject
+
+const val LOCAL_NOTIFICATIONS = "permissionState"
-@TauriPlugin
+@TauriPlugin(
+ permissions = [
+ Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "permissionState")
+ ]
+)
class NotificationPlugin(private val activity: Activity): Plugin(activity) {
- @Command
- fun requestPermission(invoke: Invoke) {
- val ret = JSObject()
- ret.put("permissionState", "granted")
- invoke.resolve(ret)
+ private var webView: WebView? = null
+ private lateinit var manager: TauriNotificationManager
+ private lateinit var notificationManager: NotificationManager
+ private lateinit var notificationStorage: NotificationStorage
+ private var channelManager = ChannelManager(activity)
+
+ companion object {
+ var instance: NotificationPlugin? = null
+
+ fun triggerNotification(notification: JSObject) {
+ instance?.trigger("notification", notification)
+ }
+ }
+
+ override fun load(webView: WebView) {
+ instance = this
+
+ super.load(webView)
+ this.webView = webView
+ notificationStorage = NotificationStorage(activity)
+
+ val manager = TauriNotificationManager(
+ notificationStorage,
+ activity,
+ activity,
+ getConfig()
+ )
+ manager.createNotificationChannel()
+
+ this.manager = manager
+
+ notificationManager = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ if (Intent.ACTION_MAIN != intent.action) {
+ return
+ }
+ val dataJson = manager.handleNotificationActionPerformed(intent, notificationStorage)
+ if (dataJson != null) {
+ trigger("actionPerformed", dataJson)
+ }
+ }
+
+ @Command
+ fun show(invoke: Invoke) {
+ val notification = Notification.fromJSObject(invoke.data)
+ val id = manager.schedule(notification)
+
+ val returnVal = JSObject().put("id", id)
+ invoke.resolve(returnVal)
+ }
+
+ @Command
+ fun batch(invoke: Invoke) {
+ val notificationArray = invoke.getArray("notifications")
+ if (notificationArray == null) {
+ invoke.reject("Missing `notifications` argument")
+ return
+ }
+
+ val notifications: MutableList =
+ ArrayList(notificationArray.length())
+ val notificationsInput: List = try {
+ notificationArray.toList()
+ } catch (e: JSONException) {
+ invoke.reject("Provided notification format is invalid")
+ return
+ }
+
+ for (jsonNotification in notificationsInput) {
+ val notification = Notification.fromJson(jsonNotification)
+ notifications.add(notification)
}
- @Command
- fun permissionState(invoke: Invoke) {
- val ret = JSObject()
- ret.put("permissionState", "granted")
- invoke.resolve(ret)
+ val ids = manager.schedule(notifications)
+ notificationStorage.appendNotifications(notifications)
+
+ val result = JSObject()
+ result.put("notifications", ids)
+ invoke.resolve(result)
+ }
+
+ @Command
+ fun cancel(invoke: Invoke) {
+ val notifications: List = invoke.getArray("notifications", JSArray()).toList()
+ if (notifications.isEmpty()) {
+ invoke.reject("Must provide notifications array as notifications option")
+ return
+ }
+
+ manager.cancel(notifications)
+ invoke.resolve()
+ }
+
+ @Command
+ fun removeActive(invoke: Invoke) {
+ val notifications = invoke.getArray("notifications")
+ if (notifications == null) {
+ notificationManager.cancelAll()
+ invoke.resolve()
+ } else {
+ try {
+ for (o in notifications.toList()) {
+ if (o is JSONObject) {
+ val notification = JSObject.fromJSONObject((o))
+ val tag = notification.getString("tag", null)
+ val id = notification.getInteger("id", 0)
+ if (tag == null) {
+ notificationManager.cancel(id)
+ } else {
+ notificationManager.cancel(tag, id)
+ }
+ } else {
+ invoke.reject("Unexpected notification type")
+ return
+ }
+ }
+ } catch (e: JSONException) {
+ invoke.reject(e.message)
+ }
+ invoke.resolve()
+ }
+ }
+
+ @Command
+ fun getPending(invoke: Invoke) {
+ val notifications= notificationStorage.getSavedNotifications()
+ val result = Notification.buildNotificationPendingList(notifications)
+ invoke.resolve(result)
+ }
+
+ @Command
+ fun registerActionTypes(invoke: Invoke) {
+ val types = invoke.getArray("types", JSArray())
+ val typesArray = NotificationAction.buildTypes(types)
+ notificationStorage.writeActionGroup(typesArray)
+ invoke.resolve()
+ }
+
+ @SuppressLint("ObsoleteSdkInt")
+ @Command
+ fun getActive(invoke: Invoke) {
+ val notifications = JSArray()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val activeNotifications = notificationManager.activeNotifications
+ for (activeNotification in activeNotifications) {
+ val jsNotification = JSObject()
+ jsNotification.put("id", activeNotification.id)
+ jsNotification.put("tag", activeNotification.tag)
+ val notification = activeNotification.notification
+ if (notification != null) {
+ jsNotification.put("title", notification.extras.getCharSequence(android.app.Notification.EXTRA_TITLE))
+ jsNotification.put("body", notification.extras.getCharSequence(android.app.Notification.EXTRA_TEXT))
+ jsNotification.put("group", notification.group)
+ jsNotification.put(
+ "groupSummary",
+ 0 != notification.flags and android.app.Notification.FLAG_GROUP_SUMMARY
+ )
+ val extras = JSObject()
+ for (key in notification.extras.keySet()) {
+ extras.put(key!!, notification.extras.getString(key))
+ }
+ jsNotification.put("data", extras)
+ }
+ notifications.put(jsNotification)
+ }
}
+ val result = JSObject()
+ result.put("notifications", notifications)
+ invoke.resolve(result)
+ }
+
+ @Command
+ fun createChannel(invoke: Invoke) {
+ channelManager.createChannel(invoke)
+ }
+
+ @Command
+ fun deleteChannel(invoke: Invoke) {
+ channelManager.deleteChannel(invoke)
+ }
+
+ @Command
+ fun listChannels(invoke: Invoke) {
+ channelManager.listChannels(invoke)
+ }
+
+ @Command
+ override fun checkPermissions(invoke: Invoke) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ val permissionsResultJSON = JSObject()
+ permissionsResultJSON.put("permissionState", getPermissionState())
+ invoke.resolve(permissionsResultJSON)
+ } else {
+ super.checkPermissions(invoke)
+ }
+ }
+
+ @Command
+ override fun requestPermissions(invoke: Invoke) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ permissionState(invoke)
+ } else {
+ if (getPermissionState(LOCAL_NOTIFICATIONS) !== PermissionState.GRANTED) {
+ requestPermissionForAlias(LOCAL_NOTIFICATIONS, invoke, "permissionsCallback")
+ }
+ }
+ }
+
+ @Command
+ fun permissionState(invoke: Invoke) {
+ val permissionsResultJSON = JSObject()
+ permissionsResultJSON.put("permissionState", getPermissionState())
+ invoke.resolve(permissionsResultJSON)
+ }
+
+ @PermissionCallback
+ private fun permissionsCallback(invoke: Invoke) {
+ val permissionsResultJSON = JSObject()
+ permissionsResultJSON.put("display", getPermissionState())
+ invoke.resolve(permissionsResultJSON)
+ }
- @Command
- fun notify(invoke: Invoke) {
- // TODO
- invoke.resolve()
+ private fun getPermissionState(): String {
+ return if (manager.areNotificationsEnabled()) {
+ "granted"
+ } else {
+ "denied"
}
+ }
}
diff --git a/plugins/notification/android/src/main/java/NotificationSchedule.kt b/plugins/notification/android/src/main/java/NotificationSchedule.kt
new file mode 100644
index 00000000..89edbc9d
--- /dev/null
+++ b/plugins/notification/android/src/main/java/NotificationSchedule.kt
@@ -0,0 +1,305 @@
+package app.tauri.notification
+
+import android.annotation.SuppressLint
+import android.text.format.DateUtils
+import app.tauri.plugin.JSObject
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+import java.util.TimeZone
+
+const val JS_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
+
+enum class NotificationInterval {
+ Year, Month, TwoWeeks, Week, Day, Hour, Minute, Second
+}
+
+fun getIntervalTime(interval: NotificationInterval, count: Int): Long {
+ return when (interval) {
+ // This case is just approximation as not all years have the same number of days
+ NotificationInterval.Year -> count * DateUtils.WEEK_IN_MILLIS * 52
+ // This case is just approximation as months have different number of days
+ NotificationInterval.Month -> count * 30 * DateUtils.DAY_IN_MILLIS
+ NotificationInterval.TwoWeeks -> count * 2 * DateUtils.WEEK_IN_MILLIS
+ NotificationInterval.Week -> count * DateUtils.WEEK_IN_MILLIS
+ NotificationInterval.Day -> count * DateUtils.DAY_IN_MILLIS
+ NotificationInterval.Hour -> count * DateUtils.HOUR_IN_MILLIS
+ NotificationInterval.Minute -> count * DateUtils.MINUTE_IN_MILLIS
+ NotificationInterval.Second -> count * DateUtils.SECOND_IN_MILLIS
+ }
+}
+
+sealed class ScheduleKind {
+ // At specific moment of time (with repeating option)
+ class At(var date: Date, val repeating: Boolean): ScheduleKind()
+ class Interval(val interval: DateMatch): ScheduleKind()
+ class Every(val interval: NotificationInterval, val count: Int): ScheduleKind()
+}
+
+@SuppressLint("SimpleDateFormat")
+class NotificationSchedule(val scheduleObj: JSObject) {
+ val kind: ScheduleKind
+ // Schedule this notification to fire even if app is idled (Doze)
+ var whileIdle: Boolean = false
+
+ init {
+ val payload = scheduleObj.getJSObject("data", JSObject())
+
+ when (val scheduleKind = scheduleObj.getString("kind", "")) {
+ "At" -> {
+ val dateString = payload.getString("date")
+ if (dateString.isNotEmpty()) {
+ val sdf = SimpleDateFormat(JS_DATE_FORMAT)
+ sdf.timeZone = TimeZone.getTimeZone("UTC")
+ val at = sdf.parse(dateString)
+ if (at == null) {
+ throw Exception("could not parse `at` date")
+ } else {
+ kind = ScheduleKind.At(at, payload.getBoolean("repeating"))
+ }
+ } else {
+ throw Exception("`at` date cannot be empty")
+ }
+ }
+ "Interval" -> {
+ val dateMatch = onFromJson(payload)
+ kind = ScheduleKind.Interval(dateMatch)
+ }
+ "Every" -> {
+ val interval = NotificationInterval.valueOf(payload.getString("interval"))
+ kind = ScheduleKind.Every(interval, payload.getInteger("count", 1))
+ }
+ else -> {
+ throw Exception("Unknown schedule kind $scheduleKind")
+ }
+ }
+ whileIdle = scheduleObj.getBoolean("allowWhileIdle", false)
+ }
+
+ private fun onFromJson(onJson: JSObject): DateMatch {
+ val match = DateMatch()
+ match.year = onJson.getInteger("year")
+ match.month = onJson.getInteger("month")
+ match.day = onJson.getInteger("day")
+ match.weekday = onJson.getInteger("weekday")
+ match.hour = onJson.getInteger("hour")
+ match.minute = onJson.getInteger("minute")
+ match.second = onJson.getInteger("second")
+ return match
+ }
+
+ fun isRemovable(): Boolean {
+ return when (kind) {
+ is ScheduleKind.At -> !kind.repeating
+ else -> false
+ }
+ }
+}
+
+class DateMatch {
+ var year: Int? = null
+ var month: Int? = null
+ var day: Int? = null
+ var weekday: Int? = null
+ var hour: Int? = null
+ var minute: Int? = null
+ var second: Int? = null
+
+ // Unit used to save the last used unit for a trigger.
+ // One of the Calendar constants values
+ var unit: Int? = -1
+
+ /**
+ * Gets a calendar instance pointing to the specified date.
+ *
+ * @param date The date to point.
+ */
+ private fun buildCalendar(date: Date): Calendar {
+ val cal: Calendar = Calendar.getInstance()
+ cal.time = date
+ cal.set(Calendar.MILLISECOND, 0)
+ return cal
+ }
+
+ /**
+ * Calculates next trigger date for
+ *
+ * @param date base date used to calculate trigger
+ * @return next trigger timestamp
+ */
+ fun nextTrigger(date: Date): Long {
+ val current: Calendar = buildCalendar(date)
+ val next: Calendar = buildNextTriggerTime(date)
+ return postponeTriggerIfNeeded(current, next)
+ }
+
+ /**
+ * Postpone trigger if first schedule matches the past
+ */
+ private fun postponeTriggerIfNeeded(current: Calendar, next: Calendar): Long {
+ if (next.timeInMillis <= current.timeInMillis && unit != -1) {
+ var incrementUnit = -1
+ if (unit == Calendar.YEAR || unit == Calendar.MONTH) {
+ incrementUnit = Calendar.YEAR
+ } else if (unit == Calendar.DAY_OF_MONTH) {
+ incrementUnit = Calendar.MONTH
+ } else if (unit == Calendar.DAY_OF_WEEK) {
+ incrementUnit = Calendar.WEEK_OF_MONTH
+ } else if (unit == Calendar.HOUR_OF_DAY) {
+ incrementUnit = Calendar.DAY_OF_MONTH
+ } else if (unit == Calendar.MINUTE) {
+ incrementUnit = Calendar.HOUR_OF_DAY
+ } else if (unit == Calendar.SECOND) {
+ incrementUnit = Calendar.MINUTE
+ }
+ if (incrementUnit != -1) {
+ next.set(incrementUnit, next.get(incrementUnit) + 1)
+ }
+ }
+ return next.timeInMillis
+ }
+
+ private fun buildNextTriggerTime(date: Date): Calendar {
+ val next: Calendar = buildCalendar(date)
+ if (year != null) {
+ next.set(Calendar.YEAR, year ?: 0)
+ if (unit == -1) unit = Calendar.YEAR
+ }
+ if (month != null) {
+ next.set(Calendar.MONTH, month ?: 0)
+ if (unit == -1) unit = Calendar.MONTH
+ }
+ if (day != null) {
+ next.set(Calendar.DAY_OF_MONTH, day ?: 0)
+ if (unit == -1) unit = Calendar.DAY_OF_MONTH
+ }
+ if (weekday != null) {
+ next.set(Calendar.DAY_OF_WEEK, weekday ?: 0)
+ if (unit == -1) unit = Calendar.DAY_OF_WEEK
+ }
+ if (hour != null) {
+ next.set(Calendar.HOUR_OF_DAY, hour ?: 0)
+ if (unit == -1) unit = Calendar.HOUR_OF_DAY
+ }
+ if (minute != null) {
+ next.set(Calendar.MINUTE, minute ?: 0)
+ if (unit == -1) unit = Calendar.MINUTE
+ }
+ if (second != null) {
+ next.set(Calendar.SECOND, second ?: 0)
+ if (unit == -1) unit = Calendar.SECOND
+ }
+ return next
+ }
+
+ override fun toString(): String {
+ return "DateMatch{" +
+ "year=" +
+ year +
+ ", month=" +
+ month +
+ ", day=" +
+ day +
+ ", weekday=" +
+ weekday +
+ ", hour=" +
+ hour +
+ ", minute=" +
+ minute +
+ ", second=" +
+ second +
+ '}'
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || javaClass != other.javaClass) return false
+ val dateMatch = other as DateMatch
+ if (if (year != null) year != dateMatch.year else dateMatch.year != null) return false
+ if (if (month != null) month != dateMatch.month else dateMatch.month != null) return false
+ if (if (day != null) day != dateMatch.day else dateMatch.day != null) return false
+ if (if (weekday != null) weekday != dateMatch.weekday else dateMatch.weekday != null) return false
+ if (if (hour != null) hour != dateMatch.hour else dateMatch.hour != null) return false
+ if (if (minute != null) minute != dateMatch.minute else dateMatch.minute != null) return false
+ return if (second != null) second == dateMatch.second else dateMatch.second == null
+ }
+
+ override fun hashCode(): Int {
+ var result = if (year != null) year.hashCode() else 0
+ result = 31 * result + if (month != null) month.hashCode() else 0
+ result = 31 * result + if (day != null) day.hashCode() else 0
+ result = 31 * result + if (weekday != null) weekday.hashCode() else 0
+ result = 31 * result + if (hour != null) hour.hashCode() else 0
+ result = 31 * result + if (minute != null) minute.hashCode() else 0
+ result += 31 + if (second != null) second.hashCode() else 0
+ return result
+ }
+
+ /**
+ * Transform DateMatch object to CronString
+ *
+ * @return
+ */
+ fun toMatchString(): String {
+ val matchString = year.toString() +
+ separator +
+ month +
+ separator +
+ day +
+ separator +
+ weekday +
+ separator +
+ hour +
+ separator +
+ minute +
+ separator +
+ second +
+ separator +
+ unit
+ return matchString.replace("null", "*")
+ }
+
+ companion object {
+ private const val separator = " "
+
+ /**
+ * Create DateMatch object from stored string
+ *
+ * @param matchString
+ * @return
+ */
+ fun fromMatchString(matchString: String): DateMatch {
+ val date = DateMatch()
+ val split = matchString.split(separator.toRegex()).dropLastWhile { it.isEmpty() }
+ .toTypedArray()
+ if (split.size == 7) {
+ date.year = getValueFromCronElement(split[0])
+ date.month = getValueFromCronElement(split[1])
+ date.day = getValueFromCronElement(split[2])
+ date.weekday = getValueFromCronElement(split[3])
+ date.hour = getValueFromCronElement(split[4])
+ date.minute = getValueFromCronElement(split[5])
+ date.unit = getValueFromCronElement(split[6])
+ }
+ if (split.size == 8) {
+ date.year = getValueFromCronElement(split[0])
+ date.month = getValueFromCronElement(split[1])
+ date.day = getValueFromCronElement(split[2])
+ date.weekday = getValueFromCronElement(split[3])
+ date.hour = getValueFromCronElement(split[4])
+ date.minute = getValueFromCronElement(split[5])
+ date.second = getValueFromCronElement(split[6])
+ date.unit = getValueFromCronElement(split[7])
+ }
+ return date
+ }
+
+ private fun getValueFromCronElement(token: String): Int? {
+ return try {
+ token.toInt()
+ } catch (e: NumberFormatException) {
+ null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/notification/android/src/main/java/NotificationStorage.kt b/plugins/notification/android/src/main/java/NotificationStorage.kt
new file mode 100644
index 00000000..bfddfcc2
--- /dev/null
+++ b/plugins/notification/android/src/main/java/NotificationStorage.kt
@@ -0,0 +1,131 @@
+package app.tauri.notification
+
+import android.content.Context
+import android.content.SharedPreferences
+import app.tauri.plugin.JSObject
+import org.json.JSONException
+import java.text.ParseException
+
+// Key for private preferences
+private const val NOTIFICATION_STORE_ID = "NOTIFICATION_STORE"
+// Key used to save action types
+private const val ACTION_TYPES_ID = "ACTION_TYPE_STORE"
+
+class NotificationStorage(private val context: Context) {
+ fun appendNotifications(localNotifications: List) {
+ val storage = getStorage(NOTIFICATION_STORE_ID)
+ val editor = storage.edit()
+ for (request in localNotifications) {
+ if (request.isScheduled) {
+ val key: String = request.id.toString()
+ editor.putString(key, request.source.toString())
+ }
+ }
+ editor.apply()
+ }
+
+ fun getSavedNotificationIds(): List {
+ val storage = getStorage(NOTIFICATION_STORE_ID)
+ val all = storage.all
+ return if (all != null) {
+ ArrayList(all.keys)
+ } else ArrayList()
+ }
+
+ fun getSavedNotifications(): List {
+ val storage = getStorage(NOTIFICATION_STORE_ID)
+ val all = storage.all
+ if (all != null) {
+ val notifications = ArrayList()
+ for (key in all.keys) {
+ val notificationString = all[key] as String?
+ val jsNotification = getNotificationFromJSONString(notificationString)
+ if (jsNotification != null) {
+ try {
+ val notification =
+ Notification.fromJSObject(jsNotification)
+ notifications.add(notification)
+ } catch (_: ParseException) {
+ }
+ }
+ }
+ return notifications
+ }
+ return ArrayList()
+ }
+
+ private fun getNotificationFromJSONString(notificationString: String?): JSObject? {
+ if (notificationString == null) {
+ return null
+ }
+ val jsNotification = try {
+ JSObject(notificationString)
+ } catch (ex: JSONException) {
+ return null
+ }
+ return jsNotification
+ }
+
+ fun getSavedNotificationAsJSObject(key: String?): JSObject? {
+ val storage = getStorage(NOTIFICATION_STORE_ID)
+ val notificationString = try {
+ storage.getString(key, null)
+ } catch (ex: ClassCastException) {
+ return null
+ } ?: return null
+
+ val jsNotification = try {
+ JSObject(notificationString)
+ } catch (ex: JSONException) {
+ return null
+ }
+ return jsNotification
+ }
+
+ fun getSavedNotification(key: String?): Notification? {
+ val jsNotification = getSavedNotificationAsJSObject(key) ?: return null
+ val notification = try {
+ Notification.fromJSObject(jsNotification)
+ } catch (ex: ParseException) {
+ return null
+ }
+ return notification
+ }
+
+ fun deleteNotification(id: String?) {
+ val editor = getStorage(NOTIFICATION_STORE_ID).edit()
+ editor.remove(id)
+ editor.apply()
+ }
+
+ private fun getStorage(key: String): SharedPreferences {
+ return context.getSharedPreferences(key, Context.MODE_PRIVATE)
+ }
+
+ fun writeActionGroup(typesMap: Map>) {
+ for ((id, notificationActions) in typesMap) {
+ val editor = getStorage(ACTION_TYPES_ID + id).edit()
+ editor.clear()
+ editor.putInt("count", notificationActions.size)
+ for (i in notificationActions.indices) {
+ editor.putString("id$i", notificationActions[i].id)
+ editor.putString("title$i", notificationActions[i].title)
+ editor.putBoolean("input$i", notificationActions[i].input)
+ }
+ editor.apply()
+ }
+ }
+
+ fun getActionGroup(forId: String): Array {
+ val storage = getStorage(ACTION_TYPES_ID + forId)
+ val count = storage.getInt("count", 0)
+ val actions: Array = arrayOfNulls(count)
+ for (i in 0 until count) {
+ val id = storage.getString("id$i", "")
+ val title = storage.getString("title$i", "")
+ val input = storage.getBoolean("input$i", false)
+ actions[i] = NotificationAction(id, title, input)
+ }
+ return actions
+ }
+}
\ No newline at end of file
diff --git a/plugins/notification/android/src/main/java/TauriNotificationManager.kt b/plugins/notification/android/src/main/java/TauriNotificationManager.kt
new file mode 100644
index 00000000..79e67908
--- /dev/null
+++ b/plugins/notification/android/src/main/java/TauriNotificationManager.kt
@@ -0,0 +1,569 @@
+package app.tauri.notification
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.AlarmManager
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.media.AudioAttributes
+import android.net.Uri
+import android.os.Build
+import android.os.Build.VERSION.SDK_INT
+import android.os.UserManager
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.app.RemoteInput
+import app.tauri.Logger
+import app.tauri.plugin.JSObject
+import app.tauri.plugin.PluginManager
+import org.json.JSONException
+import org.json.JSONObject
+import java.text.SimpleDateFormat
+import java.util.Date
+
+// Action constants
+const val NOTIFICATION_INTENT_KEY = "NotificationId"
+const val NOTIFICATION_OBJ_INTENT_KEY = "LocalNotficationObject"
+const val ACTION_INTENT_KEY = "NotificationUserAction"
+const val NOTIFICATION_IS_REMOVABLE_KEY = "NotificationRepeating"
+const val REMOTE_INPUT_KEY = "NotificationRemoteInput"
+const val DEFAULT_NOTIFICATION_CHANNEL_ID = "default"
+const val DEFAULT_PRESS_ACTION = "tap"
+
+class TauriNotificationManager(
+ private val storage: NotificationStorage,
+ private val activity: Activity?,
+ private val context: Context,
+ private val config: JSObject
+) {
+ private var defaultSoundID: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE
+ private var defaultSmallIconID: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE
+
+ fun handleNotificationActionPerformed(
+ data: Intent,
+ notificationStorage: NotificationStorage
+ ): JSObject? {
+ Logger.debug(Logger.tags("Notification"), "Notification received: " + data.dataString)
+ val notificationId =
+ data.getIntExtra(NOTIFICATION_INTENT_KEY, Int.MIN_VALUE)
+ if (notificationId == Int.MIN_VALUE) {
+ Logger.debug(Logger.tags("Notification"), "Activity started without notification attached")
+ return null
+ }
+ val isRemovable =
+ data.getBooleanExtra(NOTIFICATION_IS_REMOVABLE_KEY, true)
+ if (isRemovable) {
+ notificationStorage.deleteNotification(notificationId.toString())
+ }
+ val dataJson = JSObject()
+ val results = RemoteInput.getResultsFromIntent(data)
+ val input = results?.getCharSequence(REMOTE_INPUT_KEY)
+ dataJson.put("inputValue", input?.toString())
+ val menuAction = data.getStringExtra(ACTION_INTENT_KEY)
+ dismissVisibleNotification(notificationId)
+ dataJson.put("actionId", menuAction)
+ var request: JSONObject? = null
+ try {
+ val notificationJsonString =
+ data.getStringExtra(NOTIFICATION_OBJ_INTENT_KEY)
+ if (notificationJsonString != null) {
+ request = JSObject(notificationJsonString)
+ }
+ } catch (_: JSONException) {
+ }
+ dataJson.put("notification", request)
+ return dataJson
+ }
+
+ /**
+ * Create notification channel
+ */
+ fun createNotificationChannel() {
+ // Create the NotificationChannel, but only on API 26+ because
+ // the NotificationChannel class is new and not in the support library
+ if (SDK_INT >= Build.VERSION_CODES.O) {
+ val name: CharSequence = "Default"
+ val description = "Default"
+ val importance = NotificationManager.IMPORTANCE_DEFAULT
+ val channel = NotificationChannel(DEFAULT_NOTIFICATION_CHANNEL_ID, name, importance)
+ channel.description = description
+ val audioAttributes = AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .build()
+ val soundUri = getDefaultSoundUrl(context)
+ if (soundUri != null) {
+ channel.setSound(soundUri, audioAttributes)
+ }
+ // Register the channel with the system; you can't change the importance
+ // or other notification behaviors after this
+ val notificationManager = context.getSystemService(
+ NotificationManager::class.java
+ )
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
+ private fun trigger(notificationManager: NotificationManagerCompat, notification: Notification): Int {
+ dismissVisibleNotification(notification.id)
+ cancelTimerForNotification(notification.id)
+ buildNotification(notificationManager, notification)
+
+ return notification.id
+ }
+
+ fun schedule(notification: Notification): Int {
+ val notificationManager = NotificationManagerCompat.from(context)
+ return trigger(notificationManager, notification)
+ }
+
+ fun schedule(notifications: List): List {
+ val ids = mutableListOf()
+ val notificationManager = NotificationManagerCompat.from(context)
+
+ for (notification in notifications) {
+ val id = trigger(notificationManager, notification)
+ ids.add(id)
+ }
+
+ return ids
+ }
+
+ // TODO Progressbar support
+ // TODO System categories (DO_NOT_DISTURB etc.)
+ // TODO use NotificationCompat.MessagingStyle for latest API
+ // TODO expandable notification NotificationCompat.MessagingStyle
+ // TODO media style notification support NotificationCompat.MediaStyle
+ @SuppressLint("MissingPermission")
+ private fun buildNotification(
+ notificationManager: NotificationManagerCompat,
+ notification: Notification,
+ ) {
+ val channelId = notification.channelId ?: DEFAULT_NOTIFICATION_CHANNEL_ID
+ val mBuilder = NotificationCompat.Builder(
+ context, channelId
+ )
+ .setContentTitle(notification.title)
+ .setContentText(notification.body)
+ .setAutoCancel(notification.isAutoCancel)
+ .setOngoing(notification.isOngoing)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setGroupSummary(notification.isGroupSummary)
+ if (notification.largeBody != null) {
+ // support multiline text
+ mBuilder.setStyle(
+ NotificationCompat.BigTextStyle()
+ .bigText(notification.largeBody)
+ .setSummaryText(notification.summary)
+ )
+ } else if (notification.inboxLines != null) {
+ val inboxStyle = NotificationCompat.InboxStyle()
+ for (line in notification.inboxLines ?: listOf()) {
+ inboxStyle.addLine(line)
+ }
+ inboxStyle.setBigContentTitle(notification.title)
+ inboxStyle.setSummaryText(notification.summary)
+ mBuilder.setStyle(inboxStyle)
+ }
+ val sound = notification.getSound(context, getDefaultSound(context))
+ if (sound != null) {
+ val soundUri = Uri.parse(sound)
+ // Grant permission to use sound
+ context.grantUriPermission(
+ "com.android.systemui",
+ soundUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ )
+ mBuilder.setSound(soundUri)
+ mBuilder.setDefaults(android.app.Notification.DEFAULT_VIBRATE or android.app.Notification.DEFAULT_LIGHTS)
+ } else {
+ mBuilder.setDefaults(android.app.Notification.DEFAULT_ALL)
+ }
+ val group = notification.group
+ if (group != null) {
+ mBuilder.setGroup(group)
+ if (notification.isGroupSummary) {
+ mBuilder.setSubText(notification.summary)
+ }
+ }
+ mBuilder.setVisibility(notification.visibility ?: NotificationCompat.VISIBILITY_PRIVATE)
+ mBuilder.setOnlyAlertOnce(true)
+ mBuilder.setSmallIcon(notification.getSmallIcon(context, getDefaultSmallIcon(context)))
+ mBuilder.setLargeIcon(notification.getLargeIcon(context))
+ val iconColor = notification.getIconColor(config.getString("iconColor"))
+ if (iconColor.isNotEmpty()) {
+ try {
+ mBuilder.color = Color.parseColor(iconColor)
+ } catch (ex: IllegalArgumentException) {
+ throw Exception("Invalid color provided. Must be a hex string (ex: #ff0000")
+ }
+ }
+ createActionIntents(notification, mBuilder)
+ // notificationId is a unique int for each notification that you must define
+ val buildNotification = mBuilder.build()
+ if (notification.isScheduled) {
+ triggerScheduledNotification(buildNotification, notification)
+ } else {
+ notificationManager.notify(notification.id, buildNotification)
+ try {
+ NotificationPlugin.triggerNotification(notification.source ?: JSObject())
+ } catch (_: JSONException) {
+ }
+ }
+ }
+
+ // Create intents for open/dismiss actions
+ private fun createActionIntents(
+ notification: Notification,
+ mBuilder: NotificationCompat.Builder
+ ) {
+ // Open intent
+ val intent = buildIntent(notification, DEFAULT_PRESS_ACTION)
+ var flags = PendingIntent.FLAG_CANCEL_CURRENT
+ if (SDK_INT >= Build.VERSION_CODES.S) {
+ flags = flags or PendingIntent.FLAG_MUTABLE
+ }
+ val pendingIntent = PendingIntent.getActivity(context, notification.id, intent, flags)
+ mBuilder.setContentIntent(pendingIntent)
+
+ // Build action types
+ val actionTypeId = notification.actionTypeId
+ if (actionTypeId != null) {
+ val actionGroup = storage.getActionGroup(actionTypeId)
+ for (notificationAction in actionGroup) {
+ // TODO Add custom icons to actions
+ val actionIntent = buildIntent(notification, notificationAction!!.id)
+ val actionPendingIntent = PendingIntent.getActivity(
+ context,
+ (notification.id) + notificationAction.id.hashCode(),
+ actionIntent,
+ flags
+ )
+ val actionBuilder: NotificationCompat.Action.Builder = NotificationCompat.Action.Builder(
+ R.drawable.ic_transparent,
+ notificationAction.title,
+ actionPendingIntent
+ )
+ if (notificationAction.input) {
+ val remoteInput = RemoteInput.Builder(REMOTE_INPUT_KEY).setLabel(
+ notificationAction.title
+ ).build()
+ actionBuilder.addRemoteInput(remoteInput)
+ }
+ mBuilder.addAction(actionBuilder.build())
+ }
+ }
+
+ // Dismiss intent
+ val dissmissIntent = Intent(
+ context,
+ NotificationDismissReceiver::class.java
+ )
+ dissmissIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ dissmissIntent.putExtra(NOTIFICATION_INTENT_KEY, notification.id)
+ dissmissIntent.putExtra(ACTION_INTENT_KEY, "dismiss")
+ val schedule = notification.schedule
+ dissmissIntent.putExtra(
+ NOTIFICATION_IS_REMOVABLE_KEY,
+ schedule == null || schedule.isRemovable()
+ )
+ flags = 0
+ if (SDK_INT >= Build.VERSION_CODES.S) {
+ flags = PendingIntent.FLAG_MUTABLE
+ }
+ val deleteIntent =
+ PendingIntent.getBroadcast(context, notification.id, dissmissIntent, flags)
+ mBuilder.setDeleteIntent(deleteIntent)
+ }
+
+ private fun buildIntent(notification: Notification, action: String?): Intent {
+ val intent = if (activity != null) {
+ Intent(context, activity.javaClass)
+ } else {
+ val packageName = context.packageName
+ context.packageManager.getLaunchIntentForPackage(packageName)!!
+ }
+ intent.action = Intent.ACTION_MAIN
+ intent.addCategory(Intent.CATEGORY_LAUNCHER)
+ intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ intent.putExtra(NOTIFICATION_INTENT_KEY, notification.id)
+ intent.putExtra(ACTION_INTENT_KEY, action)
+ intent.putExtra(NOTIFICATION_OBJ_INTENT_KEY, notification.source.toString())
+ val schedule = notification.schedule
+ intent.putExtra(NOTIFICATION_IS_REMOVABLE_KEY, schedule == null || schedule.isRemovable())
+ return intent
+ }
+
+ /**
+ * Build a notification trigger, such as triggering each N seconds, or
+ * on a certain date "shape" (such as every first of the month)
+ */
+ // TODO support different AlarmManager.RTC modes depending on priority
+ @SuppressLint("SimpleDateFormat")
+ private fun triggerScheduledNotification(notification: android.app.Notification, request: Notification) {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val schedule = request.schedule
+ val notificationIntent = Intent(
+ context,
+ TimedNotificationPublisher::class.java
+ )
+ notificationIntent.putExtra(NOTIFICATION_INTENT_KEY, request.id)
+ notificationIntent.putExtra(TimedNotificationPublisher.NOTIFICATION_KEY, notification)
+ var flags = PendingIntent.FLAG_CANCEL_CURRENT
+ if (SDK_INT >= Build.VERSION_CODES.S) {
+ flags = flags or PendingIntent.FLAG_MUTABLE
+ }
+ var pendingIntent =
+ PendingIntent.getBroadcast(context, request.id, notificationIntent, flags)
+
+ when (val scheduleKind = schedule?.kind) {
+ is ScheduleKind.At -> {
+ val at = scheduleKind.date
+ if (at.time < Date().time) {
+ Logger.error(Logger.tags("Notification"), "Scheduled time must be *after* current time", null)
+ return
+ }
+ if (scheduleKind.repeating) {
+ val interval: Long = at.time - Date().time
+ alarmManager.setRepeating(AlarmManager.RTC, at.time, interval, pendingIntent)
+ } else {
+ setExactIfPossible(alarmManager, schedule, at.time, pendingIntent)
+ }
+ }
+ is ScheduleKind.Interval -> {
+ val trigger = scheduleKind.interval.nextTrigger(Date())
+ notificationIntent.putExtra(TimedNotificationPublisher.CRON_KEY, scheduleKind.interval.toMatchString())
+ pendingIntent =
+ PendingIntent.getBroadcast(context, request.id, notificationIntent, flags)
+ setExactIfPossible(alarmManager, schedule, trigger, pendingIntent)
+ val sdf = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
+ Logger.debug(
+ Logger.tags("Notification"),
+ "notification " + request.id + " will next fire at " + sdf.format(Date(trigger))
+ )
+ }
+ is ScheduleKind.Every -> {
+ val everyInterval = getIntervalTime(scheduleKind.interval, scheduleKind.count)
+ val startTime: Long = Date().time + everyInterval
+ alarmManager.setRepeating(AlarmManager.RTC, startTime, everyInterval, pendingIntent)
+ }
+ else -> {}
+ }
+ }
+
+ @SuppressLint("ObsoleteSdkInt", "MissingPermission")
+ private fun setExactIfPossible(
+ alarmManager: AlarmManager,
+ schedule: NotificationSchedule,
+ trigger: Long,
+ pendingIntent: PendingIntent
+ ) {
+ if (SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
+ if (SDK_INT >= Build.VERSION_CODES.M && schedule.whileIdle) {
+ alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, trigger, pendingIntent)
+ } else {
+ alarmManager[AlarmManager.RTC, trigger] = pendingIntent
+ }
+ } else {
+ if (SDK_INT >= Build.VERSION_CODES.M && schedule.whileIdle) {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, trigger, pendingIntent)
+ } else {
+ alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent)
+ }
+ }
+ }
+
+ fun cancel(notifications: List) {
+ for (id in notifications) {
+ dismissVisibleNotification(id)
+ cancelTimerForNotification(id)
+ storage.deleteNotification(id.toString())
+ }
+ }
+
+ private fun cancelTimerForNotification(notificationId: Int) {
+ val intent = Intent(context, TimedNotificationPublisher::class.java)
+ var flags = 0
+ if (SDK_INT >= Build.VERSION_CODES.S) {
+ flags = PendingIntent.FLAG_MUTABLE
+ }
+ val pi = PendingIntent.getBroadcast(context, notificationId, intent, flags)
+ if (pi != null) {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ alarmManager.cancel(pi)
+ }
+ }
+
+ private fun dismissVisibleNotification(notificationId: Int) {
+ val notificationManager = NotificationManagerCompat.from(
+ context
+ )
+ notificationManager.cancel(notificationId)
+ }
+
+ fun areNotificationsEnabled(): Boolean {
+ val notificationManager = NotificationManagerCompat.from(context)
+ return notificationManager.areNotificationsEnabled()
+ }
+
+ private fun getDefaultSoundUrl(context: Context): Uri? {
+ val soundId = getDefaultSound(context)
+ return if (soundId != AssetUtils.RESOURCE_ID_ZERO_VALUE) {
+ Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + soundId)
+ } else null
+ }
+
+ private fun getDefaultSound(context: Context): Int {
+ if (defaultSoundID != AssetUtils.RESOURCE_ID_ZERO_VALUE) return defaultSoundID
+ var resId: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE
+ val soundConfigResourceName = AssetUtils.getResourceBaseName(config.getString("sound"))
+ if (soundConfigResourceName != null) {
+ resId = AssetUtils.getResourceID(context, soundConfigResourceName, "raw")
+ }
+ defaultSoundID = resId
+ return resId
+ }
+
+ private fun getDefaultSmallIcon(context: Context): Int {
+ if (defaultSmallIconID != AssetUtils.RESOURCE_ID_ZERO_VALUE) return defaultSmallIconID
+ var resId: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE
+ val smallIconConfigResourceName = AssetUtils.getResourceBaseName(config.getString("icon"))
+ if (smallIconConfigResourceName != null) {
+ resId = AssetUtils.getResourceID(context, smallIconConfigResourceName, "drawable")
+ }
+ if (resId == AssetUtils.RESOURCE_ID_ZERO_VALUE) {
+ resId = android.R.drawable.ic_dialog_info
+ }
+ defaultSmallIconID = resId
+ return resId
+ }
+}
+
+class NotificationDismissReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val intExtra =
+ intent.getIntExtra(NOTIFICATION_INTENT_KEY, Int.MIN_VALUE)
+ if (intExtra == Int.MIN_VALUE) {
+ Logger.error(Logger.tags("Notification"), "Invalid notification dismiss operation", null)
+ return
+ }
+ val isRemovable =
+ intent.getBooleanExtra(NOTIFICATION_IS_REMOVABLE_KEY, true)
+ if (isRemovable) {
+ val notificationStorage = NotificationStorage(context)
+ notificationStorage.deleteNotification(intExtra.toString())
+ }
+ }
+}
+
+class TimedNotificationPublisher : BroadcastReceiver() {
+ /**
+ * Restore and present notification
+ */
+ override fun onReceive(context: Context, intent: Intent) {
+ val notificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ val notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(
+ NOTIFICATION_KEY,
+ android.app.Notification::class.java
+ )
+ } else {
+ getParcelableExtraLegacy(intent, NOTIFICATION_KEY)
+ }
+ notification?.`when` = System.currentTimeMillis()
+ val id = intent.getIntExtra(NOTIFICATION_INTENT_KEY, Int.MIN_VALUE)
+ if (id == Int.MIN_VALUE) {
+ Logger.error(Logger.tags("Notification"), "No valid id supplied", null)
+ }
+ val storage = NotificationStorage(context)
+ val notificationJson = storage.getSavedNotificationAsJSObject(id.toString())
+ if (notificationJson != null) {
+ NotificationPlugin.triggerNotification(notificationJson)
+ }
+ notificationManager.notify(id, notification)
+ if (!rescheduleNotificationIfNeeded(context, intent, id)) {
+ storage.deleteNotification(id.toString())
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun getParcelableExtraLegacy(intent: Intent, string: String): android.app.Notification? {
+ return intent.getParcelableExtra(string)
+ }
+
+ @SuppressLint("MissingPermission", "SimpleDateFormat")
+ private fun rescheduleNotificationIfNeeded(context: Context, intent: Intent, id: Int): Boolean {
+ val dateString = intent.getStringExtra(CRON_KEY)
+ if (dateString != null) {
+ val date = DateMatch.fromMatchString(dateString)
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val trigger = date.nextTrigger(Date())
+ val clone = intent.clone() as Intent
+ var flags = PendingIntent.FLAG_CANCEL_CURRENT
+ if (SDK_INT >= Build.VERSION_CODES.S) {
+ flags = flags or PendingIntent.FLAG_MUTABLE
+ }
+ val pendingIntent = PendingIntent.getBroadcast(context, id, clone, flags)
+ if (SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
+ alarmManager[AlarmManager.RTC, trigger] = pendingIntent
+ } else {
+ alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent)
+ }
+ val sdf = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
+ Logger.debug(
+ Logger.tags("Notification"),
+ "notification " + id + " will next fire at " + sdf.format(Date(trigger))
+ )
+ return true
+ }
+ return false
+ }
+
+ companion object {
+ var NOTIFICATION_KEY = "NotificationPublisher.notification"
+ var CRON_KEY = "NotificationPublisher.cron"
+ }
+}
+
+class LocalNotificationRestoreReceiver : BroadcastReceiver() {
+ @SuppressLint("ObsoleteSdkInt")
+ override fun onReceive(context: Context, intent: Intent) {
+ if (SDK_INT >= Build.VERSION_CODES.N) {
+ val um = context.getSystemService(
+ UserManager::class.java
+ )
+ if (um == null || !um.isUserUnlocked) return
+ }
+ val storage = NotificationStorage(context)
+ val ids = storage.getSavedNotificationIds()
+ val notifications = mutableListOf()
+ val updatedNotifications = mutableListOf()
+ for (id in ids) {
+ val notification = storage.getSavedNotification(id) ?: continue
+ val schedule = notification.schedule
+ if (schedule != null && schedule.kind is ScheduleKind.At) {
+ val at: Date = schedule.kind.date
+ if (at.before(Date())) {
+ // modify the scheduled date in order to show notifications that would have been delivered while device was off.
+ val newDateTime = Date().time + 15 * 1000
+ schedule.kind.date = Date(newDateTime)
+ updatedNotifications.add(notification)
+ }
+ }
+ notifications.add(notification)
+ }
+ if (updatedNotifications.size > 0) {
+ storage.appendNotifications(updatedNotifications)
+ }
+
+ val notificationManager = TauriNotificationManager(storage, null, context, PluginManager.loadConfig(context, "notification"))
+ notificationManager.schedule(notifications)
+ }
+}
diff --git a/plugins/notification/android/src/main/res/drawable/ic_transparent.xml b/plugins/notification/android/src/main/res/drawable/ic_transparent.xml
new file mode 100644
index 00000000..fc1779e2
--- /dev/null
+++ b/plugins/notification/android/src/main/res/drawable/ic_transparent.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/plugins/notification/guest-js/index.ts b/plugins/notification/guest-js/index.ts
index 7a186795..78919356 100644
--- a/plugins/notification/guest-js/index.ts
+++ b/plugins/notification/guest-js/index.ts
@@ -24,7 +24,7 @@
* @module
*/
-import { invoke } from "@tauri-apps/api/tauri";
+import { invoke, transformCallback } from "@tauri-apps/api/tauri";
/**
* Options to send a notification.
@@ -32,12 +32,272 @@ import { invoke } from "@tauri-apps/api/tauri";
* @since 1.0.0
*/
interface Options {
- /** Notification title. */
+ /**
+ * The notification identifier to reference this object later. Must be a 32-bit integer.
+ */
+ id?: number;
+ /**
+ * Identifier of the {@link Channel} that deliveres this notification.
+ *
+ * If the channel does not exist, the notification won't fire.
+ * Make sure the channel exists with {@link listChannels} and {@link createChannel}.
+ */
+ channelId?: string;
+ /**
+ * Notification title.
+ */
title: string;
- /** Optional notification body. */
+ /**
+ * Optional notification body.
+ * */
body?: string;
- /** Optional notification icon. */
+ /**
+ * Schedule this notification to fire on a later time or a fixed interval.
+ */
+ schedule?: Schedule;
+ /**
+ * Multiline text.
+ * Changes the notification style to big text.
+ * Cannot be used with `inboxLines`.
+ */
+ largeBody?: string;
+ /**
+ * Detail text for the notification with `largeBody`, `inboxLines` or `groupSummary`.
+ */
+ summary?: string;
+ /**
+ * Defines an action type for this notification.
+ */
+ actionTypeId?: string;
+ /**
+ * Identifier used to group multiple notifications.
+ *
+ * https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent/1649872-threadidentifier
+ */
+ group?: string;
+ /**
+ * Instructs the system that this notification is the summary of a group on Android.
+ */
+ groupSummary?: boolean;
+ /**
+ * The sound resource name. Only available on mobile.
+ */
+ sound?: string;
+ /**
+ * List of lines to add to the notification.
+ * Changes the notification style to inbox.
+ * Cannot be used with `largeBody`.
+ *
+ * Only supports up to 5 lines.
+ */
+ inboxLines?: string[];
+ /**
+ * Notification icon.
+ *
+ * On Android the icon must be placed in the app's `res/drawable` folder.
+ */
icon?: string;
+ /**
+ * Notification large icon (Android).
+ *
+ * The icon must be placed in the app's `res/drawable` folder.
+ */
+ largeIcon?: string;
+ /**
+ * Icon color on Android.
+ */
+ iconColor?: string;
+ /**
+ * Notification attachments.
+ */
+ attachments?: Attachment[];
+ /**
+ * Extra payload to store in the notification.
+ */
+ extra?: { [key: string]: unknown };
+ /**
+ * If true, the notification cannot be dismissed by the user on Android.
+ *
+ * An application service must manage the dismissal of the notification.
+ * It is typically used to indicate a background task that is pending (e.g. a file download)
+ * or the user is engaged with (e.g. playing music).
+ */
+ ongoing?: boolean;
+ /**
+ * Automatically cancel the notification when the user clicks on it.
+ */
+ autoCancel?: boolean;
+ /**
+ * Changes the notification presentation to be silent on iOS (no badge, no sound, not listed).
+ */
+ silent?: boolean;
+ /**
+ * Notification visibility.
+ */
+ visibility?: Visibility;
+ /**
+ * Sets the number of items this notification represents on Android.
+ */
+ number?: number;
+}
+
+type ScheduleInterval = {
+ year?: number;
+ month?: number;
+ day?: number;
+ /**
+ * 1 - Sunday
+ * 2 - Monday
+ * 3 - Tuesday
+ * 4 - Wednesday
+ * 5 - Thursday
+ * 6 - Friday
+ * 7 - Saturday
+ */
+ weekday?: number;
+ hour?: number;
+ minute?: number;
+ second?: number;
+};
+
+enum ScheduleEvery {
+ Year = "Year",
+ Month = "Month",
+ TwoWeeks = "TwoWeeks",
+ Week = "Week",
+ Day = "Day",
+ Hour = "Hour",
+ Minute = "Minute",
+ /**
+ * Not supported on iOS.
+ */
+ Second = "Second",
+}
+
+type ScheduleData =
+ | {
+ kind: "At";
+ data: {
+ date: Date;
+ repeating: boolean;
+ };
+ }
+ | {
+ kind: "Interval";
+ data: ScheduleInterval;
+ }
+ | {
+ kind: "Every";
+ data: {
+ interval: ScheduleEvery;
+ };
+ };
+
+class Schedule {
+ kind: string;
+ data: unknown;
+
+ private constructor(schedule: ScheduleData) {
+ this.kind = schedule.kind;
+ this.data = schedule.data;
+ }
+
+ static at(date: Date, repeating = false) {
+ return new Schedule({ kind: "At", data: { date, repeating } });
+ }
+
+ static interval(interval: ScheduleInterval) {
+ return new Schedule({ kind: "Interval", data: interval });
+ }
+
+ static every(kind: ScheduleEvery) {
+ return new Schedule({ kind: "Every", data: { interval: kind } });
+ }
+}
+
+/**
+ * Attachment of a notification.
+ */
+interface Attachment {
+ /** Attachment identifier. */
+ id: string;
+ /** Attachment URL. Accepts the `asset` and `file` protocols. */
+ url: string;
+}
+
+interface Action {
+ id: string;
+ title: string;
+ requiresAuthentication?: boolean;
+ foreground?: boolean;
+ destructive?: boolean;
+ input?: boolean;
+ inputButtonTitle?: string;
+ inputPlaceholder?: string;
+}
+
+interface ActionType {
+ /**
+ * The identifier of this action type
+ */
+ id: string;
+ /**
+ * The list of associated actions
+ */
+ actions: Action[];
+ hiddenPreviewsBodyPlaceholder?: string;
+ customDismissAction?: boolean;
+ allowInCarPlay?: boolean;
+ hiddenPreviewsShowTitle?: boolean;
+ hiddenPreviewsShowSubtitle?: boolean;
+}
+
+interface PendingNotification {
+ id: number;
+ title?: string;
+ body?: string;
+ schedule: Schedule;
+}
+
+interface ActiveNotification {
+ id: number;
+ tag?: string;
+ title?: string;
+ body?: string;
+ group?: string;
+ groupSummary: boolean;
+ data: Record;
+ extra: Record;
+ attachments: Attachment[];
+ actionTypeId?: string;
+ schedule?: Schedule;
+ sound?: string;
+}
+
+enum Importance {
+ None = 0,
+ Min,
+ Low,
+ Default,
+ High,
+}
+
+enum Visibility {
+ Secret = -1,
+ Private,
+ Public,
+}
+
+interface Channel {
+ id: string;
+ name: string;
+ description?: string;
+ sound?: string;
+ lights?: boolean;
+ lightColor?: string;
+ vibration?: boolean;
+ importance?: Importance;
+ visibility?: Visibility;
}
/** Possible permission values. */
@@ -108,6 +368,271 @@ function sendNotification(options: Options | string): void {
}
}
-export type { Options, Permission };
+/**
+ * Register actions that are performed when the user clicks on the notification.
+ *
+ * @example
+ * ```typescript
+ * import { registerActionTypes } from '@tauri-apps/api/notification';
+ * await registerActionTypes([{
+ * id: 'tauri',
+ * actions: [{
+ * id: 'my-action',
+ * title: 'Settings'
+ * }]
+ * }])
+ * ```
+ *
+ * @returns A promise indicating the success or failure of the operation.
+ *
+ * @since 2.0.0
+ */
+async function registerActionTypes(types: ActionType[]): Promise {
+ return invoke("plugin:notification|register_action_types", { types });
+}
+
+/**
+ * Retrieves the list of pending notifications.
+ *
+ * @example
+ * ```typescript
+ * import { pending } from '@tauri-apps/api/notification';
+ * const pendingNotifications = await pending();
+ * ```
+ *
+ * @returns A promise resolving to the list of pending notifications.
+ *
+ * @since 2.0.0
+ */
+async function pending(): Promise {
+ return invoke("plugin:notification|get_pending");
+}
+
+/**
+ * Cancels the pending notifications with the given list of identifiers.
+ *
+ * @example
+ * ```typescript
+ * import { cancel } from '@tauri-apps/api/notification';
+ * await cancel([-34234, 23432, 4311]);
+ * ```
+ *
+ * @returns A promise indicating the success or failure of the operation.
+ *
+ * @since 2.0.0
+ */
+async function cancel(notifications: number[]): Promise {
+ return invoke("plugin:notification|cancel", { notifications });
+}
+
+/**
+ * Cancels all pending notifications.
+ *
+ * @example
+ * ```typescript
+ * import { cancelAll } from '@tauri-apps/api/notification';
+ * await cancelAll();
+ * ```
+ *
+ * @returns A promise indicating the success or failure of the operation.
+ *
+ * @since 2.0.0
+ */
+async function cancelAll(): Promise {
+ return invoke("plugin:notification|cancel");
+}
+
+/**
+ * Retrieves the list of active notifications.
+ *
+ * @example
+ * ```typescript
+ * import { active } from '@tauri-apps/api/notification';
+ * const activeNotifications = await active();
+ * ```
+ *
+ * @returns A promise resolving to the list of active notifications.
+ *
+ * @since 2.0.0
+ */
+async function active(): Promise {
+ return invoke("plugin:notification|get_active");
+}
+
+/**
+ * Removes the active notifications with the given list of identifiers.
+ *
+ * @example
+ * ```typescript
+ * import { cancel } from '@tauri-apps/api/notification';
+ * await cancel([-34234, 23432, 4311])
+ * ```
+ *
+ * @returns A promise indicating the success or failure of the operation.
+ *
+ * @since 2.0.0
+ */
+async function removeActive(notifications: number[]): Promise {
+ return invoke("plugin:notification|remove_active", { notifications });
+}
+
+/**
+ * Removes all active notifications.
+ *
+ * @example
+ * ```typescript
+ * import { removeAllActive } from '@tauri-apps/api/notification';
+ * await removeAllActive()
+ * ```
+ *
+ * @returns A promise indicating the success or failure of the operation.
+ *
+ * @since 2.0.0
+ */
+async function removeAllActive(): Promise {
+ return invoke("plugin:notification|remove_active");
+}
+
+/**
+ * Removes all active notifications.
+ *
+ * @example
+ * ```typescript
+ * import { createChannel, Importance, Visibility } from '@tauri-apps/api/notification';
+ * await createChannel({
+ * id: 'new-messages',
+ * name: 'New Messages',
+ * lights: true,
+ * vibration: true,
+ * importance: Importance.Default,
+ * visibility: Visibility.Private
+ * });
+ * ```
+ *
+ * @returns A promise indicating the success or failure of the operation.
+ *
+ * @since 2.0.0
+ */
+async function createChannel(channel: Channel): Promise {
+ return invoke("plugin:notification|create_channel", { ...channel });
+}
+
+/**
+ * Removes the channel with the given identifier.
+ *
+ * @example
+ * ```typescript
+ * import { removeChannel } from '@tauri-apps/api/notification';
+ * await removeChannel();
+ * ```
+ *
+ * @returns A promise indicating the success or failure of the operation.
+ *
+ * @since 2.0.0
+ */
+async function removeChannel(id: string): Promise {
+ return invoke("plugin:notification|delete_channel", { id });
+}
+
+/**
+ * Retrieves the list of notification channels.
+ *
+ * @example
+ * ```typescript
+ * import { channels } from '@tauri-apps/api/notification';
+ * const notificationChannels = await channels();
+ * ```
+ *
+ * @returns A promise resolving to the list of notification channels.
+ *
+ * @since 2.0.0
+ */
+async function channels(): Promise {
+ return invoke("plugin:notification|getActive");
+}
+
+class EventChannel {
+ id: number;
+ unregisterFn: (channel: EventChannel) => Promise;
+
+ constructor(
+ id: number,
+ unregisterFn: (channel: EventChannel) => Promise
+ ) {
+ this.id = id;
+ this.unregisterFn = unregisterFn;
+ }
+
+ toJSON(): string {
+ return `__CHANNEL__:${this.id}`;
+ }
+
+ async unregister(): Promise {
+ return this.unregisterFn(this);
+ }
+}
+
+// TODO: use addPluginListener API on @tauri-apps/api/tauri 2.0.0-alpha.4
+async function onNotificationReceived(
+ cb: (notification: Options) => void
+): Promise {
+ const channelId = transformCallback(cb);
+ const handler = new EventChannel(channelId, (channel) =>
+ invoke("plugin:notification|remove_listener", {
+ event: "notification",
+ channelId: channel.id,
+ })
+ );
+ return invoke("plugin:notification|register_listener", {
+ event: "notification",
+ handler,
+ }).then(() => handler);
+}
+
+// TODO: use addPluginListener API on @tauri-apps/api/tauri 2.0.0-alpha.4
+async function onAction(
+ cb: (notification: Options) => void
+): Promise {
+ const channelId = transformCallback(cb);
+ const handler = new EventChannel(channelId, (channel) =>
+ invoke("plugin:notification|remove_listener", {
+ event: "actionPerformed",
+ channelId: channel.id,
+ })
+ );
+ return invoke("plugin:notification|register_listener", {
+ event: "actionPerformed",
+ handler,
+ }).then(() => handler);
+}
+
+export type {
+ Attachment,
+ Options,
+ Permission,
+ Action,
+ ActionType,
+ PendingNotification,
+ ActiveNotification,
+ Channel,
+};
-export { sendNotification, requestPermission, isPermissionGranted };
+export {
+ Importance,
+ Visibility,
+ sendNotification,
+ requestPermission,
+ isPermissionGranted,
+ registerActionTypes,
+ pending,
+ cancel,
+ cancelAll,
+ active,
+ removeActive,
+ removeAllActive,
+ createChannel,
+ removeChannel,
+ channels,
+ onNotificationReceived,
+ onAction,
+};
diff --git a/plugins/notification/ios/Package.swift b/plugins/notification/ios/Package.swift
index ff9991fa..bfcaf338 100644
--- a/plugins/notification/ios/Package.swift
+++ b/plugins/notification/ios/Package.swift
@@ -4,16 +4,16 @@
import PackageDescription
let package = Package(
- name: "tauri-plugin-{{ plugin_name }}",
+ name: "tauri-plugin-notification",
platforms: [
.iOS(.v13),
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
- name: "tauri-plugin-{{ plugin_name }}",
+ name: "tauri-plugin-notification",
type: .static,
- targets: ["tauri-plugin-{{ plugin_name }}"]),
+ targets: ["tauri-plugin-notification"]),
],
dependencies: [
.package(name: "Tauri", path: "../.tauri/tauri-api")
@@ -22,7 +22,7 @@ let package = Package(
// 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-{{ plugin_name }}",
+ name: "tauri-plugin-notification",
dependencies: [
.byName(name: "Tauri")
],
diff --git a/plugins/notification/ios/Sources/Notification.swift b/plugins/notification/ios/Sources/Notification.swift
new file mode 100644
index 00000000..52b1016f
--- /dev/null
+++ b/plugins/notification/ios/Sources/Notification.swift
@@ -0,0 +1,272 @@
+import Tauri
+import UserNotifications
+
+enum NotificationError: LocalizedError {
+ case contentNoId
+ case contentNoTitle
+ case contentNoBody
+ case triggerRepeatIntervalTooShort
+ case attachmentNoId
+ case attachmentNoUrl
+ case attachmentFileNotFound(path: String)
+ case attachmentUnableToCreate(String)
+ case pastScheduledTime
+ case invalidDate(String)
+
+ var errorDescription: String? {
+ switch self {
+ case .contentNoId:
+ return "Missing notification identifier"
+ case .contentNoTitle:
+ return "Missing notification title"
+ case .contentNoBody:
+ return "Missing notification body"
+ case .triggerRepeatIntervalTooShort:
+ return "Schedule interval too short, must be a least 1 minute"
+ case .attachmentNoId:
+ return "Missing attachment identifier"
+ case .attachmentNoUrl:
+ return "Missing attachment URL"
+ case .attachmentFileNotFound(let path):
+ return "Unable to find file \(path) for attachment"
+ case .attachmentUnableToCreate(let error):
+ return "Failed to create attachment: \(error)"
+ case .pastScheduledTime:
+ return "Scheduled time must be *after* current time"
+ case .invalidDate(let date):
+ return "Could not parse date \(date)"
+ }
+ }
+}
+
+func makeNotificationContent(_ notification: JSObject) throws -> UNNotificationContent {
+ guard let title = notification["title"] as? String else {
+ throw NotificationError.contentNoTitle
+ }
+ guard let body = notification["body"] as? String else {
+ throw NotificationError.contentNoBody
+ }
+
+ let extra = notification["extra"] as? JSObject ?? [:]
+ let schedule = notification["schedule"] as? JSObject ?? [:]
+ let content = UNMutableNotificationContent()
+ content.title = NSString.localizedUserNotificationString(forKey: title, arguments: nil)
+ content.body = NSString.localizedUserNotificationString(
+ forKey: body,
+ arguments: nil)
+
+ content.userInfo = [
+ "__EXTRA__": extra,
+ "__SCHEDULE__": schedule,
+ ]
+
+ if let actionTypeId = notification["actionTypeId"] as? String {
+ content.categoryIdentifier = actionTypeId
+ }
+
+ if let threadIdentifier = notification["group"] as? String {
+ content.threadIdentifier = threadIdentifier
+ }
+
+ if let summaryArgument = notification["summary"] as? String {
+ content.summaryArgument = summaryArgument
+ }
+
+ if let sound = notification["sound"] as? String {
+ content.sound = UNNotificationSound(named: UNNotificationSoundName(sound))
+ }
+
+ if let attachments = notification["attachments"] as? [JSObject] {
+ content.attachments = try makeAttachments(attachments)
+ }
+
+ return content
+}
+
+func makeAttachments(_ attachments: [JSObject]) throws -> [UNNotificationAttachment] {
+ var createdAttachments = [UNNotificationAttachment]()
+
+ for attachment in attachments {
+ guard let id = attachment["id"] as? String else {
+ throw NotificationError.attachmentNoId
+ }
+ guard let url = attachment["url"] as? String else {
+ throw NotificationError.attachmentNoUrl
+ }
+ guard let urlObject = makeAttachmentUrl(url) else {
+ throw NotificationError.attachmentFileNotFound(path: url)
+ }
+
+ let options = attachment["options"] as? JSObject ?? [:]
+
+ do {
+ let newAttachment = try UNNotificationAttachment(
+ identifier: id, url: urlObject, options: makeAttachmentOptions(options))
+ createdAttachments.append(newAttachment)
+ } catch {
+ throw NotificationError.attachmentUnableToCreate(error.localizedDescription)
+ }
+ }
+
+ return createdAttachments
+}
+
+func makeAttachmentUrl(_ path: String) -> URL? {
+ return URL(string: path)
+}
+
+func makeAttachmentOptions(_ options: JSObject) -> JSObject {
+ var opts: JSObject = [:]
+
+ if let iosUNNotificationAttachmentOptionsTypeHintKey = options[
+ "iosUNNotificationAttachmentOptionsTypeHintKey"] as? String
+ {
+ opts[UNNotificationAttachmentOptionsTypeHintKey] = iosUNNotificationAttachmentOptionsTypeHintKey
+ }
+ if let iosUNNotificationAttachmentOptionsThumbnailHiddenKey = options[
+ "iosUNNotificationAttachmentOptionsThumbnailHiddenKey"] as? String
+ {
+ opts[UNNotificationAttachmentOptionsThumbnailHiddenKey] =
+ iosUNNotificationAttachmentOptionsThumbnailHiddenKey
+ }
+ if let iosUNNotificationAttachmentOptionsThumbnailClippingRectKey = options[
+ "iosUNNotificationAttachmentOptionsThumbnailClippingRectKey"] as? String
+ {
+ opts[UNNotificationAttachmentOptionsThumbnailClippingRectKey] =
+ iosUNNotificationAttachmentOptionsThumbnailClippingRectKey
+ }
+ if let iosUNNotificationAttachmentOptionsThumbnailTimeKey = options[
+ "iosUNNotificationAttachmentOptionsThumbnailTimeKey"] as? String
+ {
+ opts[UNNotificationAttachmentOptionsThumbnailTimeKey] =
+ iosUNNotificationAttachmentOptionsThumbnailTimeKey
+ }
+ return opts
+}
+
+func handleScheduledNotification(_ schedule: JSObject) throws
+ -> UNNotificationTrigger?
+{
+ let kind = schedule["kind"] as? String ?? ""
+ let payload = schedule["data"] as? JSObject ?? [:]
+ switch kind {
+ case "At":
+ let date = payload["date"] as? String ?? ""
+ let dateFormatter = DateFormatter()
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX")
+ dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
+
+ if let at = dateFormatter.date(from: date) {
+ let repeats = payload["repeats"] as? Bool ?? false
+
+ let dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: at)
+
+ if dateInfo.date! < Date() {
+ throw NotificationError.pastScheduledTime
+ }
+
+ let dateInterval = DateInterval(start: Date(), end: dateInfo.date!)
+
+ // Notifications that repeat have to be at least a minute between each other
+ if repeats && dateInterval.duration < 60 {
+ throw NotificationError.triggerRepeatIntervalTooShort
+ }
+
+ return UNTimeIntervalNotificationTrigger(
+ timeInterval: dateInterval.duration, repeats: repeats)
+
+ } else {
+ throw NotificationError.invalidDate(date)
+ }
+ case "Interval":
+ let dateComponents = getDateComponents(payload)
+ return UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
+ case "Every":
+ let interval = payload["interval"] as? String ?? ""
+ let count = schedule["count"] as? Int ?? 1
+
+ if let repeatDateInterval = getRepeatDateInterval(interval, count) {
+ // Notifications that repeat have to be at least a minute between each other
+ if repeatDateInterval.duration < 60 {
+ throw NotificationError.triggerRepeatIntervalTooShort
+ }
+
+ return UNTimeIntervalNotificationTrigger(
+ timeInterval: repeatDateInterval.duration, repeats: true)
+ }
+
+ default:
+ return nil
+ }
+
+ return nil
+}
+
+/// Given our schedule format, return a DateComponents object
+/// that only contains the components passed in.
+
+func getDateComponents(_ at: JSObject) -> DateComponents {
+ // var dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
+ // dateInfo.calendar = Calendar.current
+ var dateInfo = DateComponents()
+
+ if let year = at["year"] as? Int {
+ dateInfo.year = year
+ }
+ if let month = at["month"] as? Int {
+ dateInfo.month = month
+ }
+ if let day = at["day"] as? Int {
+ dateInfo.day = day
+ }
+ if let hour = at["hour"] as? Int {
+ dateInfo.hour = hour
+ }
+ if let minute = at["minute"] as? Int {
+ dateInfo.minute = minute
+ }
+ if let second = at["second"] as? Int {
+ dateInfo.second = second
+ }
+ if let weekday = at["weekday"] as? Int {
+ dateInfo.weekday = weekday
+ }
+ return dateInfo
+}
+
+/// Compute the difference between the string representation of a date
+/// interval and today. For example, if every is "month", then we
+/// return the interval between today and a month from today.
+
+func getRepeatDateInterval(_ every: String, _ count: Int) -> DateInterval? {
+ let cal = Calendar.current
+ let now = Date()
+ switch every {
+ case "Year":
+ let newDate = cal.date(byAdding: .year, value: count, to: now)!
+ return DateInterval(start: now, end: newDate)
+ case "Month":
+ let newDate = cal.date(byAdding: .month, value: count, to: now)!
+ return DateInterval(start: now, end: newDate)
+ case "TwoWeeks":
+ let newDate = cal.date(byAdding: .weekOfYear, value: 2 * count, to: now)!
+ return DateInterval(start: now, end: newDate)
+ case "Week":
+ let newDate = cal.date(byAdding: .weekOfYear, value: count, to: now)!
+ return DateInterval(start: now, end: newDate)
+ case "Day":
+ let newDate = cal.date(byAdding: .day, value: count, to: now)!
+ return DateInterval(start: now, end: newDate)
+ case "Hour":
+ let newDate = cal.date(byAdding: .hour, value: count, to: now)!
+ return DateInterval(start: now, end: newDate)
+ case "Minute":
+ let newDate = cal.date(byAdding: .minute, value: count, to: now)!
+ return DateInterval(start: now, end: newDate)
+ case "Second":
+ let newDate = cal.date(byAdding: .second, value: count, to: now)!
+ return DateInterval(start: now, end: newDate)
+ default:
+ return nil
+ }
+}
diff --git a/plugins/notification/ios/Sources/NotificationCategory.swift b/plugins/notification/ios/Sources/NotificationCategory.swift
new file mode 100644
index 00000000..74a1e194
--- /dev/null
+++ b/plugins/notification/ios/Sources/NotificationCategory.swift
@@ -0,0 +1,131 @@
+import Tauri
+import UserNotifications
+
+enum CategoryError: LocalizedError {
+ case noId
+ case noActionId
+
+ var errorDescription: String? {
+ switch self {
+ case .noId:
+ return "Action type `id` missing"
+ case .noActionId:
+ return "Action `id` missing"
+ }
+ }
+}
+
+public func makeCategories(_ actionTypes: [JSObject]) throws {
+ var createdCategories = [UNNotificationCategory]()
+
+ let generalCategory = UNNotificationCategory(
+ identifier: "GENERAL",
+ actions: [],
+ intentIdentifiers: [],
+ options: .customDismissAction)
+
+ createdCategories.append(generalCategory)
+ for type in actionTypes {
+ guard let id = type["id"] as? String else {
+ throw CategoryError.noId
+ }
+ let hiddenBodyPlaceholder = type["hiddenPreviewsBodyPlaceholder"] as? String ?? ""
+ let actions = type["actions"] as? [JSObject] ?? []
+
+ let newActions = try makeActions(actions)
+
+ // Create the custom actions for the TIMER_EXPIRED category.
+ var newCategory: UNNotificationCategory?
+
+ newCategory = UNNotificationCategory(
+ identifier: id,
+ actions: newActions,
+ intentIdentifiers: [],
+ hiddenPreviewsBodyPlaceholder: hiddenBodyPlaceholder,
+ options: makeCategoryOptions(type))
+
+ createdCategories.append(newCategory!)
+ }
+
+ let center = UNUserNotificationCenter.current()
+ center.setNotificationCategories(Set(createdCategories))
+}
+
+func makeActions(_ actions: [JSObject]) throws -> [UNNotificationAction] {
+ var createdActions = [UNNotificationAction]()
+
+ for action in actions {
+ guard let id = action["id"] as? String else {
+ throw CategoryError.noActionId
+ }
+ let title = action["title"] as? String ?? ""
+ let input = action["input"] as? Bool ?? false
+
+ var newAction: UNNotificationAction
+ if input {
+ let inputButtonTitle = action["inputButtonTitle"] as? String
+ let inputPlaceholder = action["inputPlaceholder"] as? String ?? ""
+
+ if inputButtonTitle != nil {
+ newAction = UNTextInputNotificationAction(
+ identifier: id,
+ title: title,
+ options: makeActionOptions(action),
+ textInputButtonTitle: inputButtonTitle!,
+ textInputPlaceholder: inputPlaceholder)
+ } else {
+ newAction = UNTextInputNotificationAction(
+ identifier: id, title: title, options: makeActionOptions(action))
+ }
+ } else {
+ // Create the custom actions for the TIMER_EXPIRED category.
+ newAction = UNNotificationAction(
+ identifier: id,
+ title: title,
+ options: makeActionOptions(action))
+ }
+ createdActions.append(newAction)
+ }
+
+ return createdActions
+}
+
+func makeActionOptions(_ action: JSObject) -> UNNotificationActionOptions {
+ let foreground = action["foreground"] as? Bool ?? false
+ let destructive = action["destructive"] as? Bool ?? false
+ let requiresAuthentication = action["requiresAuthentication"] as? Bool ?? false
+
+ if foreground {
+ return .foreground
+ }
+ if destructive {
+ return .destructive
+ }
+ if requiresAuthentication {
+ return .authenticationRequired
+ }
+ return UNNotificationActionOptions(rawValue: 0)
+}
+
+func makeCategoryOptions(_ type: JSObject) -> UNNotificationCategoryOptions {
+ let customDismiss = type["customDismissAction"] as? Bool ?? false
+ let carPlay = type["allowInCarPlay"] as? Bool ?? false
+ let hiddenPreviewsShowTitle = type["hiddenPreviewsShowTitle"] as? Bool ?? false
+ let hiddenPreviewsShowSubtitle = type["hiddenPreviewsShowSubtitle"] as? Bool ?? false
+
+ if customDismiss {
+ return .customDismissAction
+ }
+ if carPlay {
+ return .allowInCarPlay
+ }
+
+ if hiddenPreviewsShowTitle {
+ return .hiddenPreviewsShowTitle
+ }
+ if hiddenPreviewsShowSubtitle {
+ return .hiddenPreviewsShowSubtitle
+ }
+
+ return UNNotificationCategoryOptions(rawValue: 0)
+}
diff --git a/plugins/notification/ios/Sources/NotificationHandler.swift b/plugins/notification/ios/Sources/NotificationHandler.swift
new file mode 100644
index 00000000..1f7cb8ba
--- /dev/null
+++ b/plugins/notification/ios/Sources/NotificationHandler.swift
@@ -0,0 +1,116 @@
+import Tauri
+import UserNotifications
+
+public class NotificationHandler: NSObject, NotificationHandlerProtocol {
+
+ public weak var plugin: Plugin?
+
+ private var notificationsMap = [String: JSObject]()
+
+ public func saveNotification(_ key: String, _ notification: JSObject) {
+ notificationsMap.updateValue(notification, forKey: key)
+ }
+
+ public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) {
+ let center = UNUserNotificationCenter.current()
+ center.requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
+ completion?(granted, error)
+ }
+ }
+
+ public func checkPermissions(with completion: ((UNAuthorizationStatus) -> Void)? = nil) {
+ let center = UNUserNotificationCenter.current()
+ center.getNotificationSettings { settings in
+ completion?(settings.authorizationStatus)
+ }
+ }
+
+ public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions {
+ let notificationData = makeNotificationRequestJSObject(notification.request)
+ self.plugin?.trigger("notification", data: notificationData)
+
+ if let options = notificationsMap[notification.request.identifier] {
+ let silent = options["silent"] as? Bool ?? false
+ if silent {
+ return UNNotificationPresentationOptions.init(rawValue: 0)
+ }
+ }
+
+ return [
+ .badge,
+ .sound,
+ .alert,
+ ]
+ }
+
+ public func didReceive(response: UNNotificationResponse) {
+ var data = JSObject()
+
+ let originalNotificationRequest = response.notification.request
+ let actionId = response.actionIdentifier
+
+ // We turn the two default actions (open/dismiss) into generic strings
+ if actionId == UNNotificationDefaultActionIdentifier {
+ data["actionId"] = "tap"
+ } else if actionId == UNNotificationDismissActionIdentifier {
+ data["actionId"] = "dismiss"
+ } else {
+ data["actionId"] = actionId
+ }
+
+ // If the type of action was for an input type, get the value
+ if let inputType = response as? UNTextInputNotificationResponse {
+ data["inputValue"] = inputType.userText
+ }
+
+ data["notification"] = makeNotificationRequestJSObject(originalNotificationRequest)
+
+ self.plugin?.trigger("actionPerformed", data: data)
+ }
+
+ /**
+ * Turn a UNNotificationRequest into a JSObject to return back to the client.
+ */
+ func makeNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject {
+ let notificationRequest = notificationsMap[request.identifier] ?? [:]
+ var notification = makePendingNotificationRequestJSObject(request)
+ notification["sound"] = notificationRequest["sound"] ?? ""
+ notification["actionTypeId"] = request.content.categoryIdentifier
+ notification["attachments"] = notificationRequest["attachments"] ?? [JSObject]()
+ return notification
+ }
+
+ func makePendingNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject {
+ var notification: JSObject = [
+ "id": Int(request.identifier) ?? -1,
+ "title": request.content.title,
+ "body": request.content.body,
+ ]
+
+ if let userInfo = JSTypes.coerceDictionaryToJSObject(request.content.userInfo) {
+ var extra = userInfo["__EXTRA__"] as? JSObject ?? userInfo
+
+ // check for any dates and convert them to strings
+ for (key, value) in extra {
+ if let date = value as? Date {
+ let dateString = ISO8601DateFormatter().string(from: date)
+ extra[key] = dateString
+ }
+ }
+
+ notification["extra"] = extra
+
+ if var schedule = userInfo["__SCHEDULE__"] as? JSObject {
+ // convert schedule at date to string
+ if let date = schedule["at"] as? Date {
+ let dateString = ISO8601DateFormatter().string(from: date)
+ schedule["at"] = dateString
+ }
+
+ notification["schedule"] = schedule
+ }
+ }
+
+ return notification
+ }
+}
diff --git a/plugins/notification/ios/Sources/NotificationManager.swift b/plugins/notification/ios/Sources/NotificationManager.swift
new file mode 100644
index 00000000..857636fb
--- /dev/null
+++ b/plugins/notification/ios/Sources/NotificationManager.swift
@@ -0,0 +1,39 @@
+import Foundation
+import UserNotifications
+
+@objc public protocol NotificationHandlerProtocol {
+ func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions
+ func didReceive(response: UNNotificationResponse)
+}
+
+@objc public class NotificationManager: NSObject, UNUserNotificationCenterDelegate {
+ public weak var notificationHandler: NotificationHandlerProtocol?
+
+ override init() {
+ super.init()
+ let center = UNUserNotificationCenter.current()
+ center.delegate = self
+ }
+
+ public func userNotificationCenter(_ center: UNUserNotificationCenter,
+ willPresent notification: UNNotification,
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+ var presentationOptions: UNNotificationPresentationOptions? = nil
+
+ if notification.request.trigger?.isKind(of: UNPushNotificationTrigger.self) != true {
+ presentationOptions = notificationHandler?.willPresent(notification: notification)
+ }
+
+ completionHandler(presentationOptions ?? [])
+ }
+
+ public func userNotificationCenter(_ center: UNUserNotificationCenter,
+ didReceive response: UNNotificationResponse,
+ withCompletionHandler completionHandler: @escaping () -> Void) {
+ if response.notification.request.trigger?.isKind(of: UNPushNotificationTrigger.self) != true {
+ notificationHandler?.didReceive(response: response)
+ }
+
+ completionHandler()
+ }
+}
diff --git a/plugins/notification/ios/Sources/NotificationPlugin.swift b/plugins/notification/ios/Sources/NotificationPlugin.swift
index 3d520a92..217c999d 100644
--- a/plugins/notification/ios/Sources/NotificationPlugin.swift
+++ b/plugins/notification/ios/Sources/NotificationPlugin.swift
@@ -1,24 +1,209 @@
+import SwiftRs
+import Tauri
import UIKit
+import UserNotifications
import WebKit
-import Tauri
-import SwiftRs
+
+enum ShowNotificationError: LocalizedError {
+ case noId
+ case make(Error)
+ case create(Error)
+
+ var errorDescription: String? {
+ switch self {
+ case .noId:
+ return "notification `id` missing"
+ case .make(let error):
+ return "Unable to make notification: \(error)"
+ case .create(let error):
+ return "Unable to create notification: \(error)"
+ }
+ }
+}
+
+func showNotification(invoke: Invoke, notification: JSObject)
+ throws -> UNNotificationRequest
+{
+ guard let identifier = notification["id"] as? Int else {
+ throw ShowNotificationError.noId
+ }
+
+ var content: UNNotificationContent
+ do {
+ content = try makeNotificationContent(notification)
+ } catch {
+ throw ShowNotificationError.make(error)
+ }
+
+ var trigger: UNNotificationTrigger?
+
+ do {
+ if let schedule = notification["schedule"] as? JSObject {
+ try trigger = handleScheduledNotification(schedule)
+ }
+ } catch {
+ throw ShowNotificationError.create(error)
+ }
+
+ // Schedule the request.
+ let request = UNNotificationRequest(
+ identifier: "\(identifier)", content: content, trigger: trigger
+ )
+
+ let center = UNUserNotificationCenter.current()
+ center.add(request) { (error: Error?) in
+ if let theError = error {
+ invoke.reject(theError.localizedDescription)
+ }
+ }
+
+ return request
+}
class NotificationPlugin: Plugin {
- @objc public func requestPermission(_ invoke: Invoke) throws {
- invoke.resolve(["permissionState": "granted"])
- }
-
- @objc public func permissionState(_ invoke: Invoke) throws {
- invoke.resolve(["permissionState": "granted"])
- }
-
- @objc public func notify(_ invoke: Invoke) throws {
- // TODO
- invoke.resolve()
- }
+ let notificationHandler = NotificationHandler()
+ let notificationManager = NotificationManager()
+
+ override init() {
+ super.init()
+ notificationManager.notificationHandler = notificationHandler
+ notificationHandler.plugin = self
+ }
+
+ @objc public func show(_ invoke: Invoke) throws {
+ let request = try showNotification(invoke: invoke, notification: invoke.data)
+ notificationHandler.saveNotification(request.identifier, invoke.data)
+ invoke.resolve([
+ "id": Int(request.identifier) ?? -1
+ ])
+ }
+
+ @objc public func batch(_ invoke: Invoke) throws {
+ guard let notifications = invoke.getArray("notifications", JSObject.self) else {
+ invoke.reject("`notifications` array is required")
+ return
+ }
+ var ids = [Int]()
+
+ for notification in notifications {
+ let request = try showNotification(invoke: invoke, notification: notification)
+ notificationHandler.saveNotification(request.identifier, notification)
+ ids.append(Int(request.identifier) ?? -1)
+ }
+
+ invoke.resolve([
+ "notifications": ids
+ ])
+ }
+
+ @objc public override func requestPermissions(_ invoke: Invoke) {
+ notificationHandler.requestPermissions { granted, error in
+ guard error == nil else {
+ invoke.reject(error!.localizedDescription)
+ return
+ }
+ invoke.resolve(["permissionState": granted ? "granted" : "denied"])
+ }
+ }
+
+ @objc public override func checkPermissions(_ invoke: Invoke) {
+ notificationHandler.checkPermissions { status in
+ let permission: String
+
+ switch status {
+ case .authorized, .ephemeral, .provisional:
+ permission = "granted"
+ case .denied:
+ permission = "denied"
+ case .notDetermined:
+ permission = "default"
+ @unknown default:
+ permission = "default"
+ }
+
+ invoke.resolve(["permissionState": permission])
+ }
+ }
+
+ @objc func cancel(_ invoke: Invoke) {
+ guard let notifications = invoke.getArray("notifications", NSNumber.self),
+ notifications.count > 0
+ else {
+ invoke.reject("`notifications` input is required")
+ return
+ }
+
+ UNUserNotificationCenter.current().removePendingNotificationRequests(
+ withIdentifiers: notifications.map({ (id) -> String in
+ return id.stringValue
+ })
+ )
+ invoke.resolve()
+ }
+
+ @objc func getPending(_ invoke: Invoke) {
+ UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: {
+ (notifications) in
+ let ret = notifications.compactMap({ [weak self] (notification) -> JSObject? in
+ return self?.notificationHandler.makePendingNotificationRequestJSObject(notification)
+ })
+
+ invoke.resolve([
+ "notifications": ret
+ ])
+ })
+ }
+
+ @objc func registerActionTypes(_ invoke: Invoke) throws {
+ guard let types = invoke.getArray("types", JSObject.self) else {
+ return
+ }
+ try makeCategories(types)
+ invoke.resolve()
+ }
+
+ @objc func removeActive(_ invoke: Invoke) {
+ if let notifications = invoke.getArray("notifications", JSObject.self) {
+ let ids = notifications.map { "\($0["id"] ?? "")" }
+ UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids)
+ invoke.resolve()
+ } else {
+ UNUserNotificationCenter.current().removeAllDeliveredNotifications()
+ DispatchQueue.main.async(execute: {
+ UIApplication.shared.applicationIconBadgeNumber = 0
+ })
+ invoke.resolve()
+ }
+ }
+
+ @objc func getActive(_ invoke: Invoke) {
+ UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: {
+ (notifications) in
+ let ret = notifications.map({ (notification) -> [String: Any] in
+ return self.notificationHandler.makeNotificationRequestJSObject(
+ notification.request)
+ })
+ invoke.resolve([
+ "notifications": ret
+ ])
+ })
+ }
+
+ @objc func createChannel(_ invoke: Invoke) {
+ invoke.reject("not implemented")
+ }
+
+ @objc func deleteChannel(_ invoke: Invoke) {
+ invoke.reject("not implemented")
+ }
+
+ @objc func listChannels(_ invoke: Invoke) {
+ invoke.reject("not implemented")
+ }
+
}
@_cdecl("init_plugin_notification")
-func initPlugin(name: SRString, webview: WKWebView?) {
- Tauri.registerPlugin(webview: webview, name: name.toString(), plugin: NotificationPlugin())
+func initPlugin() -> Plugin {
+ return NotificationPlugin()
}
diff --git a/plugins/notification/src/commands.rs b/plugins/notification/src/commands.rs
index 710235c1..4af85585 100644
--- a/plugins/notification/src/commands.rs
+++ b/plugins/notification/src/commands.rs
@@ -2,30 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
-use serde::Deserialize;
use tauri::{command, AppHandle, Runtime, State};
-use crate::{Notification, PermissionState, Result};
-
-/// 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,
-}
+use crate::{Notification, NotificationData, PermissionState, Result};
#[command]
pub(crate) async fn is_permission_granted(
_app: AppHandle,
notification: State<'_, Notification>,
-) -> Result {
- notification
- .permission_state()
- .map(|s| s == PermissionState::Granted)
+) -> Result> {
+ let state = notification.permission_state()?;
+ match state {
+ PermissionState::Granted => Ok(Some(true)),
+ PermissionState::Denied => Ok(Some(false)),
+ PermissionState::Unknown => Ok(None),
+ }
}
#[command]
@@ -40,15 +31,9 @@ pub(crate) async fn request_permission(
pub(crate) async fn notify(
_app: AppHandle,
notification: State<'_, Notification>,
- options: NotificationOptions,
+ options: NotificationData,
) -> Result<()> {
- let mut builder = notification.builder().title(options.title);
- if let Some(body) = options.body {
- builder = builder.body(body);
- }
- if let Some(icon) = options.icon {
- builder = builder.icon(icon);
- }
-
+ let mut builder = notification.builder();
+ builder.data = options;
builder.show()
}
diff --git a/plugins/notification/src/lib.rs b/plugins/notification/src/lib.rs
index cb63758a..6e566fe2 100644
--- a/plugins/notification/src/lib.rs
+++ b/plugins/notification/src/lib.rs
@@ -30,16 +30,6 @@ use desktop::Notification;
#[cfg(mobile)]
use mobile::Notification;
-#[derive(Debug, Default, Serialize)]
-struct NotificationData {
- /// The notification title.
- title: Option,
- /// The notification body.
- body: Option,
- /// The notification icon.
- icon: Option,
-}
-
/// The notification builder.
#[derive(Debug)]
pub struct NotificationBuilder {
@@ -47,7 +37,7 @@ pub struct NotificationBuilder {
app: AppHandle,
#[cfg(mobile)]
handle: PluginHandle,
- data: NotificationData,
+ pub(crate) data: NotificationData,
}
impl NotificationBuilder {
@@ -67,6 +57,21 @@ impl NotificationBuilder {
}
}
+ /// Sets the notification identifier.
+ pub fn id(mut self, id: i32) -> Self {
+ self.data.id = id;
+ self
+ }
+
+ /// Identifier of the {@link Channel} that deliveres this notification.
+ ///
+ /// If the channel does not exist, the notification won't fire.
+ /// Make sure the channel exists with {@link listChannels} and {@link createChannel}.
+ pub fn channel_id(mut self, id: impl Into) -> Self {
+ self.data.channel_id.replace(id.into());
+ self
+ }
+
/// Sets the notification title.
pub fn title(mut self, title: impl Into) -> Self {
self.data.title.replace(title.into());
@@ -79,11 +84,119 @@ impl NotificationBuilder {
self
}
- /// Sets the notification icon.
+ /// Schedule this notification to fire on a later time or a fixed interval.
+ pub fn schedule(mut self, schedule: Schedule) -> Self {
+ self.data.schedule.replace(schedule);
+ self
+ }
+
+ /// Multiline text.
+ /// Changes the notification style to big text.
+ /// Cannot be used with `inboxLines`.
+ pub fn large_body(mut self, large_body: impl Into) -> Self {
+ self.data.large_body.replace(large_body.into());
+ self
+ }
+
+ /// Detail text for the notification with `largeBody`, `inboxLines` or `groupSummary`.
+ pub fn summary(mut self, summary: impl Into) -> Self {
+ self.data.summary.replace(summary.into());
+ self
+ }
+
+ /// Defines an action type for this notification.
+ pub fn action_type_id(mut self, action_type_id: impl Into) -> Self {
+ self.data.action_type_id.replace(action_type_id.into());
+ self
+ }
+
+ /// Identifier used to group multiple notifications.
+ ///
+ /// https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent/1649872-threadidentifier
+ pub fn group(mut self, group: impl Into) -> Self {
+ self.data.group.replace(group.into());
+ self
+ }
+
+ /// Instructs the system that this notification is the summary of a group on Android.
+ pub fn group_summary(mut self) -> Self {
+ self.data.group_summary = true;
+ self
+ }
+
+ /// The sound resource name. Only available on mobile.
+ pub fn sound(mut self, sound: impl Into) -> Self {
+ self.data.sound.replace(sound.into());
+ self
+ }
+
+ /// Append an inbox line to the notification.
+ /// Changes the notification style to inbox.
+ /// Cannot be used with `largeBody`.
+ ///
+ /// Only supports up to 5 lines.
+ pub fn inbox_line(mut self, line: impl Into) -> Self {
+ self.data.inbox_lines.push(line.into());
+ self
+ }
+
+ /// Notification icon.
+ ///
+ /// On Android the icon must be placed in the app's `res/drawable` folder.
pub fn icon(mut self, icon: impl Into) -> Self {
self.data.icon.replace(icon.into());
self
}
+
+ /// Notification large icon (Android).
+ ///
+ /// The icon must be placed in the app's `res/drawable` folder.
+ pub fn large_icon(mut self, large_icon: impl Into) -> Self {
+ self.data.large_icon.replace(large_icon.into());
+ self
+ }
+
+ /// Icon color on Android.
+ pub fn icon_color(mut self, icon_color: impl Into) -> Self {
+ self.data.icon_color.replace(icon_color.into());
+ self
+ }
+
+ /// Append an attachment to the notification.
+ pub fn attachment(mut self, attachment: Attachment) -> Self {
+ self.data.attachments.push(attachment);
+ self
+ }
+
+ /// Adds an extra payload to store in the notification.
+ pub fn extra(mut self, key: impl Into, value: impl Serialize) -> Self {
+ self.data
+ .extra
+ .insert(key.into(), serde_json::to_value(value).unwrap());
+ self
+ }
+
+ /// If true, the notification cannot be dismissed by the user on Android.
+ ///
+ /// An application service must manage the dismissal of the notification.
+ /// It is typically used to indicate a background task that is pending (e.g. a file download)
+ /// or the user is engaged with (e.g. playing music).
+ pub fn ongoing(mut self) -> Self {
+ self.data.ongoing = true;
+ self
+ }
+
+ /// Automatically cancel the notification when the user clicks on it.
+ pub fn auto_cancel(mut self) -> Self {
+ self.data.auto_cancel = true;
+ self
+ }
+
+ /// Changes the notification presentation to be silent on iOS (no badge, no sound, not listed).
+ pub fn silent(mut self) -> Self {
+ self.data.silent = true;
+ self
+ }
}
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the notification APIs.
diff --git a/plugins/notification/src/mobile.rs b/plugins/notification/src/mobile.rs
index abd196ed..83513eef 100644
--- a/plugins/notification/src/mobile.rs
+++ b/plugins/notification/src/mobile.rs
@@ -10,6 +10,8 @@ use tauri::{
use crate::models::*;
+use std::collections::HashMap;
+
#[cfg(target_os = "android")]
const PLUGIN_IDENTIFIER: &str = "app.tauri.notification";
@@ -31,7 +33,8 @@ pub fn init(
impl crate::NotificationBuilder {
pub fn show(self) -> crate::Result<()> {
self.handle
- .run_mobile_plugin("notify", self.data)
+ .run_mobile_plugin::("show", self.data)
+ .map(|_| ())
.map_err(Into::into)
}
}
@@ -46,17 +49,121 @@ impl Notification {
pub fn request_permission(&self) -> crate::Result {
self.0
- .run_mobile_plugin::("requestPermission", ())
+ .run_mobile_plugin::("requestPermissions", ())
.map(|r| r.permission_state)
.map_err(Into::into)
}
pub fn permission_state(&self) -> crate::Result {
self.0
- .run_mobile_plugin::("permissionState", ())
+ .run_mobile_plugin::("checkPermissions", ())
.map(|r| r.permission_state)
.map_err(Into::into)
}
+
+ pub fn register_action_types(&self, types: Vec) -> crate::Result<()> {
+ let mut args = HashMap::new();
+ args.insert("types", types);
+ self.0
+ .run_mobile_plugin("registerActionTypes", args)
+ .map_err(Into::into)
+ }
+
+ pub fn remove_active(&self, notifications: Vec) -> crate::Result<()> {
+ let mut args = HashMap::new();
+ args.insert(
+ "notifications",
+ notifications
+ .into_iter()
+ .map(|id| {
+ let mut notification = HashMap::new();
+ notification.insert("id", id);
+ notification
+ })
+ .collect::>>(),
+ );
+ self.0
+ .run_mobile_plugin("removeActive", args)
+ .map_err(Into::into)
+ }
+
+ pub fn active(&self) -> crate::Result> {
+ self.0
+ .run_mobile_plugin::("getActive", ())
+ .map(|r| r.notifications)
+ .map_err(Into::into)
+ }
+
+ pub fn remove_all_active(&self) -> crate::Result<()> {
+ self.0
+ .run_mobile_plugin("removeActive", ())
+ .map_err(Into::into)
+ }
+
+ pub fn pending(&self) -> crate::Result> {
+ self.0
+ .run_mobile_plugin::("getPending", ())
+ .map(|r| r.notifications)
+ .map_err(Into::into)
+ }
+
+ /// Cancel pending notifications.
+ pub fn cancel(&self, notifications: Vec) -> crate::Result<()> {
+ let mut args = HashMap::new();
+ args.insert("notifications", notifications);
+ self.0.run_mobile_plugin("cancel", args).map_err(Into::into)
+ }
+
+ /// Cancel all pending notifications.
+ pub fn cancel_all(&self) -> crate::Result<()> {
+ self.0.run_mobile_plugin("cancel", ()).map_err(Into::into)
+ }
+
+ #[cfg(target_os = "android")]
+ pub fn create_channel(&self, channel: Channel) -> crate::Result<()> {
+ self.0
+ .run_mobile_plugin("createChannel", channel)
+ .map_err(Into::into)
+ }
+
+ #[cfg(target_os = "android")]
+ pub fn delete_channel(&self, id: impl Into) -> crate::Result<()> {
+ let mut args = HashMap::new();
+ args.insert("id", id.into());
+ self.0
+ .run_mobile_plugin("deleteChannel", args)
+ .map_err(Into::into)
+ }
+
+ #[cfg(target_os = "android")]
+ pub fn list_channels(&self) -> crate::Result> {
+ self.0
+ .run_mobile_plugin::("listChannels", ())
+ .map(|r| r.channels)
+ .map_err(Into::into)
+ }
+}
+
+#[cfg(target_os = "android")]
+#[derive(Deserialize)]
+struct ListChannelsResult {
+ channels: Vec,
+}
+
+#[derive(Deserialize)]
+struct PendingResponse {
+ notifications: Vec,
+}
+
+#[derive(Deserialize)]
+struct ActiveResponse {
+ notifications: Vec,
+}
+
+#[derive(Deserialize)]
+struct ShowResponse {
+ #[allow(dead_code)]
+ id: i32,
}
#[derive(Deserialize)]
diff --git a/plugins/notification/src/models.rs b/plugins/notification/src/models.rs
index d1cf0e4b..df2ae5c1 100644
--- a/plugins/notification/src/models.rs
+++ b/plugins/notification/src/models.rs
@@ -2,10 +2,201 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
-use std::fmt::Display;
+use std::{collections::HashMap, fmt::Display};
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
+use url::Url;
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Attachment {
+ id: String,
+ url: Url,
+}
+
+impl Attachment {
+ pub fn new(id: impl Into, url: Url) -> Self {
+ Self { id: id.into(), url }
+ }
+}
+
+#[derive(Debug, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ScheduleInterval {
+ pub year: Option,
+ pub month: Option,
+ pub day: Option,
+ pub weekday: Option,
+ pub hour: Option,
+ pub minute: Option,
+ pub second: Option,
+}
+
+#[derive(Debug)]
+pub enum ScheduleEvery {
+ Year,
+ Month,
+ TwoWeeks,
+ Week,
+ Day,
+ Hour,
+ Minute,
+ Second,
+}
+
+impl Display for ScheduleEvery {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::Year => "Year",
+ Self::Month => "Month",
+ Self::TwoWeeks => "TwoWeeks",
+ Self::Week => "Week",
+ Self::Day => "Day",
+ Self::Hour => "Hour",
+ Self::Minute => "Minute",
+ Self::Second => "Second",
+ }
+ )
+ }
+}
+
+impl Serialize for ScheduleEvery {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(self.to_string().as_ref())
+ }
+}
+
+impl<'de> Deserialize<'de> for ScheduleEvery {
+ fn deserialize(deserializer: D) -> std::result::Result
+ where
+ D: Deserializer<'de>,
+ {
+ let s = String::deserialize(deserializer)?;
+ match s.to_lowercase().as_str() {
+ "year" => Ok(Self::Year),
+ "month" => Ok(Self::Month),
+ "twoweeks" => Ok(Self::TwoWeeks),
+ "week" => Ok(Self::Week),
+ "day" => Ok(Self::Day),
+ "hour" => Ok(Self::Hour),
+ "minute" => Ok(Self::Minute),
+ "second" => Ok(Self::Second),
+ _ => Err(DeError::custom(format!("unknown every kind '{s}'"))),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(tag = "kind", content = "data")]
+pub enum Schedule {
+ At {
+ #[serde(
+ serialize_with = "iso8601::serialize",
+ deserialize_with = "time::serde::iso8601::deserialize"
+ )]
+ date: time::OffsetDateTime,
+ #[serde(default)]
+ repeating: bool,
+ },
+ Interval(ScheduleInterval),
+ Every {
+ interval: ScheduleEvery,
+ },
+}
+
+// custom ISO-8601 serialization that does not use 6 digits for years.
+mod iso8601 {
+ use serde::{ser::Error as _, Serialize, Serializer};
+ use time::{
+ format_description::well_known::iso8601::{Config, EncodedConfig},
+ format_description::well_known::Iso8601,
+ OffsetDateTime,
+ };
+
+ const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode();
+
+ pub fn serialize(
+ datetime: &OffsetDateTime,
+ serializer: S,
+ ) -> Result {
+ datetime
+ .format(&Iso8601::)
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct NotificationData {
+ #[serde(default = "default_id")]
+ pub(crate) id: i32,
+ pub(crate) channel_id: Option,
+ pub(crate) title: Option,
+ pub(crate) body: Option,
+ pub(crate) schedule: Option,
+ pub(crate) large_body: Option,
+ pub(crate) summary: Option,
+ pub(crate) action_type_id: Option,
+ pub(crate) group: Option,
+ #[serde(default)]
+ pub(crate) group_summary: bool,
+ pub(crate) sound: Option,
+ #[serde(default)]
+ pub(crate) inbox_lines: Vec,
+ pub(crate) icon: Option,
+ pub(crate) large_icon: Option,
+ pub(crate) icon_color: Option,
+ #[serde(default)]
+ pub(crate) attachments: Vec,
+ #[serde(default)]
+ pub(crate) extra: HashMap,
+ #[serde(default)]
+ pub(crate) ongoing: bool,
+ #[serde(default)]
+ pub(crate) auto_cancel: bool,
+ #[serde(default)]
+ pub(crate) silent: bool,
+}
+
+fn default_id() -> i32 {
+ rand::random()
+}
+
+impl Default for NotificationData {
+ fn default() -> Self {
+ Self {
+ id: default_id(),
+ channel_id: None,
+ title: None,
+ body: None,
+ schedule: None,
+ large_body: None,
+ summary: None,
+ action_type_id: None,
+ group: None,
+ group_summary: false,
+ sound: None,
+ inbox_lines: Vec::new(),
+ icon: None,
+ large_icon: None,
+ icon_color: None,
+ attachments: Vec::new(),
+ extra: Default::default(),
+ ongoing: false,
+ auto_cancel: false,
+ silent: false,
+ }
+ }
+}
+
/// Permission state.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionState {
@@ -13,6 +204,8 @@ pub enum PermissionState {
Granted,
/// Permission access has been denied.
Denied,
+ /// Unknown state. Must request permission.
+ Unknown,
}
impl Display for PermissionState {
@@ -20,6 +213,7 @@ impl Display for PermissionState {
match self {
Self::Granted => write!(f, "granted"),
Self::Denied => write!(f, "denied"),
+ Self::Unknown => write!(f, "Unknown"),
}
}
}
@@ -42,7 +236,274 @@ impl<'de> Deserialize<'de> for PermissionState {
match s.to_lowercase().as_str() {
"granted" => Ok(Self::Granted),
"denied" => Ok(Self::Denied),
+ "default" => Ok(Self::Unknown),
_ => Err(DeError::custom(format!("unknown permission state '{s}'"))),
}
}
}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PendingNotification {
+ id: i32,
+ title: Option,
+ body: Option,
+ schedule: Schedule,
+}
+
+impl PendingNotification {
+ pub fn id(&self) -> i32 {
+ self.id
+ }
+
+ pub fn title(&self) -> Option<&str> {
+ self.title.as_deref()
+ }
+
+ pub fn body(&self) -> Option<&str> {
+ self.body.as_deref()
+ }
+
+ pub fn schedule(&self) -> &Schedule {
+ &self.schedule
+ }
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ActiveNotification {
+ id: i32,
+ tag: Option,
+ title: Option,
+ body: Option,
+ group: Option,
+ #[serde(default)]
+ group_summary: bool,
+ #[serde(default)]
+ data: HashMap,
+ #[serde(default)]
+ extra: HashMap,
+ #[serde(default)]
+ attachments: Vec,
+ action_type_id: Option,
+ schedule: Option,
+ sound: Option,
+}
+
+impl ActiveNotification {
+ pub fn id(&self) -> i32 {
+ self.id
+ }
+
+ pub fn tag(&self) -> Option<&str> {
+ self.tag.as_deref()
+ }
+
+ pub fn title(&self) -> Option<&str> {
+ self.title.as_deref()
+ }
+
+ pub fn body(&self) -> Option<&str> {
+ self.body.as_deref()
+ }
+
+ pub fn group(&self) -> Option<&str> {
+ self.group.as_deref()
+ }
+
+ pub fn group_summary(&self) -> bool {
+ self.group_summary
+ }
+
+ pub fn data(&self) -> &HashMap {
+ &self.data
+ }
+
+ pub fn extra(&self) -> &HashMap {
+ &self.extra
+ }
+
+ pub fn attachments(&self) -> &[Attachment] {
+ &self.attachments
+ }
+
+ pub fn action_type_id(&self) -> Option<&str> {
+ self.action_type_id.as_deref()
+ }
+
+ pub fn schedule(&self) -> Option<&Schedule> {
+ self.schedule.as_ref()
+ }
+
+ pub fn sound(&self) -> Option<&str> {
+ self.sound.as_deref()
+ }
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ActionType {
+ id: String,
+ actions: Vec,
+ hidden_previews_body_placeholder: Option,
+ custom_dismiss_action: bool,
+ allow_in_car_play: bool,
+ hidden_previews_show_title: bool,
+ hidden_previews_show_subtitle: bool,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Action {
+ id: String,
+ title: String,
+ requires_authentication: bool,
+ foreground: bool,
+ destructive: bool,
+ input: bool,
+ input_button_title: Option,
+ input_placeholder: Option,
+}
+
+#[cfg(target_os = "android")]
+pub use android::*;
+
+#[cfg(target_os = "android")]
+mod android {
+ use serde::{Deserialize, Serialize};
+ use serde_repr::{Deserialize_repr, Serialize_repr};
+
+ #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
+ #[repr(u8)]
+ pub enum Importance {
+ None = 0,
+ Min = 1,
+ Low = 2,
+ Default = 3,
+ High = 4,
+ }
+
+ impl Default for Importance {
+ fn default() -> Self {
+ Self::Default
+ }
+ }
+
+ #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
+ #[repr(i8)]
+ pub enum Visibility {
+ Secret = -1,
+ Private = 0,
+ Public = 1,
+ }
+
+ #[derive(Debug, Serialize, Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Channel {
+ id: String,
+ name: String,
+ description: Option,
+ sound: Option,
+ lights: bool,
+ light_color: Option,
+ vibration: bool,
+ importance: Importance,
+ visibility: Option,
+ }
+
+ #[derive(Debug)]
+ pub struct ChannelBuilder(Channel);
+
+ impl Channel {
+ pub fn builder(id: impl Into, name: impl Into) -> ChannelBuilder {
+ ChannelBuilder(Self {
+ id: id.into(),
+ name: name.into(),
+ description: None,
+ sound: None,
+ lights: false,
+ light_color: None,
+ vibration: false,
+ importance: Default::default(),
+ visibility: None,
+ })
+ }
+
+ pub fn id(&self) -> &str {
+ &self.id
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn description(&self) -> Option<&str> {
+ self.description.as_deref()
+ }
+
+ pub fn sound(&self) -> Option<&str> {
+ self.sound.as_deref()
+ }
+
+ pub fn lights(&self) -> bool {
+ self.lights
+ }
+
+ pub fn light_color(&self) -> Option<&str> {
+ self.light_color.as_deref()
+ }
+
+ pub fn vibration(&self) -> bool {
+ self.vibration
+ }
+
+ pub fn importance(&self) -> Importance {
+ self.importance
+ }
+
+ pub fn visibility(&self) -> Option {
+ self.visibility
+ }
+ }
+
+ impl ChannelBuilder {
+ pub fn description(mut self, description: impl Into) -> Self {
+ self.0.description.replace(description.into());
+ self
+ }
+
+ pub fn sound(mut self, sound: impl Into) -> Self {
+ self.0.sound.replace(sound.into());
+ self
+ }
+
+ pub fn lights(mut self, lights: bool) -> Self {
+ self.0.lights = lights;
+ self
+ }
+
+ pub fn light_color(mut self, color: impl Into) -> Self {
+ self.0.light_color.replace(color.into());
+ self
+ }
+
+ pub fn vibration(mut self, vibration: bool) -> Self {
+ self.0.vibration = vibration;
+ self
+ }
+
+ pub fn importance(mut self, importance: Importance) -> Self {
+ self.0.importance = importance;
+ self
+ }
+
+ pub fn visibility(mut self, visibility: Visibility) -> Self {
+ self.0.visibility.replace(visibility);
+ self
+ }
+
+ pub fn build(self) -> Channel {
+ self.0
+ }
+ }
+}
diff --git a/plugins/shell/src/lib.rs b/plugins/shell/src/lib.rs
index 26e6d932..70c98dd9 100644
--- a/plugins/shell/src/lib.rs
+++ b/plugins/shell/src/lib.rs
@@ -46,8 +46,8 @@ impl Shell {
/// Open a (url) path with a default or specific browser opening program.
///
/// See [`crate::api::shell::open`] for how it handles security-related measures.
- pub fn open(&self, path: String, with: Option) -> Result<()> {
- open::open(&self.scope, path, with).map_err(Into::into)
+ pub fn open(&self, path: impl Into, with: Option) -> Result<()> {
+ open::open(&self.scope, path.into(), with).map_err(Into::into)
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 849e4626..02d03f7c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -50,6 +50,64 @@ importers:
specifier: ^5.0.4
version: 5.0.4
+ examples/api:
+ dependencies:
+ '@tauri-apps/api':
+ specifier: 2.0.0-alpha.3
+ version: 2.0.0-alpha.3
+ '@tauri-apps/cli':
+ specifier: 2.0.0-alpha.8
+ version: 2.0.0-alpha.8
+ '@zerodevx/svelte-json-view':
+ specifier: 0.2.1
+ version: 0.2.1
+ tauri-plugin-cli-api:
+ specifier: 0.0.0
+ version: link:../../plugins/cli
+ tauri-plugin-clipboard-api:
+ specifier: 0.0.0
+ version: link:../../plugins/clipboard
+ tauri-plugin-dialog-api:
+ specifier: 0.0.0
+ version: link:../../plugins/dialog
+ tauri-plugin-fs-api:
+ specifier: 0.0.0
+ version: link:../../plugins/fs
+ tauri-plugin-global-shortcut-api:
+ specifier: 0.0.0
+ version: link:../../plugins/global-shortcut
+ tauri-plugin-http-api:
+ specifier: 0.0.0
+ version: link:../../plugins/http
+ tauri-plugin-notification-api:
+ specifier: 0.0.0
+ version: link:../../plugins/notification
+ tauri-plugin-shell-api:
+ specifier: 0.0.0
+ version: link:../../plugins/shell
+ devDependencies:
+ '@iconify-json/codicon':
+ specifier: ^1.1.10
+ version: 1.1.10
+ '@iconify-json/ph':
+ specifier: ^1.1.1
+ version: 1.1.1
+ '@sveltejs/vite-plugin-svelte':
+ specifier: ^1.0.1
+ version: 1.0.1(svelte@3.55.1)(vite@3.0.9)
+ internal-ip:
+ specifier: ^7.0.0
+ version: 7.0.0
+ svelte:
+ specifier: ^3.49.0
+ version: 3.55.1
+ unocss:
+ specifier: ^0.39.3
+ version: 0.39.3(vite@3.0.9)
+ vite:
+ specifier: ^3.0.9
+ version: 3.0.9
+
plugins/authenticator:
dependencies:
'@tauri-apps/api':
@@ -311,6 +369,17 @@ importers:
packages:
+ /@antfu/install-pkg@0.1.1:
+ resolution: {integrity: sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==}
+ dependencies:
+ execa: 5.1.1
+ find-up: 5.0.0
+ dev: true
+
+ /@antfu/utils@0.5.2:
+ resolution: {integrity: sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==}
+ dev: true
+
/@esbuild/android-arm64@0.17.18:
resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==}
engines: {node: '>=12'}
@@ -575,6 +644,35 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true
+ /@iconify-json/codicon@1.1.10:
+ resolution: {integrity: sha512-xx3nX5k4UeDQnpX9D1T6L1RCEwyLtqu3Lqk9plYK+SoBSQ/kR73bPy9WbYyDVOw2MDw50JCSpZZYlBC718k7Sg==}
+ dependencies:
+ '@iconify/types': 1.1.0
+ dev: true
+
+ /@iconify-json/ph@1.1.1:
+ resolution: {integrity: sha512-sIHTY+c1F8x29BM49IqoccJ3T8mvVXPcrE4WOpJ3GsBaip2YqFJRYU60rw64UL6GEI13vWSD7NsZKq8ytTO87g==}
+ dependencies:
+ '@iconify/types': 1.1.0
+ dev: true
+
+ /@iconify/types@1.1.0:
+ resolution: {integrity: sha512-Jh0llaK2LRXQoYsorIH8maClebsnzTcve+7U3rQUSnC11X4jtPnFuyatqFLvMxZ8MLG8dB4zfHsbPfuvxluONw==}
+ dev: true
+
+ /@iconify/utils@1.0.33:
+ resolution: {integrity: sha512-vGeAqo7aGPxOQmGdVoXFUOuyN+0V7Lcrx2EvaiRjxUD1x6Om0Tvq2bdm7E24l2Pz++4S0mWMCVFXe/17EtKImQ==}
+ dependencies:
+ '@antfu/install-pkg': 0.1.1
+ '@antfu/utils': 0.5.2
+ '@iconify/types': 1.1.0
+ debug: 4.3.4
+ kolorist: 1.7.0
+ local-pkg: 0.4.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@jridgewell/gen-mapping@0.3.2:
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
engines: {node: '>=6.0.0'}
@@ -762,7 +860,7 @@ packages:
dependencies:
'@rollup/pluginutils': 4.2.1
debug: 4.3.4
- deepmerge: 4.2.2
+ deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.26.7
svelte: 3.55.1
@@ -772,6 +870,29 @@ packages:
- supports-color
dev: true
+ /@sveltejs/vite-plugin-svelte@1.0.1(svelte@3.55.1)(vite@3.0.9):
+ resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==}
+ engines: {node: ^14.18.0 || >= 16}
+ peerDependencies:
+ diff-match-patch: ^1.0.5
+ svelte: ^3.44.0
+ vite: ^3.0.0
+ peerDependenciesMeta:
+ diff-match-patch:
+ optional: true
+ dependencies:
+ '@rollup/pluginutils': 4.2.1
+ debug: 4.3.4
+ deepmerge: 4.3.1
+ kleur: 4.1.5
+ magic-string: 0.26.7
+ svelte: 3.55.1
+ svelte-hmr: 0.14.12(svelte@3.55.1)
+ vite: 3.0.9
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.58.0)(vite@4.3.3):
resolution: {integrity: sha512-7YeBDt4us0FiIMNsVXxyaP4Hwyn2/v9x3oqStkHU3ZdIc5O22pGwUwH33wUqYo+7Itdmo8zxJ45Qvfm3H7UUjQ==}
engines: {node: ^14.18.0 || >= 16}
@@ -796,6 +917,11 @@ packages:
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false
+ /@tauri-apps/api@2.0.0-alpha.3:
+ resolution: {integrity: sha512-F6seMDlcaxeCPy4gS0zJdp6Tet+0rd1qJi/fbKrOrhLM6Y5UtkiG1aSDnMPi+1udThSfadjhUwrLHINvfMCjzQ==}
+ engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
+ dev: false
+
/@tauri-apps/cli-darwin-arm64@2.0.0-alpha.8:
resolution: {integrity: sha512-ZF9nkkYCDiAEKZFwjEbuqTcFVp+DBgem3edKjsZDYPQpWg0VcZOSYr0o3/RPC81T1/FAy1lq478mkcMe0efvEw==}
engines: {node: '>= 10'}
@@ -1041,6 +1167,144 @@ packages:
eslint-visitor-keys: 3.4.0
dev: true
+ /@unocss/cli@0.39.3:
+ resolution: {integrity: sha512-h+qq76CJTkV7GYBSQ3vSJCn/jewFzBVh8owMYH3B1ROe5D1mCev2INYvHlsQsVVoyxnccBeuZ6st6OK56VyDjA==}
+ engines: {node: '>=14'}
+ hasBin: true
+ dependencies:
+ '@unocss/config': 0.39.3
+ '@unocss/core': 0.39.3
+ '@unocss/preset-uno': 0.39.3
+ cac: 6.7.14
+ chokidar: 3.5.3
+ colorette: 2.0.19
+ consola: 2.15.3
+ fast-glob: 3.2.12
+ pathe: 0.3.9
+ perfect-debounce: 0.1.3
+ dev: true
+
+ /@unocss/config@0.39.3:
+ resolution: {integrity: sha512-qyxjUUdi+D/vS4Snhoj0uW8ErKlfZCKdjJ+ntwnJK3c8dxAp/IuicE+6ukcLfHxT0kAw1xaRlNwamtL3MgcX/A==}
+ engines: {node: '>=14'}
+ dependencies:
+ '@unocss/core': 0.39.3
+ unconfig: 0.3.7
+ dev: true
+
+ /@unocss/core@0.39.3:
+ resolution: {integrity: sha512-8MnXKHNtp6xgsFIaFtWctnbsT60c8JSlxXA7XbGxEztOmSEhpZmLeLGe5AgmEGPH6MssqJtI0DCeTbzbbtOjfw==}
+ dev: true
+
+ /@unocss/inspector@0.39.3:
+ resolution: {integrity: sha512-j7U04I07sqK63+3cA7oju/hoGOkdN+/hAwGYkCgWGNj+HwxiU7TTEVg0qZ1FAUU/GyyI9G/c4RIpwei9dLVz9w==}
+ dependencies:
+ gzip-size: 6.0.0
+ sirv: 2.0.2
+ dev: true
+
+ /@unocss/preset-attributify@0.39.3:
+ resolution: {integrity: sha512-SZWWUfTTKyHHqlF9x6aZ+BFMIiwOsUTP4NXS3/rIroqzfvVDZtGS6/a7RVBl+M74wjqSWB/DDeS9kQiH2L/CIg==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ dev: true
+
+ /@unocss/preset-icons@0.39.3:
+ resolution: {integrity: sha512-zMTfP3pVaN2WREWY36adsY62gEm51R0CZd7v0gHOlltEG6kT1UCeyIQwOtn48wHRCesy92f70R6RIR3rwSVaCQ==}
+ dependencies:
+ '@iconify/utils': 1.0.33
+ '@unocss/core': 0.39.3
+ ohmyfetch: 0.4.21
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@unocss/preset-mini@0.39.3:
+ resolution: {integrity: sha512-XCxp3mwWsEpCo0cIJA3tLrWqdAL09gP3wv9iGh4H9o0fIPlYXjVTC1WtUHkv3C09LdZ+MH/9Ja/KqnVf3bNROA==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ dev: true
+
+ /@unocss/preset-tagify@0.39.3:
+ resolution: {integrity: sha512-OXE47cS/tiL92ZThgLOpbSFy7MPZ4upE4ZX1m9pnCaWzX7LBzp8Gw0DM+dF3IYdIfJpmU4R6b53ME8SchofuHA==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ dev: true
+
+ /@unocss/preset-typography@0.39.3:
+ resolution: {integrity: sha512-jTJOA87bEkU0RGMPSFZK3Zobr2fgkqKCYDczTjPbCiZ8UzlMJnWrpsNTN9f4UI0b6Ck8sXtMtW8sRrJsEll9jg==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ dev: true
+
+ /@unocss/preset-uno@0.39.3:
+ resolution: {integrity: sha512-EADVFqx5x4te/teqwjHb025FIy/T0QXafcVDRwUijS6OOqm5rZ7fXd/hu41XYYn3B802r/g4bDC2wO+7foNVwA==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ '@unocss/preset-mini': 0.39.3
+ '@unocss/preset-wind': 0.39.3
+ dev: true
+
+ /@unocss/preset-web-fonts@0.39.3:
+ resolution: {integrity: sha512-b23nmEGHbfvC/PCv0m0BGqFt2zX8J9ekwjfmEL1Bk1C0KL2voYGSdbSm0I8iO6sKb1CLy6qy71N/CuGtIE3FJA==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ ohmyfetch: 0.4.21
+ dev: true
+
+ /@unocss/preset-wind@0.39.3:
+ resolution: {integrity: sha512-kjMgPxt4xfmiliodKTbotJDSAqAOCy25f1jdIj9CjjFjwYsUAuiYi8UgPsEi550Bj5BlBEHFn/RhcMGvinzY8A==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ '@unocss/preset-mini': 0.39.3
+ dev: true
+
+ /@unocss/reset@0.39.3:
+ resolution: {integrity: sha512-hW3gZ3lsu6N58XEG7m1dprt15fN0xkYjAo7vSp8eT3/p7h5HE7wNgU2v9ttGBC3B2z4AWHGdspfmaH3sR8lCJw==}
+ dev: true
+
+ /@unocss/scope@0.39.3:
+ resolution: {integrity: sha512-ex2QDRgBQ5mTwBcXtCWdTDPl6/HrBv0asDWVXXv7ezjxcByJjMrHj64gMvUbAcGAoX2ic7hIEUT3Ju5i6knKFw==}
+ dev: true
+
+ /@unocss/transformer-compile-class@0.39.3:
+ resolution: {integrity: sha512-OmYP0uk+DGR5kc2T+teL6CLNj/sRxbY3SmlPx2kDbsRLc5gFccQryjj4bBk6QNOKxP5OGJpAqcw1y1JctvRgog==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ dev: true
+
+ /@unocss/transformer-directives@0.39.3:
+ resolution: {integrity: sha512-E1wzZaR6rIBQNemgDi9LoljtkYcOSiKGMUTz6kRGoxVBzaYE6Ji/YKbb22lKd6vLOFnRyCxzPHdzY9qvvl5D6w==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ css-tree: 2.3.1
+ dev: true
+
+ /@unocss/transformer-variant-group@0.39.3:
+ resolution: {integrity: sha512-YoYz87qSSEvXXUkgHbO2kz/M03dbZuedjDvvWXsBAvj20MQFpkZpbNHYf2DJ+EkO/WXd+KEF2HBwlgoANcZlaw==}
+ dependencies:
+ '@unocss/core': 0.39.3
+ dev: true
+
+ /@unocss/vite@0.39.3(vite@3.0.9):
+ resolution: {integrity: sha512-JT21v6ZwLCHPGVfjoWsOdSkMhFNiW2robhQke33WLlRGyT5U4K1SWLxNk+XPDbFdP+WZdcVJi5W5yG8Mm27WBw==}
+ peerDependencies:
+ vite: ^2.9.0
+ dependencies:
+ '@rollup/pluginutils': 4.2.1
+ '@unocss/config': 0.39.3
+ '@unocss/core': 0.39.3
+ '@unocss/inspector': 0.39.3
+ '@unocss/scope': 0.39.3
+ '@unocss/transformer-directives': 0.39.3
+ magic-string: 0.26.7
+ vite: 3.0.9
+ dev: true
+
+ /@zerodevx/svelte-json-view@0.2.1:
+ resolution: {integrity: sha512-yaLojLYTi08vccUKRg/XSRCCPoyzCZqrG+W8mVhJEGiOfFKAmWqNH6b+/il1gG3V1UaEe7amj2mzmo1mo4q1iA==}
+ dev: false
+
/acorn-jsx@5.3.2(acorn@8.8.1):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -1173,6 +1437,11 @@ packages:
streamsearch: 1.1.0
dev: true
+ /cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+ dev: true
+
/call-bind@1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
dependencies:
@@ -1219,6 +1488,10 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
+ /colorette@2.0.19:
+ resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
+ dev: true
+
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: true
@@ -1227,6 +1500,10 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
+ /consola@2.15.3:
+ resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
+ dev: true
+
/cookie@0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
@@ -1241,6 +1518,14 @@ packages:
which: 2.0.2
dev: true
+ /css-tree@2.3.1:
+ resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+ dependencies:
+ mdn-data: 2.0.30
+ source-map-js: 1.0.2
+ dev: true
+
/debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -1293,6 +1578,14 @@ packages:
object-keys: 1.1.1
dev: true
+ /defu@6.1.2:
+ resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==}
+ dev: true
+
+ /destr@1.2.2:
+ resolution: {integrity: sha512-lrbCJwD9saUQrqUfXvl6qoM+QN3W7tLV5pAOs+OqOmopCCz/JkE05MHedJR1xfk4IAnZuJXPVuN5+7jNA2ZCiA==}
+ dev: true
+
/detect-indent@6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
@@ -1323,6 +1616,10 @@ packages:
esutils: 2.0.3
dev: true
+ /duplexer@0.1.2:
+ resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
+ dev: true
+
/es-abstract@1.20.5:
resolution: {integrity: sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==}
engines: {node: '>= 0.4'}
@@ -2115,6 +2412,13 @@ packages:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true
+ /gzip-size@6.0.0:
+ resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
+ engines: {node: '>=10'}
+ dependencies:
+ duplexer: 0.1.2
+ dev: true
+
/has-bigints@1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
dev: true
@@ -2350,6 +2654,11 @@ packages:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
+ /jiti@1.18.2:
+ resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==}
+ hasBin: true
+ dev: true
+
/js-sdsl@4.2.0:
resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
dev: true
@@ -2381,6 +2690,10 @@ packages:
engines: {node: '>=6'}
dev: true
+ /kolorist@1.7.0:
+ resolution: {integrity: sha512-ymToLHqL02udwVdbkowNpzjFd6UzozMtshPQKVi5k1EjKRqKqBrOnE9QbLEb0/pV76SAiIT13hdL8R6suc+f3g==}
+ dev: true
+
/levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -2389,6 +2702,11 @@ packages:
type-check: 0.4.0
dev: true
+ /local-pkg@0.4.3:
+ resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
+ engines: {node: '>=14'}
+ dev: true
+
/locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@@ -2428,6 +2746,10 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
+ /mdn-data@2.0.30:
+ resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
+ dev: true
+
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true
@@ -2516,6 +2838,10 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
+ /node-fetch-native@0.1.8:
+ resolution: {integrity: sha512-ZNaury9r0NxaT2oL65GvdGDy+5PlSaHTovT6JV5tOW07k1TQmgC0olZETa4C9KZg0+6zBr99ctTYa3Utqj9P/Q==}
+ dev: true
+
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@@ -2556,6 +2882,15 @@ packages:
es-abstract: 1.20.5
dev: true
+ /ohmyfetch@0.4.21:
+ resolution: {integrity: sha512-VG7f/JRvqvBOYvL0tHyEIEG7XHWm7OqIfAs6/HqwWwDfjiJ1g0huIpe5sFEmyb+7hpFa1EGNH2aERWR72tlClw==}
+ dependencies:
+ destr: 1.2.2
+ node-fetch-native: 0.1.8
+ ufo: 0.8.6
+ undici: 5.22.0
+ dev: true
+
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -2645,6 +2980,14 @@ packages:
engines: {node: '>=8'}
dev: true
+ /pathe@0.3.9:
+ resolution: {integrity: sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g==}
+ dev: true
+
+ /perfect-debounce@0.1.3:
+ resolution: {integrity: sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==}
+ dev: true
+
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
@@ -2757,6 +3100,14 @@ packages:
glob: 7.2.3
dev: true
+ /rollup@2.77.3:
+ resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
/rollup@2.79.1:
resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
engines: {node: '>=10.0.0'}
@@ -3157,6 +3508,10 @@ packages:
hasBin: true
dev: true
+ /ufo@0.8.6:
+ resolution: {integrity: sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==}
+ dev: true
+
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies:
@@ -3166,6 +3521,14 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
+ /unconfig@0.3.7:
+ resolution: {integrity: sha512-1589b7oGa8ILBYpta7TndM5mLHLzHUqBfhszeZxuUBrjO/RoQ52VGVWsS3w0C0GLNxO9RPmqkf6BmIvBApaRdA==}
+ dependencies:
+ '@antfu/utils': 0.5.2
+ defu: 6.1.2
+ jiti: 1.18.2
+ dev: true
+
/undici@5.22.0:
resolution: {integrity: sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==}
engines: {node: '>=14.0'}
@@ -3173,6 +3536,35 @@ packages:
busboy: 1.6.0
dev: true
+ /unocss@0.39.3(vite@3.0.9):
+ resolution: {integrity: sha512-+BZazovI1A+jlW0+GuSSABHQjBLpu2sQkLXriBTdZiPYZAqJJdiWHuQ6VPzF4Al5WM4VPpOgX5mUYWusJ813qw==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@unocss/webpack': 0.39.3
+ peerDependenciesMeta:
+ '@unocss/webpack':
+ optional: true
+ dependencies:
+ '@unocss/cli': 0.39.3
+ '@unocss/core': 0.39.3
+ '@unocss/preset-attributify': 0.39.3
+ '@unocss/preset-icons': 0.39.3
+ '@unocss/preset-mini': 0.39.3
+ '@unocss/preset-tagify': 0.39.3
+ '@unocss/preset-typography': 0.39.3
+ '@unocss/preset-uno': 0.39.3
+ '@unocss/preset-web-fonts': 0.39.3
+ '@unocss/preset-wind': 0.39.3
+ '@unocss/reset': 0.39.3
+ '@unocss/transformer-compile-class': 0.39.3
+ '@unocss/transformer-directives': 0.39.3
+ '@unocss/transformer-variant-group': 0.39.3
+ '@unocss/vite': 0.39.3(vite@3.0.9)
+ transitivePeerDependencies:
+ - supports-color
+ - vite
+ dev: true
+
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
@@ -3206,6 +3598,33 @@ packages:
fsevents: 2.3.2
dev: true
+ /vite@3.0.9:
+ resolution: {integrity: sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ hasBin: true
+ peerDependencies:
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ esbuild: 0.14.54
+ postcss: 8.4.23
+ resolve: 1.22.1
+ rollup: 2.77.3
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
/vite@4.3.3:
resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==}
engines: {node: ^14.18.0 || >=16.0.0}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index e4cb9a8b..43839b9e 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,3 +1,4 @@
packages:
- plugins/*
- plugins/*/examples/*
+ - examples/*