winio-ui-app-kit 0.6.0

AppKit backend for winio.
Documentation
use std::{
    future::Future,
    ptr::NonNull,
    task::{RawWaker, RawWakerVTable, Waker},
    time::Duration,
};

use objc2::rc::Retained;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEventMask};
use objc2_core_foundation::{CFRetained, CFRunLoop, kCFRunLoopDefaultMode};
use objc2_foundation::{MainThreadMarker, NSDate, NSDefaultRunLoopMode};

#[cfg(feature = "compio-compat")]
use crate::get_context;
use crate::{Error, Result, catch};

#[cfg(not(feature = "compio-compat"))]
fn get_context() -> (Option<Duration>, Option<Waker>) {
    (None, None)
}

pub struct App {
    ns_app: Retained<NSApplication>,
    waker: Waker,
}

impl App {
    pub fn new() -> Result<Self> {
        let mtm = MainThreadMarker::new().ok_or(Error::NotMainThread)?;
        let ns_app = catch(|| {
            let ns_app = NSApplication::sharedApplication(mtm);
            ns_app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
            #[allow(deprecated)]
            ns_app.activateIgnoringOtherApps(true);
            ns_app
        })?;
        let waker = run_loop_waker(CFRunLoop::current().ok_or(Error::NullPointer)?);
        Ok(Self { ns_app, waker })
    }

    pub fn block_on<F: Future>(&self, future: F) -> F::Output {
        winio_pollable::block_on(future, self.waker.clone(), || {
            let (timeout, waker) = get_context();
            CFRunLoop::run_in_mode(
                unsafe { kCFRunLoopDefaultMode },
                timeout.unwrap_or(Duration::MAX).as_secs_f64(),
                true,
            );
            if let Some(waker) = waker {
                waker.wake();
            }
            unsafe {
                loop {
                    let event = self.ns_app.nextEventMatchingMask_untilDate_inMode_dequeue(
                        NSEventMask::Any,
                        Some(&NSDate::distantPast()),
                        NSDefaultRunLoopMode,
                        true,
                    );
                    if let Some(event) = event {
                        self.ns_app.sendEvent(&event);
                    } else {
                        break;
                    }
                }
            }
        })
    }
}

fn run_loop_waker(run_loop: CFRetained<CFRunLoop>) -> Waker {
    unsafe { Waker::from_raw(run_loop_raw_waker(run_loop)) }
}

fn run_loop_raw_waker(run_loop: CFRetained<CFRunLoop>) -> RawWaker {
    let data = CFRetained::into_raw(run_loop);
    RawWaker::new(
        data.as_ptr().cast_const().cast(),
        &RawWakerVTable::new(
            run_loop_clone,
            run_loop_wake,
            run_loop_wake_by_ref,
            run_loop_drop,
        ),
    )
}

unsafe fn run_loop_clone(data: *const ()) -> RawWaker {
    let data = NonNull::new(data.cast_mut().cast()).expect("data pointer is null");
    let run_loop = unsafe { CFRetained::<CFRunLoop>::retain(data) };
    run_loop_raw_waker(run_loop)
}

unsafe fn run_loop_wake(data: *const ()) {
    if let Some(data) = NonNull::new(data.cast_mut().cast()) {
        let run_loop = unsafe { CFRetained::<CFRunLoop>::from_raw(data) };
        run_loop.wake_up();
    }
}

unsafe fn run_loop_wake_by_ref(data: *const ()) {
    if let Some(data) = NonNull::new(data.cast_mut().cast()) {
        let run_loop = unsafe { CFRetained::<CFRunLoop>::retain(data) };
        run_loop.wake_up();
    }
}

unsafe fn run_loop_drop(data: *const ()) {
    if let Some(data) = NonNull::new(data.cast_mut().cast()) {
        let _ = unsafe { CFRetained::<CFRunLoop>::from_raw(data) };
    }
}