From 28ebe6e7373bae59196bff5b39d047bcc0a41137 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 30 Apr 2024 01:48:51 +0200 Subject: [PATCH] deps: Use `objc2` Fixes #181. This also prevents accessing the `NSView`/`NSWindow` from other threads than the main thread. SoftBufferError is `#[non_exhaustive]`, so it's fine to add further variants to it. I decided against doing that though, as the error is platform-specific, and not one that the program can really handle in any reasonable way (it's closer to a programmer error). --- Cargo.toml | 7 +++-- src/backends/cg.rs | 78 ++++++++++++++++++++++++++++------------------ src/lib.rs | 3 -- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c71ecb04..51e682a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,12 @@ features = ["Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging", "Win32_Foundat [target.'cfg(target_os = "macos")'.dependencies] bytemuck = { version = "1.12.3", features = ["extern_crate_alloc"] } -cocoa = "0.25.0" core-graphics = "0.23.1" foreign-types = "0.5.0" -objc = "0.2.7" +objc2 = "0.5.1" +objc2-foundation = { version = "0.2.0", features = ["NSThread"] } +objc2-app-kit = { version = "0.2.0", features = ["NSResponder", "NSView", "NSWindow"] } +objc2-quartz-core = { version = "0.2.0", features = ["CALayer", "CATransaction"] } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3.63" @@ -119,4 +121,3 @@ targets = [ "x86_64-unknown-linux-gnu", "wasm32-unknown-unknown", ] - diff --git a/src/backends/cg.rs b/src/backends/cg.rs index 3757880f..5f4f403f 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -7,12 +7,15 @@ use core_graphics::base::{ use core_graphics::color_space::CGColorSpace; use core_graphics::data_provider::CGDataProvider; use core_graphics::image::CGImage; +use objc2::runtime::AnyObject; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; -use cocoa::appkit::{NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow}; -use cocoa::base::{id, nil}; -use cocoa::quartzcore::{transaction, CALayer, ContentsGravity}; use foreign_types::ForeignType; +use objc2::msg_send; +use objc2::rc::Id; +use objc2_app_kit::{NSAutoresizingMaskOptions, NSView, NSWindow}; +use objc2_foundation::MainThreadMarker; +use objc2_quartz_core::{kCAGravityTopLeft, CALayer, CATransaction}; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -27,14 +30,19 @@ impl AsRef<[u8]> for Buffer { } pub struct CGImpl { - layer: CALayer, - window: id, + layer: Id, + window: Id, color_space: CGColorSpace, size: Option<(NonZeroU32, NonZeroU32)>, window_handle: W, _display: PhantomData, } +// TODO(madsmtm): Expose this in `objc2_app_kit`. +fn set_layer(view: &NSView, layer: &CALayer) { + unsafe { msg_send![view, setLayer: layer] } +} + impl SurfaceInterface for CGImpl { type Context = D; type Buffer<'a> = BufferImpl<'a, D, W> where Self: 'a; @@ -45,20 +53,35 @@ impl SurfaceInterface for CGImpl< RawWindowHandle::AppKit(handle) => handle, _ => return Err(InitError::Unsupported(window_src)), }; - let view = handle.ns_view.as_ptr() as id; - let window: id = unsafe { msg_send![view, window] }; - let window: id = unsafe { msg_send![window, retain] }; + + // `NSView` can only be accessed from the main thread. + let mtm = MainThreadMarker::new().ok_or(SoftBufferError::PlatformError( + Some("can only access AppKit / macOS handles from the main thread".to_string()), + None, + ))?; + let view = handle.ns_view.as_ptr(); + // SAFETY: The pointer came from `WindowHandle`, which ensures that + // the `AppKitWindowHandle` contains a valid pointer to an `NSView`. + // Unwrap is fine, since the pointer came from `NonNull`. + let view: Id = unsafe { Id::retain(view.cast()) }.unwrap(); let layer = CALayer::new(); + let subview = unsafe { NSView::initWithFrame(mtm.alloc(), view.frame()) }; + layer.setContentsGravity(unsafe { kCAGravityTopLeft }); + layer.setNeedsDisplayOnBoundsChange(false); + set_layer(&subview, &layer); unsafe { - let subview: id = NSView::alloc(nil).initWithFrame_(NSView::frame(view)); - layer.set_contents_gravity(ContentsGravity::TopLeft); - layer.set_needs_display_on_bounds_change(false); - subview.setLayer(layer.id()); - subview.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); - - view.addSubview_(subview); // retains subview (+1) = 2 - let _: () = msg_send![subview, release]; // releases subview (-1) = 1 - } + subview.setAutoresizingMask(NSAutoresizingMaskOptions( + NSAutoresizingMaskOptions::NSViewWidthSizable.0 + | NSAutoresizingMaskOptions::NSViewHeightSizable.0, + )) + }; + + let window = view.window().ok_or(SoftBufferError::PlatformError( + Some("view must be inside a window".to_string()), + None, + ))?; + + unsafe { view.addSubview(&subview) }; let color_space = CGColorSpace::create_device_rgb(); Ok(Self { layer, @@ -131,17 +154,20 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl // The CALayer has a default action associated with a change in the layer contents, causing // a quarter second fade transition to happen every time a new buffer is applied. This can // be mitigated by wrapping the operation in a transaction and disabling all actions. - transaction::begin(); - transaction::set_disable_actions(true); + CATransaction::begin(); + CATransaction::setDisableActions(true); + + self.imp + .layer + .setContentsScale(self.imp.window.backingScaleFactor()); unsafe { self.imp .layer - .set_contents_scale(self.imp.window.backingScaleFactor()); - self.imp.layer.set_contents(image.as_ptr() as id); + .setContents((image.as_ptr() as *mut AnyObject).as_ref()); }; - transaction::commit(); + CATransaction::commit(); Ok(()) } @@ -150,11 +176,3 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl self.present() } } - -impl Drop for CGImpl { - fn drop(&mut self) { - unsafe { - let _: () = msg_send![self.window, release]; - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 13e97baf..b62127c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,6 @@ #![warn(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#[cfg(target_os = "macos")] -#[macro_use] -extern crate objc; extern crate core; mod backend_dispatch;