From 65f4580be616811cc3409761dfb845cb19928c6b Mon Sep 17 00:00:00 2001 From: Takagi Tasuku Date: Mon, 13 Oct 2025 12:56:02 +0900 Subject: [PATCH 1/4] feat: add CefAppProtocol bindings Adds Rust bindings for the Objective-C protocols required for proper application handling on macOS (CrAppProtocol, CrAppControlProtocol, and CefAppProtocol) using the objc2 crate. These bindings are necessary to correctly implement an NSApplication subclass, which is a foundational step for porting the official cefsimple application logic and fixing related crashes. Refs: #96 --- Cargo.toml | 1 + cef/Cargo.toml | 1 + cef/src/application_mac.rs | 24 ++++++++++++++++++++++++ cef/src/lib.rs | 3 +++ 4 files changed, 29 insertions(+) create mode 100644 cef/src/application_mac.rs diff --git a/Cargo.toml b/Cargo.toml index e19da72..a15bcc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ libc = "0.2" libloading = "0.8" metal = "0.32" objc = "0.2" +objc2 = "0.6.3" objc2-io-surface = "0.3" plist = "1" proc-macro2 = "1" diff --git a/cef/Cargo.toml b/cef/Cargo.toml index 3259182..78f59eb 100644 --- a/cef/Cargo.toml +++ b/cef/Cargo.toml @@ -43,6 +43,7 @@ windows = { workspace = true, optional = true } [target.'cfg(target_os = "macos")'.dependencies] libloading.workspace = true +objc2.workspace = true 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..b2b6ad4 --- /dev/null +++ b/cef/src/application_mac.rs @@ -0,0 +1,24 @@ +//! This module includes the bindings of `include/cef_application_mac.h`. + +use objc2::{extern_protocol, runtime::Bool}; + +extern_protocol!( + #[allow(clippy::missing_safety_doc)] + pub unsafe trait CrAppProtocol { + #[unsafe(method(isHandlingSendEvent))] + unsafe fn is_handling_send_event(&self) -> Bool; + } +); + +extern_protocol!( + #[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!( + #[allow(clippy::missing_safety_doc)] + pub unsafe trait CefAppProtocol: CrAppControlProtocol {} +); 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; From 7b9b3cd8284b6b18b1821773b5d9975a6ea05028 Mon Sep 17 00:00:00 2001 From: Takagi Tasuku Date: Mon, 13 Oct 2025 13:51:51 +0900 Subject: [PATCH 2/4] feat: port SimpleApplication from original cefsimple Adds SimpleApplication implementing CefAppProtocol. Refs: #96 --- Cargo.toml | 1 + cef/Cargo.toml | 1 + cef/src/application_mac.rs | 101 ++++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a15bcc1..2f917f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ libloading = "0.8" metal = "0.32" objc = "0.2" objc2 = "0.6.3" +objc2-app-kit = "0.3.2" objc2-io-surface = "0.3" plist = "1" proc-macro2 = "1" diff --git a/cef/Cargo.toml b/cef/Cargo.toml index 78f59eb..ebe43c5 100644 --- a/cef/Cargo.toml +++ b/cef/Cargo.toml @@ -44,6 +44,7 @@ windows = { workspace = true, optional = true } [target.'cfg(target_os = "macos")'.dependencies] libloading.workspace = true objc2.workspace = true +objc2-app-kit = { workspace = true, default-features = false, 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 index b2b6ad4..62f4ece 100644 --- a/cef/src/application_mac.rs +++ b/cef/src/application_mac.rs @@ -1,8 +1,13 @@ -//! This module includes the bindings of `include/cef_application_mac.h`. +use std::cell::Cell; -use objc2::{extern_protocol, runtime::Bool}; +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))] @@ -11,6 +16,7 @@ extern_protocol!( ); extern_protocol!( + /// The binding of `CrAppControlProtocol`. #[allow(clippy::missing_safety_doc)] pub unsafe trait CrAppControlProtocol: CrAppProtocol { #[unsafe(method(setHandlingSendEvent:))] @@ -19,6 +25,97 @@ extern_protocol!( ); 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(()) + } + } +} From 37919492ce66c4a49aa80f1323c946727b61995e Mon Sep 17 00:00:00 2001 From: Takagi Tasuku Date: Mon, 13 Oct 2025 14:06:43 +0900 Subject: [PATCH 3/4] fix: resolve cargo build warning about default-features on macOS --- Cargo.toml | 2 +- cef/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f917f0..2b113ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ libloading = "0.8" metal = "0.32" objc = "0.2" objc2 = "0.6.3" -objc2-app-kit = "0.3.2" +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 ebe43c5..fb930c4 100644 --- a/cef/Cargo.toml +++ b/cef/Cargo.toml @@ -44,7 +44,7 @@ windows = { workspace = true, optional = true } [target.'cfg(target_os = "macos")'.dependencies] libloading.workspace = true objc2.workspace = true -objc2-app-kit = { workspace = true, default-features = false, features = ["NSApplication", "NSResponder"] } +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 } From 5d2f45910f7fddf9704f1ecfdb2f4a0a2a348691 Mon Sep 17 00:00:00 2001 From: Takagi Tasuku Date: Mon, 13 Oct 2025 14:09:56 +0900 Subject: [PATCH 4/4] feat: use SimpleApplication in cefsimple Updates the cefsimple example on macOS to use the new `cef::application_mac::SimpleApplication`. This properly initializes the application instance, resolving the crash that previously occurred during right-click events. Refs: #96 --- examples/cefsimple/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/cefsimple/src/main.rs b/examples/cefsimple/src/main.rs index 1820c2e..2aa513a 100644 --- a/examples/cefsimple/src/main.rs +++ b/examples/cefsimple/src/main.rs @@ -271,6 +271,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();