Skip to main content

winio_ui_app_kit/
compat.rs

1use std::{
2    cell::RefCell,
3    ffi::c_void,
4    io,
5    ops::Deref,
6    os::fd::AsRawFd,
7    pin::Pin,
8    task::{Context, Poll, Waker},
9    time::Duration,
10};
11
12use compio::{compat::Adapter, runtime::Runtime};
13use objc2_core_foundation::{
14    CFFileDescriptor, CFRetained, CFRunLoop, CFRunLoopSource, kCFAllocatorDefault,
15    kCFFileDescriptorReadCallBack, kCFRunLoopDefaultMode,
16};
17
18pub struct CompioAdapter {
19    runtime: Runtime,
20    fd_source: CFRetained<CFFileDescriptor>,
21    source: CFRetained<CFRunLoopSource>,
22    run_loop: CFRetained<CFRunLoop>,
23}
24
25impl Deref for CompioAdapter {
26    type Target = Runtime;
27
28    fn deref(&self) -> &Self::Target {
29        &self.runtime
30    }
31}
32
33impl Adapter for CompioAdapter {
34    fn new(runtime: Runtime) -> io::Result<Self> {
35        unsafe extern "C-unwind" fn fd_callback(
36            _fdref: *mut CFFileDescriptor,
37            _callback_types: usize,
38            _info: *mut c_void,
39        ) {
40        }
41
42        let fd_source = unsafe {
43            CFFileDescriptor::new(
44                kCFAllocatorDefault,
45                runtime.as_raw_fd(),
46                false,
47                Some(fd_callback),
48                std::ptr::null(),
49            )
50        }
51        .ok_or(io::ErrorKind::InvalidData)?;
52        let source = unsafe {
53            CFFileDescriptor::new_run_loop_source(kCFAllocatorDefault, Some(&fd_source), 0)
54        }
55        .ok_or(io::ErrorKind::InvalidData)?;
56
57        let run_loop = CFRunLoop::current().ok_or(io::ErrorKind::InvalidData)?;
58        run_loop.add_source(Some(&source), unsafe { kCFRunLoopDefaultMode });
59
60        Ok(Self {
61            runtime,
62            fd_source,
63            source,
64            run_loop,
65        })
66    }
67
68    async fn wait(&self, timeout: Option<Duration>) -> io::Result<()> {
69        self.fd_source
70            .enable_call_backs(kCFFileDescriptorReadCallBack);
71
72        WaitFuture::new(timeout).await
73    }
74
75    fn clear(&self) -> io::Result<()> {
76        Ok(())
77    }
78}
79
80impl Drop for CompioAdapter {
81    fn drop(&mut self) {
82        self.fd_source
83            .disable_call_backs(kCFFileDescriptorReadCallBack);
84        self.run_loop
85            .remove_source(Some(&self.source), unsafe { kCFRunLoopDefaultMode });
86    }
87}
88
89thread_local! {
90    static CONTEXT: RefCell<Option<(Option<Duration>, Waker)>> = const { RefCell::new(None) };
91}
92
93pub(crate) fn get_context() -> (Option<Duration>, Option<Waker>) {
94    CONTEXT.with_borrow(|ctx| {
95        if let Some((timeout, waker)) = ctx.as_ref() {
96            (*timeout, Some(waker.clone()))
97        } else {
98            (None, None)
99        }
100    })
101}
102
103fn set_context(timeout: Option<Duration>, waker: Waker) {
104    CONTEXT.with_borrow_mut(|ctx| ctx.replace((timeout, waker)));
105}
106
107fn reset_context() {
108    CONTEXT.with_borrow_mut(|ctx| ctx.take());
109}
110
111struct WaitFuture {
112    timeout: Option<Duration>,
113    polled: bool,
114}
115
116impl WaitFuture {
117    fn new(timeout: Option<Duration>) -> Self {
118        Self {
119            timeout,
120            polled: false,
121        }
122    }
123}
124
125impl Future for WaitFuture {
126    type Output = io::Result<()>;
127
128    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
129        if self.polled {
130            Poll::Ready(Ok(()))
131        } else {
132            set_context(self.timeout, cx.waker().clone());
133            self.polled = true;
134            Poll::Pending
135        }
136    }
137}
138
139impl Drop for WaitFuture {
140    fn drop(&mut self) {
141        reset_context();
142    }
143}