2025-03-01 20:18:28 +01:00
|
|
|
use std::os::raw::c_void;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
use objc2::MainThreadMarker;
|
|
|
|
|
use objc2_core_foundation::{
|
2025-04-20 07:36:49 +02:00
|
|
|
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopSource, CFRunLoopSourceContext,
|
2025-03-01 20:18:28 +01:00
|
|
|
};
|
2025-05-17 04:26:09 +02:00
|
|
|
use winit_core::event_loop::EventLoopProxyProvider;
|
2025-03-01 20:18:28 +01:00
|
|
|
|
|
|
|
|
/// A waker that signals a `CFRunLoopSource` on the main thread.
|
|
|
|
|
///
|
|
|
|
|
/// We use this to integrate with the system as cleanly as possible (instead of e.g. keeping an
|
|
|
|
|
/// atomic around that we check on each iteration of the event loop).
|
|
|
|
|
///
|
|
|
|
|
/// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
|
pub(crate) struct EventLoopProxy {
|
|
|
|
|
source: CFRetained<CFRunLoopSource>,
|
|
|
|
|
/// Cached value of `CFRunLoopGetMain`.
|
|
|
|
|
main_loop: CFRetained<CFRunLoop>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME(madsmtm): Mark `CFRunLoopSource` + `CFRunLoop` as `Send` + `Sync`.
|
|
|
|
|
unsafe impl Send for EventLoopProxy {}
|
|
|
|
|
unsafe impl Sync for EventLoopProxy {}
|
|
|
|
|
|
|
|
|
|
impl EventLoopProxy {
|
|
|
|
|
/// Create a new proxy, registering it to be performed on the main thread.
|
|
|
|
|
///
|
|
|
|
|
/// The provided closure should call `proxy_wake_up` on the application.
|
|
|
|
|
pub(crate) fn new<F: Fn() + 'static>(mtm: MainThreadMarker, signaller: F) -> Self {
|
|
|
|
|
// We use an `Arc` here to make sure that the reference-counting of the signal container is
|
|
|
|
|
// atomic (`Retained`/`CFRetained` would be valid alternatives too).
|
|
|
|
|
let signaller = Arc::new(signaller);
|
|
|
|
|
|
|
|
|
|
unsafe extern "C-unwind" fn retain<F>(info: *const c_void) -> *const c_void {
|
|
|
|
|
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
|
|
|
|
unsafe { Arc::increment_strong_count(info.cast::<F>()) };
|
|
|
|
|
info
|
|
|
|
|
}
|
|
|
|
|
unsafe extern "C-unwind" fn release<F>(info: *const c_void) {
|
|
|
|
|
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
|
|
|
|
unsafe { Arc::decrement_strong_count(info.cast::<F>()) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pointer equality / hashing.
|
|
|
|
|
extern "C-unwind" fn equal(info1: *const c_void, info2: *const c_void) -> u8 {
|
|
|
|
|
(info1 == info2) as u8
|
|
|
|
|
}
|
|
|
|
|
extern "C-unwind" fn hash(info: *const c_void) -> usize {
|
|
|
|
|
info as usize
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Call the provided closure.
|
|
|
|
|
unsafe extern "C-unwind" fn perform<F: Fn()>(info: *mut c_void) {
|
|
|
|
|
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
|
|
|
|
let signaller = unsafe { &*info.cast::<F>() };
|
|
|
|
|
(signaller)();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fire last.
|
|
|
|
|
let order = CFIndex::MAX - 1;
|
|
|
|
|
|
|
|
|
|
// This is marked `mut` to match the signature of `CFRunLoopSourceCreate`, but the
|
|
|
|
|
// information is copied, and not actually mutated.
|
|
|
|
|
let mut context = CFRunLoopSourceContext {
|
|
|
|
|
version: 0,
|
|
|
|
|
// This is retained on creation.
|
|
|
|
|
info: Arc::as_ptr(&signaller) as *mut c_void,
|
|
|
|
|
retain: Some(retain::<F>),
|
|
|
|
|
release: Some(release::<F>),
|
|
|
|
|
copyDescription: None,
|
|
|
|
|
equal: Some(equal),
|
|
|
|
|
hash: Some(hash),
|
|
|
|
|
schedule: None,
|
|
|
|
|
cancel: None,
|
|
|
|
|
perform: Some(perform::<F>),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// SAFETY: The normal callbacks are thread-safe (`retain`/`release` use atomics, and
|
|
|
|
|
// `equal`/`hash` only access a pointer).
|
|
|
|
|
//
|
|
|
|
|
// Note that the `perform` callback isn't thread-safe (we don't have `F: Send + Sync`), but
|
|
|
|
|
// that's okay, since we are on the main thread, and the source is only added to the main
|
|
|
|
|
// run loop (below), and hence only performed there.
|
|
|
|
|
//
|
|
|
|
|
// Keeping the closure alive beyond this scope is fine, because `F: 'static`.
|
|
|
|
|
let source = unsafe {
|
|
|
|
|
let _ = mtm;
|
2025-04-20 07:36:49 +02:00
|
|
|
CFRunLoopSource::new(None, order, &mut context).unwrap()
|
2025-03-01 20:18:28 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Register the source to be performed on the main thread.
|
2025-04-20 07:36:49 +02:00
|
|
|
let main_loop = CFRunLoop::main().unwrap();
|
|
|
|
|
unsafe { main_loop.add_source(Some(&source), kCFRunLoopCommonModes) };
|
2025-03-01 20:18:28 +01:00
|
|
|
|
|
|
|
|
Self { source, main_loop }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME(madsmtm): Use this on macOS too.
|
|
|
|
|
// More difficult there, since the user can re-start the event loop.
|
|
|
|
|
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
|
|
|
|
pub(crate) fn invalidate(&self) {
|
|
|
|
|
// NOTE: We do NOT fire this on `Drop`, since we want the proxy to be cloneable, such that
|
|
|
|
|
// we only need to register a single source even if there's multiple proxies in use.
|
2025-04-20 07:36:49 +02:00
|
|
|
self.source.invalidate();
|
2025-03-01 20:18:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EventLoopProxyProvider for EventLoopProxy {
|
|
|
|
|
fn wake_up(&self) {
|
|
|
|
|
// Signal the source, which ends up later invoking `perform` on the main thread.
|
|
|
|
|
//
|
|
|
|
|
// Multiple signals in quick succession are automatically coalesced into a single signal.
|
2025-04-20 07:36:49 +02:00
|
|
|
self.source.signal();
|
2025-03-01 20:18:28 +01:00
|
|
|
|
|
|
|
|
// Let the main thread know there's a new event.
|
|
|
|
|
//
|
|
|
|
|
// This is required since we may be (probably are) running on a different thread, and the
|
|
|
|
|
// main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
|
2025-04-20 07:36:49 +02:00
|
|
|
self.main_loop.wake_up();
|
2025-03-01 20:18:28 +01:00
|
|
|
}
|
|
|
|
|
}
|