This also fixes macOS returning `None` in `Window::theme()` if no theme override is set, instead it now returns the system theme. MacOS and Wayland were the only ones working correctly according to the documentation, which was an oversight. The documentation was "fixed" now. Fixes #3837.
471 lines
16 KiB
Rust
471 lines
16 KiB
Rust
use std::any::Any;
|
|
use std::cell::Cell;
|
|
use std::collections::VecDeque;
|
|
use std::marker::PhantomData;
|
|
use std::os::raw::c_void;
|
|
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
|
|
use std::ptr;
|
|
use std::rc::{Rc, Weak};
|
|
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use core_foundation::base::{CFIndex, CFRelease};
|
|
use core_foundation::runloop::{
|
|
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
|
|
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
|
};
|
|
use objc2::rc::{autoreleasepool, Retained};
|
|
use objc2::runtime::ProtocolObject;
|
|
use objc2::{msg_send_id, sel, ClassType};
|
|
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
|
|
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
|
|
|
|
use super::app::WinitApplication;
|
|
use super::app_state::ApplicationDelegate;
|
|
use super::cursor::CustomCursor;
|
|
use super::event::dummy_event;
|
|
use super::monitor::{self, MonitorHandle};
|
|
use super::observer::setup_control_flow_observers;
|
|
use crate::application::ApplicationHandler;
|
|
use crate::error::EventLoopError;
|
|
use crate::event_loop::{ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents};
|
|
use crate::platform::macos::ActivationPolicy;
|
|
use crate::platform::pump_events::PumpStatus;
|
|
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
|
|
|
|
#[derive(Default)]
|
|
pub struct PanicInfo {
|
|
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
|
|
}
|
|
|
|
// WARNING:
|
|
// As long as this struct is used through its `impl`, it is UnwindSafe.
|
|
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
|
|
impl UnwindSafe for PanicInfo {}
|
|
impl RefUnwindSafe for PanicInfo {}
|
|
impl PanicInfo {
|
|
pub fn is_panicking(&self) -> bool {
|
|
let inner = self.inner.take();
|
|
let result = inner.is_some();
|
|
self.inner.set(inner);
|
|
result
|
|
}
|
|
|
|
/// Overwrites the current state if the current state is not panicking
|
|
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
|
|
if !self.is_panicking() {
|
|
self.inner.set(Some(p));
|
|
}
|
|
}
|
|
|
|
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
|
|
self.inner.take()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ActiveEventLoop {
|
|
delegate: Retained<ApplicationDelegate>,
|
|
pub(super) mtm: MainThreadMarker,
|
|
}
|
|
|
|
impl ActiveEventLoop {
|
|
pub fn create_proxy(&self) -> EventLoopProxy {
|
|
EventLoopProxy::new(self.delegate.proxy_wake_up())
|
|
}
|
|
|
|
pub(super) fn new_root(delegate: Retained<ApplicationDelegate>) -> RootWindowTarget {
|
|
let mtm = MainThreadMarker::from(&*delegate);
|
|
let p = Self { delegate, mtm };
|
|
RootWindowTarget { p, _marker: PhantomData }
|
|
}
|
|
|
|
pub(super) fn app_delegate(&self) -> &ApplicationDelegate {
|
|
&self.delegate
|
|
}
|
|
|
|
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor {
|
|
RootCustomCursor { inner: CustomCursor::new(source.inner) }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
monitor::available_monitors()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
|
let monitor = monitor::primary_monitor();
|
|
Some(monitor)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
|
|
|
#[inline]
|
|
pub fn system_theme(&self) -> Option<Theme> {
|
|
let app = NSApplication::sharedApplication(self.mtm);
|
|
|
|
if app.respondsToSelector(sel!(effectiveAppearance)) {
|
|
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
|
|
} else {
|
|
Some(Theme::Light)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "rwh_06")]
|
|
#[inline]
|
|
pub fn raw_display_handle_rwh_06(
|
|
&self,
|
|
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
|
Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new()))
|
|
}
|
|
|
|
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
|
|
self.delegate.set_control_flow(control_flow)
|
|
}
|
|
|
|
pub(crate) fn control_flow(&self) -> ControlFlow {
|
|
self.delegate.control_flow()
|
|
}
|
|
|
|
pub(crate) fn exit(&self) {
|
|
self.delegate.exit()
|
|
}
|
|
|
|
pub(crate) fn exiting(&self) -> bool {
|
|
self.delegate.exiting()
|
|
}
|
|
|
|
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
|
|
OwnedDisplayHandle
|
|
}
|
|
|
|
pub(crate) fn hide_application(&self) {
|
|
NSApplication::sharedApplication(self.mtm).hide(None)
|
|
}
|
|
|
|
pub(crate) fn hide_other_applications(&self) {
|
|
NSApplication::sharedApplication(self.mtm).hideOtherApplications(None)
|
|
}
|
|
|
|
pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
|
|
NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm)
|
|
}
|
|
|
|
pub(crate) fn allows_automatic_window_tabbing(&self) -> bool {
|
|
NSWindow::allowsAutomaticWindowTabbing(self.mtm)
|
|
}
|
|
}
|
|
|
|
pub struct EventLoop {
|
|
/// Store a reference to the application for convenience.
|
|
///
|
|
/// We intentionally don't store `WinitApplication` since we want to have
|
|
/// the possibility of swapping that out at some point.
|
|
app: Retained<NSApplication>,
|
|
/// The application delegate that we've registered.
|
|
///
|
|
/// The delegate is only weakly referenced by NSApplication, so we must
|
|
/// keep it around here as well.
|
|
delegate: Retained<ApplicationDelegate>,
|
|
|
|
window_target: RootWindowTarget,
|
|
panic_info: Rc<PanicInfo>,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
pub(crate) struct PlatformSpecificEventLoopAttributes {
|
|
pub(crate) activation_policy: ActivationPolicy,
|
|
pub(crate) default_menu: bool,
|
|
pub(crate) activate_ignoring_other_apps: bool,
|
|
}
|
|
|
|
impl Default for PlatformSpecificEventLoopAttributes {
|
|
fn default() -> Self {
|
|
Self {
|
|
activation_policy: Default::default(), // Regular
|
|
default_menu: true,
|
|
activate_ignoring_other_apps: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EventLoop {
|
|
pub(crate) fn new(
|
|
attributes: &PlatformSpecificEventLoopAttributes,
|
|
) -> Result<Self, EventLoopError> {
|
|
let mtm = MainThreadMarker::new()
|
|
.expect("on macOS, `EventLoop` must be created on the main thread!");
|
|
|
|
let app: Retained<NSApplication> =
|
|
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
|
|
|
|
if !app.is_kind_of::<WinitApplication>() {
|
|
panic!(
|
|
"`winit` requires control over the principal class. You must create the event \
|
|
loop before other parts of your application initialize NSApplication"
|
|
);
|
|
}
|
|
|
|
let activation_policy = match attributes.activation_policy {
|
|
ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
|
|
ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
|
|
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
|
|
};
|
|
|
|
let delegate = ApplicationDelegate::new(
|
|
mtm,
|
|
activation_policy,
|
|
attributes.default_menu,
|
|
attributes.activate_ignoring_other_apps,
|
|
);
|
|
|
|
autoreleasepool(|_| {
|
|
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
|
});
|
|
|
|
let panic_info: Rc<PanicInfo> = Default::default();
|
|
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
|
|
|
|
Ok(EventLoop {
|
|
app,
|
|
delegate: delegate.clone(),
|
|
window_target: RootWindowTarget {
|
|
p: ActiveEventLoop { delegate, mtm },
|
|
_marker: PhantomData,
|
|
},
|
|
panic_info,
|
|
})
|
|
}
|
|
|
|
pub fn window_target(&self) -> &RootWindowTarget {
|
|
&self.window_target
|
|
}
|
|
|
|
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
|
|
self.run_app_on_demand(app)
|
|
}
|
|
|
|
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
|
|
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
|
|
// time and so a layered implementation would end up using a lot of CPU due to
|
|
// redundant wake ups.
|
|
pub fn run_app_on_demand<A: ApplicationHandler>(
|
|
&mut self,
|
|
mut app: A,
|
|
) -> Result<(), EventLoopError> {
|
|
self.delegate.clear_exit();
|
|
self.delegate.set_event_handler(&mut app, || {
|
|
autoreleasepool(|_| {
|
|
// clear / normalize pump_events state
|
|
self.delegate.set_wait_timeout(None);
|
|
self.delegate.set_stop_before_wait(false);
|
|
self.delegate.set_stop_after_wait(false);
|
|
self.delegate.set_stop_on_redraw(false);
|
|
|
|
if self.delegate.is_launched() {
|
|
debug_assert!(!self.delegate.is_running());
|
|
self.delegate.set_is_running(true);
|
|
self.delegate.dispatch_init_events();
|
|
}
|
|
|
|
// SAFETY: We do not run the application re-entrantly
|
|
unsafe { self.app.run() };
|
|
|
|
// While the app is running it's possible that we catch a panic
|
|
// to avoid unwinding across an objective-c ffi boundary, which
|
|
// will lead to us stopping the `NSApplication` and saving the
|
|
// `PanicInfo` so that we can resume the unwind at a controlled,
|
|
// safe point in time.
|
|
if let Some(panic) = self.panic_info.take() {
|
|
resume_unwind(panic);
|
|
}
|
|
|
|
self.delegate.internal_exit()
|
|
})
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn pump_app_events<A: ApplicationHandler>(
|
|
&mut self,
|
|
timeout: Option<Duration>,
|
|
mut app: A,
|
|
) -> PumpStatus {
|
|
self.delegate.set_event_handler(&mut app, || {
|
|
autoreleasepool(|_| {
|
|
// As a special case, if the application hasn't been launched yet then we at least
|
|
// run the loop until it has fully launched.
|
|
if !self.delegate.is_launched() {
|
|
debug_assert!(!self.delegate.is_running());
|
|
|
|
self.delegate.set_stop_on_launch();
|
|
// SAFETY: We do not run the application re-entrantly
|
|
unsafe { self.app.run() };
|
|
|
|
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
|
|
// has launched
|
|
} else if !self.delegate.is_running() {
|
|
// Even though the application may have been launched, it's possible we aren't
|
|
// running if the `EventLoop` was run before and has since
|
|
// exited. This indicates that we just starting to re-run
|
|
// the same `EventLoop` again.
|
|
self.delegate.set_is_running(true);
|
|
self.delegate.dispatch_init_events();
|
|
} else {
|
|
// Only run for as long as the given `Duration` allows so we don't block the
|
|
// external loop.
|
|
match timeout {
|
|
Some(Duration::ZERO) => {
|
|
self.delegate.set_wait_timeout(None);
|
|
self.delegate.set_stop_before_wait(true);
|
|
},
|
|
Some(duration) => {
|
|
self.delegate.set_stop_before_wait(false);
|
|
let timeout = Instant::now() + duration;
|
|
self.delegate.set_wait_timeout(Some(timeout));
|
|
self.delegate.set_stop_after_wait(true);
|
|
},
|
|
None => {
|
|
self.delegate.set_wait_timeout(None);
|
|
self.delegate.set_stop_before_wait(false);
|
|
self.delegate.set_stop_after_wait(true);
|
|
},
|
|
}
|
|
self.delegate.set_stop_on_redraw(true);
|
|
// SAFETY: We do not run the application re-entrantly
|
|
unsafe { self.app.run() };
|
|
}
|
|
|
|
// While the app is running it's possible that we catch a panic
|
|
// to avoid unwinding across an objective-c ffi boundary, which
|
|
// will lead to us stopping the application and saving the
|
|
// `PanicInfo` so that we can resume the unwind at a controlled,
|
|
// safe point in time.
|
|
if let Some(panic) = self.panic_info.take() {
|
|
resume_unwind(panic);
|
|
}
|
|
|
|
if self.delegate.exiting() {
|
|
self.delegate.internal_exit();
|
|
PumpStatus::Exit(0)
|
|
} else {
|
|
PumpStatus::Continue
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct OwnedDisplayHandle;
|
|
|
|
impl OwnedDisplayHandle {
|
|
#[cfg(feature = "rwh_06")]
|
|
#[inline]
|
|
pub fn raw_display_handle_rwh_06(
|
|
&self,
|
|
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
|
|
Ok(rwh_06::AppKitDisplayHandle::new().into())
|
|
}
|
|
}
|
|
|
|
pub(super) fn stop_app_immediately(app: &NSApplication) {
|
|
autoreleasepool(|_| {
|
|
app.stop(None);
|
|
// To stop event loop immediately, we need to post some event here.
|
|
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
|
|
app.postEvent_atStart(&dummy_event().unwrap(), true);
|
|
});
|
|
}
|
|
|
|
/// Catches panics that happen inside `f` and when a panic
|
|
/// happens, stops the `sharedApplication`
|
|
#[inline]
|
|
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
|
mtm: MainThreadMarker,
|
|
panic_info: Weak<PanicInfo>,
|
|
f: F,
|
|
) -> Option<R> {
|
|
match catch_unwind(f) {
|
|
Ok(r) => Some(r),
|
|
Err(e) => {
|
|
// It's important that we set the panic before requesting a `stop`
|
|
// because some callback are still called during the `stop` message
|
|
// and we need to know in those callbacks if the application is currently
|
|
// panicking
|
|
{
|
|
let panic_info = panic_info.upgrade().unwrap();
|
|
panic_info.set_panic(e);
|
|
}
|
|
let app = NSApplication::sharedApplication(mtm);
|
|
stop_app_immediately(&app);
|
|
None
|
|
},
|
|
}
|
|
}
|
|
|
|
pub struct EventLoopProxy {
|
|
proxy_wake_up: Arc<AtomicBool>,
|
|
source: CFRunLoopSourceRef,
|
|
}
|
|
|
|
unsafe impl Send for EventLoopProxy {}
|
|
unsafe impl Sync for EventLoopProxy {}
|
|
|
|
impl Drop for EventLoopProxy {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
CFRelease(self.source as _);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Clone for EventLoopProxy {
|
|
fn clone(&self) -> Self {
|
|
EventLoopProxy::new(self.proxy_wake_up.clone())
|
|
}
|
|
}
|
|
|
|
impl EventLoopProxy {
|
|
fn new(proxy_wake_up: Arc<AtomicBool>) -> Self {
|
|
unsafe {
|
|
// just wake up the eventloop
|
|
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
|
|
|
|
// adding a Source to the main CFRunLoop lets us wake it up and
|
|
// process user events through the normal OS EventLoop mechanisms.
|
|
let rl = CFRunLoopGetMain();
|
|
let mut context = CFRunLoopSourceContext {
|
|
version: 0,
|
|
info: ptr::null_mut(),
|
|
retain: None,
|
|
release: None,
|
|
copyDescription: None,
|
|
equal: None,
|
|
hash: None,
|
|
schedule: None,
|
|
cancel: None,
|
|
perform: event_loop_proxy_handler,
|
|
};
|
|
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
|
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
|
CFRunLoopWakeUp(rl);
|
|
|
|
EventLoopProxy { proxy_wake_up, source }
|
|
}
|
|
}
|
|
|
|
pub fn wake_up(&self) {
|
|
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
|
|
unsafe {
|
|
// let the main thread know there's a new event
|
|
CFRunLoopSourceSignal(self.source);
|
|
let rl = CFRunLoopGetMain();
|
|
CFRunLoopWakeUp(rl);
|
|
}
|
|
}
|
|
}
|