Skip to content

Commit

Permalink
feat(core): set parent window on ask and message dialog APIs (#2454)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Aug 16, 2021
1 parent b0a8c38 commit c76f4b7
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 69 deletions.
5 changes: 5 additions & 0 deletions .changes/dialog-ask-message-parent.md
@@ -0,0 +1,5 @@
---
"tauri": patch
---

**Breaking change:** Added `window_parent: Option<&Window>` as first argument to the `ask` and `message` APIs on the `tauri::api::dialog` module.
2 changes: 1 addition & 1 deletion core/tauri/Cargo.toml
Expand Up @@ -66,7 +66,7 @@ attohttpc = { version = "0.17", features = [ "json", "form" ] }
open = { version = "2.0", optional = true }
shared_child = { version = "0.3", optional = true }
os_pipe = { version = "0.9", optional = true }
rfd = "0.4.2"
rfd = { version = "0.4.3", features = ["parent"] }
raw-window-handle = { version = "0.3.3", optional = true }
minisign-verify = { version = "0.1", optional = true }
os_info = { version = "3.0.6", optional = true }
Expand Down
109 changes: 88 additions & 21 deletions core/tauri/src/api/dialog.rs
Expand Up @@ -7,6 +7,8 @@
#[cfg(any(dialog_open, dialog_save))]
use std::path::{Path, PathBuf};

use crate::{Runtime, Window};

#[cfg(not(target_os = "linux"))]
macro_rules! run_dialog {
($e:expr, $h: ident) => {{
Expand All @@ -30,6 +32,48 @@ macro_rules! run_dialog {
}};
}

/// Window parent definition.
#[cfg(any(windows, target_os = "macos"))]
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))]
pub struct WindowParent {
#[cfg(windows)]
hwnd: *mut std::ffi::c_void,
#[cfg(target_os = "macos")]
ns_window: *mut std::ffi::c_void,
}

#[cfg(any(windows, target_os = "macos"))]
unsafe impl raw_window_handle::HasRawWindowHandle for WindowParent {
#[cfg(windows)]
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(target_os = "macos")]
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let mut handle = raw_window_handle::macos::MacOSHandle::empty();
handle.ns_window = self.ns_window;
raw_window_handle::RawWindowHandle::MacOS(handle)
}
}

#[cfg(any(windows, target_os = "macos"))]
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))]
#[doc(hidden)]
pub fn window_parent<R: Runtime>(window: &Window<R>) -> crate::Result<WindowParent> {
#[cfg(windows)]
let w = WindowParent {
hwnd: window.hwnd()?,
};
#[cfg(target_os = "macos")]
let w = WindowParent {
ns_window: window.ns_window()?,
};
Ok(w)
}

/// The file dialog builder.
///
/// Constructs file picker dialogs that can select single/multiple files or directories.
Expand Down Expand Up @@ -62,7 +106,6 @@ 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);
Expand Down Expand Up @@ -91,36 +134,60 @@ impl FileDialogBuilder {
}

/// Displays a dialog with a message and an optional title with a "yes" and a "no" button.
pub fn ask<F: FnOnce(bool) + Send + 'static>(
#[allow(unused_variables)]
pub fn ask<R: Runtime, F: FnOnce(bool) + Send + 'static>(
parent_window: Option<&Window<R>>,
title: impl AsRef<str>,
message: impl AsRef<str>,
f: F,
) {
let title = title.as_ref().to_string();
let message = message.as_ref().to_string();
run_dialog!(
rfd::MessageDialog::new()
.set_title(&title)
.set_description(&message)
.set_buttons(rfd::MessageButtons::YesNo)
.set_level(rfd::MessageLevel::Info)
.show(),
f
)
#[allow(unused_mut)]
let mut builder = rfd::MessageDialog::new()
.set_title(&title)
.set_description(&message)
.set_buttons(rfd::MessageButtons::YesNo)
.set_level(rfd::MessageLevel::Info);

#[cfg(any(windows, target_os = "macos"))]
{
if let Some(window) = parent_window {
if let Ok(parent) = window_parent(window) {
builder = builder.set_parent(&parent);
}
}
}

run_dialog!(builder.show(), f)
}

