diff --git a/.changes/config.json b/.changes/config.json
index 214ec4d9..5834d96b 100644
--- a/.changes/config.json
+++ b/.changes/config.json
@@ -128,6 +128,15 @@
"manager": "javascript-disabled"
},
+ "notification": {
+ "path": "./plugins/notification",
+ "manager": "rust-disabled"
+ },
+ "notification-js": {
+ "path": "./plugins/notification",
+ "manager": "javascript-disabled"
+ },
+
"persisted-scope": {
"path": "./plugins/persisted-scope",
"manager": "rust"
diff --git a/Cargo.lock b/Cargo.lock
index bb45602b..365ada67 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1805,7 +1805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb1a9325847aa46f1e96ffea37611b9d51fc4827e67f79e7de502a297560a67b"
dependencies = [
"anyhow",
- "heck",
+ "heck 0.4.1",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
@@ -1959,6 +1959,15 @@ dependencies = [
"hashbrown",
]
+[[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"
@@ -2644,6 +2653,19 @@ 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 0.3.20",
+]
+
[[package]]
name = "malloc_buf"
version = "0.0.6"
@@ -2881,6 +2903,19 @@ dependencies = [
"serde",
]
+[[package]]
+name = "notify-rust"
+version = "4.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bfa211d18e360f08e36c364308f394b5eb23a6629150690e109a916dc6f610e"
+dependencies = [
+ "log",
+ "mac-notification-sys",
+ "serde",
+ "tauri-winrt-notification",
+ "zbus",
+]
+
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -3415,7 +3450,7 @@ dependencies = [
"base64 0.21.0",
"indexmap",
"line-wrap",
- "quick-xml",
+ "quick-xml 0.28.1",
"serde",
"time 0.3.20",
]
@@ -3538,6 +3573,15 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+[[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.28.1"
@@ -4343,7 +4387,7 @@ checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9"
dependencies = [
"dotenvy",
"either",
- "heck",
+ "heck 0.4.1",
"once_cell",
"proc-macro2",
"quote",
@@ -4493,6 +4537,27 @@ 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 1.0.109",
+]
+
[[package]]
name = "subtle"
version = "2.4.1"
@@ -4539,7 +4604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555fc8147af6256f3931a36bb83ad0023240ce9cf2b319dec8236fd1f220b05f"
dependencies = [
"cfg-expr",
- "heck",
+ "heck 0.4.1",
"pkg-config",
"toml 0.7.3",
"version-compare",
@@ -4633,7 +4698,7 @@ dependencies = [
"glib",
"glob",
"gtk",
- "heck",
+ "heck 0.4.1",
"http",
"ignore",
"jni",
@@ -4677,7 +4742,7 @@ dependencies = [
"anyhow",
"cargo_toml",
"filetime",
- "heck",
+ "heck 0.4.1",
"json-patch",
"semver",
"serde",
@@ -4720,7 +4785,7 @@ version = "2.0.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3b596485d89003d2d7869469b2830e9a846de9ac2eecd69bc7c24890234aefc"
dependencies = [
- "heck",
+ "heck 0.4.1",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -4856,6 +4921,20 @@ dependencies = [
"time 0.3.20",
]
+[[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-persisted-scope"
version = "0.1.0"
@@ -5049,7 +5128,7 @@ dependencies = [
"brotli",
"ctor",
"glob",
- "heck",
+ "heck 0.4.1",
"html5ever",
"infer",
"json-patch",
@@ -5078,6 +5157,17 @@ dependencies = [
"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.5.0"
@@ -5867,6 +5957,16 @@ dependencies = [
"web-sys",
]
+[[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"
@@ -5920,6 +6020,19 @@ dependencies = [
"windows_x86_64_msvc 0.36.1",
]
+[[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"
@@ -5978,6 +6091,19 @@ 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"
@@ -6065,6 +6191,12 @@ 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.2"
@@ -6083,6 +6215,12 @@ 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.2"
@@ -6101,6 +6239,12 @@ 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.2"
@@ -6119,6 +6263,12 @@ 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.2"
@@ -6149,6 +6299,12 @@ 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.2"
diff --git a/plugins/notification/.gitignore b/plugins/notification/.gitignore
new file mode 100644
index 00000000..1b0b469d
--- /dev/null
+++ b/plugins/notification/.gitignore
@@ -0,0 +1 @@
+/.tauri
diff --git a/plugins/notification/Cargo.toml b/plugins/notification/Cargo.toml
new file mode 100644
index 00000000..57d5c149
--- /dev/null
+++ b/plugins/notification/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "tauri-plugin-notification"
+version = "0.1.0"
+edition.workspace = true
+authors.workspace = true
+license.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[build-dependencies]
+tauri-build.workspace = true
+
+[dependencies]
+serde.workspace = true
+serde_json.workspace = true
+tauri.workspace = true
+log.workspace = true
+thiserror.workspace = true
+
+[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"
+
+[target."cfg(windows)".dependencies]
+win7-notifications = { version = "0.3.1", optional = true }
+
+[features]
+windows7-compat = [ "win7-notifications" ]
diff --git a/plugins/notification/LICENSE.spdx b/plugins/notification/LICENSE.spdx
new file mode 100644
index 00000000..cdd0df5a
--- /dev/null
+++ b/plugins/notification/LICENSE.spdx
@@ -0,0 +1,20 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+PackageName: tauri
+DataFormat: SPDXRef-1
+PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy
+PackageHomePage: https://tauri.app
+PackageLicenseDeclared: Apache-2.0
+PackageLicenseDeclared: MIT
+PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy
+PackageSummary: Tauri is a rust project that enables developers to make secure
+and small desktop applications using a web frontend.
+
+PackageComment: The package includes the following libraries; see
+Relationship information.
+
+Created: 2019-05-20T09:00:00Z
+PackageDownloadLocation: git://github.com/tauri-apps/tauri
+PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git
+PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git
+Creator: Person: Daniel Thompson-Yvetot
\ No newline at end of file
diff --git a/plugins/notification/LICENSE_APACHE-2.0 b/plugins/notification/LICENSE_APACHE-2.0
new file mode 100644
index 00000000..4947287f
--- /dev/null
+++ b/plugins/notification/LICENSE_APACHE-2.0
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/plugins/notification/LICENSE_MIT b/plugins/notification/LICENSE_MIT
new file mode 100644
index 00000000..4d754725
--- /dev/null
+++ b/plugins/notification/LICENSE_MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 - Present Tauri Apps Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/plugins/notification/README.md b/plugins/notification/README.md
new file mode 100644
index 00000000..ed5545a2
--- /dev/null
+++ b/plugins/notification/README.md
@@ -0,0 +1,65 @@
+
+
+
+
+## Install
+
+_This plugin requires a Rust version of at least **1.64**_
+
+There are three general methods of installation that we can recommend.
+
+1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
+2. Pull sources directly from Github using git tags / revision hashes (most secure)
+3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use)
+
+Install the Core plugin by adding the following to your `Cargo.toml` file:
+
+`src-tauri/Cargo.toml`
+
+```toml
+[dependencies]
+ = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
+```
+
+You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
+
+> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
+
+```sh
+pnpm add
+# or
+npm add
+# or
+yarn add
+```
+
+## Usage
+
+First you need to register the core plugin with Tauri:
+
+`src-tauri/src/main.rs`
+
+```rust
+fn main() {
+ tauri::Builder::default()
+ .plugin()
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
+```
+
+Afterwards all the plugin's APIs are available through the JavaScript guest bindings:
+
+```javascript
+
+```
+
+## Contributing
+
+PRs accepted. Please make sure to read the Contributing Guide before making a pull request.
+
+## License
+
+Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy.
+
+MIT or MIT/Apache 2.0 where applicable.
diff --git a/plugins/notification/android/.gitignore b/plugins/notification/android/.gitignore
new file mode 100644
index 00000000..c0f21ec2
--- /dev/null
+++ b/plugins/notification/android/.gitignore
@@ -0,0 +1,2 @@
+/build
+/.tauri
diff --git a/plugins/notification/android/build.gradle.kts b/plugins/notification/android/build.gradle.kts
new file mode 100644
index 00000000..5fdedcf4
--- /dev/null
+++ b/plugins/notification/android/build.gradle.kts
@@ -0,0 +1,45 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "app.tauri.notification"
+ compileSdk = 32
+
+ defaultConfig {
+ minSdk = 24
+ targetSdk = 32
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ implementation("androidx.core:core-ktx:1.9.0")
+ implementation("androidx.appcompat:appcompat:1.6.0")
+ implementation("com.google.android.material:material:1.7.0")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ implementation(project(":tauri-android"))
+}
diff --git a/plugins/notification/android/proguard-rules.pro b/plugins/notification/android/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/plugins/notification/android/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/plugins/notification/android/settings.gradle b/plugins/notification/android/settings.gradle
new file mode 100644
index 00000000..14a752e4
--- /dev/null
+++ b/plugins/notification/android/settings.gradle
@@ -0,0 +1,2 @@
+include ':tauri-android'
+project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
diff --git a/plugins/notification/android/src/androidTest/java/ExampleInstrumentedTest.kt b/plugins/notification/android/src/androidTest/java/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..814a39af
--- /dev/null
+++ b/plugins/notification/android/src/androidTest/java/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package app.tauri.notification
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("app.tauri.notification", appContext.packageName)
+ }
+}
diff --git a/plugins/notification/android/src/main/AndroidManifest.xml b/plugins/notification/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..9a40236b
--- /dev/null
+++ b/plugins/notification/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/plugins/notification/android/src/main/java/NotificationPlugin.kt b/plugins/notification/android/src/main/java/NotificationPlugin.kt
new file mode 100644
index 00000000..ab6c9df7
--- /dev/null
+++ b/plugins/notification/android/src/main/java/NotificationPlugin.kt
@@ -0,0 +1,31 @@
+package app.tauri.notification
+
+import android.app.Activity
+import app.tauri.annotation.Command
+import app.tauri.annotation.TauriPlugin
+import app.tauri.plugin.JSObject
+import app.tauri.plugin.Plugin
+import app.tauri.plugin.Invoke
+
+@TauriPlugin
+class NotificationPlugin(private val activity: Activity): Plugin(activity) {
+ @Command
+ fun requestPermission(invoke: Invoke) {
+ val ret = JSObject()
+ ret.put("permissionState", "granted")
+ invoke.resolve(ret)
+ }
+
+ @Command
+ fun permissionState(invoke: Invoke) {
+ val ret = JSObject()
+ ret.put("permissionState", "granted")
+ invoke.resolve(ret)
+ }
+
+ @Command
+ fun notify(invoke: Invoke) {
+ // TODO
+ invoke.resolve()
+ }
+}
diff --git a/plugins/notification/android/src/test/java/ExampleUnitTest.kt b/plugins/notification/android/src/test/java/ExampleUnitTest.kt
new file mode 100644
index 00000000..46693a0f
--- /dev/null
+++ b/plugins/notification/android/src/test/java/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package app.tauri.notification
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/plugins/notification/build.rs b/plugins/notification/build.rs
new file mode 100644
index 00000000..86ac3f0a
--- /dev/null
+++ b/plugins/notification/build.rs
@@ -0,0 +1,12 @@
+use std::process::exit;
+
+fn main() {
+ if let Err(error) = tauri_build::mobile::PluginBuilder::new()
+ .android_path("android")
+ .ios_path("ios")
+ .run()
+ {
+ println!("{error:#}");
+ exit(1);
+ }
+}
diff --git a/plugins/notification/guest-js/index.ts b/plugins/notification/guest-js/index.ts
new file mode 100644
index 00000000..a480105b
--- /dev/null
+++ b/plugins/notification/guest-js/index.ts
@@ -0,0 +1,113 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+/**
+ * Send toast notifications (brief auto-expiring OS window element) to your user.
+ * Can also be used with the Notification Web API.
+ *
+ * This package is also accessible with `window.__TAURI__.notification` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
+ *
+ * The APIs must be added to [`tauri.allowlist.notification`](https://tauri.app/v1/api/config/#allowlistconfig.notification) in `tauri.conf.json`:
+ * ```json
+ * {
+ * "tauri": {
+ * "allowlist": {
+ * "notification": {
+ * "all": true // enable all notification APIs
+ * }
+ * }
+ * }
+ * }
+ * ```
+ * It is recommended to allowlist only the APIs you use for optimal bundle size and security.
+ * @module
+ */
+
+import { invoke } from '@tauri-apps/api/tauri'
+
+/**
+ * Options to send a notification.
+ *
+ * @since 1.0.0
+ */
+interface Options {
+ /** Notification title. */
+ title: string
+ /** Optional notification body. */
+ body?: string
+ /** Optional notification icon. */
+ icon?: string
+}
+
+/** Possible permission values. */
+type Permission = 'granted' | 'denied' | 'default'
+
+/**
+ * Checks if the permission to send notifications is granted.
+ * @example
+ * ```typescript
+ * import { isPermissionGranted } from '@tauri-apps/api/notification';
+ * const permissionGranted = await isPermissionGranted();
+ * ```
+ *
+ * @since 1.0.0
+ */
+async function isPermissionGranted(): Promise {
+ if (window.Notification.permission !== 'default') {
+ return Promise.resolve(window.Notification.permission === 'granted')
+ }
+ return invoke('plugin:notification|is_permission_granted')
+}
+
+/**
+ * Requests the permission to send notifications.
+ * @example
+ * ```typescript
+ * import { isPermissionGranted, requestPermission } from '@tauri-apps/api/notification';
+ * let permissionGranted = await isPermissionGranted();
+ * if (!permissionGranted) {
+ * const permission = await requestPermission();
+ * permissionGranted = permission === 'granted';
+ * }
+ * ```
+ *
+ * @returns A promise resolving to whether the user granted the permission or not.
+ *
+ * @since 1.0.0
+ */
+async function requestPermission(): Promise {
+ return window.Notification.requestPermission()
+}
+
+/**
+ * Sends a notification to the user.
+ * @example
+ * ```typescript
+ * import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/api/notification';
+ * let permissionGranted = await isPermissionGranted();
+ * if (!permissionGranted) {
+ * const permission = await requestPermission();
+ * permissionGranted = permission === 'granted';
+ * }
+ * if (permissionGranted) {
+ * sendNotification('Tauri is awesome!');
+ * sendNotification({ title: 'TAURI', body: 'Tauri is awesome!' });
+ * }
+ * ```
+ *
+ * @since 1.0.0
+ */
+function sendNotification(options: Options | string): void {
+ if (typeof options === 'string') {
+ // eslint-disable-next-line no-new
+ new window.Notification(options)
+ } else {
+ // eslint-disable-next-line no-new
+ new window.Notification(options.title, options)
+ }
+}
+
+export type { Options, Permission }
+
+export { sendNotification, requestPermission, isPermissionGranted }
diff --git a/plugins/notification/ios/.gitignore b/plugins/notification/ios/.gitignore
new file mode 100644
index 00000000..5922fdaa
--- /dev/null
+++ b/plugins/notification/ios/.gitignore
@@ -0,0 +1,10 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
+DerivedData/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
+Package.resolved
diff --git a/plugins/notification/ios/Package.swift b/plugins/notification/ios/Package.swift
new file mode 100644
index 00000000..ff9991fa
--- /dev/null
+++ b/plugins/notification/ios/Package.swift
@@ -0,0 +1,31 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "tauri-plugin-{{ plugin_name }}",
+ 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 }}",
+ type: .static,
+ targets: ["tauri-plugin-{{ plugin_name }}"]),
+ ],
+ dependencies: [
+ .package(name: "Tauri", path: "../.tauri/tauri-api")
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "tauri-plugin-{{ plugin_name }}",
+ dependencies: [
+ .byName(name: "Tauri")
+ ],
+ path: "Sources")
+ ]
+)
diff --git a/plugins/notification/ios/README.md b/plugins/notification/ios/README.md
new file mode 100644
index 00000000..f4900bdd
--- /dev/null
+++ b/plugins/notification/ios/README.md
@@ -0,0 +1,3 @@
+# Tauri Plugin {{ plugin_name_original }}
+
+A description of this package.
diff --git a/plugins/notification/ios/Sources/NotificationPlugin.swift b/plugins/notification/ios/Sources/NotificationPlugin.swift
new file mode 100644
index 00000000..3d520a92
--- /dev/null
+++ b/plugins/notification/ios/Sources/NotificationPlugin.swift
@@ -0,0 +1,24 @@
+import UIKit
+import WebKit
+import Tauri
+import SwiftRs
+
+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()
+ }
+}
+
+@_cdecl("init_plugin_notification")
+func initPlugin(name: SRString, webview: WKWebView?) {
+ Tauri.registerPlugin(webview: webview, name: name.toString(), plugin: NotificationPlugin())
+}
diff --git a/plugins/notification/ios/Tests/PluginTests/PluginTests.swift b/plugins/notification/ios/Tests/PluginTests/PluginTests.swift
new file mode 100644
index 00000000..4f8e9ace
--- /dev/null
+++ b/plugins/notification/ios/Tests/PluginTests/PluginTests.swift
@@ -0,0 +1,8 @@
+import XCTest
+@testable import ExamplePlugin
+
+final class ExamplePluginTests: XCTestCase {
+ func testExample() throws {
+ let plugin = ExamplePlugin()
+ }
+}
diff --git a/plugins/notification/package.json b/plugins/notification/package.json
new file mode 100644
index 00000000..52d81e45
--- /dev/null
+++ b/plugins/notification/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "tauri-plugin-notification-api",
+ "version": "0.0.0",
+ "license": "MIT or APACHE-2.0",
+ "authors": [
+ "Tauri Programme within The Commons Conservancy"
+ ],
+ "type": "module",
+ "browser": "dist-js/index.min.js",
+ "module": "dist-js/index.mjs",
+ "types": "dist-js/index.d.ts",
+ "exports": {
+ "import": "./dist-js/index.mjs",
+ "types": "./dist-js/index.d.ts",
+ "browser": "./dist-js/index.min.js"
+ },
+ "scripts": {
+ "build": "rollup -c"
+ },
+ "files": [
+ "dist-js",
+ "!dist-js/**/*.map",
+ "README.md",
+ "LICENSE"
+ ],
+ "devDependencies": {
+ "tslib": "^2.4.1"
+ },
+ "dependencies": {
+ "@tauri-apps/api": "^1.2.0"
+ }
+}
diff --git a/plugins/notification/rollup.config.mjs b/plugins/notification/rollup.config.mjs
new file mode 100644
index 00000000..6555e98b
--- /dev/null
+++ b/plugins/notification/rollup.config.mjs
@@ -0,0 +1,11 @@
+import { readFileSync } from "fs";
+
+import { createConfig } from "../../shared/rollup.config.mjs";
+
+export default createConfig({
+ input: "guest-js/index.ts",
+ pkg: JSON.parse(
+ readFileSync(new URL("./package.json", import.meta.url), "utf8")
+ ),
+ external: [/^@tauri-apps\/api/],
+});
diff --git a/plugins/notification/src/commands.rs b/plugins/notification/src/commands.rs
new file mode 100644
index 00000000..710235c1
--- /dev/null
+++ b/plugins/notification/src/commands.rs
@@ -0,0 +1,54 @@
+// 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, 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,
+}
+
+#[command]
+pub(crate) async fn is_permission_granted(
+ _app: AppHandle,
+ notification: State<'_, Notification>,
+) -> Result {
+ notification
+ .permission_state()
+ .map(|s| s == PermissionState::Granted)
+}
+
+#[command]
+pub(crate) async fn request_permission(
+ _app: AppHandle,
+ notification: State<'_, Notification>,
+) -> Result {
+ notification.request_permission()
+}
+
+#[command]
+pub(crate) async fn notify(
+ _app: AppHandle,
+ notification: State<'_, Notification>,
+ options: NotificationOptions,
+) -> 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);
+ }
+
+ builder.show()
+}
diff --git a/plugins/notification/src/desktop.rs b/plugins/notification/src/desktop.rs
new file mode 100644
index 00000000..47be71b5
--- /dev/null
+++ b/plugins/notification/src/desktop.rs
@@ -0,0 +1,267 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::de::DeserializeOwned;
+use tauri::{plugin::PluginApi, AppHandle, Runtime};
+
+use crate::{models::*, NotificationBuilder};
+
+pub fn init(
+ app: &AppHandle,
+ _api: PluginApi,
+) -> crate::Result> {
+ Ok(Notification(app.clone()))
+}
+
+/// Access to the {{ plugin_name }} APIs.
+pub struct Notification(AppHandle);
+
+impl crate::NotificationBuilder {
+ pub fn show(self) -> crate::Result<()> {
+ let mut notification =
+ imp::Notification::new(self.app.config().tauri.bundle.identifier.clone());
+
+ if let Some(title) = self
+ .data
+ .title
+ .or_else(|| self.app.config().package.product_name.clone())
+ {
+ notification = notification.title(title);
+ }
+ if let Some(body) = self.data.body {
+ notification = notification.body(body);
+ }
+ if let Some(icon) = self.data.icon {
+ notification = notification.icon(icon);
+ }
+ #[cfg(feature = "windows7-compat")]
+ {
+ notification.notify(&self.app)?;
+ }
+ #[cfg(not(feature = "windows7-compat"))]
+ notification.show()?;
+
+ Ok(())
+ }
+}
+
+impl Notification {
+ pub fn builder(&self) -> NotificationBuilder {
+ NotificationBuilder::new(self.0.clone())
+ }
+
+ pub fn request_permission(&self) -> crate::Result {
+ Ok(PermissionState::Granted)
+ }
+
+ pub fn permission_state(&self) -> crate::Result {
+ Ok(PermissionState::Granted)
+ }
+}
+
+mod imp {
+ //! Types and functions related to desktop notifications.
+
+ #[cfg(windows)]
+ use std::path::MAIN_SEPARATOR as SEP;
+
+ /// The desktop notification definition.
+ ///
+ /// Allows you to construct a Notification data and send it.
+ ///
+ /// # Examples
+ /// ```rust,no_run
+ /// use tauri::api::notification::Notification;
+ /// // first we build the application to access the Tauri configuration
+ /// let app = tauri::Builder::default()
+ /// // on an actual app, remove the string argument
+ /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
+ /// .expect("error while building tauri application");
+ ///
+ /// // shows a notification with the given title and body
+ /// Notification::new(&app.config().tauri.bundle.identifier)
+ /// .title("New message")
+ /// .body("You've got a new message.")
+ /// .show();
+ ///
+ /// // run the app
+ /// app.run(|_app_handle, _event| {});
+ /// ```
+ #[allow(dead_code)]
+ #[derive(Debug, Default)]
+ pub struct Notification {
+ /// The notification body.
+ body: Option,
+ /// The notification title.
+ title: Option,
+ /// The notification icon.
+ icon: Option,
+ /// The notification identifier
+ identifier: String,
+ }
+
+ impl Notification {
+ /// Initializes a instance of a Notification.
+ pub fn new(identifier: impl Into) -> Self {
+ Self {
+ identifier: identifier.into(),
+ ..Default::default()
+ }
+ }
+
+ /// Sets the notification body.
+ #[must_use]
+ pub fn body(mut self, body: impl Into) -> Self {
+ self.body = Some(body.into());
+ self
+ }
+
+ /// Sets the notification title.
+ #[must_use]
+ pub fn title(mut self, title: impl Into) -> Self {
+ self.title = Some(title.into());
+ self
+ }
+
+ /// Sets the notification icon.
+ #[must_use]
+ pub fn icon(mut self, icon: impl Into) -> Self {
+ self.icon = Some(icon.into());
+ self
+ }
+
+ /// Shows the notification.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use tauri::api::notification::Notification;
+ ///
+ /// // on an actual app, remove the string argument
+ /// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json");
+ /// Notification::new(&context.config().tauri.bundle.identifier)
+ /// .title("Tauri")
+ /// .body("Tauri is awesome!")
+ /// .show()
+ /// .unwrap();
+ /// ```
+ ///
+ /// ## Platform-specific
+ ///
+ /// - **Windows**: Not supported on Windows 7. If your app targets it, enable the `windows7-compat` feature and use [`Self::notify`].
+ #[cfg_attr(
+ all(not(doc_cfg), feature = "windows7-compat"),
+ deprecated = "This function does not work on Windows 7. Use `Self::notify` instead."
+ )]
+ pub fn show(self) -> crate::Result<()> {
+ let mut notification = notify_rust::Notification::new();
+ if let Some(body) = self.body {
+ notification.body(&body);
+ }
+ if let Some(title) = self.title {
+ notification.summary(&title);
+ }
+ if let Some(icon) = self.icon {
+ notification.icon(&icon);
+ } else {
+ notification.auto_icon();
+ }
+ #[cfg(windows)]
+ {
+ let exe = tauri::utils::platform::current_exe()?;
+ let exe_dir = exe.parent().expect("failed to get exe directory");
+ let curr_dir = exe_dir.display().to_string();
+ // set the notification's System.AppUserModel.ID only when running the installed app
+ if !(curr_dir.ends_with(format!("{SEP}target{SEP}debug").as_str())
+ || curr_dir.ends_with(format!("{SEP}target{SEP}release").as_str()))
+ {
+ notification.app_id(&self.identifier);
+ }
+ }
+ #[cfg(target_os = "macos")]
+ {
+ let _ = notify_rust::set_application(if cfg!(feature = "custom-protocol") {
+ &self.identifier
+ } else {
+ "com.apple.Terminal"
+ });
+ }
+
+ tauri::async_runtime::spawn(async move {
+ let _ = notification.show();
+ });
+
+ Ok(())
+ }
+
+ /// Shows the notification. This API is similar to [`Self::show`], but it also works on Windows 7.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use tauri::api::notification::Notification;
+ ///
+ /// // on an actual app, remove the string argument
+ /// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json");
+ /// let identifier = context.config().tauri.bundle.identifier.clone();
+ ///
+ /// tauri::Builder::default()
+ /// .setup(move |app| {
+ /// Notification::new(&identifier)
+ /// .title("Tauri")
+ /// .body("Tauri is awesome!")
+ /// .notify(&app.handle())
+ /// .unwrap();
+ /// Ok(())
+ /// })
+ /// .run(context)
+ /// .expect("error while running tauri application");
+ /// ```
+ #[cfg(feature = "windows7-compat")]
+ #[cfg_attr(doc_cfg, doc(cfg(feature = "windows7-compat")))]
+ #[allow(unused_variables)]
+ pub fn notify(self, app: &tauri::AppHandle) -> crate::Result<()> {
+ #[cfg(windows)]
+ {
+ if tauri::utils::platform::is_windows_7() {
+ self.notify_win7(app)
+ } else {
+ #[allow(deprecated)]
+ self.show()
+ }
+ }
+ #[cfg(not(windows))]
+ {
+ #[allow(deprecated)]
+ self.show()
+ }
+ }
+
+ #[cfg(all(windows, feature = "windows7-compat"))]
+ fn notify_win7(self, app: &tauri::AppHandle) -> crate::Result<()> {
+ let app = app.clone();
+ let default_window_icon = app.manager.inner.default_window_icon.clone();
+ let _ = app.run_on_main_thread(move || {
+ let mut notification = win7_notifications::Notification::new();
+ if let Some(body) = self.body {
+ notification.body(&body);
+ }
+ if let Some(title) = self.title {
+ notification.summary(&title);
+ }
+ if let Some(tauri::Icon::Rgba {
+ rgba,
+ width,
+ height,
+ }) = default_window_icon
+ {
+ notification.icon(rgba, width, height);
+ }
+ let _ = notification.show();
+ });
+
+ Ok(())
+ }
+ }
+}
diff --git a/plugins/notification/src/error.rs b/plugins/notification/src/error.rs
new file mode 100644
index 00000000..339e763b
--- /dev/null
+++ b/plugins/notification/src/error.rs
@@ -0,0 +1,25 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::{ser::Serializer, Serialize};
+
+pub type Result = std::result::Result;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error(transparent)]
+ Io(#[from] std::io::Error),
+ #[cfg(mobile)]
+ #[error(transparent)]
+ PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
+}
+
+impl Serialize for Error {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(self.to_string().as_ref())
+ }
+}
diff --git a/plugins/notification/src/init.js b/plugins/notification/src/init.js
new file mode 100644
index 00000000..105a7f9b
--- /dev/null
+++ b/plugins/notification/src/init.js
@@ -0,0 +1,71 @@
+(function () {
+ let permissionSettable = false
+ let permissionValue = 'default'
+
+ function isPermissionGranted() {
+ if (window.Notification.permission !== 'default') {
+ return Promise.resolve(window.Notification.permission === 'granted')
+ }
+ return __TAURI__.invoke('plugin:notification|is_permission_granted')
+ }
+
+ function setNotificationPermission(value) {
+ permissionSettable = true
+ // @ts-expect-error we can actually set this value on the webview
+ window.Notification.permission = value
+ permissionSettable = false
+ }
+
+ function requestPermission() {
+ return __TAURI__.invoke('plugin:notification|request_permission')
+ .then(function (permission) {
+ setNotificationPermission(permission)
+ return permission
+ })
+ }
+
+ function sendNotification(options) {
+ if (typeof options === 'object') {
+ Object.freeze(options)
+ }
+
+ return __TAURI__.invoke('plugin:notification|notify', {
+ options: typeof options === 'string'
+ ? {
+ title: options
+ }
+ : options
+ })
+ }
+
+ // @ts-expect-error unfortunately we can't implement the whole type, so we overwrite it with our own version
+ window.Notification = function (title, options) {
+ const opts = options || {}
+ sendNotification(
+ Object.assign(opts, { title })
+ )
+ }
+
+ window.Notification.requestPermission = requestPermission
+
+ Object.defineProperty(window.Notification, 'permission', {
+ enumerable: true,
+ get: function () {
+ return permissionValue
+ },
+ set: function (v) {
+ if (!permissionSettable) {
+ throw new Error('Readonly property')
+ }
+ permissionValue = v
+ }
+ })
+
+ isPermissionGranted().then(function (response) {
+ if (response === null) {
+ setNotificationPermission('default')
+ } else {
+ setNotificationPermission(response ? 'granted' : 'denied')
+ }
+ })
+})()
diff --git a/plugins/notification/src/lib.rs b/plugins/notification/src/lib.rs
new file mode 100644
index 00000000..cb63758a
--- /dev/null
+++ b/plugins/notification/src/lib.rs
@@ -0,0 +1,118 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::Serialize;
+#[cfg(mobile)]
+use tauri::plugin::PluginHandle;
+#[cfg(desktop)]
+use tauri::AppHandle;
+use tauri::{
+ plugin::{Builder, TauriPlugin},
+ Manager, Runtime,
+};
+
+pub use models::*;
+
+#[cfg(desktop)]
+mod desktop;
+#[cfg(mobile)]
+mod mobile;
+
+mod commands;
+mod error;
+mod models;
+
+pub use error::{Error, Result};
+
+#[cfg(desktop)]
+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 {
+ #[cfg(desktop)]
+ app: AppHandle,
+ #[cfg(mobile)]
+ handle: PluginHandle,
+ data: NotificationData,
+}
+
+impl NotificationBuilder {
+ #[cfg(desktop)]
+ fn new(app: AppHandle) -> Self {
+ Self {
+ app,
+ data: Default::default(),
+ }
+ }
+
+ #[cfg(mobile)]
+ fn new(handle: PluginHandle) -> Self {
+ Self {
+ handle,
+ data: Default::default(),
+ }
+ }
+
+ /// Sets the notification title.
+ pub fn title(mut self, title: impl Into) -> Self {
+ self.data.title.replace(title.into());
+ self
+ }
+
+ /// Sets the notification body.
+ pub fn body(mut self, body: impl Into) -> Self {
+ self.data.body.replace(body.into());
+ self
+ }
+
+ /// Sets the notification icon.
+ pub fn icon(mut self, icon: impl Into) -> Self {
+ self.data.icon.replace(icon.into());
+ self
+ }
+}
+
+/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the notification APIs.
+pub trait NotificationExt {
+ fn notification(&self) -> &Notification;
+}
+
+impl> crate::NotificationExt for T {
+ fn notification(&self) -> &Notification {
+ self.state::>().inner()
+ }
+}
+
+/// Initializes the plugin.
+pub fn init() -> TauriPlugin {
+ Builder::new("notification")
+ .invoke_handler(tauri::generate_handler![
+ commands::notify,
+ commands::request_permission,
+ commands::is_permission_granted
+ ])
+ .js_init_script(include_str!("init.js").into())
+ .setup(|app, api| {
+ #[cfg(mobile)]
+ let notification = mobile::init(app, api)?;
+ #[cfg(desktop)]
+ let notification = desktop::init(app, api)?;
+ app.manage(notification);
+ Ok(())
+ })
+ .build()
+}
diff --git a/plugins/notification/src/mobile.rs b/plugins/notification/src/mobile.rs
new file mode 100644
index 00000000..abd196ed
--- /dev/null
+++ b/plugins/notification/src/mobile.rs
@@ -0,0 +1,66 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::{de::DeserializeOwned, Deserialize};
+use tauri::{
+ plugin::{PluginApi, PluginHandle},
+ AppHandle, Runtime,
+};
+
+use crate::models::*;
+
+#[cfg(target_os = "android")]
+const PLUGIN_IDENTIFIER: &str = "app.tauri.notification";
+
+#[cfg(target_os = "ios")]
+tauri::ios_plugin_binding!(init_plugin_notification);
+
+// initializes the Kotlin or Swift plugin classes
+pub fn init(
+ _app: &AppHandle,
+ api: PluginApi,
+) -> crate::Result> {
+ #[cfg(target_os = "android")]
+ let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "NotificationPlugin")?;
+ #[cfg(target_os = "ios")]
+ let handle = api.register_ios_plugin(init_plugin_notification)?;
+ Ok(Notification(handle))
+}
+
+impl crate::NotificationBuilder {
+ pub fn show(self) -> crate::Result<()> {
+ self.handle
+ .run_mobile_plugin("notify", self.data)
+ .map_err(Into::into)
+ }
+}
+
+/// Access to the notification APIs.
+pub struct Notification(PluginHandle);
+
+impl Notification {
+ pub fn builder(&self) -> crate::NotificationBuilder {
+ crate::NotificationBuilder::new(self.0.clone())
+ }
+
+ pub fn request_permission(&self) -> crate::Result {
+ self.0
+ .run_mobile_plugin::("requestPermission", ())
+ .map(|r| r.permission_state)
+ .map_err(Into::into)
+ }
+
+ pub fn permission_state(&self) -> crate::Result {
+ self.0
+ .run_mobile_plugin::("permissionState", ())
+ .map(|r| r.permission_state)
+ .map_err(Into::into)
+ }
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct PermissionResponse {
+ permission_state: PermissionState,
+}
diff --git a/plugins/notification/src/models.rs b/plugins/notification/src/models.rs
new file mode 100644
index 00000000..d1cf0e4b
--- /dev/null
+++ b/plugins/notification/src/models.rs
@@ -0,0 +1,48 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::fmt::Display;
+
+use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
+
+/// Permission state.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PermissionState {
+ /// Permission access has been granted.
+ Granted,
+ /// Permission access has been denied.
+ Denied,
+}
+
+impl Display for PermissionState {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Granted => write!(f, "granted"),
+ Self::Denied => write!(f, "denied"),
+ }
+ }
+}
+
+impl Serialize for PermissionState {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(self.to_string().as_ref())
+ }
+}
+
+impl<'de> Deserialize<'de> for PermissionState {
+ fn deserialize(deserializer: D) -> std::result::Result
+ where
+ D: Deserializer<'de>,
+ {
+ let s = String::deserialize(deserializer)?;
+ match s.to_lowercase().as_str() {
+ "granted" => Ok(Self::Granted),
+ "denied" => Ok(Self::Denied),
+ _ => Err(DeError::custom(format!("unknown permission state '{s}'"))),
+ }
+ }
+}
diff --git a/plugins/notification/tsconfig.json b/plugins/notification/tsconfig.json
new file mode 100644
index 00000000..5098169a
--- /dev/null
+++ b/plugins/notification/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": ["guest-js/*.ts"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f8794a87..e9eba864 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -169,6 +169,16 @@ importers:
specifier: ^2.5.0
version: 2.5.0
+ plugins/notification:
+ dependencies:
+ '@tauri-apps/api':
+ specifier: ^1.2.0
+ version: 1.2.0
+ devDependencies:
+ tslib:
+ specifier: ^2.4.1
+ version: 2.4.1
+
plugins/positioner:
dependencies:
'@tauri-apps/api':