Skip to main content

winio_ui_app_kit/
runtime.rs

1use std::{
2    future::Future,
3    ptr::NonNull,
4    task::{RawWaker, RawWakerVTable, Waker},
5    time::Duration,
6};
7
8use objc2::rc::Retained;
9use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEventMask};
10use objc2_core_foundation::{CFRetained, CFRunLoop, kCFRunLoopDefaultMode};
11use objc2_foundation::{MainThreadMarker, NSDate, NSDefaultRunLoopMode};
12
13#[cfg(feature = "compio-compat")]
14use crate::get_context;
15use crate::{Error, Result, catch};
16
17#[cfg(not(feature = "compio-compat"))]
18fn get_context() -> (Option<Duration>, Option<Waker>) {
19    (None, None)
20}
21
22pub struct App {
23    ns_app: Retained<NSApplication>,
24    waker: Waker,
25}
26
27impl App {
28    pub fn new() -> Result<Self> {
29        let mtm = MainThreadMarker::new().ok_or(Error::NotMainThread)?;
30        let ns_app = catch(|| {
31            let ns_app = NSApplication::sharedApplication(mtm);
32            ns_app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
33            #[allow(deprecated)]
34            ns_app.activateIgnoringOtherApps(true);
35            ns_app
36        })?;
37        let waker = run_loop_waker(CFRunLoop::current().ok_or(Error::NullPointer)?);
38        Ok(Self { ns_app, waker })
39    }
40
41    pub fn block_on<F: Future>(&self, future: F) -> F::Output {
42        winio_pollable::block_on(future, self.waker.clone(), || {
43            let (timeout, waker) = get_context();
44            CFRunLoop::run_in_mode(
45                unsafe { kCFRunLoopDefaultMode },
46                timeout.unwrap_or(Duration::MAX).as_secs_f64(),
47                true,
48            );
49            if let Some(waker) = waker {
50                waker.wake();
51            }
52            unsafe {
53                loop {
54                    let event = self.ns_app.nextEventMatchingMask_untilDate_inMode_dequeue(
55                        NSEventMask::Any,
56                        Some(&NSDate::distantPast()),
57                        NSDefaultRunLoopMode,
58                        true,
59                    );
60                    if let Some(event) = event {
61                        self.ns_app.sendEvent(&event);
62                    } else {
63                        break;
64                    }
65                }
66            }
67        })
68    }
69}
70
71fn run_loop_waker(run_loop: CFRetained<CFRunLoop>) -> Waker {
72    unsafe { Waker::from_raw(run_loop_raw_waker(run_loop)) }
73}
74
75fn run_loop_raw_waker(run_loop: CFRetained<CFRunLoop>) -> RawWaker {
76    let data = CFRetained::into_raw(run_loop);
77    RawWaker::new(
78        data.as_ptr().cast_const().cast(),
79        &RawWakerVTable::new(
80            run_loop_clone,
81            run_loop_wake,
82            run_loop_wake_by_ref,
83            run_loop_drop,
84        ),
85    )
86}
87
88unsafe fn run_loop_clone(data: *const ()) -> RawWaker {
89    let data = NonNull::new(data.cast_mut().cast()).expect("data pointer is null");
90    let run_loop = unsafe { CFRetained::<CFRunLoop>::retain(data) };
91    run_loop_raw_waker(run_loop)
92}
93
94unsafe fn run_loop_wake(data: *const ()) {
95    if let Some(data) = NonNull::new(data.cast_mut().cast()) {
96        let run_loop = unsafe { CFRetained::<CFRunLoop>::from_raw(data) };
97        run_loop.wake_up();
98    }
99}
100
101unsafe fn run_loop_wake_by_ref(data: *const ()) {
102    if let Some(data) = NonNull::new(data.cast_mut().cast()) {
103        let run_loop = unsafe { CFRetained::<CFRunLoop>::retain(data) };
104        run_loop.wake_up();
105    }
106}
107
108unsafe fn run_loop_drop(data: *const ()) {
109    if let Some(data) = NonNull::new(data.cast_mut().cast()) {
110        let _ = unsafe { CFRetained::<CFRunLoop>::from_raw(data) };
111    }
112}