-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Description
Hi! I've been embedding winit
in Linux and Windows without much trouble.
But in macOS I'm having some issues.
I was previously able to make it work using the old run_return
, which is no longer available in the latest versions.
When trying to replace it with pump_events
, I started getting errors that I didn’t face before — for example:
tried to get a delegate that was not the one Winit has registered
Context
I'm trying to embed winit
inside a Cocoa application.
To do this, I load a dynamic library that initializes winit
and manually call pump_winit_once
on a separate thread.
About NSApp
ownership
I’m aware of the requirement that winit
must be the one to initialize the NSApp
, and not the host Cocoa app.
That’s why I deliberately initialize winit
before creating the Cocoa app and its delegate.
This pattern allowed me to avoid the typical panic:
winit must be the one to initialize NSApp
Here’s how I set it up:
fn main() {
println!("🎯 Starting Cocoa app with winit integration...");
// Initialize winit BEFORE creating the Cocoa App
println!("🔧 Initializing winit BEFORE Cocoa...");
let lib = unsafe {
Library::new("../target/debug/libwinit_embed.dylib")
.expect("Failed to load winit_embed library")
};
let init_winit: Symbol<unsafe extern "C" fn() -> bool> = unsafe {
lib.get(b"init_winit").expect("Failed to get init_winit function")
};
let init_result = unsafe { init_winit() };
if init_result {
println!("✅ Winit initialized successfully BEFORE Cocoa");
} else {
println!("❌ Failed to initialize winit");
return;
}
// NOW create the Cocoa app
let window = Window::new(Default::default());
let app_delegate = CocoaAppDelegate { window };
App::new("com.example.cacaotest", app_delegate).run();
}
🔁 Pumping the event loop manually
let lib_clone = lib;
std::thread::spawn(move || {
let pump_winit_once: Symbol<PumpWinitOnceFn> = unsafe {
lib_clone.get(b"pump_winit_once").expect("Failed to get pump_winit_once function")
};
loop {
let exit = unsafe { pump_winit_once() };
if exit != 0 {
break;
}
sleep(Duration::from_millis(16));
}
});
Internal state management with thread_local!
thread_local! {
static EVENT_LOOP: RefCell<Option<EventLoop<()>>> = RefCell::new(None);
static APP: RefCell<PumpDemo> = RefCell::new(PumpDemo::default());
}
pub unsafe extern "C" fn pump_winit_once() -> i32 {
let mut result = 0;
EVENT_LOOP.with(|event_loop_cell| {
APP.with(|app_cell| {
let mut event_loop_opt = event_loop_cell.borrow_mut();
let mut app = app_cell.borrow_mut();
let Some(ref mut event_loop) = *event_loop_opt else {
result = 1;
return;
};
let status = event_loop.pump_app_events(Some(Duration::ZERO), &mut *app);
if let PumpStatus::Exit(code) = status {
*event_loop_opt = None;
result = code as i32;
}
});
});
result
}
Related issues
This is related to #4015 in which the solution is to use the master version of winit at that time (I think its fc6cf89)
Final thoughts
This approach was working fine with run_return
, but with pump_events
I started facing issues related to the internal delegate tracking logic. I assume some state is being checked that may not play well with dynamic library usage or Cocoa-managed event loops.
Any help or insight would be appreciated! Thanks for maintaining such an awesome crate. 🙌
macOS version
ProductName: macOS
ProductVersion: 15.5
BuildVersion: 24F74
Winit version
0.30.12