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 committed by Sebastian Dröge
parent 386bd05817
commit a091ea201c
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" once_cell = "1.0"
image = { version="0.23", optional = true } image = { version="0.23", optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.24"
[build-dependencies] [build-dependencies]
gl_generator = { version = "0.14", optional = true } 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 /// macOS has a specific requirement that there must be a run loop running on the main thread in
/// on the main thread in order to open windows and use OpenGL. /// order to open windows and use OpenGL, and that the global NSApplication instance must be
/// initialized.
#[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 {}
}
/// On macOS this launches the callback function on a thread. /// On macOS this launches the callback function on a thread.
/// On other platforms it's just executed immediately. /// 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 where
T: Send + 'static, T: Send + 'static,
{ {
use cocoa::appkit::NSApplication;
use std::thread; use std::thread;
let l = runloop::CFRunLoop::main(); unsafe {
let t = thread::spawn(move || { let app = cocoa::appkit::NSApp();
let res = main(); let t = thread::spawn(|| {
l.stop(); let res = main();
res
});
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" anyhow = "1"
termion = { version = "1.5", optional = true } termion = { version = "1.5", optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.24"
[features] [features]
tutorial5 = ["gtk", "gdk", "gst-video"] tutorial5 = ["gtk", "gdk", "gst-video"]
tutorial5-x11 = ["tutorial5"] tutorial5-x11 = ["tutorial5"]

View file

@ -1,41 +1,6 @@
/// macOS has a specific requirement that there must be a run loop running /// macOS has a specific requirement that there must be a run loop running on the main thread in
/// on the main thread in order to open windows and use OpenGL. /// order to open windows and use OpenGL, and that the global NSApplication instance must be
/// initialized.
#[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 {}
}
/// On macOS this launches the callback function on a thread. /// On macOS this launches the callback function on a thread.
/// On other platforms it's just executed immediately. /// 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 where
T: Send + 'static, T: Send + 'static,
{ {
use cocoa::appkit::NSApplication;
use std::thread; use std::thread;
let l = runloop::CFRunLoop::main(); unsafe {
let t = thread::spawn(move || { let app = cocoa::appkit::NSApp();
let res = main(); let t = thread::spawn(|| {
l.stop(); let res = main();
res
});
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()
}
} }