eframe: Use `objc2` and its framework crates (#4395)

These are a replacement to the `objc` and `cocoa` crates.

This PR prevents:
- An extra copy when creating `NSData`
- A memory leak when creating `NSImage`
- A memory leak when creating `NSString`

And is generally a readability improvement.

Note that we define `NSApp` manually for now, the implementation in
`objc2-app-kit` is currently suboptimal and wouldn't allow you to check
whether the NSApplication has been created or not.

Related: https://github.com/emilk/egui/issues/4219, this should nicely
coincide with the Winit `0.30` release.

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
Mads Marquart 2024-04-23 17:35:12 +02:00 committed by GitHub
parent 2c590636b5
commit 14194f5d3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 63 deletions

101
Cargo.lock generated
View File

@ -548,7 +548,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dd7cf50912cddc06dc5ea7c08c5e81c1b2c842a70d19def1848d54c586fed92"
dependencies = [
"objc-sys 0.3.1",
"objc-sys 0.3.3",
]
[[package]]
@ -571,6 +571,15 @@ dependencies = [
"objc2 0.4.1",
]
[[package]]
name = "block2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43ff7d91d3c1d568065b06c899777d1e48dcf76103a672a0adbc238a7f247f1e"
dependencies = [
"objc2 0.5.1",
]
[[package]]
name = "blocking"
version = "1.4.0"
@ -795,36 +804,6 @@ dependencies = [
"error-code",
]
[[package]]
name = "cocoa"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
dependencies = [
"bitflags 1.3.2",
"block",
"cocoa-foundation",
"core-foundation",
"core-graphics",
"foreign-types",
"libc",
"objc",
]
[[package]]
name = "cocoa-foundation"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
dependencies = [
"bitflags 1.3.2",
"block",
"core-foundation",
"core-graphics-types",
"libc",
"objc",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -1201,7 +1180,6 @@ name = "eframe"
version = "0.27.2"
dependencies = [
"bytemuck",
"cocoa",
"directories-next",
"document-features",
"egui",
@ -1214,7 +1192,9 @@ dependencies = [
"image",
"js-sys",
"log",
"objc",
"objc2 0.5.1",
"objc2-app-kit",
"objc2-foundation",
"parking_lot",
"percent-encoding",
"pollster",
@ -2599,9 +2579,9 @@ checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7"
[[package]]
name = "objc-sys"
version = "0.3.1"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845"
checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60"
[[package]]
name = "objc2"
@ -2620,10 +2600,43 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d"
dependencies = [
"objc-sys 0.3.1",
"objc-sys 0.3.3",
"objc2-encode 3.0.0",
]
[[package]]
name = "objc2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659"
dependencies = [
"objc-sys 0.3.3",
"objc2-encode 4.0.1",
]
[[package]]
name = "objc2-app-kit"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb79768a710a9a1798848179edb186d1af7e8a8679f369e4b8d201dd2a034047"
dependencies = [
"block2 0.5.0",
"objc2 0.5.1",
"objc2-core-data",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e092bc42eaf30a08844e6a076938c60751225ec81431ab89f5d1ccd9f958d6c"
dependencies = [
"block2 0.5.0",
"objc2 0.5.1",
"objc2-foundation",
]
[[package]]
name = "objc2-encode"
version = "2.0.0-pre.2"
@ -2639,6 +2652,22 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666"
[[package]]
name = "objc2-encode"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a"
[[package]]
name = "objc2-foundation"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfaefe14254871ea16c7d88968c0ff14ba554712a20d76421eec52f0a7fb8904"
dependencies = [
"block2 0.5.0",
"objc2 0.5.1",
]
[[package]]
name = "objc_exception"
version = "0.1.2"

View File

@ -178,8 +178,19 @@ wgpu = { workspace = true, optional = true, features = [
# mac:
[target.'cfg(any(target_os = "macos"))'.dependencies]
cocoa = "0.25.0"
objc = "0.2.7"
objc2 = "0.5.1"
objc2-foundation = { version = "0.2.0", features = [
"block2",
"NSData",
"NSString",
] }
objc2-app-kit = { version = "0.2.0", features = [
"NSApplication",
"NSImage",
"NSMenu",
"NSMenuItem",
"NSResponder",
] }
# windows:
[target.'cfg(any(target_os = "windows"))'.dependencies]

View File

@ -203,12 +203,9 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS
use crate::icon_data::IconDataExt as _;
crate::profile_function!();
use cocoa::{
appkit::{NSApp, NSApplication, NSImage, NSMenu, NSWindow},
base::{id, nil},
foundation::{NSData, NSString},
};
use objc::{msg_send, sel, sel_impl};
use objc2::ClassType;
use objc2_app_kit::{NSApplication, NSImage};
use objc2_foundation::{NSData, NSString};
let png_bytes = if let Some(icon_data) = icon_data {
match icon_data.to_png_bytes() {
@ -222,38 +219,36 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS
None
};
// SAFETY: Accessing raw data from icon in a read-only manner. Icon data is static!
// TODO(madsmtm): Move this into `objc2-app-kit`
extern "C" {
static NSApp: Option<&'static NSApplication>;
}
unsafe {
let app = NSApp();
if app.is_null() {
let app = if let Some(app) = NSApp {
app
} else {
log::debug!("NSApp is null");
return AppIconStatus::NotSetIgnored;
}
};
if let Some(png_bytes) = png_bytes {
let data = NSData::dataWithBytes_length_(
nil,
png_bytes.as_ptr().cast::<std::ffi::c_void>(),
png_bytes.len() as u64,
);
let data = NSData::from_vec(png_bytes);
log::trace!("NSImage::initWithData…");
let app_icon = NSImage::initWithData_(NSImage::alloc(nil), data);
let app_icon = NSImage::initWithData(NSImage::alloc(), &data);
crate::profile_scope!("setApplicationIconImage_");
log::trace!("setApplicationIconImage…");
app.setApplicationIconImage_(app_icon);
app.setApplicationIconImage(app_icon.as_deref());
}
// Change the title in the top bar - for python processes this would be again "python" otherwise.
let main_menu = app.mainMenu();
if !main_menu.is_null() {
let item = main_menu.itemAtIndex_(0);
if !item.is_null() {
let app_menu: id = msg_send![item, submenu];
if !app_menu.is_null() {
if let Some(main_menu) = app.mainMenu() {
if let Some(item) = main_menu.itemAtIndex(0) {
if let Some(app_menu) = item.submenu() {
crate::profile_scope!("setTitle_");
app_menu.setTitle_(NSString::alloc(nil).init_str(title));
app_menu.setTitle(&NSString::from_str(title));
}
}
}

View File

@ -47,6 +47,7 @@ deny = [
skip = [
{ name = "bitflags" }, # old 1.0 version via glutin, png, spirv, …
{ name = "block2" }, # old version via glutin->icrate
{ name = "event-listener" }, # TODO(emilk): rustls pulls in two versions of this 😭
{ name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7
{ name = "memoffset" }, # tiny dependency
@ -61,7 +62,7 @@ skip = [
skip-tree = [
{ name = "criterion" }, # dev-dependency
{ name = "fastrand" }, # old version via accesskit_unix
{ name = "foreign-types" }, # small crate. Old version via cocoa and core-graphics (winit).
{ name = "foreign-types" }, # small crate. Old version via core-graphics (winit).
{ name = "objc2" }, # old version via accesskit_macos
{ name = "polling" }, # old version via accesskit_unix
{ name = "rfd" }, # example dependency