winio-ui-app-kit 0.6.0

AppKit backend for winio.
Documentation
use std::{
    cell::RefCell,
    ffi::c_void,
    io,
    ops::Deref,
    os::fd::AsRawFd,
    pin::Pin,
    task::{Context, Poll, Waker},
    time::Duration,
};

use compio::{compat::Adapter, runtime::Runtime};
use objc2_core_foundation::{
    CFFileDescriptor, CFRetained, CFRunLoop, CFRunLoopSource, kCFAllocatorDefault,
    kCFFileDescriptorReadCallBack, kCFRunLoopDefaultMode,
};

pub struct CompioAdapter {
    runtime: Runtime,
    fd_source: CFRetained<CFFileDescriptor>,
    source: CFRetained<CFRunLoopSource>,
    run_loop: CFRetained<CFRunLoop>,
}

impl Deref for CompioAdapter {
    type Target = Runtime;

    fn deref(&self) -> &Self::Target {
        &self.runtime
    }
}

impl Adapter for CompioAdapter {
    fn new(runtime: Runtime) -> io::Result<Self> {
        unsafe extern "C-unwind" fn fd_callback(
            _fdref: *mut CFFileDescriptor,
            _callback_types: usize,
            _info: *mut c_void,
        ) {
        }

        let fd_source = unsafe {
            CFFileDescriptor::new(
                kCFAllocatorDefault,
                runtime.as_raw_fd(),
                false,
                Some(fd_callback),
                std::ptr::null(),
            )
        }
        .ok_or(io::ErrorKind::InvalidData)?;
        let source = unsafe {
            CFFileDescriptor::new_run_loop_source(kCFAllocatorDefault, Some(&fd_source), 0)
        }
        .ok_or(io::ErrorKind::InvalidData)?;

        let run_loop = CFRunLoop::current().ok_or(io::ErrorKind::InvalidData)?;
        run_loop.add_source(Some(&source), unsafe { kCFRunLoopDefaultMode });

        Ok(Self {
            runtime,
            fd_source,
            source,
            run_loop,
        })
    }

    async fn wait(&self, timeout: Option<Duration>) -> io::Result<()> {
        self.fd_source
            .enable_call_backs(kCFFileDescriptorReadCallBack);

        WaitFuture::new(timeout).await
    }

    fn clear(&self) -> io::Result<()> {
        Ok(())
    }
}

impl Drop for CompioAdapter {
    fn drop(&mut self) {
        self.fd_source
            .disable_call_backs(kCFFileDescriptorReadCallBack);
        self.run_loop
            .remove_source(Some(&self.source), unsafe { kCFRunLoopDefaultMode });
    }
}

thread_local! {
    static CONTEXT: RefCell<Option<(Option<Duration>, Waker)>> = const { RefCell::new(None) };
}

pub(crate) fn get_context() -> (Option<Duration>, Option<Waker>) {
    CONTEXT.with_borrow(|ctx| {
        if let Some((timeout, waker)) = ctx.as_ref() {
            (*timeout, Some(waker.clone()))
        } else {
            (None, None)
        }
    })
}

fn set_context(timeout: Option<Duration>, waker: Waker) {
    CONTEXT.with_borrow_mut(|ctx| ctx.replace((timeout, waker)));
}

fn reset_context() {
    CONTEXT.with_borrow_mut(|ctx| ctx.take());
}

struct WaitFuture {
    timeout: Option<Duration>,
    polled: bool,
}

impl WaitFuture {
    fn new(timeout: Option<Duration>) -> Self {
        Self {
            timeout,
            polled: false,
        }
    }
}

impl Future for WaitFuture {
    type Output = io::Result<()>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.polled {
            Poll::Ready(Ok(()))
        } else {
            set_context(self.timeout, cx.waker().clone());
            self.polled = true;
            Poll::Pending
        }
    }
}

impl Drop for WaitFuture {
    fn drop(&mut self) {
        reset_context();
    }
}