Эх сурвалжийг харах

fix(core): add windows 7 notification support (#4491)

Lucas Fernandes Nogueira 3 жил өмнө
parent
commit
57039fb216

+ 5 - 0
.changes/is-windows-7.md

@@ -0,0 +1,5 @@
+---
+"tauri-utils": patch
+---
+
+Added `platform::is_windows_7`.

+ 5 - 0
.changes/win7-notifications.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Added `Notification::notify` API behind the `windows7-compat` Cargo feature, which includes Windows 7 support.

+ 1 - 1
.github/workflows/lint-fmt-core.yml

@@ -50,7 +50,7 @@ jobs:
         clippy:
           - { args: '', key: 'empty' }
           - {
-              args: '--features compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray,http-multipart',
+              args: '--features compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart',
               key: 'all'
             }
           - { args: '--features custom-protocol', key: 'custom-protocol' }

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

@@ -89,4 +89,4 @@ jobs:
         run: |
           cargo test
           cargo test --features api-all
-          cargo test --features compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray,http-multipart
+          cargo test --features compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart

+ 1 - 1
.github/workflows/udeps.yml

@@ -29,7 +29,7 @@ jobs:
         clippy:
           - {
               path: './core/tauri/Cargo.toml',
-              args: '--features compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray,http-multipart'
+              args: '--features compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart'
             }
           - { path: './core/tauri-build/Cargo.toml', args: '--all-features' }
           - { path: './core/tauri-codegen/Cargo.toml', args: '--all-features' }

+ 11 - 0
core/tauri-utils/Cargo.toml

@@ -38,6 +38,17 @@ semver = "1"
 [target."cfg(target_os = \"linux\")".dependencies]
 heck = "0.4"
 
+[target."cfg(windows)".dependencies.windows]
+version = "0.37.0"
+features = [
+  "alloc",
+  "implement",
+  "Win32_Foundation",
+  "Win32_System_Com",
+  "Win32_System_LibraryLoader",
+  "Win32_System_SystemInformation"
+]
+
 [features]
 build = [ "proc-macro2", "quote" ]
 compression = [ "brotli" ]

+ 72 - 0
core/tauri-utils/src/platform.rs

@@ -196,3 +196,75 @@ pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result<Path
 
   res
 }
+
+#[cfg(windows)]
+pub use windows_platform::is_windows_7;
+
+#[cfg(windows)]
+mod windows_platform {
+  use windows::Win32::{
+    Foundation::FARPROC,
+    System::{
+      LibraryLoader::{GetProcAddress, LoadLibraryA},
+      SystemInformation::OSVERSIONINFOW,
+    },
+  };
+
+  /// Checks if we're running on Windows 7.
+  pub fn is_windows_7() -> bool {
+    if let Some(v) = get_windows_ver() {
+      // windows 7 is 6.1
+      if v.0 == 6 && v.1 == 1 {
+        return true;
+      }
+    }
+    false
+  }
+
+  fn get_function_impl(library: &str, function: &str) -> Option<FARPROC> {
+    assert_eq!(library.chars().last(), Some('\0'));
+    assert_eq!(function.chars().last(), Some('\0'));
+
+    let module = unsafe { LoadLibraryA(library) }.unwrap_or_default();
+    if module.is_invalid() {
+      None
+    } else {
+      Some(unsafe { GetProcAddress(module, function) })
+    }
+  }
+
+  macro_rules! get_function {
+    ($lib:expr, $func:ident) => {
+      get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
+        .map(|f| unsafe { std::mem::transmute::<windows::Win32::Foundation::FARPROC, $func>(f) })
+    };
+  }
+
+  /// Returns a tuple of (major, minor, buildnumber)
+  fn get_windows_ver() -> Option<(u32, u32, u32)> {
+    type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> i32;
+    let handle = get_function!("ntdll.dll", RtlGetVersion);
+    if let Some(rtl_get_version) = handle {
+      unsafe {
+        let mut vi = OSVERSIONINFOW {
+          dwOSVersionInfoSize: 0,
+          dwMajorVersion: 0,
+          dwMinorVersion: 0,
+          dwBuildNumber: 0,
+          dwPlatformId: 0,
+          szCSDVersion: [0; 128],
+        };
+
+        let status = (rtl_get_version)(&mut vi as _);
+
+        if status >= 0 {
+          Some((vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber))
+        } else {
+          None
+        }
+      }
+    } else {
+      None
+    }
+  }
+}

+ 6 - 3
core/tauri/Cargo.toml

