Skip to content

Commit c76f4b7

Browse files
authored
feat(core): set parent window on ask and message dialog APIs (#2454)
1 parent b0a8c38 commit c76f4b7

File tree

8 files changed

+149
-69
lines changed

8 files changed

+149
-69
lines changed

.changes/dialog-ask-message-parent.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
**Breaking change:** Added `window_parent: Option<&Window>` as first argument to the `ask` and `message` APIs on the `tauri::api::dialog` module.

core/tauri/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ attohttpc = { version = "0.17", features = [ "json", "form" ] }
6666
open = { version = "2.0", optional = true }
6767
shared_child = { version = "0.3", optional = true }
6868
os_pipe = { version = "0.9", optional = true }
69-
rfd = "0.4.2"
69+
rfd = { version = "0.4.3", features = ["parent"] }
7070
raw-window-handle = { version = "0.3.3", optional = true }
7171
minisign-verify = { version = "0.1", optional = true }
7272
os_info = { version = "3.0.6", optional = true }

core/tauri/src/api/dialog.rs

+88-21
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#[cfg(any(dialog_open, dialog_save))]
88
use std::path::{Path, PathBuf};
99

10+
use crate::{Runtime, Window};
11+
1012
#[cfg(not(target_os = "linux"))]
1113
macro_rules! run_dialog {
1214
($e:expr, $h: ident) => {{
@@ -30,6 +32,48 @@ macro_rules! run_dialog {
3032
}};
3133
}
3234

35+
/// Window parent definition.
36+
#[cfg(any(windows, target_os = "macos"))]
37+
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))]
38+
pub struct WindowParent {
39+
#[cfg(windows)]
40+
hwnd: *mut std::ffi::c_void,
41+
#[cfg(target_os = "macos")]
42+
ns_window: *mut std::ffi::c_void,
43+
}
44+
45+
#[cfg(any(windows, target_os = "macos"))]
46+
unsafe impl raw_window_handle::HasRawWindowHandle for WindowParent {
47+
#[cfg(windows)]
48+
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
49+
let mut handle = raw_window_handle::windows::WindowsHandle::empty();
50+
handle.hwnd = self.hwnd;
51+
raw_window_handle::RawWindowHandle::Windows(handle)
52+
}
53+
54+
#[cfg(target_os = "macos")]
55+
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
56+
let mut handle = raw_window_handle::macos::MacOSHandle::empty();
57+
handle.ns_window = self.ns_window;
58+
raw_window_handle::RawWindowHandle::MacOS(handle)
59+
}
60+
}
61+
62+
#[cfg(any(windows, target_os = "macos"))]
63+
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))]
64+
#[doc(hidden)]
65+
pub fn window_parent<R: Runtime>(window: &Window<R>) -> crate::Result<WindowParent> {
66+
#[cfg(windows)]
67+
let w = WindowParent {
68+
hwnd: window.hwnd()?,
69+
};
70+
#[cfg(target_os = "macos")]
71+
let w = WindowParent {
72+
ns_window: window.ns_window()?,
73+
};
74+
Ok(w)
75+
}
76+
3377
/// The file dialog builder.
3478
///
3579
/// Constructs file picker dialogs that can select single/multiple files or directories.
@@ -62,7 +106,6 @@ impl FileDialogBuilder {
62106
self
63107
}
64108

65-
#[cfg(windows)]
66109
/// Sets the parent window of the dialog.
67110
pub fn set_parent<W: raw_window_handle::HasRawWindowHandle>(mut self, parent: &W) -> Self {
68111
self.0 = self.0.set_parent(parent);
@@ -91,36 +134,60 @@ impl FileDialogBuilder {
91134
}
92135

93136
/// Displays a dialog with a message and an optional title with a "yes" and a "no" button.
94-
pub fn ask<F: FnOnce(bool) + Send + 'static>(
137+
#[allow(unused_variables)]
138+
pub fn ask<R: Runtime, F: FnOnce(bool) + Send + 'static>(
139+
parent_window: Option<&Window<R>>,
95140
title: impl AsRef<str>,
96141
message: impl AsRef<str>,
97142
f: F,
98143
) {
99144
let title = title.as_ref().to_string();
100145
let message = message.as_ref().to_string();
101-
run_dialog!(
102-
rfd::MessageDialog::new()
103-
.set_title(&title)
104-
.set_description(&message)
105-
.set_buttons(rfd::MessageButtons::YesNo)
106-
.set_level(rfd::MessageLevel::Info)
107-
.show(),
108-
f
109-
)
146+
#[allow(unused_mut)]
147+
let mut builder = rfd::MessageDialog::new()
148+
.set_title(&title)
149+
.set_description(&message)
150+
.set_buttons(rfd::MessageButtons::YesNo)
151+
.set_level(rfd::MessageLevel::Info);
152+
153+
#[cfg(any(windows, target_os = "macos"))]
154+
{
155+
if let Some(window) = parent_window {
156+
if let Ok(parent) = window_parent(window) {
157+
builder = builder.set_parent(&parent);
158+
}
159+
}
160+
}
161+
162+
run_dialog!(builder.show(), f)
110163
}
111164

