diff --git a/Cargo.toml b/Cargo.toml index 1c22620..2070e1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,8 @@ libc = "0.2" libloading = "0.8" metal = "0.32" objc = "0.2" +objc2 = "0.6.3" +objc2-app-kit = { version = "0.3.2", default-features = false } objc2-io-surface = "0.3" plist = "1" proc-macro2 = "1" diff --git a/cef/Cargo.toml b/cef/Cargo.toml index 3259182..fb930c4 100644 --- a/cef/Cargo.toml +++ b/cef/Cargo.toml @@ -43,6 +43,8 @@ windows = { workspace = true, optional = true } [target.'cfg(target_os = "macos")'.dependencies] libloading.workspace = true +objc2.workspace = true +objc2-app-kit = { workspace = true, features = ["NSApplication", "NSResponder"] } objc2-io-surface = { workspace = true, optional = true } objc = { workspace = true, optional = true } metal = { workspace = true, optional = true } diff --git a/cef/src/application_mac.rs b/cef/src/application_mac.rs new file mode 100644 index 0000000..62f4ece --- /dev/null +++ b/cef/src/application_mac.rs @@ -0,0 +1,121 @@ +use std::cell::Cell; + +use objc2::{ + define_class, extern_protocol, msg_send, rc::Retained, runtime::*, ClassType, DefinedClass, + MainThreadMarker, +}; +use objc2_app_kit::{NSApp, NSApplication}; + +extern_protocol!( + /// The binding of `CrAppProtocol`. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait CrAppProtocol { + #[unsafe(method(isHandlingSendEvent))] + unsafe fn is_handling_send_event(&self) -> Bool; + } +); + +extern_protocol!( + /// The binding of `CrAppControlProtocol`. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait CrAppControlProtocol: CrAppProtocol { + #[unsafe(method(setHandlingSendEvent:))] + unsafe fn set_handling_send_event(&self, handling_send_event: Bool); + } +); + +extern_protocol!( + /// The binding of `CefAppProtocol`. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait CefAppProtocol: CrAppControlProtocol {} +); + +/// Instance variables of `SimpleApplication`. +pub struct SimpleApplicationIvars { + handling_send_event: Cell, +} + +define_class!( + /// A default `NSApplication` subclass that implements the required CEF protocols. + /// + /// This class provides the necessary `CefAppProtocol` conformance to + /// ensure that events are handled correctly by the Chromium framework on macOS. + /// + /// # Usage + /// + /// For most new applications built with cef-rs, this is the class you should use. + /// It must be activated by calling the `init` method at the very beginning + /// of your `main` function. + /// + /// ```no_run + /// // In your main function: + /// #[cfg(target_os = "macos")] + /// cef::application_mac::SimpleApplication::init(); + /// ``` + /// + /// # Custom Implementations + /// + /// You should not use this implementation if you are integrating cef-rs + /// into an existing macOS application that already has its own custom + /// `NSApplication` subclass. + /// + /// In that scenario, you should instead implement the [`CrAppProtocol`], + /// [`CrAppControlProtocol`], and [`CefAppProtocol`] traits on your existing + /// application class with objc2 crate. + #[unsafe(super(NSApplication))] + #[ivars = SimpleApplicationIvars] + pub struct SimpleApplication; + + unsafe impl CrAppControlProtocol for SimpleApplication { + #[unsafe(method(setHandlingSendEvent:))] + unsafe fn set_handling_send_event(&self, handling_send_event: Bool) { + self.ivars().handling_send_event.set(handling_send_event); + } + } + + unsafe impl CrAppProtocol for SimpleApplication { + #[unsafe(method(isHandlingSendEvent))] + unsafe fn is_handling_send_event(&self) -> Bool { + self.ivars().handling_send_event.get() + } + } + + unsafe impl CefAppProtocol for SimpleApplication {} +); + +impl SimpleApplication { + /// Initializes the global `NSApplication` instance with our custom `SimpleApplication`. + /// + /// This function must be called on the main thread before any other UI code + /// creates the shared application instance. It ensures that CEF's event + /// handling requirements are met. + /// + /// If other UI code has already created a shared application instance, + /// this will return an error. + /// + /// # Safety + /// + /// This function interacts with UI and should only be called once + /// at the beginning + pub fn init() -> Result, ()> { + let mtm = MainThreadMarker::new() + .expect("`SimpleApplication::init` must be called on the main thread"); + + unsafe { + // Initialize mac application instance. + // SAFETY: mtm ensure that here is the main thread. + let _: Retained = msg_send![SimpleApplication::class(), sharedApplication]; + } + + // If there was an invocation to NSApp prior to here, + // then the NSApp will not be a SimpleApplication. + // objc2's downcast ensure that this doesn't happen. + let app = NSApp(mtm); + + if let Ok(app) = app.downcast() { + Ok(app) + } else { + Err(()) + } + } +} diff --git a/cef/src/lib.rs b/cef/src/lib.rs index 7fa32ff..e9e5983 100644 --- a/cef/src/lib.rs +++ b/cef/src/lib.rs @@ -4,6 +4,9 @@ pub mod args; pub mod rc; pub mod string; +#[cfg(target_os = "macos")] +pub mod application_mac; + #[cfg(target_os = "macos")] pub mod library_loader; diff --git a/examples/cefsimple/src/main.rs b/examples/cefsimple/src/main.rs index a366282..9f1eb8b 100644 --- a/examples/cefsimple/src/main.rs +++ b/examples/cefsimple/src/main.rs @@ -114,6 +114,9 @@ fn main() { loader }; + #[cfg(target_os = "macos")] + cef::application_mac::SimpleApplication::init().unwrap(); + let _ = api_hash(sys::CEF_API_VERSION_LAST, 0); let args = Args::new();