소스 검색

fix(core): set parent window handle on dialogs, closes #1876 (#1889)

Lucas Fernandes Nogueira 4 년 전
부모
커밋
abf78c5860

+ 5 - 0
.changes/dialog-parent.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Set the Tauri window as parent for dialogs.

+ 7 - 0
.changes/window-parent.md

@@ -0,0 +1,7 @@
+---
+"tauri": patch
+"tauri-runtime": patch
+"tauri-runtime-wry": patch
+---
+
+Adds window native handle getter (HWND on Windows).

+ 17 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -384,6 +384,11 @@ impl From<FileDropEventWrapper> for FileDropEvent {
   }
 }
 
+#[cfg(windows)]
+struct Hwnd(*mut std::ffi::c_void);
+#[cfg(windows)]
+unsafe impl Send for Hwnd {}
+
 #[derive(Debug, Clone)]
 enum WindowMessage {
   // Getters
@@ -397,6 +402,8 @@ enum WindowMessage {
   CurrentMonitor(Sender<Option<MonitorHandle>>),
   PrimaryMonitor(Sender<Option<MonitorHandle>>),
   AvailableMonitors(Sender<Vec<MonitorHandle>>),
+  #[cfg(windows)]
+  Hwnd(Sender<Hwnd>),
   // Setters
   SetResizable(bool),
   SetTitle(String),
@@ -547,6 +554,11 @@ impl Dispatch for WryDispatcher {
     )
   }
 
+  #[cfg(windows)]
+  fn hwnd(&self) -> Result<*mut std::ffi::c_void> {
+    Ok(dispatcher_getter!(self, WindowMessage::Hwnd).0)
+  }
+
   // Setters
 
   fn print(&self) -> Result<()> {
@@ -1126,6 +1138,11 @@ fn handle_event_loop(
             WindowMessage::AvailableMonitors(tx) => {
               tx.send(window.available_monitors().collect()).unwrap()
             }
+            #[cfg(windows)]
+            WindowMessage::Hwnd(tx) => {
+              use wry::application::platform::windows::WindowExtWindows;
+              tx.send(Hwnd(window.hwnd())).unwrap()
+            }
             // Setters
             WindowMessage::SetResizable(resizable) => window.set_resizable(resizable),
             WindowMessage::SetTitle(title) => window.set_title(&title),

+ 4 - 0
core/tauri-runtime/src/lib.rs

@@ -220,6 +220,10 @@ pub trait Dispatch: Clone + Send + Sized + 'static {
   /// Returns the list of all the monitors available on the system.
   fn available_monitors(&self) -> crate::Result<Vec<Monitor>>;
 
+  /// Returns the native handle that is used by this window.
+  #[cfg(windows)]
+  fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void>;
+
   // SETTERS
 
   /// Opens the dialog to prints the contents of the webview.

+ 3 - 2
core/tauri/Cargo.toml

@@ -80,6 +80,7 @@ os_pipe = { version = "0.9", optional = true }
 
 # Dialogs
 rfd = "0.4"
+raw-window-handle = { version="0.3.3", optional = true }
 
 # Updater
 minisign-verify = { version = "0.1", optional = true }
@@ -126,8 +127,8 @@ shell-all = [ "shell-open", "shell-execute" ]
 shell-execute = [ "shared_child", "os_pipe" ]
 shell-open = [ "open" ]
 dialog-all = [ "dialog-open", "dialog-save" ]
-dialog-open = [ ]
-dialog-save = [ ]
+dialog-open = [ "raw-window-handle" ]
+dialog-save = [ "raw-window-handle" ]
 http-all = [ ]
 http-request = [ ]
 notification-all = [ "notify-rust" ]

+ 7 - 0
core/tauri/src/api/dialog.rs

@@ -36,6 +36,13 @@ impl FileDialogBuilder {
     self
   }
 
+  #[cfg(windows)]
+  /// Sets the parent window of the dialog.
+  pub fn set_parent<W: raw_window_handle::HasRawWindowHandle>(mut self, parent: &W) -> Self {
+    self.0 = self.0.set_parent(parent);
+    self
+  }
+
   /// Pick one file.
   pub fn pick_file(self) -> Option<PathBuf> {
     self.0.pick_file()

+ 14 - 5
core/tauri/src/endpoints.rs

@@ -107,14 +107,23 @@ impl Module {
       // on macOS, the dialog must run on another thread: https://github.com/rust-windowing/winit/issues/1779
       // we do the same on Windows just to stay consistent with `tao` (and it also improves UX because of the event loop)
       #[cfg(not(target_os = "linux"))]
-      Self::Dialog(cmd) => resolver
-        .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }),
+      Self::Dialog(cmd) => resolver.respond_async(async move {
+        cmd
+          .run(window)
+          .and_then(|r| r.json)
+          .map_err(InvokeError::from)
+      }),
       // on Linux, the dialog must run on the main thread.
       #[cfg(target_os = "linux")]
       Self::Dialog(cmd) => {
-        let _ = window.run_on_main_thread(|| {
-          resolver
-            .respond_closure(move || cmd.run().and_then(|r| r.json).map_err(InvokeError::from))
+        let window_ = window.clone();
+        let _ = window.run_on_main_thread(move || {
+          resolver.respond_closure(move || {
+            cmd
+              .run(window_)
+              .and_then(|r| r.json)
+              .map_err(InvokeError::from)
+          })
         });
       }
       Self::Cli(cmd) => {

+ 47 - 6
core/tauri/src/endpoints/dialog.rs

@@ -5,7 +5,10 @@
 use super::InvokeResponse;
 #[cfg(any(dialog_open, dialog_save))]
 use crate::api::dialog::FileDialogBuilder;
-use crate::api::dialog::{ask as ask_dialog, message as message_dialog, AskResponse};
+use crate::{
+  api::dialog::{ask as ask_dialog, message as message_dialog, AskResponse},
+  Params, Window,
+};
 use serde::Deserialize;
 
 use std::path::PathBuf;
@@ -68,15 +71,16 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  pub fn run(self) -> crate::Result<InvokeResponse> {
+  #[allow(unused_variables)]
+  pub fn run<P: Params>(self, window: Window<P>) -> crate::Result<InvokeResponse> {
     match self {
       #[cfg(dialog_open)]
-      Self::OpenDialog { options } => open(options),
+      Self::OpenDialog { options } => open(window, options),
       #[cfg(not(dialog_open))]
       Self::OpenDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > open".to_string())),
 
       #[cfg(dialog_save)]
-      Self::SaveDialog { options } => save(options),
+      Self::SaveDialog { options } => save(window, options),
       #[cfg(not(dialog_save))]
       Self::SaveDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > save".to_string())),
 
@@ -139,10 +143,39 @@ fn set_default_path(
   }
 }
 
+#[cfg(windows)]
+struct WindowParent {
+  hwnd: *mut std::ffi::c_void,
+}
+
+#[cfg(windows)]
+unsafe impl raw_window_handle::HasRawWindowHandle for WindowParent {
+  fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
+    let mut handle = raw_window_handle::windows::WindowsHandle::empty();
+    handle.hwnd = self.hwnd;
+    raw_window_handle::RawWindowHandle::Windows(handle)
+  }
+}
+
+#[cfg(windows)]
+fn parent<P: Params>(window: Window<P>) -> crate::Result<WindowParent> {
+  Ok(WindowParent {
+    hwnd: window.hwnd()?,
+  })
+}
+
 /// Shows an open dialog.
 #[cfg(dialog_open)]
-pub fn open(options: OpenDialogOptions) -> crate::Result<InvokeResponse> {
+#[allow(unused_variables)]
+pub fn open<P: Params>(
+  window: Window<P>,
+  options: OpenDialogOptions,
+) -> crate::Result<InvokeResponse> {
   let mut dialog_builder = FileDialogBuilder::new();
+  #[cfg(windows)]
+  {
+    dialog_builder = dialog_builder.set_parent(&parent(window)?);
+  }
   if let Some(default_path) = options.default_path {
     if !default_path.exists() {
       return Err(crate::Error::DialogDefaultPathNotExists(default_path));
@@ -165,8 +198,16 @@ pub fn open(options: OpenDialogOptions) -> crate::Result<InvokeResponse> {
 
 /// Shows a save dialog.
 #[cfg(dialog_save)]
-pub fn save(options: SaveDialogOptions) -> crate::Result<InvokeResponse> {
+#[allow(unused_variables)]
+pub fn save<P: Params>(
+  window: Window<P>,
+  options: SaveDialogOptions,
+) -> crate::Result<InvokeResponse> {
   let mut dialog_builder = FileDialogBuilder::new();
+  #[cfg(windows)]
+  {
+    dialog_builder = dialog_builder.set_parent(&parent(window)?);
+  }
   if let Some(default_path) = options.default_path {
     if !default_path.exists() {
       return Err(crate::Error::DialogDefaultPathNotExists(default_path));

+ 6 - 0
core/tauri/src/window.rs

@@ -385,6 +385,12 @@ impl<P: Params> Window<P> {
       .map_err(Into::into)
   }
 
+  /// Returns the native handle that is used by this window.
+  #[cfg(windows)]
+  pub fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void> {
+    self.window.dispatcher.hwnd().map_err(Into::into)
+  }
+
   // Setters
 
   /// Opens the dialog to prints the contents of the webview.