diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0d2239db3..64bf022674 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,7 @@ jobs: - { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, } - { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, } - { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, } + - { name: 'macOS Private APIs', target: aarch64-apple-darwin, os: macos-latest, options: '--package winit --features=private-apple-apis' } - { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, } - { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, } - { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, } diff --git a/winit-appkit/Cargo.toml b/winit-appkit/Cargo.toml index 2d98f67318..6551dab517 100644 --- a/winit-appkit/Cargo.toml +++ b/winit-appkit/Cargo.toml @@ -58,6 +58,7 @@ objc2-app-kit = { workspace = true, features = [ "NSTrackingArea", "NSToolbar", "NSView", + "NSVisualEffectView", "NSWindow", "NSWindowScripting", "NSWindowTabGroup", @@ -117,5 +118,5 @@ winit-common = { workspace = true, features = ["core-foundation", "event-handler winit.workspace = true [package.metadata.docs.rs] -all-features = true +features = ["serde"] targets = ["aarch64-apple-darwin", "x86_64-apple-darwin"] diff --git a/winit-appkit/src/view.rs b/winit-appkit/src/view.rs index 74cd8c2e48..9aea35d6e6 100644 --- a/winit-appkit/src/view.rs +++ b/winit-appkit/src/view.rs @@ -151,6 +151,7 @@ define_class!( #[unsafe(super(NSView, NSResponder, NSObject))] #[ivars = ViewState] #[name = "WinitView"] + #[derive(Debug)] pub(super) struct WinitView; /// This documentation attribute makes rustfmt work for some reason? diff --git a/winit-appkit/src/window_delegate.rs b/winit-appkit/src/window_delegate.rs index a2077a58aa..b160fad211 100644 --- a/winit-appkit/src/window_delegate.rs +++ b/winit-appkit/src/window_delegate.rs @@ -19,14 +19,19 @@ use objc2::{ }; use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, - NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, - NSColor, NSDragOperation, NSDraggingContext, NSDraggingDestination, NSDraggingInfo, - NSDraggingSession, NSDraggingSource, NSPasteboardTypeFileURL, NSPasteboardTypeHTML, - NSPasteboardTypePNG, NSPasteboardTypeSound, NSPasteboardTypeString, NSPasteboardTypeTIFF, - NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification, - NSWindow, NSWindowButton, NSWindowCollectionBehavior, NSWindowDelegate, NSWindowLevel, - NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, - NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle, + NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, + NSAutoresizingMaskOptions, NSBackingStoreType, NSColor, NSDragOperation, NSDraggingContext, + NSDraggingDestination, NSDraggingInfo, NSDraggingSession, NSDraggingSource, + NSPasteboardTypeFileURL, NSPasteboardTypeHTML, NSPasteboardTypePNG, NSPasteboardTypeSound, + NSPasteboardTypeString, NSPasteboardTypeTIFF, NSRequestUserAttentionType, NSScreen, NSToolbar, + NSView, NSViewFrameDidChangeNotification, NSWindow, NSWindowButton, NSWindowCollectionBehavior, + NSWindowDelegate, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, + NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, + NSWindowToolbarStyle, +}; +#[cfg(not(feature = "private-apple-apis"))] +use objc2_app_kit::{ + NSVisualEffectBlendingMode, NSVisualEffectMaterial, NSVisualEffectState, NSVisualEffectView, }; use objc2_core_foundation::{CGFloat, CGPoint}; use objc2_core_graphics::{ @@ -74,6 +79,11 @@ pub(crate) struct State { window: Retained, + view: Retained, + + #[cfg(not(feature = "private-apple-apis"))] + blur_view: RefCell>>, + // During `windowDidResize`, we use this to only send Moved if the position changed. // // This is expressed in desktop coordinates, and flipped to match Winit's coordinate system. @@ -659,7 +669,7 @@ fn new_window( macos_attrs: &WindowAttributesMacOS, is_popup: bool, mtm: MainThreadMarker, -) -> Option> { +) -> Option<(Retained, Retained)> { autoreleasepool(|_| { let screen = match attrs.fullscreen.clone() { Some(Fullscreen::Borderless(Some(monitor))) @@ -861,13 +871,22 @@ fn new_window( view.setWantsLayer(true); } + let content_view = NSView::new(mtm); + window.setContentView(Some(&content_view)); + + view.setFrame(content_view.bounds()); + view.setAutoresizingMask( + NSAutoresizingMaskOptions::ViewWidthSizable + | NSAutoresizingMaskOptions::ViewHeightSizable, + ); + content_view.addSubview(&view); + // Configure the new view as the "key view" for the window - window.setContentView(Some(&view)); window.setInitialFirstResponder(Some(&view)); // Configure the view to send notifications whenever its frame rectangle changes. // - // We explicitly do this _after_ setting the view as the content view of the window, to + // We explicitly do this _after_ adding the view to the window, to // avoid a resize event when creating the window. view.setPostsFrameChangedNotifications(true); // `setPostsFrameChangedNotifications` posts the notification immediately, so register the @@ -888,7 +907,7 @@ fn new_window( window.setBackgroundColor(Some(&NSColor::clearColor())); } - Some(window) + Some((window, view)) }) } @@ -918,7 +937,7 @@ impl WindowDelegate { } } - let window = new_window(app_state, &attrs, &macos_attrs, is_popup, mtm) + let (window, view) = new_window(app_state, &attrs, &macos_attrs, is_popup, mtm) .ok_or_else(|| os_error!("couldn't create `NSWindow`"))?; match attrs.parent_window() { @@ -964,6 +983,9 @@ impl WindowDelegate { let delegate = mtm.alloc().set_ivars(State { app_state: Rc::clone(app_state), window: window.retain(), + view, + #[cfg(not(feature = "private-apple-apis"))] + blur_view: RefCell::new(None), previous_position: Cell::new(flip_window_screen_coordinates(window.frame())), previous_scale_factor: Cell::new(scale_factor), surface_resize_increments: Cell::new(surface_resize_increments), @@ -1058,10 +1080,8 @@ impl WindowDelegate { Ok(delegate) } - #[track_caller] pub(super) fn view(&self) -> Retained { - // The view inside WinitWindow should always be set and be `WinitView`. - self.window().contentView().unwrap().downcast().unwrap() + self.ivars().view.clone() } #[track_caller] @@ -1164,8 +1184,43 @@ impl WindowDelegate { }; } - // TODO: Implement blur using public methods somehow? - let _ = blur; + #[cfg(not(feature = "private-apple-apis"))] + { + if !blur { + let installed = self.ivars().blur_view.borrow_mut().take(); + if let Some(installed) = installed { + installed.removeFromSuperview(); + } + return; + } + + if self.ivars().blur_view.borrow().is_some() { + return; + } + + let mtm = MainThreadMarker::from(self); + let content_view = + self.window().contentView().expect("window always has a content view"); + + let new_blur_view = NSVisualEffectView::new(mtm); + new_blur_view.setBlendingMode(NSVisualEffectBlendingMode::BehindWindow); + new_blur_view.setState(NSVisualEffectState::Active); + if available!(macos = 10.14) { + new_blur_view.setMaterial(NSVisualEffectMaterial::WindowBackground); + } + new_blur_view.setFrame(content_view.bounds()); + new_blur_view.setAutoresizingMask( + NSAutoresizingMaskOptions::ViewWidthSizable + | NSAutoresizingMaskOptions::ViewHeightSizable, + ); + content_view.addSubview_positioned_relativeTo( + &new_blur_view, + NSWindowOrderingMode::Below, + Some(&self.view()), + ); + + *self.ivars().blur_view.borrow_mut() = Some(new_blur_view); + } } pub fn set_visible(&self, visible: bool) { diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index 0bda71233e..085c90e30c 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -952,7 +952,14 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// /// ## Platform-specific /// - /// - **macOS**: Must enable the `private-apple-apis` Cargo feature. + /// - **macOS:** Renders the system window background material behind the window's contents, + /// which is tinted and follows the window's appearance, rather than a blur of a specific + /// radius. The window's `contentView` is a container holding the view returned by + /// `raw-window-handle`, and the material is a sibling behind it. With the + /// `private-apple-apis` Cargo feature enabled, a private API is used instead to apply an + /// untinted backdrop blur of a fixed radius; this can cause App Store rejection. On macOS + /// 10.12 and older, enabling blur makes the window's views layer-backed, which may break the + /// association with an attached `NSOpenGLContext`. /// - **Android / iOS / X11 / Web / Windows:** Unsupported. /// - **Wayland:** Only works with `org_kde_kwin_blur_manager` or /// `ext_background_effect_manager_v1` protocol. diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index fcbf778889..0af84b9311 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -89,6 +89,10 @@ changelog entry. - Updated `windows-sys` to `v0.61`. - On older macOS versions (tested up to 12.7.6), applications now receive mouse movement events for unfocused windows, matching the behavior on other platforms. - On macOS, using the private API `CGSSetWindowBackgroundBlurRadius` for `Window::set_blur` is now disabled by default. It can be re-enabled using the Cargo feature `private-apple-apis`. +- On macOS, `Window::set_blur` is now implemented with the public `NSVisualEffectView` when `private-apple-apis` is disabled, so binaries no longer link `_CGSSetWindowBackgroundBlurRadius`, which the Mac App Store rejects under Guideline 2.5.1. +- On macOS, blurred windows now render the system window background material rather than an untinted backdrop blur at a fixed radius of 80, and the radius is no longer configurable; enable the `private-apple-apis` Cargo feature to keep the previous implementation, which `--all-features` also enables. +- On macOS, the window's `contentView` is now a plain container `NSView` holding winit's view, rather than being winit's view itself; the handle returned by `raw-window-handle` is unchanged. +- On macOS 10.12 and older, enabling blur now makes the window's views layer-backed, which may break the association with an attached `NSOpenGLContext`. ### Removed diff --git a/winit/src/lib.rs b/winit/src/lib.rs index de1ccb5c49..c02f29772b 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -196,8 +196,9 @@ //! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). //! * `mint`: Enables mint (math interoperability standard types) conversions. //! * `private-apple-apis`: Enables private APIs whose usage might cause rejections from the App -//! Store. Currently enables the use of `CGSSetWindowBackgroundBlurRadius`, commonly used for -//! terminal emulators. +//! Store. Currently switches `Window::set_blur` on macOS to use +//! `CGSSetWindowBackgroundBlurRadius`, which applies an untinted blur of a fixed radius and is +//! commonly used by terminal emulators, instead of the default `NSVisualEffectView` material. //! //! See the [`platform`] module for documentation on platform-specific cargo //! features.