use std::fmt;
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant};
use objc2::rc::{Retained, autoreleasepool};
use objc2::runtime::ProtocolObject;
use objc2::{AnyThread, MainThreadMarker, available};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSDraggingItem, NSWindow,
};
use objc2_core_foundation::{
CFIndex, CFRunLoopActivity, CGPoint, CGRect, CGSize, kCFRunLoopCommonModes,
};
use objc2_foundation::{NSArray, NSNotificationCenter, NSObjectProtocol, NSString};
use rwh_06::HasDisplayHandle;
use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
use winit_core::data_transfer::{
DataTransfer, DataTransferId, DataTransferSend, SendData, TransferType, TypeHint,
};
use winit_core::error::{EventLoopError, RequestError};
use winit_core::event::WindowEvent;
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, AsyncRequestSerial, ControlFlow, DeviceEvents,
DndAction, DragIcon, EventLoopProvider, EventLoopProxy as CoreEventLoopProxy,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::{Theme, WindowId};
use super::app::override_send_event;
use super::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
use super::monitor;
use crate::ActivationPolicy;
use crate::cursor::image_from_icon;
use crate::dnd::{PasteboardTypeSpec, PasteboardWriter, dnd_actions_to_ns_drag_operation};
use crate::window::Window;
#[derive(Debug)]
pub struct ActiveEventLoop {
pub(super) app_state: Rc<AppState>,
pub(super) mtm: MainThreadMarker,
}
impl ActiveEventLoop {
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)
}
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(self.app_state.event_loop_proxy().clone())
}
fn create_window(
&self,
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
source: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?)))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
monitor::available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
}
fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
let monitor = monitor::primary_monitor();
Some(CoreMonitorHandle(Arc::new(monitor)))
}
fn listen_device_events(&self, _allowed: DeviceEvents) {}
fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm);
if available!(macos = 10.14) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
}
}
fn set_control_flow(&self, control_flow: ControlFlow) {
self.app_state.set_control_flow(control_flow)
}
fn control_flow(&self) -> ControlFlow {
self.app_state.control_flow()
}
fn exit(&self) {
self.app_state.exit()
}
fn exiting(&self) -> bool {
self.app_state.exiting()
}
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
fn fetch_data_transfer(
&self,
id: DataTransferId,
type_: &dyn TransferType,
) -> Result<AsyncRequestSerial, RequestError> {
let Some(pb) = self.app_state.pasteboards().get(id) else {
return Err(RequestError::Ignored);
};
let Some(window_id) = self.app_state.pasteboards().window_id(id) else {
return Err(RequestError::Ignored);
};
let serial = AsyncRequestSerial::get();
let Some(type_) = PasteboardTypeSpec::from_dyn(type_) else {
return Err(os_error!(format!("Pasteboard does not contain type {type_:?}")).into());
};
let data = Arc::new(pb.with_type(type_));
self.app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::DataTransferReceived {
id,
serial,
value: data,
});
});
Ok(serial)
}
fn data_transfer(&self, id: DataTransferId) -> Result<Box<dyn DataTransfer>, RequestError> {
let Some(pb) = self.app_state.pasteboards().get(id) else {
return Err(RequestError::Ignored);
};
Ok(Box::new(pb))
}
fn set_valid_dnd_actions(
&self,
id: DataTransferId,
actions: &[DndAction],
) -> Result<(), RequestError> {
let mut state = self.app_state.drag_state().borrow_mut();
let Some(drag_state) = &mut *state else {
return Err(os_error!(UnknownDataTransfer(id)).into());
};
if drag_state.id != id {
return Err(os_error!(UnknownDataTransfer(id)).into());
}
drag_state.valid_actions.clear();
drag_state.valid_actions.extend_from_slice(actions);
Ok(())
}
fn start_drag(
&self,
source: WindowId,
send_data: Box<dyn DataTransferSend>,
actions: &[DndAction],
icon: Option<DragIcon>,
) -> Result<DataTransferId, RequestError> {
let drag_operation = dnd_actions_to_ns_drag_operation(actions);
self.app_state
.with_window_delegate_on_main(source, move |delegate| {
let (dragging_rect_offset_x, dragging_rect_offset_y) =
icon.as_ref().map(|icon| (icon.offset_x, icon.offset_y)).unwrap_or_default();
let drag_image = icon.and_then(|icon| image_from_icon(&icon.icon).ok());
let Some(event) = delegate.window().currentEvent() else {
return Err(RequestError::Ignored);
};
let dragging_rect_size = drag_image
.as_ref()
.map(|img| img.size())
.unwrap_or(CGSize::new(16., 16.));
let event_location = event.locationInWindow();
let dragging_rect_location = CGPoint::new(
event_location.x + dragging_rect_offset_x as f64,
event_location.y - dragging_rect_size.height - dragging_rect_offset_y as f64,
);
let dragging_rect = CGRect::new(dragging_rect_location, dragging_rect_size);
let mut uris = send_data
.data_for_type(&TypeHint::UriList)
.and_then(|file_uris| {
let ns_url_from_str = |str: String| NSString::from_str(&str);
match file_uris {
SendData::Uris(os_strings) => Some(
None.into_iter().chain(os_strings.into_iter().map(ns_url_from_str)),
),
SendData::String(string) => Some(
Some(NSString::from_str(&string))
.into_iter()
.chain(Vec::new().into_iter().map(ns_url_from_str)),
),
SendData::Bytes(_) => None,
_ => None,
}
})
.into_iter()
.flatten();
let first_uri = uris.next();
let mut pasteboard_items = uris
.map(|ns_url| {
let dragging_item = NSDraggingItem::initWithPasteboardWriter(
NSDraggingItem::alloc(),
ProtocolObject::from_ref(&*ns_url),
);
dragging_item
})
.collect::<Vec<_>>();
let first_dragging_item = NSDraggingItem::initWithPasteboardWriter(
NSDraggingItem::alloc(),
ProtocolObject::from_ref(&*PasteboardWriter::new(send_data, first_uri)),
);
unsafe {
first_dragging_item.setDraggingFrame_contents(
dragging_rect,
drag_image.as_ref().map(AsRef::as_ref),
)
};
pasteboard_items.insert(0, first_dragging_item);
let pasteboard_items = NSArray::from_retained_slice(&pasteboard_items);
let session = delegate.window().beginDraggingSessionWithItems_event_source(
&pasteboard_items,
&event,
ProtocolObject::from_ref(&*delegate),
);
let id = DataTransferId::from_raw(session.draggingSequenceNumber() as i64);
delegate.view().set_dragging_session(session, drag_operation);
Ok(id)
})
.ok_or(RequestError::Ignored)?
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct UnknownDataTransfer(pub DataTransferId);
impl fmt::Display for UnknownDataTransfer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let id = self.0.into_raw();
write!(f, "Unknown data transfer with ID {id}")
}
}
impl std::error::Error for UnknownDataTransfer {}
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
#[derive(Debug)]
pub struct EventLoop {
app: Retained<NSApplication>,
app_state: Rc<AppState>,
window_target: ActiveEventLoop,
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
_before_waiting_observer: MainRunLoopObserver,
_after_waiting_observer: MainRunLoopObserver,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes {
pub activation_policy: Option<ActivationPolicy>,
pub default_menu: bool,
pub activate_ignoring_other_apps: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
}
}
impl EventLoop {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!");
let activation_policy = match attributes.activation_policy {
None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
};
let app_state = AppState::setup_global(
mtm,
activation_policy,
attributes.default_menu,
attributes.activate_ignoring_other_apps,
)
.ok_or_else(|| EventLoopError::RecreationAttempt)?;
let app = NSApplication::sharedApplication(mtm);
override_send_event(&app);
let center = NSNotificationCenter::defaultCenter();
let weak_app_state = Rc::downgrade(&app_state);
let _did_finish_launching_observer = create_observer(
¢er,
unsafe { NSApplicationDidFinishLaunchingNotification },
move |notification| {
let _entered = debug_span!("NSApplicationDidFinishLaunchingNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() {
app_state.did_finish_launching(notification);
}
},
);
let weak_app_state = Rc::downgrade(&app_state);
let _will_terminate_observer = create_observer(
¢er,
unsafe { NSApplicationWillTerminateNotification },
move |notification| {
let _entered = debug_span!("NSApplicationWillTerminateNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() {
app_state.will_terminate(notification);
}
},
);
let main_loop = MainRunLoop::get(mtm);
let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
main_loop.add_observer(start, mode);
main_loop.add_observer(end, mode);
});
let app_state_clone = Rc::clone(&app_state);
let _before_waiting_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::BeforeWaiting,
true,
CFIndex::MAX - 1,
move |_| app_state_clone.cleared(),
);
main_loop.add_observer(&_before_waiting_observer, mode);
let app_state_clone = Rc::clone(&app_state);
let _after_waiting_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::AfterWaiting,
true,
CFIndex::MIN + 1,
move |_| app_state_clone.wakeup(),
);
main_loop.add_observer(&_after_waiting_observer, mode);
Ok(EventLoop {
app,
app_state: app_state.clone(),
window_target: ActiveEventLoop { app_state, mtm },
_did_finish_launching_observer,
_will_terminate_observer,
_tracing_observers,
_before_waiting_observer,
_after_waiting_observer,
})
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.window_target
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: A,
) -> Result<(), EventLoopError> {
self.app_state.clear_exit();
self.app_state.set_event_handler(app, || {
autoreleasepool(|_| {
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(false);
self.app_state.set_stop_after_wait(false);
self.app_state.set_stop_on_redraw(false);
if self.app_state.is_launched() {
debug_assert!(!self.app_state.is_running());
self.app_state.set_is_running(true);
self.app_state.dispatch_init_events();
}
self.app.run();
self.app_state.internal_exit()
})
});
Ok(())
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
) -> PumpStatus {
self.app_state.set_event_handler(app, || {
autoreleasepool(|_| {
if !self.app_state.is_launched() {
debug_assert!(!self.app_state.is_running());
self.app_state.set_stop_on_launch();
self.app.run();
} else if !self.app_state.is_running() {
self.app_state.set_is_running(true);
self.app_state.dispatch_init_events();
} else {
match timeout {
Some(Duration::ZERO) => {
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(true);
},
Some(duration) => {
self.app_state.set_stop_before_wait(false);
let timeout = Instant::now() + duration;
self.app_state.set_wait_timeout(Some(timeout));
self.app_state.set_stop_after_wait(true);
},
None => {
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(false);
self.app_state.set_stop_after_wait(true);
},
}
self.app_state.set_stop_on_redraw(true);
self.app.run();
}
if self.app_state.exiting() {
self.app_state.internal_exit();
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
}
})
})
}
}
impl EventLoopProvider for EventLoop {
fn run_app<A: ApplicationHandler + 'static>(
mut self,
mut app: A,
) -> Result<(), EventLoopError> {
let result = self.run_app_on_demand(&mut app);
drop(app);
result
}
fn create_proxy(&self) -> CoreEventLoopProxy {
self.window_target().create_proxy()
}
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
self.window_target().owned_display_handle()
}
fn listen_device_events(&self, allowed: DeviceEvents) {
self.window_target().listen_device_events(allowed);
}
fn set_control_flow(&self, control_flow: ControlFlow) {
self.window_target().set_control_flow(control_flow);
}
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> {
self.window_target().create_custom_cursor(custom_cursor)
}
}
pub(crate) struct OwnedDisplayHandle;
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
pub(super) fn stop_app_immediately(app: &NSApplication) {
autoreleasepool(|_| {
app.stop(None);
app.postEvent_atStart(&dummy_event().unwrap(), true);
});
}
pub(super) fn notify_windows_of_exit(app: &NSApplication) {
for window in app.windows() {
window.close();
}
}