/// Displays a message dialog.
pub fn message(title: impl AsRef<str>, message: impl AsRef<str>) {
#[allow(unused_variables)]
pub fn message<R: Runtime>(
parent_window: Option<&Window<R>>,
title: impl AsRef<str>,
message: impl AsRef<str>,
) {
let title = title.as_ref().to_string();
let message = message.as_ref().to_string();
let cb = |_| {};
run_dialog!(
rfd::MessageDialog::new()
.set_title(&title)
.set_description(&message)
.set_buttons(rfd::MessageButtons::Ok)
.set_level(rfd::MessageLevel::Info)
.show(),
cb
)

#[allow(unused_mut)]
let mut builder = rfd::MessageDialog::new()
.set_title(&title)
.set_description(&message)
.set_buttons(rfd::MessageButtons::Ok)
.set_level(rfd::MessageLevel::Info);

#[cfg(any(windows, target_os = "macos"))]
{
if let Some(window) = parent_window {
if let Ok(parent) = window_parent(window) {
builder = builder.set_parent(&parent);
}
}
}

run_dialog!(builder.show(), cb)
}
2 changes: 1 addition & 1 deletion core/tauri/src/endpoints.rs
Expand Up @@ -128,7 +128,7 @@ impl Module {
}
Self::Notification(cmd) => resolver.respond_closure(move || {
cmd
.run(config, &package_info)
.run(window, config, &package_info)
.and_then(|r| r.json)
.map_err(InvokeError::from)
}),
Expand Down
46 changes: 16 additions & 30 deletions core/tauri/src/endpoints/dialog.rs
Expand Up @@ -3,6 +3,8 @@
// SPDX-License-Identifier: MIT

use super::InvokeResponse;
#[cfg(any(windows, target_os = "macos"))]
use crate::api::dialog::window_parent;
#[cfg(any(dialog_open, dialog_save))]
use crate::api::dialog::FileDialogBuilder;
use crate::{
Expand Down Expand Up @@ -77,7 +79,7 @@ impl Cmd {
pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
match self {
#[cfg(dialog_open)]
Self::OpenDialog { options } => open(window, options),
Self::OpenDialog { options } => open(&window, options),
#[cfg(not(dialog_open))]
Self::OpenDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > open".to_string())),

Expand All @@ -93,12 +95,13 @@ impl Cmd {
.expect("failed to get binary filename")
.to_string_lossy()
.to_string();
message_dialog(app_name, message);
message_dialog(Some(&window), app_name, message);
Ok(().into())
}
Self::AskDialog { title, message } => {
let exe = std::env::current_exe()?;
let answer = ask(
&window,
title.unwrap_or_else(|| {
exe
.file_stem()
Expand Down Expand Up @@ -132,38 +135,17 @@ fn set_default_path(
}
}

#[cfg(all(windows, any(dialog_open, dialog_save)))]
struct WindowParent {
hwnd: *mut std::ffi::c_void,
}

#[cfg(all(windows, any(dialog_open, dialog_save)))]
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(all(windows, any(dialog_open, dialog_save)))]
fn parent<R: Runtime>(window: Window<R>) -> crate::Result<WindowParent> {
Ok(WindowParent {
hwnd: window.hwnd()?,
})
}

/// Shows an open dialog.
#[cfg(dialog_open)]
#[allow(unused_variables)]
pub fn open<R: Runtime>(
window: Window<R>,
window: &Window<R>,
options: OpenDialogOptions,
) -> crate::Result<InvokeResponse> {
let mut dialog_builder = FileDialogBuilder::new();
#[cfg(windows)]
#[cfg(any(windows, target_os = "macos"))]
{
dialog_builder = dialog_builder.set_parent(&parent(window)?);
dialog_builder = dialog_builder.set_parent(&window_parent(window)?);
}
if let Some(default_path) = options.default_path {
if !default_path.exists() {
Expand Down Expand Up @@ -197,9 +179,9 @@ pub fn save<R: Runtime>(
options: SaveDialogOptions,
) -> crate::Result<InvokeResponse> {
let mut dialog_builder = FileDialogBuilder::new();
#[cfg(windows)]
#[cfg(any(windows, target_os = "macos"))]
{
dialog_builder = dialog_builder.set_parent(&parent(window)?);
dialog_builder = dialog_builder.set_parent(&window_parent(&window)?);
}
if let Some(default_path) = options.default_path {
dialog_builder = set_default_path(dialog_builder, default_path);
Expand All @@ -214,8 +196,12 @@ pub fn save<R: Runtime>(
}

/// Shows a dialog with a yes/no question.
pub fn ask(title: String, message: String) -> crate::Result<InvokeResponse> {
pub fn ask<R: Runtime>(
window: &Window<R>,
title: String,
message: String,
) -> crate::Result<InvokeResponse> {
let (tx, rx) = channel();
ask_dialog(title, message, move |m| tx.send(m).unwrap());
ask_dialog(Some(window), title, message, move |m| tx.send(m).unwrap());
Ok(rx.recv().unwrap().into())
}
14 changes: 10 additions & 4 deletions core/tauri/src/endpoints/notification.rs
Expand Up @@ -7,7 +7,7 @@ use serde::Deserialize;

#[cfg(notification_all)]
use crate::api::notification::Notification;
use crate::{Config, PackageInfo};
use crate::{Config, PackageInfo, Runtime, Window};

use std::sync::Arc;

Expand Down Expand Up @@ -42,8 +42,9 @@ pub enum Cmd {

impl Cmd {
#[allow(unused_variables)]
pub fn run(
pub fn run<R: Runtime>(
self,
window: Window<R>,
config: Arc<Config>,
package_info: &PackageInfo,
) -> crate::Result<InvokeResponse> {
Expand All @@ -60,7 +61,7 @@ impl Cmd {
}
Self::RequestNotificationPermission => {
#[cfg(notification_all)]
return request_permission(&config, package_info).map(Into::into);
return request_permission(&window, &config, package_info).map(Into::into);
#[cfg(not(notification_all))]
Ok(PERMISSION_DENIED.into())
}
Expand Down Expand Up @@ -96,7 +97,11 @@ pub fn is_permission_granted(
}

#[cfg(notification_all)]
pub fn request_permission(config: &Config, package_info: &PackageInfo) -> crate::Result<String> {
pub fn request_permission<R: Runtime>(
window: &Window<R>,
config: &Config,
package_info: &PackageInfo,
) -> crate::Result<String> {
let mut settings = crate::settings::read_settings(config, package_info);
if let Some(allow_notification) = settings.allow_notification {
return Ok(if allow_notification {
Expand All @@ -107,6 +112,7 @@ pub fn request_permission(config: &Config, package_info: &PackageInfo) -> crate:
}
let (tx, rx) = std::sync::mpsc::channel();
crate::api::dialog::ask(
Some(window),
"Permissions",
"This app wants to show notifications. Do you allow?",
move |answer| {
Expand Down
16 changes: 13 additions & 3 deletions core/tauri/src/updater/mod.rs
Expand Up @@ -394,8 +394,15 @@ pub(crate) async fn check_update_with_dialog<R: Runtime>(
// if dialog enabled only
if updater.should_update && updater_config.dialog {
let body = updater.body.clone().unwrap_or_else(|| String::from(""));
let dialog =
prompt_for_install(&updater.clone(), &package_info.name, &body.clone(), pubkey).await;
let window_ = window.clone();
let dialog = prompt_for_install(
window_,
&updater.clone(),
&package_info.name,
&body.clone(),
pubkey,
)
.await;

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

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

// Ask user if we need to restart the application
ask(
Some(&window),
"Ready to Restart",
"The installation was successful, do you want to restart the application now?",
|should_exit| {
Expand Down

0 comments on commit c76f4b7

Please sign in to comment.