From 446bb7ec3ea05e85ff5ffd104ccc8898a00d3d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Brzezi=C5=84ski?= Date: Mon, 29 Jan 2024 14:12:40 +0100 Subject: [PATCH] examples/tutorials: fix macOS run() wrapper terminating too early Using terminate() kills the whole process instead of just stopping the event loop, so we're back to the 'old' way. However, if the provided function finishes too early, that can also fail (will call stop() on a not-yet-running NSApp). Creating a delegate and waiting for the callback makes sure NSApp is running before the actual main() is called. Also, for whatever reason only tutorials were changed to use terminate(). Now both tutorials and examples are using identical code. Part-of: --- Cargo.lock | 1 + examples/Cargo.toml | 1 + examples/src/examples-common.rs | 40 +++++++++++++++++++-- tutorials/src/tutorials-common.rs | 58 ++++++++++++++++++++++++++++--- 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e4db226b..97fcc5e0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,7 @@ dependencies = [ "image", "memfd", "memmap2", + "objc", "pango", "pangocairo", "raw-window-handle", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 960d9a088..94c772e95 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -52,6 +52,7 @@ windows = { version = "0.52", features=["Win32_Graphics_Direct3D11", [target.'cfg(target_os = "macos")'.dependencies] cocoa = "0.25" +objc = "0.2.7" [build-dependencies] gl_generator = { version = "0.14", optional = true } diff --git a/examples/src/examples-common.rs b/examples/src/examples-common.rs index 6d0e801ae..cf307b405 100644 --- a/examples/src/examples-common.rs +++ b/examples/src/examples-common.rs @@ -17,13 +17,47 @@ pub fn run T + Send + 'static>(main: F) -> T where T: Send + 'static, { - use std::thread; + use std::{ + ffi::c_void, + sync::mpsc::{channel, Sender}, + thread, + }; - use cocoa::appkit::NSApplication; + use cocoa::{ + appkit::{NSApplication, NSWindow}, + base::id, + delegate, + }; + use objc::{ + class, msg_send, + runtime::{Object, Sel}, + sel, sel_impl, + }; unsafe { let app = cocoa::appkit::NSApp(); - let t = thread::spawn(|| { + let (send, recv) = channel::<()>(); + + extern "C" fn on_finish_launching(this: &Object, _cmd: Sel, _notification: id) { + let send = unsafe { + let send_pointer = *this.get_ivar::<*const c_void>("send"); + let boxed = Box::from_raw(send_pointer as *mut Sender<()>); + *boxed + }; + send.send(()).unwrap(); + } + + let delegate = delegate!("AppDelegate", { + app: id = app, + send: *const c_void = Box::into_raw(Box::new(send)) as *const c_void, + (applicationDidFinishLaunching:) => on_finish_launching as extern fn(&Object, Sel, id) + }); + app.setDelegate_(delegate); + + let t = thread::spawn(move || { + // Wait for the NSApp to launch to avoid possibly calling stop_() too early + recv.recv().unwrap(); + let res = main(); let app = cocoa::appkit::NSApp(); diff --git a/tutorials/src/tutorials-common.rs b/tutorials/src/tutorials-common.rs index cac316fff..cf307b405 100644 --- a/tutorials/src/tutorials-common.rs +++ b/tutorials/src/tutorials-common.rs @@ -17,18 +17,66 @@ pub fn run T + Send + 'static>(main: F) -> T where T: Send + 'static, { - use std::thread; + use std::{ + ffi::c_void, + sync::mpsc::{channel, Sender}, + thread, + }; - use cocoa::appkit::NSApplication; - use objc::{msg_send, sel, sel_impl}; + use cocoa::{ + appkit::{NSApplication, NSWindow}, + base::id, + delegate, + }; + use objc::{ + class, msg_send, + runtime::{Object, Sel}, + sel, sel_impl, + }; unsafe { let app = cocoa::appkit::NSApp(); - let t = thread::spawn(|| { + let (send, recv) = channel::<()>(); + + extern "C" fn on_finish_launching(this: &Object, _cmd: Sel, _notification: id) { + let send = unsafe { + let send_pointer = *this.get_ivar::<*const c_void>("send"); + let boxed = Box::from_raw(send_pointer as *mut Sender<()>); + *boxed + }; + send.send(()).unwrap(); + } + + let delegate = delegate!("AppDelegate", { + app: id = app, + send: *const c_void = Box::into_raw(Box::new(send)) as *const c_void, + (applicationDidFinishLaunching:) => on_finish_launching as extern fn(&Object, Sel, id) + }); + app.setDelegate_(delegate); + + let t = thread::spawn(move || { + // Wait for the NSApp to launch to avoid possibly calling stop_() too early + recv.recv().unwrap(); + let res = main(); let app = cocoa::appkit::NSApp(); - let _: () = msg_send![app, terminate: cocoa::base::nil]; + app.stop_(cocoa::base::nil); + + // 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 });