diff --git a/Cargo.lock b/Cargo.lock index 09a94d9..f5a118e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,15 +352,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" -[[package]] -name = "deferred-future" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5131db302b3cb350203ec927491d6e7dc34da02ac14978c923444a42e34f33b" -dependencies = [ - "futures", -] - [[package]] name = "dispatch" version = "0.2.0" @@ -1038,7 +1029,6 @@ dependencies = [ "block2", "core-foundation", "core-foundation-sys", - "deferred-future", "futures", "glib-sys", "gobject-sys", diff --git a/Cargo.toml b/Cargo.toml index 38db2c7..ddabb15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,6 @@ objc2-app-kit = { version = "0.2.0", features = [ "NSView", "NSWindow", ] } -deferred-future = "0.1.5" core-foundation = "0.10.0" core-foundation-sys = "0.8.7" diff --git a/examples/msg.rs b/examples/msg.rs index f9066ea..56ded6b 100644 --- a/examples/msg.rs +++ b/examples/msg.rs @@ -1,5 +1,5 @@ -use ::std::io::{ Read, self }; -use ::futures::executor::block_on; +use std::io::{self, Read}; + fn main() { #[cfg(not(feature = "gtk3"))] let res = ""; @@ -23,16 +23,18 @@ fn main() { .set_buttons(rfd::MessageButtons::OkCancel) .set_level(rfd::MessageLevel::Error) .show(); - println!("被点击按钮是 {}。敲击 Ctrl+D 继续。", res); + println!("res: {}, Ctrl+D", res); + let mut stdin = io::stdin(); let mut buffer: Vec = vec![]; stdin.read_to_end(&mut buffer).unwrap(); + #[cfg(any( target_os = "windows", target_os = "macos", all( any( - target_os = "linux", + target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", @@ -41,17 +43,20 @@ fn main() { feature = "gtk3" ) ))] - block_on(async move { + futures::executor::block_on(async move { let res = rfd::AsyncMessageDialog::new() .set_title("Msg!") .set_description("Description!") .set_buttons(rfd::MessageButtons::OkCancel) - .show().await; - println!("被点击按钮是 {}", res); + .show() + .await; + println!("res: {}", res); }); - println!("敲击 Ctrl+D 继续。"); + + println!("Ctrl+D"); let mut stdin = io::stdin(); let mut buffer: Vec = vec![]; stdin.read_to_end(&mut buffer).unwrap(); - println!("结束"); -} \ No newline at end of file + println!(); +} + diff --git a/src/backend/macos/utils/user_alert.rs b/src/backend/macos/utils/user_alert.rs index 098c1f1..bba7a14 100644 --- a/src/backend/macos/utils/user_alert.rs +++ b/src/backend/macos/utils/user_alert.rs @@ -1,12 +1,27 @@ -use ::objc2_foundation::MainThreadMarker; -use ::deferred_future::ThreadDeferredFuture; -use ::std::{ mem::MaybeUninit, ptr, sync::PoisonError, thread }; -use ::core_foundation::{ base::TCFType, string::CFString }; -use ::core_foundation_sys::{ base::CFOptionFlags, date::CFTimeInterval, url::CFURLRef, user_notification::{ CFUserNotificationDisplayAlert, kCFUserNotificationStopAlertLevel, kCFUserNotificationCautionAlertLevel, kCFUserNotificationNoteAlertLevel, kCFUserNotificationDefaultResponse, kCFUserNotificationAlternateResponse, kCFUserNotificationOtherResponse, kCFUserNotificationCancelResponse } }; use crate::{ - message_dialog::{ MessageButtons, MessageDialog, MessageDialogResult, MessageLevel }, - backend::{ DialogFutureType, macos::utils::{ FocusManager, PolicyManager } } + backend::{ + macos::utils::{FocusManager, PolicyManager}, + DialogFutureType, + }, + message_dialog::{MessageButtons, MessageDialog, MessageDialogResult, MessageLevel}, }; + +use core_foundation::{base::TCFType, string::CFString}; +use core_foundation_sys::{ + base::CFOptionFlags, + date::CFTimeInterval, + url::CFURLRef, + user_notification::{ + kCFUserNotificationAlternateResponse, kCFUserNotificationCancelResponse, + kCFUserNotificationCautionAlertLevel, kCFUserNotificationDefaultResponse, + kCFUserNotificationNoteAlertLevel, kCFUserNotificationOtherResponse, + kCFUserNotificationStopAlertLevel, CFUserNotificationDisplayAlert, + }, +}; +use objc2_foundation::MainThreadMarker; + +use std::{mem::MaybeUninit, ptr, thread}; + struct UserAlert { timeout: CFTimeInterval, flags: CFOptionFlags, @@ -22,16 +37,29 @@ struct UserAlert { _focus_manager: Option, _policy_manager: Option, } + impl UserAlert { fn new(opt: MessageDialog, mtm: Option) -> Self { let mut buttons: [Option; 3] = match &opt.buttons { MessageButtons::Ok => [None, None, None], MessageButtons::OkCancel => [None, Some("Cancel".to_string()), None], MessageButtons::YesNo => [Some("Yes".to_string()), Some("No".to_string()), None], - MessageButtons::YesNoCancel => [Some("Yes".to_string()), Some("No".to_string()), Some("Cancel".to_string())], + MessageButtons::YesNoCancel => [ + Some("Yes".to_string()), + Some("No".to_string()), + Some("Cancel".to_string()), + ], MessageButtons::OkCustom(ok_text) => [Some(ok_text.to_string()), None, None], - MessageButtons::OkCancelCustom(ok_text, cancel_text) => [Some(ok_text.to_string()), Some(cancel_text.to_string()), None], - MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => [Some(yes_text.to_string()), Some(no_text.to_string()), Some(cancel_text.to_string())] + MessageButtons::OkCancelCustom(ok_text, cancel_text) => [ + Some(ok_text.to_string()), + Some(cancel_text.to_string()), + None, + ], + MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => [ + Some(yes_text.to_string()), + Some(no_text.to_string()), + Some(cancel_text.to_string()), + ], }; UserAlert { timeout: 0_f64, @@ -41,7 +69,7 @@ impl UserAlert { flags: match opt.level { MessageLevel::Info => kCFUserNotificationNoteAlertLevel, MessageLevel::Warning => kCFUserNotificationCautionAlertLevel, - MessageLevel::Error => kCFUserNotificationStopAlertLevel + MessageLevel::Error => kCFUserNotificationStopAlertLevel, }, alert_header: opt.title, alert_message: opt.description, @@ -50,29 +78,38 @@ impl UserAlert { other_button_title: buttons[2].take(), buttons: opt.buttons, _policy_manager: mtm.map(|mtm| PolicyManager::new(mtm)), - _focus_manager: mtm.map(|mtm| FocusManager::new(mtm)) + _focus_manager: mtm.map(|mtm| FocusManager::new(mtm)), } } + fn run(self) -> MessageDialogResult { - let alert_header = CFString::new(&self.alert_header[..]); + let alert_header = CFString::new(&self.alert_header[..]); let alert_message = CFString::new(&self.alert_message[..]); - let default_button_title = self.default_button_title.map(|string| CFString::new(&string[..])); - let alternate_button_title = self.alternate_button_title.map(|value| CFString::new(&value[..])); - let other_button_title = self.other_button_title.map(|value| CFString::new(&value[..])); + let default_button_title = self + .default_button_title + .map(|string| CFString::new(&string[..])); + let alternate_button_title = self + .alternate_button_title + .map(|value| CFString::new(&value[..])); + let other_button_title = self + .other_button_title + .map(|value| CFString::new(&value[..])); let mut response_flags = MaybeUninit::::uninit(); - let is_cancel = unsafe { CFUserNotificationDisplayAlert( - self.timeout, - self.flags, - self.icon_url, - self.sound_url, - self.localization_url, - alert_header.as_concrete_TypeRef(), - alert_message.as_concrete_TypeRef(), - default_button_title.map_or(ptr::null(), |value| value.as_concrete_TypeRef()), - alternate_button_title.map_or(ptr::null(), |value| value.as_concrete_TypeRef()), - other_button_title.map_or(ptr::null(), |value| value.as_concrete_TypeRef()), - response_flags.as_mut_ptr() - ) }; + let is_cancel = unsafe { + CFUserNotificationDisplayAlert( + self.timeout, + self.flags, + self.icon_url, + self.sound_url, + self.localization_url, + alert_header.as_concrete_TypeRef(), + alert_message.as_concrete_TypeRef(), + default_button_title.map_or(ptr::null(), |value| value.as_concrete_TypeRef()), + alternate_button_title.map_or(ptr::null(), |value| value.as_concrete_TypeRef()), + other_button_title.map_or(ptr::null(), |value| value.as_concrete_TypeRef()), + response_flags.as_mut_ptr(), + ) + }; if is_cancel != 0 { return MessageDialogResult::Cancel; } @@ -81,34 +118,85 @@ impl UserAlert { return MessageDialogResult::Cancel; } match self.buttons { - MessageButtons::Ok if response == kCFUserNotificationDefaultResponse => MessageDialogResult::Ok, - MessageButtons::OkCancel if response == kCFUserNotificationDefaultResponse => MessageDialogResult::Ok, - MessageButtons::OkCancel if response == kCFUserNotificationAlternateResponse => MessageDialogResult::Cancel, - MessageButtons::YesNo if response == kCFUserNotificationDefaultResponse => MessageDialogResult::Yes, - MessageButtons::YesNo if response == kCFUserNotificationAlternateResponse => MessageDialogResult::No, - MessageButtons::YesNoCancel if response == kCFUserNotificationDefaultResponse => MessageDialogResult::Yes, - MessageButtons::YesNoCancel if response == kCFUserNotificationAlternateResponse => MessageDialogResult::No, - MessageButtons::YesNoCancel if response == kCFUserNotificationOtherResponse => MessageDialogResult::Cancel, - MessageButtons::OkCustom(custom) if response == kCFUserNotificationDefaultResponse => MessageDialogResult::Custom(custom.to_owned()), - MessageButtons::OkCancelCustom(custom, _) if response == kCFUserNotificationDefaultResponse => MessageDialogResult::Custom(custom.to_owned()), - MessageButtons::OkCancelCustom(_, custom) if response == kCFUserNotificationAlternateResponse => MessageDialogResult::Custom(custom.to_owned()), - MessageButtons::YesNoCancelCustom(custom, _, _) if response == kCFUserNotificationDefaultResponse => MessageDialogResult::Custom(custom.to_owned()), - MessageButtons::YesNoCancelCustom(_, custom, _) if response == kCFUserNotificationAlternateResponse => MessageDialogResult::Custom(custom.to_owned()), - MessageButtons::YesNoCancelCustom(_, _, custom) if response == kCFUserNotificationOtherResponse => MessageDialogResult::Custom(custom.to_owned()), + MessageButtons::Ok if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Ok + } + MessageButtons::OkCancel if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Ok + } + MessageButtons::OkCancel if response == kCFUserNotificationAlternateResponse => { + MessageDialogResult::Cancel + } + MessageButtons::YesNo if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Yes + } + MessageButtons::YesNo if response == kCFUserNotificationAlternateResponse => { + MessageDialogResult::No + } + MessageButtons::YesNoCancel if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Yes + } + MessageButtons::YesNoCancel if response == kCFUserNotificationAlternateResponse => { + MessageDialogResult::No + } + MessageButtons::YesNoCancel if response == kCFUserNotificationOtherResponse => { + MessageDialogResult::Cancel + } + MessageButtons::OkCustom(custom) if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::OkCancelCustom(custom, _) + if response == kCFUserNotificationDefaultResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::OkCancelCustom(_, custom) + if response == kCFUserNotificationAlternateResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(custom, _, _) + if response == kCFUserNotificationDefaultResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(_, custom, _) + if response == kCFUserNotificationAlternateResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(_, _, custom) + if response == kCFUserNotificationOtherResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } _ => MessageDialogResult::Cancel, } } } + pub fn sync_pop_dialog(opt: MessageDialog, mtm: MainThreadMarker) -> MessageDialogResult { UserAlert::new(opt, Some(mtm)).run() } + pub fn async_pop_dialog(opt: MessageDialog) -> DialogFutureType { - let deferred_future = ThreadDeferredFuture::default(); - let defer = deferred_future.defer(); + let (tx, rx) = crate::oneshot::channel(); + thread::spawn(move || { - let mut defer = defer.lock().unwrap_or_else(PoisonError::into_inner); let message_dialog_result = UserAlert::new(opt.clone(), None).run(); - defer.complete(message_dialog_result); + if let Err(err) = tx.send(message_dialog_result) { + log::error!("UserAler result send error: {err}"); + } }); - Box::pin(deferred_future) -} \ No newline at end of file + + Box::pin(async { + match rx.await { + Ok(res) => res, + Err(err) => { + log::error!("UserAler error: {err}"); + MessageDialogResult::Cancel + } + } + }) +} + diff --git a/src/lib.rs b/src/lib.rs index a1ba2ec..d81741c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,6 +129,8 @@ mod file_handle; pub use file_handle::FileHandle; mod file_dialog; +#[cfg(target_os = "macos")] +mod oneshot; #[cfg(not(target_arch = "wasm32"))] pub use file_dialog::FileDialog; diff --git a/src/oneshot.rs b/src/oneshot.rs new file mode 100644 index 0000000..3536985 --- /dev/null +++ b/src/oneshot.rs @@ -0,0 +1,255 @@ +use std::fmt; +use std::pin::Pin; +use std::future::Future; +use std::sync::{Mutex, Arc, atomic::{AtomicBool, Ordering::SeqCst}}; +use std::task::{Context, Poll, Waker}; + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Receiver { + inner: Arc>, +} + +pub struct Sender { + inner: Arc>, +} + +// The channels do not ever project Pin to the inner T +impl Unpin for Receiver {} +impl Unpin for Sender {} + +/// Internal state of the `Receiver`/`Sender` pair above. This is all used as +/// the internal synchronization between the two for send/recv operations. +struct Inner { + /// Indicates whether this oneshot is complete yet. This is filled in both + /// by `Sender::drop` and by `Receiver::drop`, and both sides interpret it + /// appropriately. + /// + /// For `Receiver`, if this is `true`, then it's guaranteed that `data` is + /// unlocked and ready to be inspected. + /// + /// For `Sender` if this is `true` then the oneshot has gone away and it + /// can return ready from `poll_canceled`. + complete: AtomicBool, + + /// The actual data being transferred as part of this `Receiver`. This is + /// filled in by `Sender::complete` and read by `Receiver::poll`. + /// + /// Note that this is protected by `Lock`, but it is in theory safe to + /// replace with an `UnsafeCell` as it's actually protected by `complete` + /// above. I wouldn't recommend doing this, however, unless someone is + /// supremely confident in the various atomic orderings here and there. + data: Mutex>, + + /// Field to store the task which is blocked in `Receiver::poll`. + /// + /// This is filled in when a oneshot is polled but not ready yet. Note that + /// the `Lock` here, unlike in `data` above, is important to resolve races. + /// Both the `Receiver` and the `Sender` halves understand that if they + /// can't acquire the lock then some important interference is happening. + rx_task: Mutex>, + + /// Like `rx_task` above, except for the task blocked in + /// `Sender::poll_canceled`. Additionally, `Lock` cannot be `UnsafeCell`. + tx_task: Mutex>, +} + +pub fn channel() -> (Sender, Receiver) { + let inner = Arc::new(Inner::new()); + let receiver = Receiver { + inner: inner.clone(), + }; + let sender = Sender { inner }; + (sender, receiver) +} + +impl Inner { + fn new() -> Self { + Self { + complete: AtomicBool::new(false), + data: Mutex::new(None), + rx_task: Mutex::new(None), + tx_task: Mutex::new(None), + } + } + + fn send(&self, t: T) -> Result<(), T> { + if self.complete.load(SeqCst) { + return Err(t); + } + + // Note that this lock acquisition may fail if the receiver + // is closed and sets the `complete` flag to `true`, whereupon + // the receiver may call `poll()`. + if let Ok(mut slot) = self.data.try_lock() { + assert!(slot.is_none()); + *slot = Some(t); + drop(slot); + + // If the receiver called `close()` between the check at the + // start of the function, and the lock being released, then + // the receiver may not be around to receive it, so try to + // pull it back out. + if self.complete.load(SeqCst) { + // If lock acquisition fails, then receiver is actually + // receiving it, so we're good. + if let Ok(mut slot) = self.data.try_lock() { + if let Some(t) = slot.take() { + return Err(t); + } + } + } + Ok(()) + } else { + // Must have been closed + Err(t) + } + } + + fn drop_tx(&self) { + // Flag that we're a completed `Sender` and try to wake up a receiver. + // Whether or not we actually stored any data will get picked up and + // translated to either an item or cancellation. + // + // Note that if we fail to acquire the `rx_task` lock then that means + // we're in one of two situations: + // + // 1. The receiver is trying to block in `poll` + // 2. The receiver is being dropped + // + // In the first case it'll check the `complete` flag after it's done + // blocking to see if it succeeded. In the latter case we don't need to + // wake up anyone anyway. So in both cases it's ok to ignore the `None` + // case of `try_lock` and bail out. + // + // The first case crucially depends on `Lock` using `SeqCst` ordering + // under the hood. If it instead used `Release` / `Acquire` ordering, + // then it would not necessarily synchronize with `inner.complete` + // and deadlock might be possible, as was observed in + // https://github.com/rust-lang/futures-rs/pull/219. + self.complete.store(true, SeqCst); + + if let Ok(mut slot) = self.rx_task.try_lock() { + if let Some(task) = slot.take() { + drop(slot); + task.wake(); + } + } + + // If we registered a task for cancel notification drop it to reduce + // spurious wakeups + if let Ok(mut slot) = self.tx_task.try_lock() { + drop(slot.take()); + } + } + + fn recv(&self, cx: &mut Context<'_>) -> Poll> { + // Check to see if some data has arrived. If it hasn't then we need to + // block our task. + // + // Note that the acquisition of the `rx_task` lock might fail below, but + // the only situation where this can happen is during `Sender::drop` + // when we are indeed completed already. If that's happening then we + // know we're completed so keep going. + let done = if self.complete.load(SeqCst) { + true + } else { + let task = cx.waker().clone(); + match self.rx_task.try_lock() { + Ok(mut slot) => { + *slot = Some(task); + false + } + Err(_) => true, + } + }; + + // If we're `done` via one of the paths above, then look at the data and + // figure out what the answer is. If, however, we stored `rx_task` + // successfully above we need to check again if we're completed in case + // a message was sent while `rx_task` was locked and couldn't notify us + // otherwise. + // + // If we're not done, and we're not complete, though, then we've + // successfully blocked our task and we return `Pending`. + if done || self.complete.load(SeqCst) { + // If taking the lock fails, the sender will realise that the we're + // `done` when it checks the `complete` flag on the way out, and + // will treat the send as a failure. + if let Ok(mut slot) = self.data.try_lock() { + if let Some(data) = slot.take() { + return Poll::Ready(Ok(data)); + } + } + Poll::Ready(Err(Canceled)) + } else { + Poll::Pending + } + } + + fn drop_rx(&self) { + // Indicate to the `Sender` that we're done, so any future calls to + // `poll_canceled` are weeded out. + self.complete.store(true, SeqCst); + + // If we've blocked a task then there's no need for it to stick around, + // so we need to drop it. If this lock acquisition fails, though, then + // it's just because our `Sender` is trying to take the task, so we + // let them take care of that. + if let Ok(mut slot) = self.rx_task.try_lock() { + let task = slot.take(); + drop(slot); + drop(task); + } + + // Finally, if our `Sender` wants to get notified of us going away, it + // would have stored something in `tx_task`. Here we try to peel that + // out and unpark it. + // + // Note that the `try_lock` here may fail, but only if the `Sender` is + // in the process of filling in the task. If that happens then we + // already flagged `complete` and they'll pick that up above. + if let Ok(mut handle) = self.tx_task.try_lock() { + if let Some(task) = handle.take() { + drop(handle); + task.wake() + } + } + } +} + +impl Sender { + pub fn send(self, t: T) -> Result<(), T> { + self.inner.send(t) + } +} + +impl Drop for Sender { + fn drop(&mut self) { + self.inner.drop_tx() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Canceled; + +impl fmt::Display for Canceled { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "oneshot canceled") + } +} + +impl std::error::Error for Canceled {} + +impl Future for Receiver { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.inner.recv(cx) + } +} + +impl Drop for Receiver { + fn drop(&mut self) { + self.inner.drop_rx() + } +}