examples/tutorials: Use cocoa crate to initialize the shared NSApplication instance

This is required for OpenGL to work nowadays on macOS. Simply running an
CFRunLoop on the main thread is not sufficient.

Thanks to Philippe Normand for testing this on macOS and making sure it
actually compiles and works.
This commit is contained in:
Sebastian Dröge 2021-07-12 13:13:34 +03:00
parent 3b03ab0660
commit 061f85d410
4 changed files with 74 additions and 92 deletions

View file

@ -36,6 +36,9 @@ glutin = { version = "0.27", optional = true }
once_cell = "1.0"
image = { version="0.23", optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.24"
[build-dependencies]
gl_generator = { version = "0.14", optional = true }

View file

@ -1,41 +1,6 @@
/// macOS has a specific requirement that there must be a run loop running
/// on the main thread in order to open windows and use OpenGL.
#[cfg(target_os = "macos")]
mod runloop {
use std::os::raw::c_void;
pub struct CFRunLoop(*mut c_void);
#[link(name = "foundation", kind = "framework")]
extern "C" {
fn CFRunLoopRun();
fn CFRunLoopGetMain() -> *mut c_void;
fn CFRunLoopStop(l: *mut c_void);
}
impl CFRunLoop {
pub fn run() {
unsafe {
CFRunLoopRun();
}
}
#[doc(alias = "get_main")]
pub fn main() -> CFRunLoop {
unsafe {
let r = CFRunLoopGetMain();
assert!(!r.is_null());
CFRunLoop(r)
}
}
pub fn stop(&self) {
unsafe { CFRunLoopStop(self.0) }
}
}
unsafe impl Send for CFRunLoop {}
}
/// macOS has a specific requirement that there must be a run loop running on the main thread in
/// order to open windows and use OpenGL, and that the global NSApplication instance must be
/// initialized.
/// On macOS this launches the callback function on a thread.
/// On other platforms it's just executed immediately.
@ -52,16 +17,38 @@ pub fn run<T, F: FnOnce() -> T + Send + 'static>(main: F) -> T
where
T: Send + 'static,
{
use cocoa::appkit::NSApplication;
use std::thread;
let l = runloop::CFRunLoop::main();
let t = thread::spawn(move || {
let res = main();
l.stop();
res
});
unsafe {
let app = cocoa::appkit::NSApp();
let t = thread::spawn(|| {
let res = main();
runloop::CFRunLoop::run();
let app = cocoa::appkit::NSApp();
app.stop_(cocoa::base::nil);
t.join().unwrap()
// Stopping the event loop requires an actual event
let event = cocoa::appkit::NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
cocoa::base::nil,
cocoa::appkit::NSEventType::NSApplicationDefined,
cocoa::foundation::NSPoint { x: 0.0, y: 0.0 },
cocoa::appkit::NSEventModifierFlags::empty(),
0.0,
0,
cocoa::base::nil,
cocoa::appkit::NSEventSubtype::NSApplicationActivatedEventType,
0,
0,
);
app.postEvent_atStart_(event, cocoa::base::YES);
res
});
app.run();
t.join().unwrap()
}
}

View file

@ -18,6 +18,9 @@ byte-slice-cast = "1"
anyhow = "1"
termion = { version = "1.5", optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.24"
[features]
tutorial5 = ["gtk", "gdk", "gst-video"]
tutorial5-x11 = ["tutorial5"]

View file

@ -1,41 +1,6 @@
/// macOS has a specific requirement that there must be a run loop running
/// on the main thread in order to open windows and use OpenGL.
#[cfg(target_os = "macos")]
mod runloop {
use std::os::raw::c_void;
pub struct CFRunLoop(*mut c_void);
#[link(name = "foundation", kind = "framework")]
extern "C" {
fn CFRunLoopRun();
fn CFRunLoopGetMain() -> *mut c_void;
fn CFRunLoopStop(l: *mut c_void);
}
impl CFRunLoop {
pub fn run() {
unsafe {
CFRunLoopRun();
}
}
#[doc(alias = "get_main")]
pub fn main() -> CFRunLoop {
unsafe {
let r = CFRunLoopGetMain();
assert!(!r.is_null());
CFRunLoop(r)
}
}
pub fn stop(&self) {
unsafe { CFRunLoopStop(self.0) }
}
}
unsafe impl Send for CFRunLoop {}
}
/// macOS has a specific requirement that there must be a run loop running on the main thread in
/// order to open windows and use OpenGL, and that the global NSApplication instance must be
/// initialized.
/// On macOS this launches the callback function on a thread.
/// On other platforms it's just executed immediately.
@ -52,16 +17,40 @@ pub fn run<T, F: FnOnce() -> T + Send + 'static>(main: F) -> T
where
T: Send + 'static,
{
use cocoa::appkit::NSApplication;
use std::thread;
let l = runloop::CFRunLoop::main();
let t = thread::spawn(move || {
let res = main();
l.stop();
res
});
unsafe {
let app = cocoa::appkit::NSApp();
let t = thread::spawn(|| {
let res = main();
runloop::CFRunLoop::run();
let app = cocoa::appkit::NSApp();
app.stop_(cocoa::base::nil);
t.join().unwrap()
// Stopping the event loop requires an actual event
let event = cocoa::appkit::NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
cocoa::base::nil,
cocoa::appkit::NSEventType::NSApplicationDefined,
cocoa::foundation::NSPoint { x: 0.0, y: 0.0 },
cocoa::appkit::NSEventModifierFlags::empty(),
0.0,
0,
cocoa::base::nil,
cocoa::appkit::NSEventSubtype::NSApplicationActivatedEventType,
0,
0,
);
app.postEvent_atStart_(event, cocoa::base::YES);
std::process::exit(0);
res
});
app.run();
t.join().unwrap()
}
}