Skip to content

Commit abf78c5

Browse files
authored
fix(core): set parent window handle on dialogs, closes #1876 (#1889)
1 parent 977b3a8 commit abf78c5

9 files changed

Lines changed: 110 additions & 13 deletions

File tree

.changes/dialog-parent.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Set the Tauri window as parent for dialogs.

.changes/window-parent.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": patch
3+
"tauri-runtime": patch
4+
"tauri-runtime-wry": patch
5+
---
6+
7+
Adds window native handle getter (HWND on Windows).

core/tauri-runtime-wry/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,11 @@ impl From<FileDropEventWrapper> for FileDropEvent {
384384
}
385385
}
386386

387+
#[cfg(windows)]
388+
struct Hwnd(*mut std::ffi::c_void);
389+
#[cfg(windows)]
390+
unsafe impl Send for Hwnd {}
391+
387392
#[derive(Debug, Clone)]
388393
enum WindowMessage {
389394
// Getters
@@ -397,6 +402,8 @@ enum WindowMessage {
397402
CurrentMonitor(Sender<Option<MonitorHandle>>),
398403
PrimaryMonitor(Sender<Option<MonitorHandle>>),
399404
AvailableMonitors(Sender<Vec<MonitorHandle>>),
405+
#[cfg(windows)]
406+
Hwnd(Sender<Hwnd>),
400407
// Setters
401408
SetResizable(bool),
402409
SetTitle(String),
@@ -547,6 +554,11 @@ impl Dispatch for WryDispatcher {
547554
)
548555
}
549556

557+
#[cfg(windows)]
558+
fn hwnd(&self) -> Result<*mut std::ffi::c_void> {
559+
Ok(dispatcher_getter!(self, WindowMessage::Hwnd).0)
560+
}
561+
550562
// Setters
551563

552564
fn print(&self) -> Result<()> {
@@ -1126,6 +1138,11 @@ fn handle_event_loop(
11261138
WindowMessage::AvailableMonitors(tx) => {
11271139
tx.send(window.available_monitors().collect()).unwrap()
11281140
}
1141+
#[cfg(windows)]
1142+
WindowMessage::Hwnd(tx) => {
1143+
use wry::application::platform::windows::WindowExtWindows;
1144+
tx.send(Hwnd(window.hwnd())).unwrap()
1145+
}
11291146
// Setters
11301147
WindowMessage::SetResizable(resizable) => window.set_resizable(resizable),
11311148
WindowMessage::SetTitle(title) => window.set_title(&title),

core/tauri-runtime/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ pub trait Dispatch: Clone + Send + Sized + 'static {
220220
/// Returns the list of all the monitors available on the system.
221221
fn available_monitors(&self) -> crate::Result<Vec<Monitor>>;
222222

223+
/// Returns the native handle that is used by this window.
224+
#[cfg(windows)]
225+
fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void>;
226+
223227
// SETTERS
224228

225229
/// Opens the dialog to prints the contents of the webview.

core/tauri/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ os_pipe = { version = "0.9", optional = true }
8080

8181
# Dialogs
8282
rfd = "0.4"
83+
raw-window-handle = { version="0.3.3", optional = true }
8384

8485
# Updater
8586
minisign-verify = { version = "0.1", optional = true }
@@ -126,8 +127,8 @@ shell-all = [ "shell-open", "shell-execute" ]
126127
shell-execute = [ "shared_child", "os_pipe" ]
127128
shell-open = [ "open" ]
128129
dialog-all = [ "dialog-open", "dialog-save" ]
129-
dialog-open = [ ]
130-
dialog-save = [ ]
130+
dialog-open = [ "raw-window-handle" ]
131+
dialog-save = [ "raw-window-handle" ]
131132
http-all = [ ]
132133
http-request = [ ]
133134
notification-all = [ "notify-rust" ]

core/tauri/src/api/dialog.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ impl FileDialogBuilder {
3636
self
3737
}
3838

39+
#[cfg(windows)]
40+
/// Sets the parent window of the dialog.
41+
pub fn set_parent<W: raw_window_handle::HasRawWindowHandle>(mut self, parent: &W) -> Self {
42+
self.0 = self.0.set_parent(parent);
43+
self
44+
}
45+
3946
/// Pick one file.
4047
pub fn pick_file(self) -> Option<PathBuf> {
4148
self.0.pick_file()

core/tauri/src/endpoints.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,23 @@ impl Module {
107107
// on macOS, the dialog must run on another thread: https://github.com/rust-windowing/winit/issues/1779
108108
// we do the same on Windows just to stay consistent with `tao` (and it also improves UX because of the event loop)
109109
#[cfg(not(target_os = "linux"))]
110-
Self::Dialog(cmd) => resolver
111-
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }),
110+
Self::Dialog(cmd) => resolver.respond_async(async move {
111+
cmd
112+
.run(window)
113+
.and_then(|r| r.json)
114+
.map_err(InvokeError::from)
115+
}),
112116
// on Linux, the dialog must run on the main thread.
113117
#[cfg(target_os = "linux")]
114118
Self::Dialog(cmd) => {
115-
let _ = window.run_on_main_thread(|| {
116-
resolver
117-
.respond_closure(move || cmd.run().and_then(|r| r.json).map_err(InvokeError::from))
119+
let window_ = window.clone();
120+
let _ = window.run_on_main_thread(move || {
121+
resolver.respond_closure(move || {
122+
cmd
123+
.run(window_)
124+
.and_then(|r| r.json)
125+
.map_err(InvokeError::from)
126+
})
118127
});
119128
}
120129
Self::Cli(cmd) => {

core/tauri/src/endpoints/dialog.rs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
use super::InvokeResponse;
66
#[cfg(any(dialog_open, dialog_save))]
77
use crate::api::dialog::FileDialogBuilder;
8-
use crate::api::dialog::{ask as ask_dialog, message as message_dialog, AskResponse};
8+
use crate::{
9+
api::dialog::{ask as ask_dialog, message as message_dialog, AskResponse},
10+
Params, Window,
11+
};
912
use serde::Deserialize;
1013

1114
use std::path::PathBuf;
@@ -68,15 +71,16 @@ pub enum Cmd {
6871
}
6972

7073
impl Cmd {
71-
pub fn run(self) -> crate::Result<InvokeResponse> {
74+
#[allow(unused_variables)]
75+
pub fn run<P: Params>(self, window: Window<P>) -> crate::Result<InvokeResponse> {
7276
match self {
7377
#[cfg(dialog_open)]
74-
Self::OpenDialog { options } => open(options),
78+
Self::OpenDialog { options } => open(window, options),
7579
#[cfg(not(dialog_open))]
7680
Self::OpenDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > open".to_string())),
7781

7882
#[cfg(dialog_save)]
79-
Self::SaveDialog { options } => save(options),
83+
Self::SaveDialog { options } => save(window, options),
8084
#[cfg(not(dialog_save))]
8185
Self::SaveDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > save".to_string())),
8286

@@ -139,10 +143,39 @@ fn set_default_path(
139143
}
140144
}
141145

146+
#[cfg(windows)]
147+
struct WindowParent {
148+
hwnd: *mut std::ffi::c_void,
149+
}
150+
151+
#[cfg(windows)]
152+
unsafe impl raw_window_handle::HasRawWindowHandle for WindowParent {
153+
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
154+
let mut handle = raw_window_handle::windows::WindowsHandle::empty();
155+
handle.hwnd = self.hwnd;
156+
raw_window_handle::RawWindowHandle::Windows(handle)
157+
}
158+
}
159+
160+
#[cfg(windows)]
161+
fn parent<P: Params>(window: Window<P>) -> crate::Result<WindowParent> {
162+
Ok(WindowParent {
163+
hwnd: window.hwnd()?,
164+
})
165+
}
166+
142167
/// Shows an open dialog.
143168
#[cfg(dialog_open)]
144-
pub fn open(options: OpenDialogOptions) -> crate::Result<InvokeResponse> {
169+
#[allow(unused_variables)]
170+
pub fn open<P: Params>(
171+
window: Window<P>,
172+
options: OpenDialogOptions,
173+
) -> crate::Result<InvokeResponse> {
145174
let mut dialog_builder = FileDialogBuilder::new();
175+
#[cfg(windows)]
176+
{
177+
dialog_builder = dialog_builder.set_parent(&parent(window)?);
178+
}
146179
if let Some(default_path) = options.default_path {
147180
if !default_path.exists() {
148181
return Err(crate::Error::DialogDefaultPathNotExists(default_path));
@@ -165,8 +198,16 @@ pub fn open(options: OpenDialogOptions) -> crate::Result<InvokeResponse> {
165198

166199
/// Shows a save dialog.
167200
#[cfg(dialog_save)]
168-
pub fn save(options: SaveDialogOptions) -> crate::Result<InvokeResponse> {
201+
#[allow(unused_variables)]
202+
pub fn save<P: Params>(
203+
window: Window<P>,
204+
options: SaveDialogOptions,
205+
) -> crate::Result<InvokeResponse> {
169206
let mut dialog_builder = FileDialogBuilder::new();
207+
#[cfg(windows)]
208+
{
209+
dialog_builder = dialog_builder.set_parent(&parent(window)?);
210+
}
170211
if let Some(default_path) = options.default_path {
171212
if !default_path.exists() {
172213
return Err(crate::Error::DialogDefaultPathNotExists(default_path));

core/tauri/src/window.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,12 @@ impl<P: Params> Window<P> {
385385
.map_err(Into::into)
386386
}
387387

388+
/// Returns the native handle that is used by this window.
389+
#[cfg(windows)]
390+
pub fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void> {
391+
self.window.dispatcher.hwnd().map_err(Into::into)
392+
}
393+
388394
// Setters
389395

390396
/// Opens the dialog to prints the contents of the webview.

0 commit comments

Comments
 (0)