mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-11-28 12:27:16 +03:00
feat(core): set parent window on ask and message dialog APIs (#2454)
This commit is contained in:
parent
b0a8c38a73
commit
c76f4b7d39
5
.changes/dialog-ask-message-parent.md
Normal file
5
.changes/dialog-ask-message-parent.md
Normal file
@ -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.
|
@ -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 }
|
||||
|
@ -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) => {{
|
||||
@ -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.
|
||||
@ -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);
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}),
|
||||
|
@ -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::{
|
||||
@ -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())),
|
||||
|
||||
@ -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()
|
||||
@ -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() {
|
||||
@ -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);
|
||||
@ -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())
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -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> {
|
||||
@ -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())
|
||||
}
|
||||
@ -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 {
|
||||
@ -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| {
|
||||
|
@ -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(
|
||||
@ -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,
|
||||
@ -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 {}.
|
||||
@ -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| {
|
||||
|
@ -183,18 +183,24 @@ fn main() {
|
||||
// Triggered when a window is trying to close
|
||||
Event::CloseRequested { label, api, .. } => {
|
||||
let app_handle = app_handle.clone();
|
||||
let window = app_handle.get_window(&label).unwrap();
|
||||
// use the exposed close api, and prevent the event loop to close
|
||||
api.prevent_close();
|
||||
// ask the user if he wants to quit
|
||||
ask(
|
||||
"Tauri API",
|
||||
"Are you sure that you want to close this window?",
|
||||
move |answer| {
|
||||
if answer {
|
||||
app_handle.get_window(&label).unwrap().close().unwrap();
|
||||
}
|
||||
},
|
||||
);
|
||||
// we need to run this on another thread because this is the event loop callback handler
|
||||
// and the dialog API needs to communicate with the event loop.
|
||||
std::thread::spawn(move || {
|
||||
ask(
|
||||
Some(&window),
|
||||
"Tauri API",
|
||||
"Are you sure that you want to close this window?",
|
||||
move |answer| {
|
||||
if answer {
|
||||
app_handle.get_window(&label).unwrap().close().unwrap();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Keep the event loop running even if all windows are closed
|
||||
|
Loading…
Reference in New Issue
Block a user