From af6c343d0e0c862674ab1189187293e7a73686eb Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 14 Aug 2023 21:19:57 +0200 Subject: [PATCH] Improve macOS/iOS/Web thread safety Co-authored-by: daxpedda --- CHANGELOG.md | 1 + Cargo.toml | 3 +- src/platform/ios.rs | 17 +- src/platform/macos.rs | 34 +-- src/platform/web.rs | 9 +- src/platform_impl/android/mod.rs | 8 + src/platform_impl/ios/window.rs | 194 ++++++-------- src/platform_impl/linux/mod.rs | 8 + src/platform_impl/macos/event.rs | 9 +- src/platform_impl/macos/event_loop.rs | 34 ++- src/platform_impl/macos/mod.rs | 45 +--- .../macos/{util/mod.rs => util.rs} | 7 +- src/platform_impl/macos/util/async.rs | 218 ---------------- src/platform_impl/macos/window.rs | 182 +++++++++++--- src/platform_impl/orbital/window.rs | 8 + src/platform_impl/web/async.rs | 44 ++-- src/platform_impl/web/web_sys/mod.rs | 8 - src/platform_impl/web/window.rs | 238 +++++++----------- src/platform_impl/windows/window.rs | 10 + src/window.rs | 199 +++++++++------ 20 files changed, 552 insertions(+), 724 deletions(-) rename src/platform_impl/macos/{util/mod.rs => util.rs} (95%) delete mode 100644 src/platform_impl/macos/util/async.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d15cf3e..32214584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- Make iOS windows usable from other threads. - Reexport `raw-window-handle` in `window` module. - **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables. - **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result` diff --git a/Cargo.toml b/Cargo.toml index dc729bbe..ee17cb27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,11 +78,11 @@ objc2 = "0.4.1" [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.22.3" -dispatch = "0.2.0" [target.'cfg(target_os = "macos")'.dependencies.icrate] version = "0.0.4" features = [ + "dispatch", "Foundation", "Foundation_NSArray", "Foundation_NSAttributedString", @@ -98,6 +98,7 @@ features = [ [target.'cfg(target_os = "ios")'.dependencies.icrate] version = "0.0.4" features = [ + "dispatch", "Foundation", "Foundation_NSArray", "Foundation_NSString", diff --git a/src/platform/ios.rs b/src/platform/ios.rs index 63c701f4..e02708f1 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -78,28 +78,33 @@ pub trait WindowExtIOS { impl WindowExtIOS for Window { #[inline] fn set_scale_factor(&self, scale_factor: f64) { - self.window.set_scale_factor(scale_factor) + self.window + .maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor)) } #[inline] fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { - self.window.set_valid_orientations(valid_orientations) + self.window + .maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations)) } #[inline] fn set_prefers_home_indicator_hidden(&self, hidden: bool) { - self.window.set_prefers_home_indicator_hidden(hidden) + self.window + .maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden)) } #[inline] fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { - self.window - .set_preferred_screen_edges_deferring_system_gestures(edges) + self.window.maybe_queue_on_main(move |w| { + w.set_preferred_screen_edges_deferring_system_gestures(edges) + }) } #[inline] fn set_prefers_status_bar_hidden(&self, hidden: bool) { - self.window.set_prefers_status_bar_hidden(hidden) + self.window + .maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden)) } } diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 114f3e7d..d3fedeb1 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -84,72 +84,78 @@ pub trait WindowExtMacOS { impl WindowExtMacOS for Window { #[inline] fn simple_fullscreen(&self) -> bool { - self.window.simple_fullscreen() + self.window.maybe_wait_on_main(|w| w.simple_fullscreen()) } #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { - self.window.set_simple_fullscreen(fullscreen) + self.window + .maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen)) } #[inline] fn has_shadow(&self) -> bool { - self.window.has_shadow() + self.window.maybe_wait_on_main(|w| w.has_shadow()) } #[inline] fn set_has_shadow(&self, has_shadow: bool) { - self.window.set_has_shadow(has_shadow) + self.window + .maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow)) } #[inline] fn set_tabbing_identifier(&self, identifier: &str) { - self.window.set_tabbing_identifier(identifier); + self.window + .maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier)) } #[inline] fn tabbing_identifier(&self) -> String { - self.window.tabbing_identifier() + self.window.maybe_wait_on_main(|w| w.tabbing_identifier()) } #[inline] fn select_next_tab(&self) { - self.window.select_next_tab(); + self.window.maybe_queue_on_main(|w| w.select_next_tab()) } #[inline] fn select_previous_tab(&self) { - self.window.select_previous_tab(); + self.window.maybe_queue_on_main(|w| w.select_previous_tab()) } #[inline] fn select_tab_at_index(&self, index: usize) { - self.window.select_tab_at_index(index); + self.window + .maybe_queue_on_main(move |w| w.select_tab_at_index(index)) } #[inline] fn num_tabs(&self) -> usize { - self.window.num_tabs() + self.window.maybe_wait_on_main(|w| w.num_tabs()) } #[inline] fn is_document_edited(&self) -> bool { - self.window.is_document_edited() + self.window.maybe_wait_on_main(|w| w.is_document_edited()) } #[inline] fn set_document_edited(&self, edited: bool) { - self.window.set_document_edited(edited) + self.window + .maybe_queue_on_main(move |w| w.set_document_edited(edited)) } #[inline] fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { - self.window.set_option_as_alt(option_as_alt) + self.window + .maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt)) } #[inline] fn option_as_alt(&self) -> OptionAsAlt { - self.window.option_as_alt() + self.window.maybe_wait_on_main(|w| w.option_as_alt()) } } diff --git a/src/platform/web.rs b/src/platform/web.rs index 7a1bb66f..f1dd64fd 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -31,7 +31,7 @@ use crate::event::Event; use crate::event_loop::ControlFlow; use crate::event_loop::EventLoop; use crate::event_loop::EventLoopWindowTarget; -use crate::window::WindowBuilder; +use crate::window::{Window, WindowBuilder}; use web_sys::HtmlCanvasElement; @@ -40,6 +40,13 @@ pub trait WindowExtWebSys { fn canvas(&self) -> Option; } +impl WindowExtWebSys for Window { + #[inline] + fn canvas(&self) -> Option { + self.window.canvas() + } +} + pub trait WindowBuilderExtWebSys { /// Pass an [`HtmlCanvasElement`] to be used for this [`Window`](crate::window::Window). If /// [`None`], [`WindowBuilder::build()`] will create one. diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index aa4003b8..09c3161a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -797,6 +797,14 @@ impl Window { }) } + pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { + f(self) + } + + pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { + f(self) + } + pub fn id(&self) -> WindowId { WindowId } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 33004957..44ac9ce9 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -1,11 +1,8 @@ #![allow(clippy::unnecessary_cast)] -use std::{ - collections::VecDeque, - ops::{Deref, DerefMut}, -}; +use std::collections::VecDeque; -use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker}; +use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{class, msg_send}; @@ -31,9 +28,9 @@ use crate::{ }; pub struct Inner { - pub(crate) window: Id, - pub(crate) view_controller: Id, - pub(crate) view: Id, + window: Id, + view_controller: Id, + view: Id, gl_or_metal_backed: bool, } @@ -76,68 +73,58 @@ impl Inner { pub fn pre_present_notify(&self) {} pub fn inner_position(&self) -> Result, NotSupportedError> { - unsafe { - let safe_area = self.safe_area_screen_space(); - let position = LogicalPosition { - x: safe_area.origin.x as f64, - y: safe_area.origin.y as f64, - }; - let scale_factor = self.scale_factor(); - Ok(position.to_physical(scale_factor)) - } + let safe_area = self.safe_area_screen_space(); + let position = LogicalPosition { + x: safe_area.origin.x as f64, + y: safe_area.origin.y as f64, + }; + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) } pub fn outer_position(&self) -> Result, NotSupportedError> { - unsafe { - let screen_frame = self.screen_frame(); - let position = LogicalPosition { - x: screen_frame.origin.x as f64, - y: screen_frame.origin.y as f64, - }; - let scale_factor = self.scale_factor(); - Ok(position.to_physical(scale_factor)) - } + let screen_frame = self.screen_frame(); + let position = LogicalPosition { + x: screen_frame.origin.x as f64, + y: screen_frame.origin.y as f64, + }; + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) } pub fn set_outer_position(&self, physical_position: Position) { - unsafe { - let scale_factor = self.scale_factor(); - let position = physical_position.to_logical::(scale_factor); - let screen_frame = self.screen_frame(); - let new_screen_frame = CGRect { - origin: CGPoint { - x: position.x as _, - y: position.y as _, - }, - size: screen_frame.size, - }; - let bounds = self.rect_from_screen_space(new_screen_frame); - self.window.setBounds(bounds); - } + let scale_factor = self.scale_factor(); + let position = physical_position.to_logical::(scale_factor); + let screen_frame = self.screen_frame(); + let new_screen_frame = CGRect { + origin: CGPoint { + x: position.x as _, + y: position.y as _, + }, + size: screen_frame.size, + }; + let bounds = self.rect_from_screen_space(new_screen_frame); + self.window.setBounds(bounds); } pub fn inner_size(&self) -> PhysicalSize { - unsafe { - let scale_factor = self.scale_factor(); - let safe_area = self.safe_area_screen_space(); - let size = LogicalSize { - width: safe_area.size.width as f64, - height: safe_area.size.height as f64, - }; - size.to_physical(scale_factor) - } + let scale_factor = self.scale_factor(); + let safe_area = self.safe_area_screen_space(); + let size = LogicalSize { + width: safe_area.size.width as f64, + height: safe_area.size.height as f64, + }; + size.to_physical(scale_factor) } pub fn outer_size(&self) -> PhysicalSize { - unsafe { - let scale_factor = self.scale_factor(); - let screen_frame = self.screen_frame(); - let size = LogicalSize { - width: screen_frame.size.width as f64, - height: screen_frame.size.height as f64, - }; - size.to_physical(scale_factor) - } + let scale_factor = self.scale_factor(); + let screen_frame = self.screen_frame(); + let size = LogicalSize { + width: screen_frame.size.width as f64, + height: screen_frame.size.height as f64, + }; + size.to_physical(scale_factor) } pub fn request_inner_size(&self, _size: Size) -> Option> { @@ -262,22 +249,20 @@ impl Inner { } pub(crate) fn fullscreen(&self) -> Option { - unsafe { - let monitor = self.current_monitor_inner(); - let uiscreen = monitor.ui_screen(); - let screen_space_bounds = self.screen_frame(); - let screen_bounds = uiscreen.bounds(); + let monitor = self.current_monitor_inner(); + let uiscreen = monitor.ui_screen(); + let screen_space_bounds = self.screen_frame(); + let screen_bounds = uiscreen.bounds(); - // TODO: track fullscreen instead of relying on brittle float comparisons - if screen_space_bounds.origin.x == screen_bounds.origin.x - && screen_space_bounds.origin.y == screen_bounds.origin.y - && screen_space_bounds.size.width == screen_bounds.size.width - && screen_space_bounds.size.height == screen_bounds.size.height - { - Some(Fullscreen::Borderless(Some(monitor))) - } else { - None - } + // TODO: track fullscreen instead of relying on brittle float comparisons + if screen_space_bounds.origin.x == screen_bounds.origin.x + && screen_space_bounds.origin.y == screen_bounds.origin.y + && screen_space_bounds.size.width == screen_bounds.size.width + && screen_space_bounds.size.height == screen_bounds.size.height + { + Some(Fullscreen::Borderless(Some(monitor))) + } else { + None } } @@ -375,32 +360,7 @@ impl Inner { } pub struct Window { - pub inner: Inner, -} - -impl Drop for Window { - fn drop(&mut self) { - assert_main_thread!("`Window::drop` can only be run on the main thread on iOS"); - } -} - -unsafe impl Send for Window {} -unsafe impl Sync for Window {} - -impl Deref for Window { - type Target = Inner; - - fn deref(&self) -> &Inner { - assert_main_thread!("`Window` methods can only be run on the main thread on iOS"); - &self.inner - } -} - -impl DerefMut for Window { - fn deref_mut(&mut self) -> &mut Inner { - assert_main_thread!("`Window` methods can only be run on the main thread on iOS"); - &mut self.inner - } + inner: MainThreadBound, } impl Window { @@ -497,15 +457,25 @@ impl Window { } } + let inner = Inner { + window, + view_controller, + view, + gl_or_metal_backed, + }; Ok(Window { - inner: Inner { - window, - view_controller, - view, - gl_or_metal_backed, - }, + inner: MainThreadBound::new(inner, mtm), }) } + + pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) { + // For now, don't actually do queuing, since it may be less predictable + self.maybe_wait_on_main(f) + } + + pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { + self.inner.get_on_main(|inner, _mtm| f(inner)) + } } // WindowExtIOS @@ -542,27 +512,23 @@ impl Inner { } impl Inner { - // requires main thread - unsafe fn screen_frame(&self) -> CGRect { + fn screen_frame(&self) -> CGRect { self.rect_to_screen_space(self.window.bounds()) } - // requires main thread - unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect { + fn rect_to_screen_space(&self, rect: CGRect) -> CGRect { let screen_space = self.window.screen().coordinateSpace(); self.window .convertRect_toCoordinateSpace(rect, &screen_space) } - // requires main thread - unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect { + fn rect_from_screen_space(&self, rect: CGRect) -> CGRect { let screen_space = self.window.screen().coordinateSpace(); self.window .convertRect_fromCoordinateSpace(rect, &screen_space) } - // requires main thread - unsafe fn safe_area_screen_space(&self) -> CGRect { + fn safe_area_screen_space(&self) -> CGRect { let bounds = self.window.bounds(); if app_state::os_capabilities().safe_area { let safe_area = self.window.safeAreaInsets(); @@ -580,7 +546,7 @@ impl Inner { } else { let screen_frame = self.rect_to_screen_space(bounds); let status_bar_frame = { - let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect( + let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect( "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS", ); app.statusBarFrame() diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 6b9cd25d..a226748f 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -306,6 +306,14 @@ impl Window { } } + pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { + f(self) + } + + pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { + f(self) + } + #[inline] pub fn id(&self) -> WindowId { match self { diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 5dc61e66..44df7ceb 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -4,10 +4,12 @@ use core_foundation::{ base::CFRelease, data::{CFDataGetBytePtr, CFDataRef}, }; +use icrate::Foundation::MainThreadMarker; use objc2::rc::Id; use smol_str::SmolStr; use super::appkit::{NSEvent, NSEventModifierFlags}; +use super::util::Never; use super::window::WinitWindow; use crate::{ dpi::LogicalSize, @@ -16,10 +18,7 @@ use crate::{ Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode, }, platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, - platform_impl::platform::{ - ffi, - util::{get_kbd_type, Never}, - }, + platform_impl::platform::ffi, }; #[derive(Debug)] @@ -75,7 +74,7 @@ pub fn get_modifierless_char(scancode: u16) -> Key { } layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout; } - let keyboard_type = get_kbd_type(); + let keyboard_type = MainThreadMarker::run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() }); let mut result_len = 0; let mut dead_keys = 0; diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index f4f2ea49..06c80df0 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -17,12 +17,12 @@ use core_foundation::runloop::{ kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, }; -use icrate::Foundation::is_main_thread; +use icrate::Foundation::MainThreadMarker; use objc2::rc::{autoreleasepool, Id}; use objc2::{msg_send_id, ClassType}; use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; -use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent, NSWindow}; +use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent, NSWindow}; use crate::{ error::EventLoopError, event::Event, @@ -66,15 +66,8 @@ impl PanicInfo { } pub struct EventLoopWindowTarget { - pub sender: mpsc::Sender, // this is only here to be cloned elsewhere pub receiver: mpsc::Receiver, -} - -impl Default for EventLoopWindowTarget { - fn default() -> Self { - let (sender, receiver) = mpsc::channel(); - EventLoopWindowTarget { sender, receiver } - } + mtm: MainThreadMarker, } impl EventLoopWindowTarget { @@ -97,11 +90,11 @@ impl EventLoopWindowTarget { impl EventLoopWindowTarget { pub(crate) fn hide_application(&self) { - NSApp().hide(None) + NSApplication::shared(self.mtm).hide(None) } pub(crate) fn hide_other_applications(&self) { - NSApp().hideOtherApplications(None) + NSApplication::shared(self.mtm).hideOtherApplications(None) } pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) { @@ -118,8 +111,10 @@ pub struct EventLoop { /// it around here as well. _delegate: Id, + sender: mpsc::Sender, window_target: Rc>, panic_info: Rc, + mtm: MainThreadMarker, /// We make sure that the callback closure is dropped during a panic /// by making the event loop own it. @@ -151,9 +146,8 @@ impl EventLoop { pub(crate) fn new( attributes: &PlatformSpecificEventLoopAttributes, ) -> Result { - if !is_main_thread() { - panic!("On macOS, `EventLoop` must be created on the main thread!"); - } + let mtm = MainThreadMarker::new() + .expect("On macOS, `EventLoop` must be created on the main thread!"); // This must be done before `NSApp()` (equivalent to sending // `sharedApplication`) is called anywhere else, or we'll end up @@ -180,12 +174,16 @@ impl EventLoop { let panic_info: Rc = Default::default(); setup_control_flow_observers(Rc::downgrade(&panic_info)); + + let (sender, receiver) = mpsc::channel(); Ok(EventLoop { _delegate: delegate, + sender, window_target: Rc::new(RootWindowTarget { - p: Default::default(), + p: EventLoopWindowTarget { receiver, mtm }, _marker: PhantomData, }), + mtm, panic_info, _callback: None, }) @@ -233,7 +231,7 @@ impl EventLoop { self._callback = Some(Rc::clone(&callback)); let exit_code = autoreleasepool(|_| { - let app = NSApp(); + let app = NSApplication::shared(self.mtm); // A bit of juggling with the callback references to make sure // that `self.callback` is the only owner of the callback. @@ -408,7 +406,7 @@ impl EventLoop { } pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy::new(self.window_target.p.sender.clone()) + EventLoopProxy::new(self.sender.clone()) } } diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index c12f7281..749647a6 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -17,10 +17,8 @@ mod view; mod window; mod window_delegate; -use std::{fmt, ops::Deref}; +use std::fmt; -use self::window::WinitWindow; -use self::window_delegate::WinitWindowDelegate; pub(crate) use self::{ event::KeyEventExtra, event_loop::{ @@ -29,11 +27,9 @@ pub(crate) use self::{ monitor::{MonitorHandle, VideoMode}, window::{PlatformSpecificWindowBuilderAttributes, WindowId}, }; -use crate::{ - error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes, -}; -use objc2::rc::{autoreleasepool, Id}; +use crate::event::DeviceId as RootDeviceId; +pub(crate) use self::window::Window; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; @@ -49,47 +45,12 @@ impl DeviceId { // Constant device ID; to be removed when if backend is updated to report real device IDs. pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); -pub(crate) struct Window { - pub(crate) window: Id, - // We keep this around so that it doesn't get dropped until the window does. - _delegate: Id, -} - -impl Drop for Window { - fn drop(&mut self) { - // Ensure the window is closed - util::close_sync(&self.window); - } -} - #[derive(Debug)] pub enum OsError { CGError(core_graphics::base::CGError), CreationError(&'static str), } -unsafe impl Send for Window {} -unsafe impl Sync for Window {} - -impl Deref for Window { - type Target = WinitWindow; - #[inline] - fn deref(&self) -> &Self::Target { - &self.window - } -} - -impl Window { - pub(crate) fn new( - _window_target: &EventLoopWindowTarget, - attributes: WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?; - Ok(Window { window, _delegate }) - } -} - impl fmt::Display for OsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util.rs similarity index 95% rename from src/platform_impl/macos/util/mod.rs rename to src/platform_impl/macos/util.rs index bca2643e..2447a5dc 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util.rs @@ -1,9 +1,3 @@ -#![allow(clippy::unnecessary_cast)] - -mod r#async; - -pub(crate) use self::r#async::*; - use core_graphics::display::CGDisplay; use icrate::Foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger}; @@ -50,6 +44,7 @@ impl Drop for TraceGuard { // For consistency with other platforms, this will... // 1. translate the bottom-left window corner into the top-left window corner // 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one +#[allow(clippy::unnecessary_cast)] pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64 } diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs deleted file mode 100644 index 6f430361..00000000 --- a/src/platform_impl/macos/util/async.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::ops::Deref; - -use dispatch::Queue; -use icrate::Foundation::{is_main_thread, CGFloat, NSPoint, NSSize, NSString}; -use objc2::rc::autoreleasepool; - -use crate::{ - dpi::{LogicalPosition, LogicalSize}, - platform_impl::platform::{ - appkit::{NSScreen, NSWindow, NSWindowLevel, NSWindowStyleMask}, - ffi, - window::WinitWindow, - }, -}; - -// Unsafe wrapper type that allows us to dispatch things that aren't Send. -// This should *only* be used to dispatch to the main queue. -// While it is indeed not guaranteed that these types can safely be sent to -// other threads, we know that they're safe to use on the main thread. -struct MainThreadSafe(T); - -unsafe impl Send for MainThreadSafe {} - -impl Deref for MainThreadSafe { - type Target = T; - fn deref(&self) -> &T { - &self.0 - } -} - -fn run_on_main(f: impl FnOnce() -> R + Send) -> R { - if is_main_thread() { - f() - } else { - Queue::main().exec_sync(f) - } -} - -fn set_style_mask(window: &NSWindow, mask: NSWindowStyleMask) { - window.setStyleMask(mask); - // If we don't do this, key handling will break - // (at least until the window is clicked again/etc.) - let _ = window.makeFirstResponder(Some(&window.contentView())); -} - -// Always use this function instead of trying to modify `styleMask` directly! -// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch. -// Otherwise, this would vomit out errors about not being on the main thread -// and fail to do anything. -pub(crate) fn set_style_mask_sync(window: &NSWindow, mask: NSWindowStyleMask) { - let window = MainThreadSafe(window); - run_on_main(move || { - set_style_mask(&window, mask); - }) -} - -// `setContentSize:` isn't thread-safe either, though it doesn't log any errors -// and just fails silently. Anyway, GCD to the rescue! -pub(crate) fn set_content_size_sync(window: &NSWindow, size: LogicalSize) { - let window = MainThreadSafe(window); - run_on_main(move || { - window.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat)); - }); -} - -// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy -// to log errors. -pub(crate) fn set_frame_top_left_point_sync(window: &NSWindow, point: NSPoint) { - let window = MainThreadSafe(window); - run_on_main(move || { - window.setFrameTopLeftPoint(point); - }); -} - -// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. -pub(crate) fn set_level_sync(window: &NSWindow, level: NSWindowLevel) { - let window = MainThreadSafe(window); - run_on_main(move || { - window.setLevel(level); - }); -} - -// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently. -pub(crate) fn set_ignore_mouse_events_sync(window: &NSWindow, ignore: bool) { - let window = MainThreadSafe(window); - run_on_main(move || { - window.setIgnoresMouseEvents(ignore); - }); -} - -// `toggleFullScreen` is thread-safe, but our additional logic to account for -// window styles isn't. -pub(crate) fn toggle_full_screen_sync(window: &WinitWindow, not_fullscreen: bool) { - let window = MainThreadSafe(window); - run_on_main(move || { - // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we - // set a normal style temporarily. The previous state will be - // restored in `WindowDelegate::window_did_exit_fullscreen`. - if not_fullscreen { - let curr_mask = window.styleMask(); - let required = - NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; - if !curr_mask.contains(required) { - set_style_mask(&window, required); - window - .lock_shared_state("toggle_full_screen_sync") - .saved_style = Some(curr_mask); - } - } - // Window level must be restored from `CGShieldingWindowLevel() - // + 1` back to normal in order for `toggleFullScreen` to do - // anything - window.setLevel(NSWindowLevel::Normal); - window.toggleFullScreen(None); - }); -} - -pub(crate) unsafe fn restore_display_mode_sync(ns_screen: u32) { - run_on_main(move || { - unsafe { ffi::CGRestorePermanentDisplayConfiguration() }; - assert_eq!( - unsafe { ffi::CGDisplayRelease(ns_screen) }, - ffi::kCGErrorSuccess - ); - }); -} - -// `setMaximized` is not thread-safe -pub(crate) fn set_maximized_sync(window: &WinitWindow, is_zoomed: bool, maximized: bool) { - let window = MainThreadSafe(window); - run_on_main(move || { - let mut shared_state = window.lock_shared_state("set_maximized_sync"); - // Save the standard frame sized if it is not zoomed - if !is_zoomed { - shared_state.standard_frame = Some(window.frame()); - } - - shared_state.maximized = maximized; - - if shared_state.fullscreen.is_some() { - // Handle it in window_did_exit_fullscreen - return; - } - - if window - .styleMask() - .contains(NSWindowStyleMask::NSResizableWindowMask) - { - drop(shared_state); - // Just use the native zoom if resizable - window.zoom(None); - } else { - // if it's not resizable, we set the frame directly - let new_rect = if maximized { - let screen = NSScreen::main().expect("no screen found"); - screen.visibleFrame() - } else { - shared_state.saved_standard_frame() - }; - drop(shared_state); - window.setFrame_display(new_rect, false); - } - }); -} - -// `orderOut:` isn't thread-safe. Calling it from another thread actually works, -// but with an odd delay. -pub(crate) fn order_out_sync(window: &NSWindow) { - let window = MainThreadSafe(window); - run_on_main(move || { - window.orderOut(None); - }); -} - -// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread -// actually works, but with an odd delay. -pub(crate) fn make_key_and_order_front_sync(window: &NSWindow) { - let window = MainThreadSafe(window); - run_on_main(move || { - window.makeKeyAndOrderFront(None); - }); -} - -// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the -// window drag regions, which throws an exception when not done in the main -// thread -pub(crate) fn set_title_sync(window: &NSWindow, title: &str) { - let window = MainThreadSafe(window); - run_on_main(move || { - window.setTitle(&NSString::from_str(title)); - }); -} - -// `close:` is thread-safe, but we want the event to be triggered from the main -// thread. Though, it's a good idea to look into that more... -pub(crate) fn close_sync(window: &NSWindow) { - let window = MainThreadSafe(window); - run_on_main(move || { - autoreleasepool(move |_| { - window.close(); - }); - }); -} - -pub(crate) fn set_ime_cursor_area_sync( - window: &WinitWindow, - logical_spot: LogicalPosition, - size: LogicalSize, -) { - let window = MainThreadSafe(window); - run_on_main(move || { - window.view().set_ime_cursor_area(logical_spot, size); - }); -} - -pub(crate) fn get_kbd_type() -> u8 { - run_on_main(|| unsafe { ffi::LMGetKbdType() }) -} diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 8d36d098..5ec9c5e5 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -22,6 +22,7 @@ use crate::{ platform_impl::platform::{ app_state::AppState, appkit::NSWindowOrderingMode, + event_loop::EventLoopWindowTarget, ffi, monitor::{self, MonitorHandle, VideoMode}, util, @@ -36,8 +37,8 @@ use crate::{ }; use core_graphics::display::{CGDisplay, CGPoint}; use icrate::Foundation::{ - is_main_thread, CGFloat, NSArray, NSCopying, NSInteger, NSObject, NSPoint, NSRect, NSSize, - NSString, + CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint, + NSRect, NSSize, NSString, }; use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{autoreleasepool, Id}; @@ -50,6 +51,47 @@ use super::appkit::{ NSWindowTabbingMode, NSWindowTitleVisibility, }; +pub(crate) struct Window { + window: MainThreadBound>, + // We keep this around so that it doesn't get dropped until the window does. + _delegate: MainThreadBound>, +} + +impl Drop for Window { + fn drop(&mut self) { + self.window + .get_on_main(|window, _| autoreleasepool(|_| window.close())) + } +} + +impl Window { + pub(crate) fn new( + _window_target: &EventLoopWindowTarget, + attributes: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + let mtm = MainThreadMarker::new() + .expect("windows can only be created on the main thread on macOS"); + let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?; + Ok(Window { + window: MainThreadBound::new(window, mtm), + _delegate: MainThreadBound::new(_delegate, mtm), + }) + } + + pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&WinitWindow) + Send + 'static) { + // For now, don't actually do queuing, since it may be less predictable + self.maybe_wait_on_main(f) + } + + pub(crate) fn maybe_wait_on_main( + &self, + f: impl FnOnce(&WinitWindow) -> R + Send, + ) -> R { + self.window.get_on_main(|window, _mtm| f(window)) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub usize); @@ -250,16 +292,12 @@ impl Drop for SharedStateMutexGuard<'_> { impl WinitWindow { #[allow(clippy::type_complexity)] - pub(crate) fn new( + fn new( attrs: WindowAttributes, pl_attrs: PlatformSpecificWindowBuilderAttributes, ) -> Result<(Id, Id), RootOsError> { trace_scope!("WinitWindow::new"); - if !is_main_thread() { - panic!("Windows can only be created on the main thread on macOS"); - } - let this = autoreleasepool(|_| { let screen = match attrs.fullscreen.clone().map(Into::into) { Some(Fullscreen::Borderless(Some(monitor))) @@ -537,16 +575,21 @@ impl WinitWindow { SharedStateMutexGuard::new(self.shared_state.lock().unwrap(), called_from_fn) } - fn set_style_mask_sync(&self, mask: NSWindowStyleMask) { - util::set_style_mask_sync(self, mask); + fn set_style_mask(&self, mask: NSWindowStyleMask) { + self.setStyleMask(mask); + // If we don't do this, key handling will break + // (at least until the window is clicked again/etc.) + let _ = self.makeFirstResponder(Some(&self.contentView())); } +} +impl WinitWindow { pub fn id(&self) -> WindowId { WindowId(self as *const Self as usize) } pub fn set_title(&self, title: &str) { - util::set_title_sync(self, title); + self.setTitle(&NSString::from_str(title)) } pub fn set_transparent(&self, transparent: bool) { @@ -555,8 +598,8 @@ impl WinitWindow { pub fn set_visible(&self, visible: bool) { match visible { - true => util::make_key_and_order_front_sync(self), - false => util::order_out_sync(self), + true => self.makeKeyAndOrderFront(None), + false => self.orderOut(None), } } @@ -595,7 +638,7 @@ impl WinitWindow { pub fn set_outer_position(&self, position: Position) { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); - util::set_frame_top_left_point_sync(self, util::window_position(position)); + self.setFrameTopLeftPoint(util::window_position(position)); } #[inline] @@ -617,7 +660,8 @@ impl WinitWindow { #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); - util::set_content_size_sync(self, size.to_logical(scale_factor)); + let size: LogicalSize = size.to_logical(scale_factor); + self.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat)); None } @@ -725,7 +769,7 @@ impl WinitWindow { } else { mask &= !NSWindowStyleMask::NSResizableWindowMask; } - self.set_style_mask_sync(mask); + self.set_style_mask(mask); } // Otherwise, we don't change the mask until we exit fullscreen. } @@ -753,7 +797,7 @@ impl WinitWindow { // This must happen before the button's "enabled" status has been set, // hence we do it synchronously. - self.set_style_mask_sync(mask); + self.set_style_mask(mask); // We edit the button directly instead of using `NSResizableWindowMask`, // since that mask also affect the resizability of the window (which is @@ -849,7 +893,7 @@ impl WinitWindow { #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { - util::set_ignore_mouse_events_sync(self, !hittest); + self.setIgnoresMouseEvents(!hittest); Ok(()) } @@ -862,14 +906,14 @@ impl WinitWindow { NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; let needs_temp_mask = !curr_mask.contains(required); if needs_temp_mask { - self.set_style_mask_sync(required); + self.set_style_mask(required); } let is_zoomed = self.isZoomed(); // Roll back temp styles if needs_temp_mask { - self.set_style_mask_sync(curr_mask); + self.set_style_mask(curr_mask); } is_zoomed @@ -900,7 +944,7 @@ impl WinitWindow { drop(shared_state_lock); - self.set_style_mask_sync(mask); + self.set_style_mask(mask); self.set_maximized(maximized); } @@ -929,7 +973,38 @@ impl WinitWindow { if is_zoomed == maximized { return; }; - util::set_maximized_sync(self, is_zoomed, maximized); + + let mut shared_state = self.lock_shared_state("set_maximized"); + // Save the standard frame sized if it is not zoomed + if !is_zoomed { + shared_state.standard_frame = Some(self.frame()); + } + + shared_state.maximized = maximized; + + if shared_state.fullscreen.is_some() { + // Handle it in window_did_exit_fullscreen + return; + } + + if self + .styleMask() + .contains(NSWindowStyleMask::NSResizableWindowMask) + { + drop(shared_state); + // Just use the native zoom if resizable + self.zoom(None); + } else { + // if it's not resizable, we set the frame directly + let new_rect = if maximized { + let screen = NSScreen::main().expect("no screen found"); + screen.visibleFrame() + } else { + shared_state.saved_standard_frame() + }; + drop(shared_state); + self.setFrame_display(new_rect, false); + } } #[inline] @@ -985,7 +1060,7 @@ impl WinitWindow { // The coordinate system here has its origin at bottom-left // and Y goes up screen_frame.origin.y += screen_frame.size.height; - util::set_frame_top_left_point_sync(self, screen_frame.origin); + self.setFrameTopLeftPoint(screen_frame.origin); } } @@ -1061,22 +1136,43 @@ impl WinitWindow { self.lock_shared_state("set_fullscreen").fullscreen = fullscreen.clone(); - match (&old_fullscreen, &fullscreen) { - (&None, &Some(_)) => { - util::toggle_full_screen_sync(self, old_fullscreen.is_none()); + fn toggle_fullscreen(window: &WinitWindow) { + // Window level must be restored from `CGShieldingWindowLevel() + // + 1` back to normal in order for `toggleFullScreen` to do + // anything + window.setLevel(NSWindowLevel::Normal); + window.toggleFullScreen(None); + } + + match (old_fullscreen, fullscreen) { + (None, Some(_)) => { + // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we + // set a normal style temporarily. The previous state will be + // restored in `WindowDelegate::window_did_exit_fullscreen`. + let curr_mask = self.styleMask(); + let required = NSWindowStyleMask::NSTitledWindowMask + | NSWindowStyleMask::NSResizableWindowMask; + if !curr_mask.contains(required) { + self.set_style_mask(required); + self.lock_shared_state("set_fullscreen").saved_style = Some(curr_mask); + } + toggle_fullscreen(self); } - (&Some(Fullscreen::Borderless(_)), &None) => { + (Some(Fullscreen::Borderless(_)), None) => { // State is restored by `window_did_exit_fullscreen` - util::toggle_full_screen_sync(self, old_fullscreen.is_none()); + toggle_fullscreen(self); } - (&Some(Fullscreen::Exclusive(ref video_mode)), &None) => { + (Some(Fullscreen::Exclusive(ref video_mode)), None) => { unsafe { - util::restore_display_mode_sync(video_mode.monitor().native_identifier()) + ffi::CGRestorePermanentDisplayConfiguration(); + assert_eq!( + ffi::CGDisplayRelease(video_mode.monitor().native_identifier()), + ffi::kCGErrorSuccess + ); }; - // Rest of the state is restored by `window_did_exit_fullscreen` - util::toggle_full_screen_sync(self, old_fullscreen.is_none()); + toggle_fullscreen(self); } - (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => { + (Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => { // If we're already in fullscreen mode, calling // `CGDisplayCapture` will place the shielding window on top of // our window, which results in a black display and is not what @@ -1099,7 +1195,7 @@ impl WinitWindow { NSWindowLevel(unsafe { ffi::CGShieldingWindowLevel() } as NSInteger + 1); self.setLevel(window_level); } - (&Some(Fullscreen::Exclusive(ref video_mode)), &Some(Fullscreen::Borderless(_))) => { + (Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => { let presentation_options = self .lock_shared_state("set_fullscreen") .save_presentation_opts @@ -1111,7 +1207,11 @@ impl WinitWindow { NSApp().setPresentationOptions(presentation_options); unsafe { - util::restore_display_mode_sync(video_mode.monitor().native_identifier()) + ffi::CGRestorePermanentDisplayConfiguration(); + assert_eq!( + ffi::CGDisplayRelease(video_mode.monitor().native_identifier()), + ffi::kCGErrorSuccess + ); }; // Restore the normal window level following the Borderless fullscreen @@ -1156,7 +1256,7 @@ impl WinitWindow { } new_mask }; - self.set_style_mask_sync(new_mask); + self.set_style_mask(new_mask); } #[inline] @@ -1171,7 +1271,7 @@ impl WinitWindow { WindowLevel::AlwaysOnBottom => NSWindowLevel::BELOW_NORMAL, WindowLevel::Normal => NSWindowLevel::Normal, }; - util::set_level_sync(self, level); + self.setLevel(level); } #[inline] @@ -1191,7 +1291,7 @@ impl WinitWindow { let scale_factor = self.scale_factor(); let logical_spot = spot.to_logical(scale_factor); let size = size.to_logical(scale_factor); - util::set_ime_cursor_area_sync(self, logical_spot, size); + self.view().set_ime_cursor_area(logical_spot, size); } #[inline] @@ -1209,7 +1309,7 @@ impl WinitWindow { if !is_minimized && is_visible { NSApp().activateIgnoringOtherApps(true); - util::make_key_and_order_front_sync(self); + self.makeKeyAndOrderFront(None); } } @@ -1263,9 +1363,9 @@ impl WinitWindow { fn toggle_style_mask(&self, mask: NSWindowStyleMask, on: bool) { let current_style_mask = self.styleMask(); if on { - util::set_style_mask_sync(self, current_style_mask | mask); + self.set_style_mask(current_style_mask | mask); } else { - util::set_style_mask_sync(self, current_style_mask & (!mask)); + self.set_style_mask(current_style_mask & (!mask)); } } @@ -1360,7 +1460,7 @@ impl WindowExtMacOS for WinitWindow { true } else { let new_mask = self.saved_style(&mut shared_state_lock); - self.set_style_mask_sync(new_mask); + self.set_style_mask(new_mask); shared_state_lock.is_simple_fullscreen = false; let save_presentation_opts = shared_state_lock.save_presentation_opts; diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index 4cbc4fec..47e11aa1 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -126,6 +126,14 @@ impl Window { }) } + pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { + f(self) + } + + pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { + f(self) + } + #[inline] pub fn id(&self) -> WindowId { WindowId { diff --git a/src/platform_impl/web/async.rs b/src/platform_impl/web/async.rs index 2fc4fe39..8aa70e34 100644 --- a/src/platform_impl/web/async.rs +++ b/src/platform_impl/web/async.rs @@ -96,16 +96,6 @@ impl MainThreadSafe { } }) } - - fn with_mut(&self, f: impl FnOnce(&mut T) -> R) -> Option { - Self::MAIN_THREAD.with(|is_main_thread| { - if *is_main_thread.deref() { - Some(f(self.value.write().unwrap().as_mut().unwrap())) - } else { - None - } - }) - } } impl Clone for MainThreadSafe { @@ -219,17 +209,15 @@ impl AsyncReceiver { pub struct Dispatcher(MainThreadSafe>); -pub enum Closure { - Ref(Box), - RefMut(Box), -} +pub struct Closure(Box); impl Dispatcher { #[track_caller] pub fn new(value: T) -> Option { - MainThreadSafe::new(value, |value, closure| match closure { - Closure::Ref(f) => f(value.read().unwrap().as_ref().unwrap()), - Closure::RefMut(f) => f(value.write().unwrap().as_mut().unwrap()), + MainThreadSafe::new(value, |value, Closure(closure)| { + // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything + // funny with it here. See `Self::queue()`. + closure(value.read().unwrap().as_ref().unwrap()) }) .map(Self) } @@ -238,30 +226,26 @@ impl Dispatcher { if self.is_main_thread() { self.0.with(f).unwrap() } else { - self.0.send(Closure::Ref(Box::new(f))) + self.0.send(Closure(Box::new(f))) } } - pub fn dispatch_mut(&self, f: impl 'static + FnOnce(&mut T) + Send) { - if self.is_main_thread() { - self.0.with_mut(f).unwrap() - } else { - self.0.send(Closure::RefMut(Box::new(f))) - } - } - - pub fn queue(&self, f: impl 'static + FnOnce(&T) -> R + Send) -> R { + pub fn queue(&self, f: impl FnOnce(&T) -> R + Send) -> R { if self.is_main_thread() { self.0.with(f).unwrap() } else { let pair = Arc::new((Mutex::new(None), Condvar::new())); - let closure = Closure::Ref(Box::new({ + let closure = Box::new({ let pair = pair.clone(); - move |value| { + move |value: &T| { *pair.0.lock().unwrap() = Some(f(value)); pair.1.notify_one(); } - })); + }) as Box; + // SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is + // safe because this function won't return until `f` has finished executing. See + // `Self::new()`. + let closure = Closure(unsafe { std::mem::transmute(closure) }); self.0.send(closure); diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 7f2abc05..72a6d703 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -16,8 +16,6 @@ pub use self::resize_scaling::ResizeScaleHandle; pub use self::timeout::{IdleCallback, Timeout}; use crate::dpi::{LogicalPosition, LogicalSize}; -use crate::platform::web::WindowExtWebSys; -use crate::window::Window; use wasm_bindgen::closure::Closure; use web_sys::{ CssStyleDeclaration, Document, Element, HtmlCanvasElement, PageTransitionEvent, VisibilityState, @@ -52,12 +50,6 @@ pub fn on_page_transition( } } -impl WindowExtWebSys for Window { - fn canvas(&self) -> Option { - self.window.canvas() - } -} - pub fn scale_factor(window: &web_sys::Window) -> f64 { window.device_pixel_ratio() } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index bbaaa466..b655d3ed 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -13,24 +13,23 @@ use super::r#async::Dispatcher; use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen}; use std::cell::RefCell; -use std::collections::vec_deque::IntoIter as VecDequeIter; use std::collections::VecDeque; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; pub struct Window { - id: WindowId, - has_focus: Arc, - pub inner: Dispatcher, + inner: Dispatcher, } pub struct Inner { + id: WindowId, pub window: web_sys::Window, document: Document, canvas: Rc>, previous_pointer: RefCell<&'static str>, destroy_fn: Option>, + has_focus: Arc, } impl Window { @@ -55,41 +54,42 @@ impl Window { let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id))); let has_focus = canvas.borrow().has_focus.clone(); - let window = Window { + let inner = Inner { id, + window: window.clone(), + document: document.clone(), + canvas, + previous_pointer: RefCell::new("auto"), + destroy_fn: Some(destroy_fn), has_focus, - inner: Dispatcher::new(Inner { - window: window.clone(), - document: document.clone(), - canvas, - previous_pointer: RefCell::new("auto"), - destroy_fn: Some(destroy_fn), - }) - .unwrap(), }; - window.set_title(&attr.title); - window.set_maximized(attr.maximized); - window.set_visible(attr.visible); - window.set_window_icon(attr.window_icon); + inner.set_title(&attr.title); + inner.set_maximized(attr.maximized); + inner.set_visible(attr.visible); + inner.set_window_icon(attr.window_icon); - Ok(window) + Ok(Window { + inner: Dispatcher::new(inner).unwrap(), + }) + } + + pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) { + self.inner.dispatch(f) + } + + pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { + self.inner.queue(f) } pub fn canvas(&self) -> Option { self.inner.with(|inner| inner.canvas.borrow().raw().clone()) } +} +impl Inner { pub fn set_title(&self, title: &str) { - if self - .inner - .with(|inner| inner.canvas.borrow().set_attribute("alt", title)) - .is_none() - { - let title = title.to_owned(); - self.inner - .dispatch(move |inner| inner.canvas.borrow().set_attribute("alt", &title)); - } + self.canvas.borrow().set_attribute("alt", title) } pub fn set_transparent(&self, _transparent: bool) {} @@ -104,21 +104,17 @@ impl Window { } pub fn request_redraw(&self) { - self.inner.dispatch(move |inner| { - inner.canvas.borrow().request_animation_frame(); - }); + self.canvas.borrow().request_animation_frame(); } pub fn pre_present_notify(&self) {} pub fn outer_position(&self) -> Result, NotSupportedError> { - self.inner.queue(|inner| { - Ok(inner - .canvas - .borrow() - .position() - .to_physical(inner.scale_factor())) - }) + Ok(self + .canvas + .borrow() + .position() + .to_physical(self.scale_factor())) } pub fn inner_position(&self) -> Result, NotSupportedError> { @@ -127,17 +123,15 @@ impl Window { } pub fn set_outer_position(&self, position: Position) { - self.inner.dispatch(move |inner| { - let canvas = inner.canvas.borrow(); - let position = position.to_logical::(inner.scale_factor()); + let canvas = self.canvas.borrow(); + let position = position.to_logical::(self.scale_factor()); - backend::set_canvas_position(canvas.document(), canvas.raw(), canvas.style(), position) - }); + backend::set_canvas_position(canvas.document(), canvas.raw(), canvas.style(), position) } #[inline] pub fn inner_size(&self) -> PhysicalSize { - self.inner.queue(|inner| inner.canvas.borrow().inner_size()) + self.canvas.borrow().inner_size() } #[inline] @@ -148,43 +142,24 @@ impl Window { #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { - self.inner.dispatch(move |inner| { - let size = size.to_logical(inner.scale_factor()); - let canvas = inner.canvas.borrow(); - backend::set_canvas_size(canvas.document(), canvas.raw(), canvas.style(), size); - }); - + let size = size.to_logical(self.scale_factor()); + let canvas = self.canvas.borrow(); + backend::set_canvas_size(canvas.document(), canvas.raw(), canvas.style(), size); None } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { - self.inner.dispatch(move |inner| { - let dimensions = - dimensions.map(|dimensions| dimensions.to_logical(inner.scale_factor())); - let canvas = inner.canvas.borrow(); - backend::set_canvas_min_size( - canvas.document(), - canvas.raw(), - canvas.style(), - dimensions, - ) - }) + let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor())); + let canvas = self.canvas.borrow(); + backend::set_canvas_min_size(canvas.document(), canvas.raw(), canvas.style(), dimensions) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { - self.inner.dispatch(move |inner| { - let dimensions = - dimensions.map(|dimensions| dimensions.to_logical(inner.scale_factor())); - let canvas = inner.canvas.borrow(); - backend::set_canvas_max_size( - canvas.document(), - canvas.raw(), - canvas.style(), - dimensions, - ) - }) + let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor())); + let canvas = self.canvas.borrow(); + backend::set_canvas_max_size(canvas.document(), canvas.raw(), canvas.style(), dimensions) } #[inline] @@ -216,19 +191,13 @@ impl Window { #[inline] pub fn scale_factor(&self) -> f64 { - self.inner.queue(|inner| inner.scale_factor()) + super::backend::scale_factor(&self.window) } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - self.inner.dispatch(move |inner| { - *inner.previous_pointer.borrow_mut() = cursor.name(); - backend::set_canvas_style_property( - inner.canvas.borrow().raw(), - "cursor", - cursor.name(), - ); - }); + *self.previous_pointer.borrow_mut() = cursor.name(); + backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", cursor.name()); } #[inline] @@ -238,36 +207,31 @@ impl Window { #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { - self.inner.queue(move |inner| { - let lock = match mode { - CursorGrabMode::None => false, - CursorGrabMode::Locked => true, - CursorGrabMode::Confined => { - return Err(ExternalError::NotSupported(NotSupportedError::new())) - } - }; + let lock = match mode { + CursorGrabMode::None => false, + CursorGrabMode::Locked => true, + CursorGrabMode::Confined => { + return Err(ExternalError::NotSupported(NotSupportedError::new())) + } + }; - inner - .canvas - .borrow() - .set_cursor_lock(lock) - .map_err(ExternalError::Os) - }) + self.canvas + .borrow() + .set_cursor_lock(lock) + .map_err(ExternalError::Os) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { - self.inner.dispatch(move |inner| { - if !visible { - backend::set_canvas_style_property(inner.canvas.borrow().raw(), "cursor", "none"); - } else { - backend::set_canvas_style_property( - inner.canvas.borrow().raw(), - "cursor", - &inner.previous_pointer.borrow(), - ); - } - }); + if !visible { + backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", "none"); + } else { + backend::set_canvas_style_property( + self.canvas.borrow().raw(), + "cursor", + &self.previous_pointer.borrow(), + ); + } } #[inline] @@ -309,24 +273,20 @@ impl Window { #[inline] pub(crate) fn fullscreen(&self) -> Option { - self.inner.queue(|inner| { - if inner.canvas.borrow().is_fullscreen() { - Some(Fullscreen::Borderless(Some(MonitorHandle))) - } else { - None - } - }) + if self.canvas.borrow().is_fullscreen() { + Some(Fullscreen::Borderless(Some(MonitorHandle))) + } else { + None + } } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { - self.inner.dispatch(move |inner| { - if fullscreen.is_some() { - inner.canvas.borrow().request_fullscreen(); - } else if inner.canvas.borrow().is_fullscreen() { - backend::exit_fullscreen(&inner.document); - } - }); + if fullscreen.is_some() { + self.canvas.borrow().request_fullscreen(); + } else if self.canvas.borrow().is_fullscreen() { + backend::exit_fullscreen(&self.document); + } } #[inline] @@ -365,9 +325,7 @@ impl Window { #[inline] pub fn focus_window(&self) { - self.inner.dispatch(|inner| { - let _ = inner.canvas.borrow().raw().focus(); - }) + let _ = self.canvas.borrow().raw().focus(); } #[inline] @@ -381,8 +339,8 @@ impl Window { } #[inline] - pub fn available_monitors(&self) -> VecDequeIter { - VecDeque::new().into_iter() + pub fn available_monitors(&self) -> VecDeque { + VecDeque::new() } #[inline] @@ -412,14 +370,12 @@ impl Window { #[inline] pub fn theme(&self) -> Option { - self.inner.queue(|inner| { - backend::is_dark_mode(&inner.window).map(|is_dark_mode| { - if is_dark_mode { - Theme::Dark - } else { - Theme::Light - } - }) + backend::is_dark_mode(&self.window).map(|is_dark_mode| { + if is_dark_mode { + Theme::Dark + } else { + Theme::Light + } }) } @@ -437,23 +393,13 @@ impl Window { } } -impl Drop for Window { +impl Drop for Inner { fn drop(&mut self) { - self.inner.dispatch_mut(|inner| { - if let Some(destroy_fn) = inner.destroy_fn.take() { - destroy_fn(); - } - }); + if let Some(destroy_fn) = self.destroy_fn.take() { + destroy_fn(); + } } } - -impl Inner { - #[inline] - pub fn scale_factor(&self) -> f64 { - super::backend::scale_factor(&self.window) - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub(crate) u32); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 6e71a9e9..d90deb76 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -104,6 +104,16 @@ impl Window { unsafe { init(w_attr, pl_attr, event_loop) } } + pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { + // TODO: Use `thread_executor` here + f(self) + } + + pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { + // TODO: Use `thread_executor` here + f(self) + } + fn window_state_lock(&self) -> MutexGuard<'_, WindowState> { self.window_state.lock().unwrap() } diff --git a/src/window.rs b/src/window.rs index ef117cc8..678a9ebe 100644 --- a/src/window.rs +++ b/src/window.rs @@ -23,6 +23,18 @@ pub use raw_window_handle; /// Represents a window. /// +/// +/// # Threading +/// +/// This is `Send + Sync`, meaning that it can be freely used from other +/// threads. +/// +/// However, some platforms (macOS, Web and iOS) only allow user interface +/// interactions on the main thread, so on those platforms, if you use the +/// window from a thread other than the main, the code is scheduled to run on +/// the main thread, and your thread may be blocked until that completes. +/// +/// /// # Example /// /// ```no_run @@ -59,13 +71,15 @@ impl fmt::Debug for Window { impl Drop for Window { fn drop(&mut self) { - // If the window is in exclusive fullscreen, we must restore the desktop - // video mode (generally this would be done on application exit, but - // closing the window doesn't necessarily always mean application exit, - // such as when there are multiple windows) - if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() { - self.set_fullscreen(None); - } + self.window.maybe_wait_on_main(|w| { + // If the window is in exclusive fullscreen, we must restore the desktop + // video mode (generally this would be done on application exit, but + // closing the window doesn't necessarily always mean application exit, + // such as when there are multiple windows) + if let Some(Fullscreen::Exclusive(_)) = w.fullscreen().map(|f| f.into()) { + w.set_fullscreen(None); + } + }) } } @@ -468,12 +482,10 @@ impl WindowBuilder { self, window_target: &EventLoopWindowTarget, ) -> Result { - platform_impl::Window::new(&window_target.p, self.window, self.platform_specific).map( - |window| { - window.request_redraw(); - Window { window } - }, - ) + let window = + platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)?; + window.maybe_queue_on_main(|w| w.request_redraw()); + Ok(Window { window }) } } @@ -501,7 +513,7 @@ impl Window { /// Returns an identifier unique to the window. #[inline] pub fn id(&self) -> WindowId { - WindowId(self.window.id()) + self.window.maybe_wait_on_main(|w| WindowId(w.id())) } /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. @@ -524,7 +536,7 @@ impl Window { /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc #[inline] pub fn scale_factor(&self) -> f64 { - self.window.scale_factor() + self.window.maybe_wait_on_main(|w| w.scale_factor()) } /// Queues a [`Event::RedrawRequested`] event to be emitted that aligns with the windowing @@ -555,7 +567,7 @@ impl Window { /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested #[inline] pub fn request_redraw(&self) { - self.window.request_redraw() + self.window.maybe_queue_on_main(|w| w.request_redraw()) } /// Notify the windowing system that you're before presenting to the window. @@ -592,7 +604,7 @@ impl Window { /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested #[inline] pub fn pre_present_notify(&self) { - self.window.pre_present_notify(); + self.window.maybe_queue_on_main(|w| w.pre_present_notify()); } /// Reset the dead key state of the keyboard. @@ -608,7 +620,7 @@ impl Window { // at least, then this function should be provided through a platform specific // extension trait pub fn reset_dead_keys(&self) { - self.window.reset_dead_keys(); + self.window.maybe_queue_on_main(|w| w.reset_dead_keys()) } } @@ -630,7 +642,7 @@ impl Window { /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { - self.window.inner_position() + self.window.maybe_wait_on_main(|w| w.inner_position()) } /// Returns the position of the top-left hand corner of the window relative to the @@ -651,7 +663,7 @@ impl Window { /// - **Android / Wayland:** Always returns [`NotSupportedError`]. #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { - self.window.outer_position() + self.window.maybe_wait_on_main(|w| w.outer_position()) } /// Modifies the position of the window. @@ -683,7 +695,9 @@ impl Window { /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] pub fn set_outer_position>(&self, position: P) { - self.window.set_outer_position(position.into()) + let position = position.into(); + self.window + .maybe_queue_on_main(move |w| w.set_outer_position(position)) } /// Returns the physical size of the window's client area. @@ -700,7 +714,7 @@ impl Window { /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] pub fn inner_size(&self) -> PhysicalSize { - self.window.inner_size() + self.window.maybe_wait_on_main(|w| w.inner_size()) } /// Request the new size for the window. @@ -741,7 +755,9 @@ impl Window { #[inline] #[must_use] pub fn request_inner_size>(&self, size: S) -> Option> { - self.window.request_inner_size(size.into()) + let size = size.into(); + self.window + .maybe_wait_on_main(|w| w.request_inner_size(size)) } /// Returns the physical size of the entire window. @@ -757,7 +773,7 @@ impl Window { /// [`Window::inner_size`]._ #[inline] pub fn outer_size(&self) -> PhysicalSize { - self.window.outer_size() + self.window.maybe_wait_on_main(|w| w.outer_size()) } /// Sets a minimum dimension size for the window. @@ -780,7 +796,9 @@ impl Window { /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_min_inner_size>(&self, min_size: Option) { - self.window.set_min_inner_size(min_size.map(|s| s.into())) + let min_size = min_size.map(|s| s.into()); + self.window + .maybe_queue_on_main(move |w| w.set_min_inner_size(min_size)) } /// Sets a maximum dimension size for the window. @@ -803,7 +821,9 @@ impl Window { /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_max_inner_size>(&self, max_size: Option) { - self.window.set_max_inner_size(max_size.map(|s| s.into())) + let max_size = max_size.map(|s| s.into()); + self.window + .maybe_queue_on_main(move |w| w.set_max_inner_size(max_size)) } /// Returns window resize increments if any were set. @@ -813,7 +833,7 @@ impl Window { /// - **iOS / Android / Web / Wayland / Windows / Orbital:** Always returns [`None`]. #[inline] pub fn resize_increments(&self) -> Option> { - self.window.resize_increments() + self.window.maybe_wait_on_main(|w| w.resize_increments()) } /// Sets window resize increments. @@ -828,8 +848,9 @@ impl Window { /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_resize_increments>(&self, increments: Option) { + let increments = increments.map(Into::into); self.window - .set_resize_increments(increments.map(Into::into)) + .maybe_queue_on_main(move |w| w.set_resize_increments(increments)) } } @@ -842,7 +863,7 @@ impl Window { /// - **iOS / Android:** Unsupported. #[inline] pub fn set_title(&self, title: &str) { - self.window.set_title(title) + self.window.maybe_wait_on_main(|w| w.set_title(title)) } /// Change the window transparency state. @@ -859,7 +880,8 @@ impl Window { /// - **Windows / X11 / Web / iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_transparent(&self, transparent: bool) { - self.window.set_transparent(transparent) + self.window + .maybe_queue_on_main(move |w| w.set_transparent(transparent)) } /// Modifies the window's visibility. @@ -872,7 +894,8 @@ impl Window { /// - **iOS:** Can only be called on the main thread. #[inline] pub fn set_visible(&self, visible: bool) { - self.window.set_visible(visible) + self.window + .maybe_queue_on_main(move |w| w.set_visible(visible)) } /// Gets the window's current visibility state. @@ -885,7 +908,7 @@ impl Window { /// - **Wayland / iOS / Android / Web:** Unsupported. #[inline] pub fn is_visible(&self) -> Option { - self.window.is_visible() + self.window.maybe_wait_on_main(|w| w.is_visible()) } /// Sets whether the window is resizable or not. @@ -904,7 +927,8 @@ impl Window { /// [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized #[inline] pub fn set_resizable(&self, resizable: bool) { - self.window.set_resizable(resizable) + self.window + .maybe_queue_on_main(move |w| w.set_resizable(resizable)) } /// Gets the window's current resizable state. @@ -915,7 +939,7 @@ impl Window { /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn is_resizable(&self) -> bool { - self.window.is_resizable() + self.window.maybe_wait_on_main(|w| w.is_resizable()) } /// Sets the enabled window buttons. @@ -925,7 +949,8 @@ impl Window { /// - **Wayland / X11 / Orbital:** Not implemented. /// - **Web / iOS / Android:** Unsupported. pub fn set_enabled_buttons(&self, buttons: WindowButtons) { - self.window.set_enabled_buttons(buttons) + self.window + .maybe_queue_on_main(move |w| w.set_enabled_buttons(buttons)) } /// Gets the enabled window buttons. @@ -935,7 +960,7 @@ impl Window { /// - **Wayland / X11 / Orbital:** Not implemented. Always returns [`WindowButtons::all`]. /// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`]. pub fn enabled_buttons(&self) -> WindowButtons { - self.window.enabled_buttons() + self.window.maybe_wait_on_main(|w| w.enabled_buttons()) } /// Sets the window to minimized or back @@ -946,7 +971,8 @@ impl Window { /// - **Wayland:** Un-minimize is unsupported. #[inline] pub fn set_minimized(&self, minimized: bool) { - self.window.set_minimized(minimized); + self.window + .maybe_queue_on_main(move |w| w.set_minimized(minimized)) } /// Gets the window's current minimized state. @@ -963,7 +989,7 @@ impl Window { /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn is_minimized(&self) -> Option { - self.window.is_minimized() + self.window.maybe_wait_on_main(|w| w.is_minimized()) } /// Sets the window to maximized or back. @@ -973,7 +999,8 @@ impl Window { /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_maximized(&self, maximized: bool) { - self.window.set_maximized(maximized) + self.window + .maybe_queue_on_main(move |w| w.set_maximized(maximized)) } /// Gets the window's current maximized state. @@ -983,7 +1010,7 @@ impl Window { /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn is_maximized(&self) -> bool { - self.window.is_maximized() + self.window.maybe_wait_on_main(|w| w.is_maximized()) } /// Sets the window to fullscreen or back. @@ -1012,7 +1039,8 @@ impl Window { /// [transient activation]: https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { - self.window.set_fullscreen(fullscreen.map(|f| f.into())) + self.window + .maybe_queue_on_main(move |w| w.set_fullscreen(fullscreen.map(|f| f.into()))) } /// Gets the window's current fullscreen state. @@ -1024,7 +1052,8 @@ impl Window { /// - **Wayland:** Can return `Borderless(None)` when there are no monitors. #[inline] pub fn fullscreen(&self) -> Option { - self.window.fullscreen().map(|f| f.into()) + self.window + .maybe_wait_on_main(|w| w.fullscreen().map(|f| f.into())) } /// Turn window decorations on or off. @@ -1038,7 +1067,8 @@ impl Window { /// - **iOS / Android / Web:** No effect. #[inline] pub fn set_decorations(&self, decorations: bool) { - self.window.set_decorations(decorations) + self.window + .maybe_queue_on_main(move |w| w.set_decorations(decorations)) } /// Gets the window's current decorations state. @@ -1051,7 +1081,7 @@ impl Window { /// - **iOS / Android / Web:** Always returns `true`. #[inline] pub fn is_decorated(&self) -> bool { - self.window.is_decorated() + self.window.maybe_wait_on_main(|w| w.is_decorated()) } /// Change the window level. @@ -1060,7 +1090,8 @@ impl Window { /// /// See [`WindowLevel`] for details. pub fn set_window_level(&self, level: WindowLevel) { - self.window.set_window_level(level) + self.window + .maybe_queue_on_main(move |w| w.set_window_level(level)) } /// Sets the window icon. @@ -1079,7 +1110,8 @@ impl Window { /// said, it's usually in the same ballpark as on Windows. #[inline] pub fn set_window_icon(&self, window_icon: Option) { - self.window.set_window_icon(window_icon) + self.window + .maybe_queue_on_main(move |w| w.set_window_icon(window_icon)) } /// Set the IME cursor editing area, where the `position` is the top left corner of that area @@ -1119,8 +1151,10 @@ impl Window { /// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0 #[inline] pub fn set_ime_cursor_area, S: Into>(&self, position: P, size: S) { + let position = position.into(); + let size = size.into(); self.window - .set_ime_cursor_area(position.into(), size.into()) + .maybe_queue_on_main(move |w| w.set_ime_cursor_area(position, size)) } /// Sets whether the window should get IME events @@ -1145,7 +1179,8 @@ impl Window { /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput #[inline] pub fn set_ime_allowed(&self, allowed: bool) { - self.window.set_ime_allowed(allowed); + self.window + .maybe_queue_on_main(move |w| w.set_ime_allowed(allowed)) } /// Sets the IME purpose for the window using [`ImePurpose`]. @@ -1155,7 +1190,8 @@ impl Window { /// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported. #[inline] pub fn set_ime_purpose(&self, purpose: ImePurpose) { - self.window.set_ime_purpose(purpose); + self.window + .maybe_queue_on_main(move |w| w.set_ime_purpose(purpose)) } /// Brings the window to the front and sets input focus. Has no effect if the window is @@ -1170,7 +1206,7 @@ impl Window { /// - **iOS / Android / Wayland / Orbital:** Unsupported. #[inline] pub fn focus_window(&self) { - self.window.focus_window() + self.window.maybe_queue_on_main(|w| w.focus_window()) } /// Gets whether the window has keyboard focus. @@ -1180,7 +1216,7 @@ impl Window { /// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused #[inline] pub fn has_focus(&self) -> bool { - self.window.has_focus() + self.window.maybe_wait_on_main(|w| w.has_focus()) } /// Requests user attention to the window, this has no effect if the application @@ -1198,7 +1234,8 @@ impl Window { /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. #[inline] pub fn request_user_attention(&self, request_type: Option) { - self.window.request_user_attention(request_type) + self.window + .maybe_queue_on_main(move |w| w.request_user_attention(request_type)) } /// Sets the current window theme. Use `None` to fallback to system default. @@ -1212,7 +1249,7 @@ impl Window { /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_theme(&self, theme: Option) { - self.window.set_theme(theme) + self.window.maybe_queue_on_main(move |w| w.set_theme(theme)) } /// Returns the current window theme. @@ -1223,7 +1260,7 @@ impl Window { /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported. #[inline] pub fn theme(&self) -> Option { - self.window.theme() + self.window.maybe_wait_on_main(|w| w.theme()) } /// Prevents the window contents from being captured by other apps. @@ -1237,7 +1274,8 @@ impl Window { /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone pub fn set_content_protected(&self, _protected: bool) { #[cfg(any(macos_platform, windows_platform))] - self.window.set_content_protected(_protected); + self.window + .maybe_queue_on_main(move |w| w.set_content_protected(_protected)) } /// Gets the current title of the window. @@ -1247,7 +1285,7 @@ impl Window { /// - **iOS / Android / x11 / Wayland / Web:** Unsupported. Always returns an empty string. #[inline] pub fn title(&self) -> String { - self.window.title() + self.window.maybe_wait_on_main(|w| w.title()) } } @@ -1260,7 +1298,8 @@ impl Window { /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - self.window.set_cursor_icon(cursor); + self.window + .maybe_queue_on_main(move |w| w.set_cursor_icon(cursor)) } /// Changes the position of the cursor in window coordinates. @@ -1283,7 +1322,9 @@ impl Window { /// - **iOS / Android / Web / Wayland / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { - self.window.set_cursor_position(position.into()) + let position = position.into(); + self.window + .maybe_wait_on_main(|w| w.set_cursor_position(position)) } /// Set grabbing [mode]([`CursorGrabMode`]) on the cursor preventing it from leaving the window. @@ -1303,7 +1344,7 @@ impl Window { /// ``` #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { - self.window.set_cursor_grab(mode) + self.window.maybe_wait_on_main(|w| w.set_cursor_grab(mode)) } /// Modifies the cursor's visibility. @@ -1320,7 +1361,8 @@ impl Window { /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_cursor_visible(&self, visible: bool) { - self.window.set_cursor_visible(visible) + self.window + .maybe_queue_on_main(move |w| w.set_cursor_visible(visible)) } /// Moves the window with the left mouse button until the button is released. @@ -1336,7 +1378,7 @@ impl Window { /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { - self.window.drag_window() + self.window.maybe_wait_on_main(|w| w.drag_window()) } /// Resizes the window with the left mouse button until the button is released. @@ -1350,7 +1392,8 @@ impl Window { /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { - self.window.drag_resize_window(direction) + self.window + .maybe_wait_on_main(|w| w.drag_resize_window(direction)) } /// Modifies whether the window catches cursor events. @@ -1363,7 +1406,8 @@ impl Window { /// - **iOS / Android / Web / X11 / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { - self.window.set_cursor_hittest(hittest) + self.window + .maybe_wait_on_main(|w| w.set_cursor_hittest(hittest)) } } @@ -1379,8 +1423,7 @@ impl Window { #[inline] pub fn current_monitor(&self) -> Option { self.window - .current_monitor() - .map(|inner| MonitorHandle { inner }) + .maybe_wait_on_main(|w| w.current_monitor().map(|inner| MonitorHandle { inner })) } /// Returns the list of all the monitors available on the system. @@ -1394,11 +1437,11 @@ impl Window { /// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors #[inline] pub fn available_monitors(&self) -> impl Iterator { - #[allow(clippy::useless_conversion)] // false positive on some platforms - self.window - .available_monitors() - .into_iter() - .map(|inner| MonitorHandle { inner }) + self.window.maybe_wait_on_main(|w| { + w.available_monitors() + .into_iter() + .map(|inner| MonitorHandle { inner }) + }) } /// Returns the primary monitor of the system. @@ -1416,10 +1459,10 @@ impl Window { #[inline] pub fn primary_monitor(&self) -> Option { self.window - .primary_monitor() - .map(|inner| MonitorHandle { inner }) + .maybe_wait_on_main(|w| w.primary_monitor().map(|inner| MonitorHandle { inner })) } } + unsafe impl HasRawWindowHandle for Window { /// Returns a [`raw_window_handle::RawWindowHandle`] for the Window /// @@ -1437,7 +1480,11 @@ unsafe impl HasRawWindowHandle for Window { /// [`Event::Resumed`]: crate::event::Event::Resumed /// [`Event::Suspended`]: crate::event::Event::Suspended fn raw_window_handle(&self) -> RawWindowHandle { - self.window.raw_window_handle() + struct Wrapper(RawWindowHandle); + unsafe impl Send for Wrapper {} + self.window + .maybe_wait_on_main(|w| Wrapper(w.raw_window_handle())) + .0 } } @@ -1447,7 +1494,11 @@ unsafe impl HasRawDisplayHandle for Window { /// /// [`EventLoop`]: crate::event_loop::EventLoop fn raw_display_handle(&self) -> RawDisplayHandle { - self.window.raw_display_handle() + struct Wrapper(RawDisplayHandle); + unsafe impl Send for Wrapper {} + self.window + .maybe_wait_on_main(|w| Wrapper(w.raw_display_handle())) + .0 } }