From df533e5718e45e4721b9f3f5150c08e5e3b4344c Mon Sep 17 00:00:00 2001 From: Jasmine Schweitzer Date: Wed, 15 Oct 2025 14:24:42 -0400 Subject: [PATCH] Implement set_window_icon for macOS --- winit-appkit/Cargo.toml | 3 ++ winit-appkit/src/window_delegate.rs | 67 ++++++++++++++++++++++------- winit-core/src/window.rs | 4 +- winit/src/changelog/unreleased.md | 1 + 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/winit-appkit/Cargo.toml b/winit-appkit/Cargo.toml index a4f590ea29..cce90caca8 100644 --- a/winit-appkit/Cargo.toml +++ b/winit-appkit/Cargo.toml @@ -28,6 +28,7 @@ objc2.workspace = true objc2-app-kit = { workspace = true, features = [ "std", "objc2-core-foundation", + "objc2-core-graphics", "NSAppearance", "NSApplication", "NSBitmapImageRep", @@ -70,6 +71,8 @@ objc2-core-foundation = { workspace = true, features = [ objc2-core-graphics = { workspace = true, features = [ "std", "libc", + "CGColorSpace", + "CGDataProvider", "CGDirectDisplay", "CGDisplayConfiguration", "CGDisplayFade", diff --git a/winit-appkit/src/window_delegate.rs b/winit-appkit/src/window_delegate.rs index 3d59bd3b82..db1c744ddd 100644 --- a/winit-appkit/src/window_delegate.rs +++ b/winit-appkit/src/window_delegate.rs @@ -13,13 +13,13 @@ use dpi::{ use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::{AnyObject, ProtocolObject}; use objc2::{ - available, define_class, msg_send, sel, ClassType, DefinedClass, MainThreadMarker, + available, define_class, msg_send, sel, AnyThread, ClassType, DefinedClass, MainThreadMarker, MainThreadOnly, Message, }; use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, - NSColor, NSDraggingDestination, NSDraggingInfo, NSRequestUserAttentionType, NSScreen, + NSColor, NSDraggingDestination, NSDraggingInfo, NSImage, NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification, NSWindow, NSWindowButton, NSWindowDelegate, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, @@ -31,9 +31,11 @@ use objc2_core_foundation::{CGFloat, CGPoint}; use objc2_core_graphics::{ kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, kCGDisplayFadeReservationInvalidToken, kCGFloatingWindowLevel, kCGNormalWindowLevel, CGAcquireDisplayFadeReservation, - CGAssociateMouseAndMouseCursorPosition, CGDisplayCapture, CGDisplayFade, CGDisplayRelease, - CGDisplaySetDisplayMode, CGReleaseDisplayFadeReservation, - CGRestorePermanentDisplayConfiguration, CGShieldingWindowLevel, CGWarpMouseCursorPosition, + CGAssociateMouseAndMouseCursorPosition, CGBitmapInfo, CGColorRenderingIntent, CGColorSpace, + CGDataProvider, CGDataProviderReleaseDataCallback, CGDisplayCapture, CGDisplayFade, + CGDisplayRelease, CGDisplaySetDisplayMode, CGImage, CGImageAlphaInfo, CGImageByteOrderInfo, + CGReleaseDisplayFadeReservation, CGRestorePermanentDisplayConfiguration, + CGShieldingWindowLevel, CGWarpMouseCursorPosition, }; use objc2_foundation::{ ns_string, NSArray, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey, @@ -45,7 +47,7 @@ use tracing::{trace, warn}; use winit_core::cursor::Cursor; use winit_core::error::{NotSupportedError, RequestError}; use winit_core::event::{SurfaceSizeWriter, WindowEvent}; -use winit_core::icon::Icon; +use winit_core::icon::{Icon, RgbaIcon}; use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider}; use winit_core::window::{ CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme, @@ -842,6 +844,8 @@ impl WindowDelegate { delegate.set_window_level(attrs.window_level); + delegate.set_window_icon(attrs.window_icon); + delegate.set_cursor(attrs.cursor); // Set fullscreen mode after we setup everything @@ -1657,15 +1661,48 @@ impl WindowDelegate { } #[inline] - pub fn set_window_icon(&self, _icon: Option) { - // macOS doesn't have window icons. Though, there is - // `setRepresentedFilename`, but that's semantically distinct and should - // only be used when the window is in some way representing a specific - // file/directory. For instance, Terminal.app uses this for the CWD. - // Anyway, that should eventually be implemented as - // `WindowAttributesExt::with_represented_file` or something, and doesn't - // have anything to do with `set_window_icon`. - // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html + pub fn set_window_icon(&self, icon: Option) { + let icon = icon.map(|icon| { + let icon = icon.cast_ref::().unwrap(); + + let data_provider = unsafe { + &CGDataProvider::with_data( + ptr::null_mut(), + icon.buffer().as_ptr() as _, + icon.buffer().len(), + CGDataProviderReleaseDataCallback::None, + ) + .unwrap() + }; + + let image = unsafe { + CGImage::new( + icon.width() as usize, + icon.height() as usize, + 8, + 8 * 4, + 4 * icon.width() as usize, + Some(&CGColorSpace::new_device_rgb().unwrap()), + CGBitmapInfo(CGImageByteOrderInfo::Order32Big.0 | CGImageAlphaInfo::Last.0), + Some(data_provider), + ptr::null(), + true, + CGColorRenderingIntent::RenderingIntentDefault, + ) + } + .unwrap(); + + NSImage::initWithCGImage_size( + NSImage::alloc(), + &image, + NSSize::new(icon.width() as f64, icon.height() as f64), + ) + }); + + let mtm = MainThreadMarker::from(self); + let app = NSApplication::sharedApplication(mtm); + + unsafe { app.setApplicationIconImage(icon.as_deref()) }; } pub fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> { diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index 36631604d2..544dbebeec 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -1050,7 +1050,9 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / / macOS / Orbital:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. + /// + /// - **macOS:** Sets the application icon (NSApplication.applicationIconImage). /// /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index c2a9e22c09..137aad3d10 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -88,6 +88,7 @@ changelog entry. - Add more `ImePurpose` values. - Add `ImeHints` to request particular IME behaviour. - Add Pen input support on Wayland, Windows, and Web via new Pointer event. +- On macOS, added implementation for `Window::set_window_icon` ### Changed