1use std::rc::Rc;
2use std::sync::Arc;
3use std::time::{Duration, Instant};
4
5use objc2::rc::{Retained, autoreleasepool};
6use objc2::runtime::ProtocolObject;
7use objc2::{AnyThread, MainThreadMarker, available};
8use objc2_app_kit::{
9 NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
10 NSApplicationWillTerminateNotification, NSDraggingItem, NSPasteboard, NSPasteboardItem,
11 NSPasteboardTypeFileURL, NSPasteboardWriting, NSWindow,
12};
13use objc2_core_foundation::{
14 CFIndex, CFRunLoopActivity, CGPoint, CGRect, CGSize, kCFRunLoopCommonModes,
15};
16use objc2_foundation::{NSArray, NSNotificationCenter, NSObjectProtocol, NSString};
17use rwh_06::HasDisplayHandle;
18use tracing::debug_span;
19use wingy_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
20use wingy_common::foundation::create_observer;
21use wingy_core::application::ApplicationHandler;
22use wingy_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
23use wingy_core::data_transfer::{
24 DataTransfer, DataTransferId, DataTransferSend, SendData, TransferType, TypeHint, TypedData,
25};
26use wingy_core::error::{CreateWindowError, CustomCursorError, EventLoopError, TransferError};
27use wingy_core::event::WindowEvent;
28use wingy_core::event_loop::pump_events::PumpStatus;
29use wingy_core::event_loop::{
30 ActiveEventLoop as RootActiveEventLoop, AsyncRequestSerial, ControlFlow, DeviceEvents,
31 DndAction, DragIcon, EventLoopProvider, EventLoopProxy as CoreEventLoopProxy,
32 OwnedDisplayHandle as CoreOwnedDisplayHandle,
33};
34use wingy_core::monitor::MonitorHandle as CoreMonitorHandle;
35use wingy_core::window::{Theme, WindowId};
36
37use super::app::override_send_event;
38use super::app_state::AppState;
39use super::cursor::CustomCursor;
40use super::event::dummy_event;
41use super::monitor;
42use crate::ActivationPolicy;
43use crate::cursor::image_from_icon;
44use crate::data_transfer::{
45 Pasteboard, PasteboardType, PasteboardTypeSpec, PasteboardWriter,
46 dnd_actions_to_ns_drag_operation,
47};
48use crate::window::Window;
49
50#[derive(Debug)]
51pub struct ActiveEventLoop {
52 pub(super) app_state: Rc<AppState>,
53 pub(super) mtm: MainThreadMarker,
54}
55
56impl ActiveEventLoop {
57 pub(crate) fn hide_application(&self) {
58 NSApplication::sharedApplication(self.mtm).hide(None)
59 }
60
61 pub(crate) fn hide_other_applications(&self) {
62 NSApplication::sharedApplication(self.mtm).hideOtherApplications(None)
63 }
64
65 pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
66 NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm)
67 }
68
69 pub(crate) fn allows_automatic_window_tabbing(&self) -> bool {
70 NSWindow::allowsAutomaticWindowTabbing(self.mtm)
71 }
72
73 fn clipboard_by_id(&self, id: DataTransferId) -> Option<Pasteboard> {
74 let pb = Pasteboard::new(id, NSPasteboard::generalPasteboard(), self.mtm);
75 (self.app_state.data_transfer().clipboard_transfer_id(pb.change_count()) == id)
76 .then_some(pb)
77 }
78}
79
80impl RootActiveEventLoop for ActiveEventLoop {
81 fn create_proxy(&self) -> CoreEventLoopProxy {
82 CoreEventLoopProxy::new(self.app_state.event_loop_proxy().clone())
83 }
84
85 fn create_window(
86 &self,
87 window_attributes: wingy_core::window::WindowAttributes,
88 ) -> Result<Box<dyn wingy_core::window::Window>, CreateWindowError> {
89 Ok(Box::new(Window::new(self, window_attributes)?))
90 }
91
92 fn create_custom_cursor(
93 &self,
94 source: CustomCursorSource,
95 ) -> Result<CoreCustomCursor, CustomCursorError> {
96 Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?)))
97 }
98
99 fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
100 Box::new(
101 monitor::available_monitors()
102 .into_iter()
103 .map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
104 )
105 }
106
107 fn primary_monitor(&self) -> Option<wingy_core::monitor::MonitorHandle> {
108 let monitor = monitor::primary_monitor();
109 Some(CoreMonitorHandle(Arc::new(monitor)))
110 }
111
112 fn listen_device_events(&self, _allowed: DeviceEvents) {}
113
114 fn system_theme(&self) -> Option<Theme> {
115 let app = NSApplication::sharedApplication(self.mtm);
116
117 if available!(macos = 10.14) {
119 Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
120 } else {
121 Some(Theme::Light)
122 }
123 }
124
125 fn set_control_flow(&self, control_flow: ControlFlow) {
126 self.app_state.set_control_flow(control_flow)
127 }
128
129 fn control_flow(&self) -> ControlFlow {
130 self.app_state.control_flow()
131 }
132
133 fn exit(&self) {
134 self.app_state.exit()
135 }
136
137 fn exiting(&self) -> bool {
138 self.app_state.exiting()
139 }
140
141 fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
142 CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
143 }
144
145 fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
146 self
147 }
148
149 fn fetch_data_transfer(
150 &self,
151 id: DataTransferId,
152 type_: &dyn TransferType,
153 ) -> Result<AsyncRequestSerial, TransferError> {
154 let (pb, window_id) = match self.app_state.data_transfer().pasteboards().get(id) {
155 Some(pb) => {
156 let Some(window_id) = self.app_state.data_transfer().pasteboards().window_id(id)
157 else {
158 return Err(TransferError::Failed);
159 };
160 (pb, window_id)
161 },
162 None => {
163 let pb = self.clipboard_by_id(id).ok_or(TransferError::UnknownTransfer(id))?;
164 let window_id =
165 self.app_state.clipboard_window(self.mtm).ok_or(TransferError::Failed)?;
166 (pb, window_id)
167 },
168 };
169
170 let type_ = match type_.cast_ref::<PasteboardType>() {
171 Some(pb_type) => pb_type.clone(),
172 None => {
173 let hint = type_.hint().ok_or(TransferError::Failed)?;
174 pb.types()
175 .iter()
176 .find(|advertised| {
177 advertised.hint().is_some_and(|haystack| haystack.matches(&hint))
178 })
179 .cloned()
180 .ok_or(TransferError::Failed)?
181 },
182 };
183
184 let serial = AsyncRequestSerial::get();
185
186 let data: Arc<dyn TypedData> =
187 Arc::new(pb.with_type(PasteboardTypeSpec::PasteboardType(type_)));
188
189 self.app_state.maybe_queue_with_handler(move |app, event_loop| {
190 app.window_event(
191 event_loop,
192 window_id,
193 WindowEvent::DataTransferReceived { id, serial, value: data },
194 );
195 });
196
197 Ok(serial)
198 }
199
200 fn data_transfer(&self, id: DataTransferId) -> Result<Box<dyn DataTransfer>, TransferError> {
201 if let Some(pb) = self.app_state.data_transfer().pasteboards().get(id) {
202 return Ok(Box::new(pb));
203 }
204
205 let pb = self.clipboard_by_id(id).ok_or(TransferError::UnknownTransfer(id))?;
206
207 Ok(Box::new(pb))
208 }
209
210 fn clipboard(&self) -> Result<Option<DataTransferId>, TransferError> {
211 let pb = NSPasteboard::generalPasteboard();
212 if pb.types().is_none_or(|types| types.is_empty()) {
213 return Ok(None);
214 }
215
216 Ok(Some(self.app_state.data_transfer().clipboard_transfer_id(pb.changeCount())))
217 }
218
219 fn set_clipboard(&self, send_data: Box<dyn DataTransferSend>) -> Result<(), TransferError> {
220 let mut uris = match send_data.data_for_type(&TypeHint::UriList) {
221 Some(SendData::Uris(uris)) => uris,
222 Some(SendData::String(string)) => vec![string],
223 _ => vec![],
224 }
225 .into_iter()
226 .map(|uri| NSString::from_str(&uri));
227
228 let first_uri = uris.next();
229
230 let mut objects: Vec<Retained<ProtocolObject<dyn NSPasteboardWriting>>> =
231 vec![ProtocolObject::from_retained(PasteboardWriter::new(send_data, first_uri))];
232
233 for uri in uris {
234 let item = NSPasteboardItem::new();
235 if !item.setString_forType(&uri, unsafe { NSPasteboardTypeFileURL }) {
236 return Err(os_error!("failed to write a URI to a pasteboard item").into());
237 }
238 objects.push(ProtocolObject::from_retained(item));
239 }
240
241 let pb = NSPasteboard::generalPasteboard();
242 pb.clearContents();
243 if pb.writeObjects(&NSArray::from_retained_slice(&objects)) {
244 Ok(())
245 } else {
246 Err(os_error!("failed to write to the general pasteboard").into())
247 }
248 }
249
250 fn set_valid_dnd_actions(
251 &self,
252 id: DataTransferId,
253 actions: &[DndAction],
254 ) -> Result<(), TransferError> {
255 if self.app_state.data_transfer().set_valid_dnd_actions(id, actions) {
256 Ok(())
257 } else {
258 Err(TransferError::UnknownTransfer(id))
259 }
260 }
261
262 fn start_drag(
263 &self,
264 source: WindowId,
265 send_data: Box<dyn DataTransferSend>,
266 actions: &[DndAction],
267 icon: Option<DragIcon>,
268 ) -> Result<DataTransferId, TransferError> {
269 let drag_operation = dnd_actions_to_ns_drag_operation(actions);
270
271 self.app_state
272 .with_window_delegate_on_main(source, move |delegate| {
273 let (dragging_rect_offset_x, dragging_rect_offset_y) =
274 icon.as_ref().map(|icon| (icon.offset_x, icon.offset_y)).unwrap_or_default();
275 let drag_image = icon.and_then(|icon| image_from_icon(&icon.icon).ok());
276
277 let Some(event) = delegate.window().currentEvent() else {
278 return Err(TransferError::Failed);
279 };
280
281 let dragging_rect_size = drag_image
282 .as_ref()
283 .map(|img| img.size())
284 .unwrap_or(CGSize::new(16., 16.));
287
288 let event_location = event.locationInWindow();
289 let dragging_rect_location = CGPoint::new(
290 event_location.x + dragging_rect_offset_x as f64,
291 event_location.y - dragging_rect_size.height - dragging_rect_offset_y as f64,
294 );
295 let dragging_rect = CGRect::new(dragging_rect_location, dragging_rect_size);
296
297 let mut uris = send_data
298 .data_for_type(&TypeHint::UriList)
299 .and_then(|file_uris| {
300 let ns_url_from_str = |str: String| NSString::from_str(&str);
302 match file_uris {
305 SendData::Uris(os_strings) => Some(
306 None.into_iter().chain(os_strings.into_iter().map(ns_url_from_str)),
307 ),
308 SendData::String(string) => Some(
309 Some(NSString::from_str(&string))
310 .into_iter()
311 .chain(Vec::new().into_iter().map(ns_url_from_str)),
312 ),
313 SendData::Bytes(_) => None,
314 _ => None,
315 }
316 })
317 .into_iter()
318 .flatten();
319
320 let first_uri = uris.next();
321
322 let mut pasteboard_items = uris
323 .map(|ns_url| {
324 let dragging_item = NSDraggingItem::initWithPasteboardWriter(
325 NSDraggingItem::alloc(),
326 ProtocolObject::from_ref(&*ns_url),
327 );
328
329 dragging_item
332 })
333 .collect::<Vec<_>>();
334
335 let first_dragging_item = NSDraggingItem::initWithPasteboardWriter(
336 NSDraggingItem::alloc(),
337 ProtocolObject::from_ref(&*PasteboardWriter::new(send_data, first_uri)),
338 );
339
340 unsafe {
341 first_dragging_item.setDraggingFrame_contents(
342 dragging_rect,
343 drag_image.as_ref().map(AsRef::as_ref),
344 )
345 };
346
347 pasteboard_items.insert(0, first_dragging_item);
348
349 let pasteboard_items = NSArray::from_retained_slice(&pasteboard_items);
350
351 let session = delegate.window().beginDraggingSessionWithItems_event_source(
352 &pasteboard_items,
353 &event,
354 ProtocolObject::from_ref(&*delegate),
355 );
356
357 let id = DataTransferId::from_raw(session.draggingSequenceNumber() as i64);
358
359 delegate.view().set_dragging_session(session, drag_operation);
360
361 Ok(id)
362 })
363 .ok_or(TransferError::Failed)?
364 }
365}
366
367impl rwh_06::HasDisplayHandle for ActiveEventLoop {
368 fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
369 let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
370 unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
371 }
372}
373
374#[derive(Debug)]
375pub struct EventLoop {
376 app: Retained<NSApplication>,
381 app_state: Rc<AppState>,
382
383 window_target: ActiveEventLoop,
384
385 _did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
390 _will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
391
392 _tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
393 _before_waiting_observer: MainRunLoopObserver,
394 _after_waiting_observer: MainRunLoopObserver,
395}
396
397#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
398pub struct PlatformSpecificEventLoopAttributes {
399 pub activation_policy: Option<ActivationPolicy>,
400 pub default_menu: bool,
401 pub activate_ignoring_other_apps: bool,
402}
403
404impl Default for PlatformSpecificEventLoopAttributes {
405 fn default() -> Self {
406 Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
407 }
408}
409
410impl EventLoop {
411 pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
412 let mtm = MainThreadMarker::new()
413 .expect("on macOS, `EventLoop` must be created on the main thread!");
414
415 let activation_policy = match attributes.activation_policy {
416 None => None,
417 Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
418 Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
419 Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
420 };
421
422 let app_state = AppState::setup_global(
423 mtm,
424 activation_policy,
425 attributes.default_menu,
426 attributes.activate_ignoring_other_apps,
427 )
428 .ok_or_else(|| EventLoopError::RecreationAttempt)?;
429
430 let app = NSApplication::sharedApplication(mtm);
432
433 override_send_event(&app);
435
436 let center = NSNotificationCenter::defaultCenter();
437
438 let weak_app_state = Rc::downgrade(&app_state);
439 let _did_finish_launching_observer = create_observer(
440 ¢er,
441 unsafe { NSApplicationDidFinishLaunchingNotification },
443 move |notification| {
444 let _entered = debug_span!("NSApplicationDidFinishLaunchingNotification").entered();
445 if let Some(app_state) = weak_app_state.upgrade() {
446 app_state.did_finish_launching(notification);
447 }
448 },
449 );
450
451 let weak_app_state = Rc::downgrade(&app_state);
452 let _will_terminate_observer = create_observer(
453 ¢er,
454 unsafe { NSApplicationWillTerminateNotification },
456 move |notification| {
457 let _entered = debug_span!("NSApplicationWillTerminateNotification").entered();
458 if let Some(app_state) = weak_app_state.upgrade() {
459 app_state.will_terminate(notification);
460 }
461 },
462 );
463
464 let main_loop = MainRunLoop::get(mtm);
465 let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
466
467 let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
469 main_loop.add_observer(start, mode);
470 main_loop.add_observer(end, mode);
471 });
472
473 let app_state_clone = Rc::clone(&app_state);
474 let _before_waiting_observer = MainRunLoopObserver::new(
475 mtm,
476 CFRunLoopActivity::BeforeWaiting,
477 true,
478 CFIndex::MAX - 1,
481 move |_| app_state_clone.cleared(),
482 );
483 main_loop.add_observer(&_before_waiting_observer, mode);
484
485 let app_state_clone = Rc::clone(&app_state);
486 let _after_waiting_observer = MainRunLoopObserver::new(
487 mtm,
488 CFRunLoopActivity::AfterWaiting,
489 true,
490 CFIndex::MIN + 1,
493 move |_| app_state_clone.wakeup(),
494 );
495 main_loop.add_observer(&_after_waiting_observer, mode);
496
497 Ok(EventLoop {
498 app,
499 app_state: app_state.clone(),
500 window_target: ActiveEventLoop { app_state, mtm },
501 _did_finish_launching_observer,
502 _will_terminate_observer,
503 _tracing_observers,
504 _before_waiting_observer,
505 _after_waiting_observer,
506 })
507 }
508
509 pub fn window_target(&self) -> &dyn RootActiveEventLoop {
510 &self.window_target
511 }
512
513 pub fn run_app_on_demand<A: ApplicationHandler>(
518 &mut self,
519 app: A,
520 ) -> Result<(), EventLoopError> {
521 self.app_state.clear_exit();
522 self.app_state.set_event_handler(app, || {
523 autoreleasepool(|_| {
524 self.app_state.set_wait_timeout(None);
526 self.app_state.set_stop_before_wait(false);
527 self.app_state.set_stop_after_wait(false);
528 self.app_state.set_stop_on_redraw(false);
529
530 if self.app_state.is_launched() {
531 debug_assert!(!self.app_state.is_running());
532 self.app_state.set_is_running(true);
533 self.app_state.dispatch_init_events();
534 }
535
536 self.app.run();
538
539 self.app_state.internal_exit()
540 })
541 });
542
543 Ok(())
544 }
545
546 pub fn pump_app_events<A: ApplicationHandler>(
547 &mut self,
548 timeout: Option<Duration>,
549 app: A,
550 ) -> PumpStatus {
551 self.app_state.set_event_handler(app, || {
552 autoreleasepool(|_| {
553 if !self.app_state.is_launched() {
556 debug_assert!(!self.app_state.is_running());
557
558 self.app_state.set_stop_on_launch();
559 self.app.run();
560
561 } else if !self.app_state.is_running() {
564 self.app_state.set_is_running(true);
569 self.app_state.dispatch_init_events();
570 } else {
571 match timeout {
574 Some(Duration::ZERO) => {
575 self.app_state.set_wait_timeout(None);
576 self.app_state.set_stop_before_wait(true);
577 },
578 Some(duration) => {
579 self.app_state.set_stop_before_wait(false);
580 let timeout = Instant::now() + duration;
581 self.app_state.set_wait_timeout(Some(timeout));
582 self.app_state.set_stop_after_wait(true);
583 },
584 None => {
585 self.app_state.set_wait_timeout(None);
586 self.app_state.set_stop_before_wait(false);
587 self.app_state.set_stop_after_wait(true);
588 },
589 }
590 self.app_state.set_stop_on_redraw(true);
591 self.app.run();
592 }
593
594 if self.app_state.exiting() {
595 self.app_state.internal_exit();
596 PumpStatus::Exit(0)
597 } else {
598 PumpStatus::Continue
599 }
600 })
601 })
602 }
603}
604
605impl EventLoopProvider for EventLoop {
606 fn run_app<A: ApplicationHandler + 'static>(
607 mut self,
608 mut app: A,
609 ) -> Result<(), EventLoopError> {
610 let result = self.run_app_on_demand(&mut app);
611 drop(app);
613 result
614 }
615
616 fn create_proxy(&self) -> CoreEventLoopProxy {
617 self.window_target().create_proxy()
618 }
619
620 fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
621 self.window_target().owned_display_handle()
622 }
623
624 fn listen_device_events(&self, allowed: DeviceEvents) {
625 self.window_target().listen_device_events(allowed);
626 }
627
628 fn set_control_flow(&self, control_flow: ControlFlow) {
629 self.window_target().set_control_flow(control_flow);
630 }
631
632 fn create_custom_cursor(
633 &self,
634 custom_cursor: CustomCursorSource,
635 ) -> Result<CoreCustomCursor, CustomCursorError> {
636 self.window_target().create_custom_cursor(custom_cursor)
637 }
638}
639
640pub(crate) struct OwnedDisplayHandle;
641
642impl HasDisplayHandle for OwnedDisplayHandle {
643 fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
644 let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
645 unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
646 }
647}
648
649pub(super) fn stop_app_immediately(app: &NSApplication) {
650 autoreleasepool(|_| {
651 app.stop(None);
652 app.postEvent_atStart(&dummy_event().unwrap(), true);
655 });
656}
657
658pub(super) fn notify_windows_of_exit(app: &NSApplication) {
669 for window in app.windows() {
670 window.close();
671 }
672}