112165
/// Displays a message dialog.
113-
pub fn message(title: impl AsRef<str>, message: impl AsRef<str>) {
166+
#[allow(unused_variables)]
167+
pub fn message<R: Runtime>(
168+
parent_window: Option<&Window<R>>,
169+
title: impl AsRef<str>,
170+
message: impl AsRef<str>,
171+
) {
114172
let title = title.as_ref().to_string();
115173
let message = message.as_ref().to_string();
116174
let cb = |_| {};
117-
run_dialog!(
118-
rfd::MessageDialog::new()
119-
.set_title(&title)
120-
.set_description(&message)
121-
.set_buttons(rfd::MessageButtons::Ok)
122-
.set_level(rfd::MessageLevel::Info)
123-
.show(),
124-
cb
125-
)
175+
176+
#[allow(unused_mut)]
177+
let mut builder = rfd::MessageDialog::new()
178+
.set_title(&title)
179+
.set_description(&message)
180+
.set_buttons(rfd::MessageButtons::Ok)
181+
.set_level(rfd::MessageLevel::Info);
182+
183+
#[cfg(any(windows, target_os = "macos"))]
184+
{
185+
if let Some(window) = parent_window {
186+
if let Ok(parent) = window_parent(window) {
187+
builder = builder.set_parent(&parent);
188+
}
189+
}
190+
}
191+
192+
run_dialog!(builder.show(), cb)
126193
}

core/tauri/src/endpoints.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ impl Module {
128128
}
129129
Self::Notification(cmd) => resolver.respond_closure(move || {
130130
cmd
131-
.run(config, &package_info)
131+
.run(window, config, &package_info)
132132
.and_then(|r| r.json)
133133
.map_err(InvokeError::from)
134134
}),

core/tauri/src/endpoints/dialog.rs

+16-30
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// SPDX-License-Identifier: MIT
44

55
use super::InvokeResponse;
6+
#[cfg(any(windows, target_os = "macos"))]
7+
use crate::api::dialog::window_parent;
68
#[cfg(any(dialog_open, dialog_save))]
79
use crate::api::dialog::FileDialogBuilder;
810
use crate::{
@@ -77,7 +79,7 @@ impl Cmd {
7779
pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
7880
match self {
7981
#[cfg(dialog_open)]
80-
Self::OpenDialog { options } => open(window, options),
82+
Self::OpenDialog { options } => open(&window, options),
8183
#[cfg(not(dialog_open))]
8284
Self::OpenDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > open".to_string())),
8385

@@ -93,12 +95,13 @@ impl Cmd {
9395
.expect("failed to get binary filename")
9496
.to_string_lossy()
9597
.to_string();
96-
message_dialog(app_name, message);
98+
message_dialog(Some(&window), app_name, message);
9799
Ok(().into())
98100
}
99101
Self::AskDialog { title, message } => {
100102
let exe = std::env::current_exe()?;
101103
let answer = ask(
104+
&window,
102105
title.unwrap_or_else(|| {
103106
exe
104107
.file_stem()
@@ -132,38 +135,17 @@ fn set_default_path(
132135
}
133136
}
134137

135-
#[cfg(all(windows, any(dialog_open, dialog_save)))]
136-
struct WindowParent {
137-
hwnd: *mut std::ffi::c_void,
138-
}
139-
140-
#[cfg(all(windows, any(dialog_open, dialog_save)))]
141-
unsafe impl raw_window_handle::HasRawWindowHandle for WindowParent {
142-
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
143-
let mut handle = raw_window_handle::windows::WindowsHandle::empty();
144-
handle.hwnd = self.hwnd;
145-
raw_window_handle::RawWindowHandle::Windows(handle)
146-
}
147-
}
148-
149-
#[cfg(all(windows, any(dialog_open, dialog_save)))]
150-
fn parent<R: Runtime>(window: Window<R>) -> crate::Result<WindowParent> {
151-
Ok(WindowParent {
152-
hwnd: window.hwnd()?,
153-
})
154-
}
155-
156138
/// Shows an open dialog.
157139
#[cfg(dialog_open)]
158140
#[allow(unused_variables)]
159141
pub fn open<R: Runtime>(
160-
window: Window<R>,
142+
window: &Window<R>,
161143
options: OpenDialogOptions,
162144
) -> crate::Result<InvokeResponse> {
163145
let mut dialog_builder = FileDialogBuilder::new();
164-
#[cfg(windows)]
146+
#[cfg(any(windows, target_os = "macos"))]
165147
{
166-
dialog_builder = dialog_builder.set_parent(&parent(window)?);
148+
dialog_builder = dialog_builder.set_parent(&window_parent(window)?);
167149
}
168150
if let Some(default_path) = options.default_path {
169151
if !default_path.exists() {
@@ -197,9 +179,9 @@ pub fn save<R: Runtime>(
197179
options: SaveDialogOptions,
198180
) -> crate::Result<InvokeResponse> {
199181
let mut dialog_builder = FileDialogBuilder::new();
200-
#[cfg(windows)]
182+
#[cfg(any(windows, target_os = "macos"))]
201183
{
202-
dialog_builder = dialog_builder.set_parent(&parent(window)?);
184+
dialog_builder = dialog_builder.set_parent(&window_parent(&window)?);
203185
}
204186
if let Some(default_path) = options.default_path {
205187
dialog_builder = set_default_path(dialog_builder, default_path);
@@ -214,8 +196,12 @@ pub fn save<R: Runtime>(
214196
}
215197

216198
/// Shows a dialog with a yes/no question.
217-
pub fn ask(title: String, message: String) -> crate::Result<InvokeResponse> {
199+
pub fn ask<R: Runtime>(
200+
window: &Window<R>,
201+
title: String,
202+
message: String,
203+
) -> crate::Result<InvokeResponse> {
218204
let (tx, rx) = channel();
219-
ask_dialog(title, message, move |m| tx.send(m).unwrap());
205+
ask_dialog(Some(window), title, message, move |m| tx.send(m).unwrap());
220206
Ok(rx.recv().unwrap().into())
221207
}

core/tauri/src/endpoints/notification.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use serde::Deserialize;
77

88
#[cfg(notification_all)]
99
use crate::api::notification::Notification;
10-
use crate::{Config, PackageInfo};
10+
use crate::{Config, PackageInfo, Runtime, Window};
1111

1212
use std::sync::Arc;
1313

@@ -42,8 +42,9 @@ pub enum Cmd {
4242

4343
impl Cmd {
4444
#[allow(unused_variables)]
45-
pub fn run(
45+
pub fn run<R: Runtime>(
4646
self,
47+
window: Window<R>,
4748
config: Arc<Config>,
4849
package_info: &PackageInfo,
4950
) -> crate::Result<InvokeResponse> {
@@ -60,7 +61,7 @@ impl Cmd {
6061
}
6162
Self::RequestNotificationPermission => {
6263
#[cfg(notification_all)]
63-
return request_permission(&config, package_info).map(Into::into);
64+
return request_permission(&window, &config, package_info).map(Into::into);
6465
#[cfg(not(notification_all))]
6566
Ok(PERMISSION_DENIED.into())
6667
}
@@ -96,7 +97,11 @@ pub fn is_permission_granted(
9697
}
9798

9899
#[cfg(notification_all)]
99-
pub fn request_permission(config: &Config, package_info: &PackageInfo) -> crate::Result<String> {
100+
pub fn request_permission<R: Runtime>(
101+
window: &Window<R>,
102+
config: &Config,
103+
package_info: &PackageInfo,
104+
) -> crate::Result<String> {
100105
let mut settings = crate::settings::read_settings(config, package_info);
101106
if let Some(allow_notification) = settings.allow_notification {
102107
return Ok(if allow_notification {
@@ -107,6 +112,7 @@ pub fn request_permission(config: &Config, package_info: &PackageInfo) -> crate:
107112
}
108113
let (tx, rx) = std::sync::mpsc::channel();
109114
crate::api::dialog::ask(
115+
Some(window),
110116
"Permissions",
111117
"This app wants to show notifications. Do you allow?",
112118
move |answer| {

core/tauri/src/updater/mod.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,15 @@ pub(crate) async fn check_update_with_dialog<R: Runtime>(
394394
// if dialog enabled only
395395
if updater.should_update && updater_config.dialog {
396396
let body = updater.body.clone().unwrap_or_else(|| String::from(""));
397-
let dialog =
398-
prompt_for_install(&updater.clone(), &package_info.name, &body.clone(), pubkey).await;
397+
let window_ = window.clone();
398+
let dialog = prompt_for_install(
399+
window_,
400+
&updater.clone(),
401+
&package_info.name,
402+
&body.clone(),
403+
pubkey,
404+
)
405+
.await;
399406

400407
if dialog.is_err() {
401408
send_status_update(
@@ -516,7 +523,8 @@ fn send_status_update<R: Runtime>(window: Window<R>, status: &str, error: Option
516523

517524
// Prompt a dialog asking if the user want to install the new version
518525
// Maybe we should add an option to customize it in future versions.
519-
async fn prompt_for_install(
526+
async fn prompt_for_install<R: Runtime>(
527+
window: Window<R>,
520528
updater: &self::core::Update,
521529
app_name: &str,
522530
body: &str,
@@ -530,6 +538,7 @@ async fn prompt_for_install(
530538
// todo(lemarier): We should review this and make sure we have
531539
// something more conventional.
532540
ask(
541+
Some(&window),
533542
format!(r#"A new version of {} is available! "#, app_name),
534543
format!(
535544
r#"{} {} is now available -- you have {}.
@@ -552,6 +561,7 @@ Release Notes:
552561

553562
// Ask user if we need to restart the application
554563
ask(
564+
Some(&window),
555565
"Ready to Restart",
556566
"The installation was successful, do you want to restart the application now?",
557567
|should_exit| {

0 commit comments

Comments
 (0)