Browse Source

feat: add notification sound (#7269)

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.studio>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Amr Bashir 2 years ago
parent
commit
6c408b736c

+ 6 - 0
.changes/notification-sound.md

@@ -0,0 +1,6 @@
+---
+'tauri': 'minor:feat'
+'@tauri-apps/api': 'minor:feat'
+---
+
+Add option to specify notification sound.

+ 1 - 4
.github/workflows/check-generated-files.yml

@@ -32,7 +32,6 @@ jobs:
           filters: |
             api:
               - 'tooling/api/src/**'
-              - 'tooling/api/docs/js-api.json'
               - 'core/tauri/scripts/bundle.global.js'
             schema:
               - 'core/tauri-utils/src/config.rs'
@@ -50,9 +49,7 @@ jobs:
         working-directory: tooling/api
         run: yarn && yarn build
       - name: check api
-        run: |
-          git restore tooling/api/docs/js-api.json
-          ./.scripts/ci/has-diff.sh
+        run: ./.scripts/ci/has-diff.sh
 
   schema:
     runs-on: ubuntu-latest

+ 1 - 1
.github/workflows/test-core.yml

@@ -99,7 +99,7 @@ jobs:
           cargo update -p is-terminal --precise 0.4.7
           cargo update -p colored --precise 2.0.2
           cargo update -p tempfile --precise 3.6.0
-          cargo update -p serde_with:3.1.0 --precise 3.0.0
+          cargo update -p serde_with:3.2.0 --precise 3.0.0
 
       - name: test
         run: cargo test --target ${{ matrix.platform.target }} ${{ matrix.features.args }}

+ 0 - 1
.prettierignore

@@ -9,5 +9,4 @@ dist
 /tooling/cli/templates
 /tooling/cli/node
 /tooling/cli/schema.json
-/tooling/api/docs/js-api.json
 /core/tauri-config-schema/schema.json

+ 1 - 1
core/tauri/Cargo.toml

@@ -103,7 +103,7 @@ objc = "0.2"
 
 [target."cfg(windows)".dependencies]
 webview2-com = "0.19.1"
-win7-notifications = { version = "0.3.1", optional = true }
+win7-notifications = { version = "0.4", optional = true }
 
   [target."cfg(windows)".dependencies.windows]
   version = "0.39.0"

+ 65 - 0
core/tauri/src/api/notification.rs

@@ -40,6 +40,50 @@ pub struct Notification {
   icon: Option<String>,
   /// The notification identifier
   identifier: String,
+  /// The notification sound
+  sound: Option<Sound>,
+}
+
+/// Notification sound.
+#[derive(Debug)]
+pub enum Sound {
+  /// The default notification sound.
+  Default,
+  /// A custom notification sound.
+  ///
+  /// ## Platform-specific
+  ///
+  /// Each OS has a different sound name so you will need to conditionally specify an appropriate sound
+  /// based on the OS in use, for a list of sounds see:
+  /// - **Linux**: can be one of the sounds listed in <https://0pointer.de/public/sound-naming-spec.html>
+  /// - **Windows**: can be one of the sounds listed in <https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-audio>
+  ///   but without the prefix, for example, if `ms-winsoundevent:Notification.Default` you would use `Default` and
+  ///   if `ms-winsoundevent:Notification.Looping.Alarm2`, you would use `Alarm2`.
+  ///   Windows 7 is not supported, if a sound is provided, it will play the default sound, otherwise it will be silent.
+  /// - **macOS**: you can specify the name of the sound you'd like to play when the notification is shown.
+  /// Any of the default sounds (under System Preferences > Sound) can be used, in addition to custom sound files.
+  /// Be sure that the sound file is under one of the following locations:
+  ///   - `~/Library/Sounds`
+  ///   - `/Library/Sounds`
+  ///   - `/Network/Library/Sounds`
+  ///   - `/System/Library/Sounds`
+  ///
+  ///   See the [`NSSound`] docs for more information.
+  ///
+  /// [`NSSound`]: https://developer.apple.com/documentation/appkit/nssound
+  Custom(String),
+}
+
+impl From<String> for Sound {
+  fn from(value: String) -> Self {
+    Self::Custom(value)
+  }
+}
+
+impl From<&str> for Sound {
+  fn from(value: &str) -> Self {
+    Self::Custom(value.into())
+  }
 }
 
 impl Notification {
@@ -72,6 +116,15 @@ impl Notification {
     self
   }
 
+  /// Sets the notification sound. By default the notification has no sound.
+  ///
+  /// See [`Sound`] for more information.
+  #[must_use]
+  pub fn sound(mut self, sound: impl Into<Sound>) -> Self {
+    self.sound.replace(sound.into());
+    self
+  }
+
   /// Shows the notification.
   ///
   /// # Examples
@@ -108,6 +161,17 @@ impl Notification {
     } else {
       notification.auto_icon();
     }
+    if let Some(sound) = self.sound {
+      notification.sound_name(&match sound {
+        #[cfg(target_os = "macos")]
+        Sound::Default => "NSUserNotificationDefaultSoundName".to_string(),
+        #[cfg(windows)]
+        Sound::Default => "Default".to_string(),
+        #[cfg(all(unix, not(target_os = "macos")))]
+        Sound::Default => "message-new-instant".to_string(),
+        Sound::Custom(c) => c,
+      });
+    }
     #[cfg(windows)]
     {
       let exe = tauri_utils::platform::current_exe()?;
@@ -191,6 +255,7 @@ impl Notification {
       if let Some(title) = self.title {
         notification.summary(&title);
       }
+      notification.silent(self.sound.is_none());
       if let Some(crate::Icon::Rgba {
         rgba,
         width,

+ 48 - 2
core/tauri/src/endpoints/notification.rs

@@ -6,7 +6,7 @@
 
 use super::InvokeContext;
 use crate::Runtime;
-use serde::Deserialize;
+use serde::{Deserialize, Deserializer};
 use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 #[cfg(notification_all)]
@@ -17,6 +17,36 @@ const PERMISSION_GRANTED: &str = "granted";
 // `Denied` response from `request_permission`. Matches the Web API return value.
 const PERMISSION_DENIED: &str = "denied";
 
+#[derive(Debug, Clone)]
+pub enum SoundDto {
+  Default,
+  Custom(String),
+}
+
+#[cfg(notification_all)]
+impl From<SoundDto> for crate::api::notification::Sound {
+  fn from(sound: SoundDto) -> Self {
+    match sound {
+      SoundDto::Default => crate::api::notification::Sound::Default,
+      SoundDto::Custom(s) => crate::api::notification::Sound::Custom(s),
+    }
+  }
+}
+
+impl<'de> Deserialize<'de> for SoundDto {
+  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+  where
+    D: Deserializer<'de>,
+  {
+    let s = String::deserialize(deserializer)?;
+    if s.to_lowercase() == "default" {
+      Ok(Self::Default)
+    } else {
+      Ok(Self::Custom(s))
+    }
+  }
+}
+
 /// The options for the notification API.
 #[derive(Debug, Clone, Deserialize)]
 pub struct NotificationOptions {
@@ -26,6 +56,8 @@ pub struct NotificationOptions {
   pub body: Option<String>,
   /// The notification icon.
   pub icon: Option<String>,
+  /// The notification sound.
+  pub sound: Option<SoundDto>,
 }
 
 /// The API descriptor.
@@ -56,6 +88,9 @@ impl Cmd {
     if let Some(icon) = options.icon {
       notification = notification.icon(icon);
     }
+    if let Some(sound) = options.sound {
+      notification = notification.sound(sound);
+    }
     #[cfg(feature = "windows7-compat")]
     {
       notification.notify(&context.window.app_handle)?;
@@ -84,16 +119,27 @@ impl Cmd {
 
 #[cfg(test)]
 mod tests {
-  use super::NotificationOptions;
+  use super::{NotificationOptions, SoundDto};
 
   use quickcheck::{Arbitrary, Gen};
 
+  impl Arbitrary for SoundDto {
+    fn arbitrary(g: &mut Gen) -> Self {
+      if bool::arbitrary(g) {
+        Self::Default
+      } else {
+        Self::Custom(String::arbitrary(g))
+      }
+    }
+  }
+
   impl Arbitrary for NotificationOptions {
     fn arbitrary(g: &mut Gen) -> Self {
       Self {
         title: String::arbitrary(g),
         body: Option::arbitrary(g),
         icon: Option::arbitrary(g),
+        sound: Option::arbitrary(g),
       }
     }
   }

+ 0 - 1
core/tests/app-updater/frameworks/test.framework/Headers

@@ -1 +0,0 @@
-Versions/Current/Headers

+ 1 - 0
core/tests/app-updater/frameworks/test.framework/Headers

@@ -0,0 +1 @@
+Versions/Current/Headers

+ 0 - 1
core/tests/app-updater/frameworks/test.framework/Resources

@@ -1 +0,0 @@
-Versions/Current/Resources

+ 1 - 0
core/tests/app-updater/frameworks/test.framework/Resources

@@ -0,0 +1 @@
+Versions/Current/Resources

+ 0 - 1
core/tests/app-updater/frameworks/test.framework/Versions/A/test

@@ -1 +0,0 @@
-A

+ 1 - 0
core/tests/app-updater/frameworks/test.framework/Versions/Current

@@ -0,0 +1 @@
+A

+ 0 - 1
core/tests/app-updater/frameworks/test.framework/test

@@ -1 +0,0 @@
-Versions/Current/test

+ 1 - 0
core/tests/app-updater/frameworks/test.framework/test

@@ -0,0 +1 @@
+Versions/Current/test

File diff suppressed because it is too large
+ 0 - 0
examples/api/dist/assets/index.js


File diff suppressed because it is too large
+ 214 - 226
examples/api/src-tauri/Cargo.lock


+ 8 - 4
examples/api/src/views/Notifications.svelte

@@ -1,11 +1,15 @@
 <script>
+  import { sendNotification as notify } from '@tauri-apps/api/notification'
+
   export let onMessage
 
   // send the notification directly
   // the backend is responsible for checking the permission
-  function _sendNotification() {
-    new Notification('Notification title', {
-      body: 'This is the notification body'
+  async function _sendNotification() {
+    notify({
+      title: 'Notification title',
+      body: 'This is the notification body',
+      sound: 'default'
     })
   }
 
@@ -29,6 +33,6 @@
   }
 </script>
 
-<button class="btn" id="notification" on:click={_sendNotification}>
+<button class="btn" id="notification" on:click={sendNotification}>
   Send test notification
 </button>

+ 0 - 1
tooling/api/.gitignore

@@ -65,4 +65,3 @@ package-lock.json
 
 # Documentation output
 docs/*
-!docs/js-api.json

+ 25 - 0
tooling/api/src/notification.ts

@@ -38,6 +38,31 @@ interface Options {
   body?: string
   /** Optional notification icon. */
   icon?: string
+  /**
+   * Optional notification sound.
+   *
+   * #### Platform-specific
+   *
+   * Each OS has a different sound name so you will need to conditionally specify an appropriate sound
+   * based on the OS in use, 'default' represents the default system sound. For a list of sounds see:
+   * - **Linux**: can be one of the sounds listed in {@link https://0pointer.de/public/sound-naming-spec.html}
+   * - **Windows**: can be one of the sounds listed in {@link https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-audio}
+   *   but without the prefix, for example, if `ms-winsoundevent:Notification.Default` you would use `Default` and
+   *   if `ms-winsoundevent:Notification.Looping.Alarm2`, you would use `Alarm2`.
+   *   Windows 7 is not supported, if a sound is provided, it will play the default sound, otherwise it will be silent.
+   * - **macOS**: you can specify the name of the sound you'd like to play when the notification is shown.
+   * Any of the default sounds (under System Preferences > Sound) can be used, in addition to custom sound files.
+   * Be sure that the sound file is copied under the app bundle (e.g., `YourApp.app/Contents/Resources`), or one of the following locations:
+   *   - `~/Library/Sounds`
+   *   - `/Library/Sounds`
+   *   - `/Network/Library/Sounds`
+   *   - `/System/Library/Sounds`
+   *
+   *   See the {@link https://developer.apple.com/documentation/appkit/nssound | NSSound} docs for more information.
+   *
+   * @since 1.5.0
+   */
+  sound?: 'default' | string
 }
 
 /** Possible permission values. */

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