Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions cef/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
121 changes: 121 additions & 0 deletions cef/src/application_mac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use std::cell::Cell;
Copy link
Contributor

@csmoe csmoe Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This SimpleApplication should be move to examples?
As for projects integrate cef, they usually have their own NSApplication definition, We should leave it in examples code for reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @csmoe, thanks for taking the time to review my PR.

That is a fair point. My initial decision to place SimpleApplication in the cef crate was based on an earlier discussion with @wravery on the issue.

He suggested the following, which led me to believe it would be a useful utility within the cef crate itself:

I agree, it would be good to include these in the crate itself, although I recommend putting them in the cef crate rather than the underlying cef-dll-sys crate. There are already precedents there for Mac-only modules like cef::library_loader and cef::sandbox which do similar things.

I think even having a concrete implementation of SimpleApplication with safe defaults makes sense...

#96 (comment)

Your point that most users integrating cef will likely have their own NSApplication also makes a lot of sense. It's possible I mistakenly assumed his suggestion to "include these in the cef crate itself" also applied to the concrete SimpleApplication implementation, and not just the protocol bindings.

What are your thoughts on this, @wravery?

I'm happy to move the SimpleApplication code to examples if that's the preferred approach. I just want to make sure I'm following the best direction for the project.


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<Bool>,
}

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<Retained<Self>, ()> {
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<AnyObject> = 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(())
}
}
}
3 changes: 3 additions & 0 deletions cef/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions examples/cefsimple/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down