@@ -24,6 +24,7 @@ features = [
   "wry",
   "custom-protocol",
   "api-all",
+  "windows7-compat",
   "cli",
   "updater",
   "fs-extract-api",
@@ -105,10 +106,11 @@ objc = "0.2"
 
 [target."cfg(windows)".dependencies]
 webview2-com = "0.16.0"
+win7-notifications = { version = "0.3.0", optional = true }
 
-  [target."cfg(windows)".dependencies.windows]
-  version = "0.37.0"
-  features = [ "Win32_Foundation" ]
+[target."cfg(windows)".dependencies.windows]
+version = "0.37.0"
+features = [ "Win32_Foundation" ]
 
 [build-dependencies]
 heck = "0.4"
@@ -162,6 +164,7 @@ macos-private-api = [
   "tauri-runtime/macos-private-api",
   "tauri-runtime-wry/macos-private-api"
 ]
+windows7-compat = [ "win7-notifications" ]
 window-data-url = [ "data-url" ]
 api-all = [
   "clipboard-all",

+ 92 - 1
core/tauri/src/api/notification.rs

@@ -73,6 +73,28 @@ impl Notification {
   }
 
   /// Shows the notification.
+  ///
+  /// # Examples
+  ///
+  /// ```no_run
+  /// use tauri::api::notification::Notification;
+  ///
+  /// // on an actual app, remove the string argument
+  /// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json");
+  /// Notification::new(&context.config().tauri.bundle.identifier)
+  ///   .title("Tauri")
+  ///   .body("Tauri is awesome!")
+  ///   .show()
+  ///   .unwrap();
+  /// ```
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **Windows**: Not supported on Windows 7. If your app targets it, enable the `windows7-compat` feature and use [`Self::notify`].
+  #[cfg_attr(
+    all(not(doc_cfg), feature = "windows7-compat"),
+    deprecated = "This function does not work on Windows 7. Use `Self::notify` instead."
+  )]
   pub fn show(self) -> crate::api::Result<()> {
     let mut notification = notify_rust::Notification::new();
     if let Some(body) = self.body {
@@ -108,7 +130,76 @@ impl Notification {
     }
 
     crate::async_runtime::spawn(async move {
-      notification.show().expect("failed to show notification");
+      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<R: crate::Runtime>(self, app: &crate::AppHandle<R>) -> crate::api::Result<()> {
+    #[cfg(windows)]
+    {
+      if crate::utils::platform::is_windows_7() {
+        self.notify_win7(app)
+      } else {
+        #[allow(deprecated)]
+        self.show()
+      }
+    }
+    #[cfg(not(windows))]
+    {
+      #[allow(deprecated)]
+      self.show()
+    }
+  }
+
+  #[cfg(all(windows, feature = "windows7-compat"))]
+  fn notify_win7<R: crate::Runtime>(self, app: &crate::AppHandle<R>) -> crate::api::Result<()> {
+    let app = app.clone();
+    let default_window_icon = app.manager.inner.default_window_icon.clone();
+    let _ = app.run_on_main_thread(move || {
+      let mut notification = win7_notifications::Notification::new();
+      if let Some(body) = self.body {
+        notification.body(&body);
+      }
+      if let Some(title) = self.title {
+        notification.summary(&title);
+      }
+      if let Some(crate::Icon::Rgba {
+        rgba,
+        width,
+        height,
+      }) = default_window_icon
+      {
+        notification.icon(rgba, width, height);
+      }
+      let _ = notification.show();
     });
 
     Ok(())

+ 1 - 1
core/tauri/src/app.rs

@@ -321,7 +321,7 @@ impl<R: Runtime> AssetResolver<R> {
 #[derive(Debug)]
 pub struct AppHandle<R: Runtime> {
   runtime_handle: R::Handle,
-  manager: WindowManager<R>,
+  pub(crate) manager: WindowManager<R>,
   #[cfg(feature = "global-shortcut")]
   global_shortcut_manager: R::GlobalShortcutManager,
   #[cfg(feature = "clipboard")]

+ 5 - 0
core/tauri/src/endpoints/notification.rs

@@ -56,6 +56,11 @@ impl Cmd {
     if let Some(icon) = options.icon {
       notification = notification.icon(icon);
     }
+    #[cfg(feature = "windows7-compat")]
+    {
+      notification.notify(&context.window.app_handle)?;
+    }
+    #[cfg(not(feature = "windows7-compat"))]
     notification.show()?;
     Ok(())
   }

+ 1 - 0
core/tauri/src/lib.rs

@@ -32,6 +32,7 @@
 //! - **cli**: Enables usage of `clap` for CLI argument parsing. Enabled by default if the `cli` config is defined on the `tauri.conf.json` file.
 //! - **system-tray**: Enables application system tray API. Enabled by default if the `systemTray` config is defined on the `tauri.conf.json` file.
 //! - **macos-private-api**: Enables features only available in **macOS**'s private APIs, currently the `transparent` window functionality and the `fullScreenEnabled` preference setting to `true`. Enabled by default if the `tauri > macosPrivateApi` config flag is set to `true` on the `tauri.conf.json` file.
+//! - **windows7-compat**: Enables compatibility with Windows 7 for the notification API.
 //! - **window-data-url**: Enables usage of data URLs on the webview.
 //! - **compression** *(enabled by default): Enables asset compression. You should only disable this if you want faster compile times in release builds - it produces larger binaries.
 //! - **config-json5**: Adds support to JSON5 format for `tauri.conf.json`.

+ 1 - 1
core/tauri/src/manager.rs

@@ -208,7 +208,7 @@ pub struct InnerWindowManager<R: Runtime> {
 
   config: Arc<Config>,
   assets: Arc<dyn Assets>,
-  default_window_icon: Option<Icon>,
+  pub(crate) default_window_icon: Option<Icon>,
   pub(crate) app_icon: Option<Vec<u8>>,
 
   package_info: PackageInfo,

+ 17 - 1
examples/api/src-tauri/Cargo.toml

@@ -12,9 +12,25 @@ tauri-build = { path = "../../../core/tauri-build", features = ["isolation"] }
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = [ "derive" ] }
-tauri = { path = "../../../core/tauri", features = ["api-all", "cli", "global-shortcut", "http-multipart", "icon-ico", "icon-png", "isolation", "macos-private-api", "reqwest-client", "system-tray", "updater"] }
 tiny_http = "0.11"
 
+[dependencies.tauri]
+path = "../../../core/tauri"
+features = [
+  "api-all",
+  "cli",
+  "global-shortcut",
+  "http-multipart",
+  "icon-ico",
+  "icon-png",
+  "isolation",
+  "macos-private-api",
+  "windows7-compat",
+  "reqwest-client",
+  "system-tray",
+  "updater"
+]
+
 [features]
 default = [ "custom-protocol" ]
 custom-protocol = [ "tauri/custom-protocol" ]