From 8d206f1241c317e89d642a93755f2266651c8fe7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 30 Aug 2022 03:40:49 +0200 Subject: [PATCH 1/8] Remove UnownedWindow::inner_rect --- src/platform_impl/macos/window.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 3fa514a317..0395482e6e 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -374,7 +374,6 @@ pub struct UnownedWindow { pub ns_view: IdRef, // never changes shared_state: Arc>, decorations: AtomicBool, - pub inner_rect: Option>, } unsafe impl Send for UnownedWindow {} @@ -437,16 +436,12 @@ impl UnownedWindow { let maximized = win_attribs.maximized; let visible = win_attribs.visible; let decorations = win_attribs.decorations; - let inner_rect = win_attribs - .inner_size - .map(|size| size.to_physical(scale_factor)); let window = Arc::new(UnownedWindow { ns_view, ns_window, shared_state: Arc::new(Mutex::new(win_attribs.into())), decorations: AtomicBool::new(decorations), - inner_rect, }); let delegate = new_delegate(&window, fullscreen.is_some()); From 45274f74c232babeea8ec08f8dfc531873754435 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 3 Sep 2022 00:20:00 +0200 Subject: [PATCH 2/8] Refactor custom view to use much less `unsafe` The compiler fence is safe to get rid of now since `interpretKeyEvents` takes `&mut self` --- src/platform_impl/macos/appkit/application.rs | 49 +- src/platform_impl/macos/appkit/event.rs | 177 +++ src/platform_impl/macos/appkit/mod.rs | 16 +- src/platform_impl/macos/appkit/responder.rs | 17 +- .../macos/appkit/text_input_context.rs | 31 + src/platform_impl/macos/appkit/view.rs | 70 +- src/platform_impl/macos/appkit/window.rs | 60 +- src/platform_impl/macos/event.rs | 30 +- src/platform_impl/macos/ffi.rs | 43 +- src/platform_impl/macos/util/mod.rs | 19 - src/platform_impl/macos/view.rs | 1394 ++++++++--------- src/platform_impl/macos/window.rs | 64 +- src/platform_impl/macos/window_delegate.rs | 4 +- 13 files changed, 1076 insertions(+), 898 deletions(-) create mode 100644 src/platform_impl/macos/appkit/event.rs create mode 100644 src/platform_impl/macos/appkit/text_input_context.rs diff --git a/src/platform_impl/macos/appkit/application.rs b/src/platform_impl/macos/appkit/application.rs index 6ec74981f5..1681c4d5c5 100644 --- a/src/platform_impl/macos/appkit/application.rs +++ b/src/platform_impl/macos/appkit/application.rs @@ -1,7 +1,9 @@ -use objc2::foundation::NSObject; -use objc2::{extern_class, ClassType}; +use objc2::foundation::{MainThreadMarker, NSObject}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::Object; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; -use super::NSResponder; +use super::{NSEvent, NSResponder}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] @@ -12,3 +14,44 @@ extern_class!( type Super = NSResponder; } ); + +pub(crate) fn NSApp() -> Id { + let msg = "tried to access NSApp on something that was not the main thread"; + NSApplication::shared(MainThreadMarker::new().expect(msg)) +} + +extern_methods!( + unsafe impl NSApplication { + /// This can only be called on the main thread since it may initialize + /// the application and since it's parameters may be changed by the main + /// thread at any time (hence it is only safe to access on the main thread). + pub fn shared(_mtm: MainThreadMarker) -> Id { + let app: Option<_> = unsafe { msg_send_id![Self::class(), sharedApplication] }; + // SAFETY: `sharedApplication` always initializes the app if it isn't already + unsafe { app.unwrap_unchecked() } + } + + pub fn currentEvent(&self) -> Option> { + unsafe { msg_send_id![self, currentEvent] } + } + + // TODO: NSApplicationDelegate + #[sel(setDelegate:)] + pub unsafe fn setDelegate(&self, delegate: &Object); + + #[sel(hide:)] + pub fn hide(&self, sender: Option<&Object>); + + #[sel(hideOtherApplications:)] + pub fn hideOtherApplications(&self, sender: Option<&Object>); + + #[sel(stop:)] + pub fn stop(&self, sender: Option<&Object>); + + #[sel(activateIgnoringOtherApps:)] + pub fn activateIgnoringOtherApps(&self, ignore: bool); + + #[sel(run)] + pub unsafe fn run(&self); + } +); diff --git a/src/platform_impl/macos/appkit/event.rs b/src/platform_impl/macos/appkit/event.rs new file mode 100644 index 0000000000..6ba04c3d87 --- /dev/null +++ b/src/platform_impl/macos/appkit/event.rs @@ -0,0 +1,177 @@ +use std::os::raw::c_ushort; + +use objc2::encode::{Encode, Encoding}; +use objc2::foundation::{CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSUInteger}; +use objc2::rc::{Id, Shared}; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSEvent; + + unsafe impl ClassType for NSEvent { + type Super = NSObject; + } +); + +// > Safely handled only on the same thread, whether that be the main thread +// > or a secondary thread; otherwise you run the risk of having events get +// > out of sequence. +// +// + +extern_methods!( + unsafe impl NSEvent { + #[sel(locationInWindow)] + pub fn locationInWindow(&self) -> NSPoint; + + // TODO: MainThreadMarker + #[sel(pressedMouseButtons)] + pub fn pressedMouseButtons() -> NSUInteger; + + #[sel(modifierFlags)] + pub fn modifierFlags(&self) -> NSEventModifierFlags; + + // In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character, + // and there is no easy way to navtively retrieve the layout-dependent character. + // In winit, we use keycode to refer to the key's character, and so this function aligns + // AppKit's terminology with ours. + #[sel(keyCode)] + pub fn scancode(&self) -> c_ushort; + + #[sel(magnification)] + pub fn magnification(&self) -> CGFloat; + + #[sel(phase)] + pub fn phase(&self) -> NSEventPhase; + + #[sel(momentumPhase)] + pub fn momentumPhase(&self) -> NSEventPhase; + + #[sel(scrollingDeltaX)] + pub fn scrollingDeltaX(&self) -> CGFloat; + + #[sel(scrollingDeltaY)] + pub fn scrollingDeltaY(&self) -> CGFloat; + + #[sel(hasPreciseScrollingDeltas)] + pub fn hasPreciseScrollingDeltas(&self) -> bool; + + #[sel(rotation)] + pub fn rotation(&self) -> f32; + + #[sel(pressure)] + pub fn pressure(&self) -> f32; + + #[sel(stage)] + pub fn stage(&self) -> NSInteger; + + pub fn characters(&self) -> Option> { + unsafe { msg_send_id![self, characters] } + } + + pub fn charactersIgnoringModifiers(&self) -> Option> { + unsafe { msg_send_id![self, charactersIgnoringModifiers] } + } + } +); + +unsafe impl NSCopying for NSEvent { + type Ownership = Shared; + type Output = NSEvent; +} + +bitflags! { + pub struct NSEventModifierFlags: NSUInteger { + const NSAlphaShiftKeyMask = 1 << 16; + const NSShiftKeyMask = 1 << 17; + const NSControlKeyMask = 1 << 18; + const NSAlternateKeyMask = 1 << 19; + const NSCommandKeyMask = 1 << 20; + const NSNumericPadKeyMask = 1 << 21; + const NSHelpKeyMask = 1 << 22; + const NSFunctionKeyMask = 1 << 23; + const NSDeviceIndependentModifierFlagsMask = 0xffff0000; + } +} + +unsafe impl Encode for NSEventModifierFlags { + const ENCODING: Encoding = NSUInteger::ENCODING; +} + +bitflags! { + pub struct NSEventPhase: NSUInteger { + const NSEventPhaseNone = 0; + const NSEventPhaseBegan = 0x1 << 0; + const NSEventPhaseStationary = 0x1 << 1; + const NSEventPhaseChanged = 0x1 << 2; + const NSEventPhaseEnded = 0x1 << 3; + const NSEventPhaseCancelled = 0x1 << 4; + const NSEventPhaseMayBegin = 0x1 << 5; + } +} + +unsafe impl Encode for NSEventPhase { + const ENCODING: Encoding = NSUInteger::ENCODING; +} + +#[allow(dead_code)] +#[repr(i16)] // short +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NSEventSubtype { + // TODO: Not sure what these values are + // NSMouseEventSubtype = NX_SUBTYPE_DEFAULT, + // NSTabletPointEventSubtype = NX_SUBTYPE_TABLET_POINT, + // NSTabletProximityEventSubtype = NX_SUBTYPE_TABLET_PROXIMITY + // NSTouchEventSubtype = NX_SUBTYPE_MOUSE_TOUCH, + NSWindowExposedEventType = 0, + NSApplicationActivatedEventType = 1, + NSApplicationDeactivatedEventType = 2, + NSWindowMovedEventType = 4, + NSScreenChangedEventType = 8, + NSAWTEventType = 16, +} + +unsafe impl Encode for NSEventSubtype { + const ENCODING: Encoding = i16::ENCODING; +} + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(usize)] // NSUInteger +pub enum NSEventType { + NSLeftMouseDown = 1, + NSLeftMouseUp = 2, + NSRightMouseDown = 3, + NSRightMouseUp = 4, + NSMouseMoved = 5, + NSLeftMouseDragged = 6, + NSRightMouseDragged = 7, + NSMouseEntered = 8, + NSMouseExited = 9, + NSKeyDown = 10, + NSKeyUp = 11, + NSFlagsChanged = 12, + NSAppKitDefined = 13, + NSSystemDefined = 14, + NSApplicationDefined = 15, + NSPeriodic = 16, + NSCursorUpdate = 17, + NSScrollWheel = 22, + NSTabletPoint = 23, + NSTabletProximity = 24, + NSOtherMouseDown = 25, + NSOtherMouseUp = 26, + NSOtherMouseDragged = 27, + NSEventTypeGesture = 29, + NSEventTypeMagnify = 30, + NSEventTypeSwipe = 31, + NSEventTypeRotate = 18, + NSEventTypeBeginGesture = 19, + NSEventTypeEndGesture = 20, + NSEventTypePressure = 34, +} + +unsafe impl Encode for NSEventType { + const ENCODING: Encoding = NSUInteger::ENCODING; +} diff --git a/src/platform_impl/macos/appkit/mod.rs b/src/platform_impl/macos/appkit/mod.rs index 02c3effdec..003e7869cc 100644 --- a/src/platform_impl/macos/appkit/mod.rs +++ b/src/platform_impl/macos/appkit/mod.rs @@ -1,18 +1,30 @@ +//! Safe bindings for the AppKit framework. +//! +//! These are split out from the rest of `winit` to make safety easier to review. +//! In the future, these should probably live in another crate like `cacao`. +//! +//! TODO: Main thread safety. #![deny(unsafe_op_in_unsafe_fn)] // Objective-C methods have different conventions, and it's much easier to // understand if we just use the same names #![allow(non_snake_case)] +#![allow(clippy::enum_variant_names)] +#![allow(non_upper_case_globals)] mod application; mod cursor; +mod event; mod image; mod responder; +mod text_input_context; mod view; mod window; -pub(crate) use self::application::NSApplication; +pub(crate) use self::application::{NSApp, NSApplication}; pub(crate) use self::cursor::NSCursor; +pub(crate) use self::event::{NSEvent, NSEventModifierFlags, NSEventPhase}; pub(crate) use self::image::NSImage; pub(crate) use self::responder::NSResponder; -pub(crate) use self::view::NSView; +pub(crate) use self::text_input_context::NSTextInputContext; +pub(crate) use self::view::{NSTrackingRectTag, NSView}; pub(crate) use self::window::NSWindow; diff --git a/src/platform_impl/macos/appkit/responder.rs b/src/platform_impl/macos/appkit/responder.rs index 5181b98606..a1ae1877bc 100644 --- a/src/platform_impl/macos/appkit/responder.rs +++ b/src/platform_impl/macos/appkit/responder.rs @@ -1,5 +1,8 @@ -use objc2::foundation::NSObject; -use objc2::{extern_class, ClassType}; +use objc2::foundation::{NSArray, NSObject}; +use objc2::rc::Shared; +use objc2::{extern_class, extern_methods, ClassType}; + +use super::NSEvent; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] @@ -9,3 +12,13 @@ extern_class!( type Super = NSObject; } ); + +// Documented as "Thread-Unsafe". + +extern_methods!( + unsafe impl NSResponder { + // TODO: Allow "immutably" on main thread + #[sel(interpretKeyEvents:)] + pub unsafe fn interpretKeyEvents(&mut self, events: &NSArray); + } +); diff --git a/src/platform_impl/macos/appkit/text_input_context.rs b/src/platform_impl/macos/appkit/text_input_context.rs new file mode 100644 index 0000000000..79a9611b5f --- /dev/null +++ b/src/platform_impl/macos/appkit/text_input_context.rs @@ -0,0 +1,31 @@ +use objc2::foundation::{NSObject, NSString}; +use objc2::rc::{Id, Shared}; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +type NSTextInputSourceIdentifier = NSString; + +extern_class!( + /// Main-Thread-Only! + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSTextInputContext; + + unsafe impl ClassType for NSTextInputContext { + type Super = NSObject; + } +); + +extern_methods!( + unsafe impl NSTextInputContext { + #[sel(invalidateCharacterCoordinates)] + pub fn invalidateCharacterCoordinates(&self); + + #[sel(discardMarkedText)] + pub fn discardMarkedText(&self); + + pub fn selectedKeyboardInputSource( + &self, + ) -> Option> { + unsafe { msg_send_id![self, selectedKeyboardInputSource] } + } + } +); diff --git a/src/platform_impl/macos/appkit/view.rs b/src/platform_impl/macos/appkit/view.rs index a66720e2c5..3a0f2be4f8 100644 --- a/src/platform_impl/macos/appkit/view.rs +++ b/src/platform_impl/macos/appkit/view.rs @@ -1,7 +1,13 @@ -use objc2::foundation::{NSObject, NSRect}; -use objc2::{extern_class, extern_methods, ClassType}; +use std::ffi::c_void; +use std::num::NonZeroIsize; +use std::ptr; -use super::{NSCursor, NSResponder}; +use objc2::foundation::{NSObject, NSPoint, NSRect}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::Object; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +use super::{NSCursor, NSResponder, NSTextInputContext}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] @@ -13,16 +19,74 @@ extern_class!( } ); +// Documented as "Main Thread Only". +// > generally thread safe, although operations on views such as creating, +// > resizing, and moving should happen on the main thread. +// +// +// > If you want to use a thread to draw to a view, bracket all drawing code +// > between the lockFocusIfCanDraw and unlockFocus methods of NSView. +// + extern_methods!( /// Getter methods unsafe impl NSView { + #[sel(frame)] + pub fn frame(&self) -> NSRect; + #[sel(bounds)] pub fn bounds(&self) -> NSRect; + + pub fn inputContext( + &self, + // _mtm: MainThreadMarker, + ) -> Option> { + unsafe { msg_send_id![self, inputContext] } + } + + #[sel(visibleRect)] + pub fn visibleRect(&self) -> NSRect; + + #[sel(hasMarkedText)] + pub fn hasMarkedText(&self) -> bool; + + #[sel(convertPoint:fromView:)] + pub fn convertPoint_fromView(&self, point: NSPoint, view: Option<&NSView>) -> NSPoint; } unsafe impl NSView { + #[sel(setWantsBestResolutionOpenGLSurface:)] + pub fn setWantsBestResolutionOpenGLSurface(&mut self, value: bool); + + #[sel(setWantsLayer:)] + pub fn setWantsLayer(&mut self, wants_layer: bool); + + #[sel(setPostsFrameChangedNotifications:)] + pub fn setPostsFrameChangedNotifications(&mut self, value: bool); + + #[sel(removeTrackingRect:)] + pub fn removeTrackingRect(&self, tag: NSTrackingRectTag); + + #[sel(addTrackingRect:owner:userData:assumeInside:)] + unsafe fn inner_addTrackingRect( + &self, + rect: NSRect, + owner: &Object, + user_data: *mut c_void, + assume_inside: bool, + ) -> Option; + + pub fn add_tracking_rect(&self, rect: NSRect, assume_inside: bool) -> NSTrackingRectTag { + // SAFETY: The user data is NULL, so it is valid + unsafe { self.inner_addTrackingRect(rect, self, ptr::null_mut(), assume_inside) } + .expect("failed creating tracking rect") + } + #[sel(addCursorRect:cursor:)] // NSCursor safe to take by shared reference since it is already immutable pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor); } ); + +/// +pub type NSTrackingRectTag = NonZeroIsize; // NSInteger, but non-zero! diff --git a/src/platform_impl/macos/appkit/window.rs b/src/platform_impl/macos/appkit/window.rs index c73b6b8a8f..49a59396bb 100644 --- a/src/platform_impl/macos/appkit/window.rs +++ b/src/platform_impl/macos/appkit/window.rs @@ -1,9 +1,12 @@ -use objc2::foundation::NSObject; -use objc2::{extern_class, ClassType}; +use objc2::foundation::{CGFloat, NSObject, NSRect, NSSize}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::Object; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; use super::NSResponder; extern_class!( + /// Main-Thread-Only! #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSWindow; @@ -12,3 +15,56 @@ extern_class!( type Super = NSResponder; } ); + +// Documented as "Main Thread Only", but: +// > Thread safe in that you can create and manage them on a secondary thread. +// +// +// +// So could in theory be `Send`, and perhaps also `Sync` - but we would like +// interior mutability on windows, since that's just much easier, and in that +// case, they can't be! + +extern_methods!( + unsafe impl NSWindow { + #[sel(frame)] + pub fn frame(&self) -> NSRect; + + #[sel(backingScaleFactor)] + pub fn backingScaleFactor(&self) -> CGFloat; + + #[sel(contentRectForFrameRect:)] + pub fn contentRectForFrameRect(&self, windowFrame: NSRect) -> NSRect; + + #[sel(setContentSize:)] + pub fn setContentSize(&self, contentSize: NSSize); + + #[sel(setMinSize:)] + pub fn setMinSize(&self, minSize: NSSize); + + #[sel(setMaxSize:)] + pub fn setMaxSize(&self, maxSize: NSSize); + + #[sel(setFrame:display:)] + pub fn setFrame_display(&self, frameRect: NSRect, flag: bool); + + #[sel(setMovable:)] + pub fn setMovable(&self, movable: bool); + + #[sel(miniaturize:)] + pub fn miniaturize(&self, sender: Option<&Object>); + + #[sel(sender:)] + pub fn deminiaturize(&self, sender: Option<&Object>); + + #[sel(selectNextKeyView:)] + pub fn selectNextKeyView(&self, sender: Option<&Object>); + + #[sel(selectPreviousKeyView:)] + pub fn selectPreviousKeyView(&self, sender: Option<&Object>); + + pub fn firstResponder(&self) -> Option> { + unsafe { msg_send_id![self, firstResponder] } + } + } +); diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 343aaeb286..5e240ba7e4 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -1,10 +1,6 @@ use std::os::raw::c_ushort; -use cocoa::{ - appkit::{NSEvent, NSEventModifierFlags}, - base::id, -}; - +use super::appkit::{NSEvent, NSEventModifierFlags}; use crate::{ dpi::LogicalSize, event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}, @@ -241,8 +237,8 @@ pub fn check_function_keys(string: &str) -> Option { None } -pub fn event_mods(event: id) -> ModifiersState { - let flags = unsafe { NSEvent::modifierFlags(event) }; +pub(super) fn event_mods(event: &NSEvent) -> ModifiersState { + let flags = event.modifierFlags(); let mut m = ModifiersState::empty(); m.set( ModifiersState::SHIFT, @@ -263,21 +259,13 @@ pub fn event_mods(event: id) -> ModifiersState { m } -pub fn get_scancode(event: cocoa::base::id) -> c_ushort { - // In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character, - // and there is no easy way to navtively retrieve the layout-dependent character. - // In winit, we use keycode to refer to the key's character, and so this function aligns - // AppKit's terminology with ours. - unsafe { msg_send![event, keyCode] } -} - -pub unsafe fn modifier_event( - ns_event: id, +pub(super) fn modifier_event( + event: &NSEvent, keymask: NSEventModifierFlags, was_key_pressed: bool, ) -> Option> { - if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask) - || was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) + if !was_key_pressed && event.modifierFlags().contains(keymask) + || was_key_pressed && !event.modifierFlags().contains(keymask) { let state = if was_key_pressed { ElementState::Released @@ -285,7 +273,7 @@ pub unsafe fn modifier_event( ElementState::Pressed }; - let scancode = get_scancode(ns_event); + let scancode = event.scancode(); let virtual_keycode = scancode_to_keycode(scancode); #[allow(deprecated)] Some(WindowEvent::KeyboardInput { @@ -294,7 +282,7 @@ pub unsafe fn modifier_event( state, scancode: scancode as _, virtual_keycode, - modifiers: event_mods(ns_event), + modifiers: event_mods(event), }, is_synthetic: false, }) diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index b5bb6f5ebd..aba07535c2 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -4,7 +4,6 @@ use std::ffi::c_void; -use cocoa::base::id; use core_foundation::{ array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef, }; @@ -12,50 +11,10 @@ use core_graphics::{ base::CGError, display::{CGDirectDisplayID, CGDisplayConfigRef}, }; -use objc::foundation::{NSInteger, NSUInteger}; +use objc2::foundation::NSInteger; pub const NSNotFound: NSInteger = NSInteger::max_value(); -pub trait NSMutableAttributedString: Sized { - unsafe fn alloc(_: Self) -> id { - msg_send![class!(NSMutableAttributedString), alloc] - } - - unsafe fn init(self) -> id; // *mut NSMutableAttributedString - unsafe fn initWithString(self, string: id) -> id; - unsafe fn initWithAttributedString(self, string: id) -> id; - - unsafe fn string(self) -> id; // *mut NSString - unsafe fn mutableString(self) -> id; // *mut NSMutableString - unsafe fn length(self) -> NSUInteger; -} - -impl NSMutableAttributedString for id { - unsafe fn init(self) -> id { - msg_send![self, init] - } - - unsafe fn initWithString(self, string: id) -> id { - msg_send![self, initWithString: string] - } - - unsafe fn initWithAttributedString(self, string: id) -> id { - msg_send![self, initWithAttributedString: string] - } - - unsafe fn string(self) -> id { - msg_send![self, string] - } - - unsafe fn mutableString(self) -> id { - msg_send![self, mutableString] - } - - unsafe fn length(self) -> NSUInteger { - msg_send![self, length] - } -} - pub const kCGBaseWindowLevelKey: NSInteger = 0; pub const kCGMinimumWindowLevelKey: NSInteger = 1; pub const kCGDesktopWindowLevelKey: NSInteger = 2; diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 0eac0f10a2..7b39c36b14 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -3,7 +3,6 @@ mod r#async; pub use self::r#async::*; use std::ops::{BitAnd, Deref}; -use std::os::raw::c_uchar; use cocoa::{ appkit::{CGFloat, NSApp, NSWindowStyleMask}, @@ -160,21 +159,3 @@ pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, o // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! window.makeFirstResponder_(view); } - -/// For invalid utf8 sequences potentially returned by `UTF8String`, -/// it behaves identically to `String::from_utf8_lossy` -/// -/// Safety: Assumes that `string` is an instance of `NSAttributedString` or `NSString` -pub unsafe fn id_to_string_lossy(string: id) -> String { - let has_attr = msg_send![string, isKindOfClass: class!(NSAttributedString)]; - let characters = if has_attr { - // This is a *mut NSAttributedString - msg_send![string, string] - } else { - // This is already a *mut NSString - string - }; - let utf8_sequence = - std::slice::from_raw_parts(characters.UTF8String() as *const c_uchar, characters.len()); - String::from_utf8_lossy(utf8_sequence).into_owned() -} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 9c2e6ae306..be4bb7119d 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -1,25 +1,20 @@ use std::{ - boxed::Box, - collections::VecDeque, - os::raw::*, - ptr, slice, str, - sync::{ - atomic::{compiler_fence, Ordering}, - Mutex, - }, + boxed::Box, collections::VecDeque, mem::ManuallyDrop, os::raw::*, ptr, str, sync::Mutex, }; -use cocoa::{ - appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow}, - base::{id, nil}, - foundation::{NSPoint, NSRect, NSSize, NSString}, +use objc2::declare::{Ivar, IvarDrop}; +use objc2::foundation::{ + NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, + NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; -use objc2::foundation::{NSInteger, NSObject, NSRange, NSUInteger}; -use objc2::rc::{Id, Shared}; -use objc2::runtime::{Bool, Object, Sel}; +use objc2::rc::{Id, Owned, Shared}; +use objc2::runtime::{Object, Sel}; use objc2::{declare_class, ClassType}; -use super::appkit::{NSCursor, NSResponder, NSView as NSViewClass}; +use super::appkit::{ + NSApp, NSCursor, NSEvent, NSEventModifierFlags, NSEventPhase, NSResponder, NSTrackingRectTag, + NSView, NSWindow, +}; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{ @@ -29,17 +24,17 @@ use crate::{ platform_impl::platform::{ app_state::AppState, event::{ - char_to_keycode, check_function_keys, event_mods, get_scancode, modifier_event, - scancode_to_keycode, EventWrapper, + char_to_keycode, check_function_keys, event_mods, modifier_event, scancode_to_keycode, + EventWrapper, }, - ffi::*, - util::{self, id_to_string_lossy, IdRef}, + util::{self, IdRef}, window::get_window_id, DEVICE_ID, }, window::WindowId, }; +#[derive(Debug)] pub struct CursorState { pub visible: bool, pub(super) cursor: Id, @@ -69,12 +64,12 @@ enum ImeState { Commited, } +#[derive(Debug)] pub(super) struct ViewState { - ns_window: id, pub cursor_state: Mutex, ime_position: LogicalPosition, pub(super) modifiers: ModifiersState, - tracking_rect: Option, + tracking_rect: Option, ime_state: ImeState, input_source: String, @@ -88,86 +83,14 @@ pub(super) struct ViewState { forward_key_to_app: bool, } -impl ViewState { - fn get_scale_factor(&self) -> f64 { - (unsafe { NSWindow::backingScaleFactor(self.ns_window) }) as f64 - } - - fn is_ime_enabled(&self) -> bool { - !matches!(self.ime_state, ImeState::Disabled) - } -} - -pub fn new_view(ns_window: id) -> IdRef { - let state = ViewState { - ns_window, - cursor_state: Default::default(), - ime_position: LogicalPosition::new(0.0, 0.0), - modifiers: Default::default(), - tracking_rect: None, - ime_state: ImeState::Disabled, - input_source: String::new(), - ime_allowed: false, - forward_key_to_app: false, - }; - unsafe { - // This is free'd in `dealloc` - let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; - let ns_view: id = msg_send![WinitView::class(), alloc]; - IdRef::new(msg_send![ns_view, initWithWinit: state_ptr]) - } -} - -pub unsafe fn set_ime_position(ns_view: id, position: LogicalPosition) { - let state_ptr: *mut c_void = *(*ns_view).ivar_mut("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - state.ime_position = position; - let input_context: id = msg_send![ns_view, inputContext]; - let _: () = msg_send![input_context, invalidateCharacterCoordinates]; -} - -pub unsafe fn set_ime_allowed(ns_view: id, ime_allowed: bool) { - let state_ptr: *mut c_void = *(*ns_view).ivar_mut("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - if state.ime_allowed == ime_allowed { - return; - } - state.ime_allowed = ime_allowed; - if state.ime_allowed { - return; - } - let marked_text_ref: &mut id = (*ns_view).ivar_mut("markedText"); - - // Clear markedText - let _: () = msg_send![*marked_text_ref, release]; - let marked_text = - ::init(NSMutableAttributedString::alloc(nil)); - *marked_text_ref = marked_text; - - if state.ime_state != ImeState::Disabled { - state.ime_state = ImeState::Disabled; - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Disabled), - })); - } -} - -fn get_characters(event: id, ignore_modifiers: bool) -> String { - unsafe { - let characters: id = if ignore_modifiers { - msg_send![event, charactersIgnoringModifiers] - } else { - msg_send![event, characters] - }; - - assert_ne!(characters, nil); - let slice = - slice::from_raw_parts(characters.UTF8String() as *const c_uchar, characters.len()); - - let string = str::from_utf8_unchecked(slice); - string.to_owned() +fn get_characters(event: &NSEvent, ignore_modifiers: bool) -> String { + if ignore_modifiers { + event.charactersIgnoringModifiers() + } else { + event.characters() } + .expect("expected characters to be non-null") + .to_string() } // As defined in: https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT @@ -185,9 +108,9 @@ fn is_corporate_character(c: char) -> bool { } // Retrieves a layout-independent keycode given an event. -fn retrieve_keycode(event: id) -> Option { +fn retrieve_keycode(event: &NSEvent) -> Option { #[inline] - fn get_code(ev: id, raw: bool) -> Option { + fn get_code(ev: &NSEvent, raw: bool) -> Option { let characters = get_characters(ev, raw); characters.chars().next().and_then(char_to_keycode) } @@ -203,137 +126,66 @@ fn retrieve_keycode(event: id) -> Option { // can vary, but we know that they are encoded // in characters property. code.or_else(|| { - let scancode = get_scancode(event); + let scancode = event.scancode(); scancode_to_keycode(scancode).or_else(|| check_function_keys(&get_characters(event, true))) }) } -// Update `state.modifiers` if `event` has something different -fn update_potentially_stale_modifiers(state: &mut ViewState, event: id) { - let event_modifiers = event_mods(event); - if state.modifiers != event_modifiers { - state.modifiers = event_modifiers; - - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::ModifiersChanged(state.modifiers), - })); - } -} - -fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) { - unsafe { - let state_ptr: *mut c_void = *this.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - update_potentially_stale_modifiers(state, event); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::MouseInput { - device_id: DEVICE_ID, - state: button_state, - button, - modifiers: event_mods(event), - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - -fn mouse_motion(this: &Object, event: id) { - unsafe { - let state_ptr: *mut c_void = *this.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - // We have to do this to have access to the `NSView` trait... - let view: id = this as *const _ as *mut _; - - let window_point = event.locationInWindow(); - let view_point = view.convertPoint_fromView_(window_point, nil); - let view_rect = NSView::frame(view); - - if view_point.x.is_sign_negative() - || view_point.y.is_sign_negative() - || view_point.x > view_rect.size.width - || view_point.y > view_rect.size.height - { - let mouse_buttons_down: NSUInteger = msg_send![class!(NSEvent), pressedMouseButtons]; - if mouse_buttons_down == 0 { - // Point is outside of the client area (view) and no buttons are pressed - return; - } - } - - let x = view_point.x as f64; - let y = view_rect.size.height as f64 - view_point.y as f64; - let logical_position = LogicalPosition::new(x, y); - - update_potentially_stale_modifiers(state, event); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::CursorMoved { - device_id: DEVICE_ID, - position: logical_position.to_physical(state.get_scale_factor()), - modifiers: event_mods(event), - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - declare_class!( #[derive(Debug)] #[allow(non_snake_case)] - struct WinitView { - winitState: *mut c_void, - markedText: id, + pub(super) struct WinitView { + _ns_window: IvarDrop>, + state: IvarDrop>, + marked_text: IvarDrop>, } unsafe impl ClassType for WinitView { #[inherits(NSResponder, NSObject)] - type Super = NSViewClass; + type Super = NSView; } unsafe impl WinitView { - #[sel(dealloc)] - fn dealloc(&mut self) { - unsafe { - let marked_text: id = *self.ivar("markedText"); - let _: () = msg_send![marked_text, release]; - let state: *mut c_void = *self.ivar("winitState"); - drop(Box::from_raw(state as *mut ViewState)); - } - } + #[sel(initWithId:)] + fn init_with_id(&mut self, window: *mut NSWindow) -> Option<&mut Self> { + let this: Option<&mut Self> = unsafe { msg_send![super(self), init] }; + this.map(|this| { + let state = ViewState { + cursor_state: Default::default(), + ime_position: LogicalPosition::new(0.0, 0.0), + modifiers: Default::default(), + tracking_rect: None, + ime_state: ImeState::Disabled, + input_source: String::new(), + ime_allowed: false, + forward_key_to_app: false, + }; - #[sel(initWithWinit:)] - fn init_with_winit(&mut self, state: *mut c_void) -> Option<&mut Self> { - let this: Option<&mut Self> = unsafe { msg_send![self, init] }; - this.map(|this| unsafe { - (*this).set_ivar("winitState", state); - let marked_text = - ::init(NSMutableAttributedString::alloc(nil)); - (*this).set_ivar("markedText", marked_text); - let _: () = msg_send![&mut *this, setPostsFrameChangedNotifications: true]; + Ivar::write( + &mut this._ns_window, + unsafe { Id::retain(window) }.expect("non-null window"), + ); + Ivar::write(&mut this.state, Box::new(state)); + Ivar::write(&mut this.marked_text, NSMutableAttributedString::new()); + + this.setPostsFrameChangedNotifications(true); let notification_center: &Object = - msg_send![class!(NSNotificationCenter), defaultCenter]; + unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] }; // About frame change let frame_did_change_notification_name = - IdRef::new(NSString::alloc(nil).init_str("NSViewFrameDidChangeNotification")); - let _: () = msg_send![ - notification_center, - addObserver: &*this - selector: sel!(frameDidChange:) - name: *frame_did_change_notification_name - object: &*this - ]; - - let winit_state = &mut *(state as *mut ViewState); - winit_state.input_source = this.current_input_source(); + NSString::from_str("NSViewFrameDidChangeNotification"); + let _: () = unsafe { + msg_send![ + notification_center, + addObserver: &*this, + selector: sel!(frameDidChange:), + name: &*frame_did_change_notification_name, + object: &*this, + ] + }; + + this.state.input_source = this.current_input_source(); this }) } @@ -341,71 +193,46 @@ declare_class!( unsafe impl WinitView { #[sel(viewDidMoveToWindow)] - fn view_did_move_to_window(&self) { + fn view_did_move_to_window(&mut self) { trace_scope!("viewDidMoveToWindow"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - if let Some(tracking_rect) = state.tracking_rect.take() { - let _: () = msg_send![self, removeTrackingRect: tracking_rect]; - } - - let rect: NSRect = msg_send![self, visibleRect]; - let tracking_rect: NSInteger = msg_send![ - self, - addTrackingRect: rect, - owner: self, - userData: ptr::null_mut::(), - assumeInside: false, - ]; - state.tracking_rect = Some(tracking_rect); + if let Some(tracking_rect) = self.state.tracking_rect.take() { + self.removeTrackingRect(tracking_rect); } + + let rect = self.visibleRect(); + let tracking_rect = self.add_tracking_rect(rect, false); + self.state.tracking_rect = Some(tracking_rect); } #[sel(frameDidChange:)] - fn frame_did_change(&self, _event: id) { + fn frame_did_change(&mut self, _event: &NSEvent) { trace_scope!("frameDidChange:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - if let Some(tracking_rect) = state.tracking_rect.take() { - let _: () = msg_send![self, removeTrackingRect: tracking_rect]; - } - - let rect: NSRect = msg_send![self, visibleRect]; - let tracking_rect: NSInteger = msg_send![ - self, - addTrackingRect: rect, - owner: self, - userData: ptr::null_mut::(), - assumeInside: false, - ]; - state.tracking_rect = Some(tracking_rect); - - // Emit resize event here rather than from windowDidResize because: - // 1. When a new window is created as a tab, the frame size may change without a window resize occurring. - // 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height). - let logical_size = - LogicalSize::new(rect.size.width as f64, rect.size.height as f64); - let size = logical_size.to_physical::(state.get_scale_factor()); - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Resized(size), - })); + if let Some(tracking_rect) = self.state.tracking_rect.take() { + self.removeTrackingRect(tracking_rect); } + + let rect = self.visibleRect(); + let tracking_rect = self.add_tracking_rect(rect, false); + self.state.tracking_rect = Some(tracking_rect); + + // Emit resize event here rather than from windowDidResize because: + // 1. When a new window is created as a tab, the frame size may change without a window resize occurring. + // 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height). + let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); + let size = logical_size.to_physical::(self.scale_factor()); + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::Resized(size), + })); } #[sel(drawRect:)] - fn draw_rect(&self, rect: NSRect) { + fn draw_rect(&mut self, rect: NSRect) { trace_scope!("drawRect:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - AppState::handle_redraw(WindowId(get_window_id(state.ns_window))); + AppState::handle_redraw(self.window_id()); + unsafe { let _: () = msg_send![super(self), drawRect: rect]; } } @@ -428,13 +255,9 @@ declare_class!( #[sel(resetCursorRects)] fn reset_cursor_rects(&self) { trace_scope!("resetCursorRects"); - let state = unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - &mut *(state_ptr as *mut ViewState) - }; - let bounds = self.bounds(); - let cursor_state = state.cursor_state.lock().unwrap(); + let cursor_state = self.state.cursor_state.lock().unwrap(); + // We correctly invoke `addCursorRect` only from inside `resetCursorRects` if cursor_state.visible { self.addCursorRect(bounds, &cursor_state.cursor); } else { @@ -447,23 +270,17 @@ declare_class!( #[sel(hasMarkedText)] fn has_marked_text(&self) -> bool { trace_scope!("hasMarkedText"); - unsafe { - let marked_text: id = *self.ivar("markedText"); - marked_text.length() > 0 - } + self.marked_text.len_utf16() > 0 } #[sel(markedRange)] fn marked_range(&self) -> NSRange { trace_scope!("markedRange"); - unsafe { - let marked_text: id = *self.ivar("markedText"); - let length = marked_text.length(); - if length > 0 { - NSRange::new(0, length) - } else { - util::EMPTY_RANGE - } + let length = self.marked_text.len_utf16(); + if length > 0 { + NSRange::new(0, length) + } else { + util::EMPTY_RANGE } } @@ -476,93 +293,88 @@ declare_class!( #[sel(setMarkedText:selectedRange:replacementRange:)] fn set_marked_text( &mut self, - string: id, + string: &NSObject, _selected_range: NSRange, _replacement_range: NSRange, ) { trace_scope!("setMarkedText:selectedRange:replacementRange:"); - unsafe { - // Get pre-edit text - let marked_text_ref: &mut id = self.ivar_mut("markedText"); - - // Update markedText - let _: () = msg_send![*marked_text_ref, release]; - let marked_text = NSMutableAttributedString::alloc(nil); - let has_attr = msg_send![string, isKindOfClass: class!(NSAttributedString)]; - if has_attr { - marked_text.initWithAttributedString(string); - } else { - marked_text.initWithString(string); - }; - *marked_text_ref = marked_text; - // Update ViewState with new marked text - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - let preedit_string = id_to_string_lossy(string); - - // Notify IME is active if application still doesn't know it. - if state.ime_state == ImeState::Disabled { - state.input_source = self.current_input_source(); - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Enabled), - })); - } - - // Don't update state to preedit when we've just commited a string, since the following - // preedit string will be None anyway. - if state.ime_state != ImeState::Commited { - state.ime_state = ImeState::Preedit; - } + // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. + let (marked_text, preedit_string) = if string.is_kind_of::() { + let string: *const NSObject = string; + let string: *const NSAttributedString = string.cast(); + let string = unsafe { &*string }; + ( + NSMutableAttributedString::from_attributed_nsstring(string), + string.string().to_string(), + ) + } else { + let string: *const NSObject = string; + let string: *const NSString = string.cast(); + let string = unsafe { &*string }; + ( + NSMutableAttributedString::from_nsstring(string), + string.to_string(), + ) + }; - // Empty string basically means that there's no preedit, so indicate that by sending - // `None` cursor range. - let cursor_range = if preedit_string.is_empty() { - None - } else { - Some((preedit_string.len(), preedit_string.len())) - }; + // Update marked text + *self.marked_text = marked_text; - // Send WindowEvent for updating marked text + // Notify IME is active if application still doesn't know it. + if self.state.ime_state == ImeState::Disabled { + self.state.input_source = self.current_input_source(); AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range)), + window_id: self.window_id(), + event: WindowEvent::Ime(Ime::Enabled), })); } + + // Don't update self.state to preedit when we've just commited a string, since the following + // preedit string will be None anyway. + if self.state.ime_state != ImeState::Commited { + self.state.ime_state = ImeState::Preedit; + } + + // Empty string basically means that there's no preedit, so indicate that by sending + // `None` cursor range. + let cursor_range = if preedit_string.is_empty() { + None + } else { + Some((preedit_string.len(), preedit_string.len())) + }; + + // Send WindowEvent for updating marked text + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range)), + })); } #[sel(unmarkText)] - fn unmark_text(&self) { + fn unmark_text(&mut self) { trace_scope!("unmarkText"); - unsafe { - let marked_text: id = *self.ivar("markedText"); - let mutable_string = marked_text.mutableString(); - let s: id = msg_send![class!(NSString), new]; - let _: () = msg_send![mutable_string, setString: s]; - let _: () = msg_send![s, release]; - let input_context: &Object = msg_send![self, inputContext]; - let _: () = msg_send![input_context, discardMarkedText]; - - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), - })); - if state.is_ime_enabled() { - // Leave the Preedit state - state.ime_state = ImeState::Enabled; - } else { - warn!("Expected to have IME enabled when receiving unmarkText"); - } + *self.marked_text = NSMutableAttributedString::new(); + + let input_context = self.inputContext().expect("input context"); + input_context.discardMarkedText(); + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), + })); + if self.is_ime_enabled() { + // Leave the Preedit self.state + self.state.ime_state = ImeState::Enabled; + } else { + warn!("Expected to have IME enabled when receiving unmarkText"); } } #[sel(validAttributesForMarkedText)] - fn valid_attributes_for_marked_text(&self) -> id { + fn valid_attributes_for_marked_text(&self) -> *const NSArray { trace_scope!("validAttributesForMarkedText"); - unsafe { msg_send![class!(NSArray), array] } + Id::autorelease_return(NSArray::new()) } #[sel(attributedSubstringForProposedRange:actualRange:)] @@ -570,9 +382,9 @@ declare_class!( &self, _range: NSRange, _actual_range: *mut c_void, // *mut NSRange - ) -> id { + ) -> *const NSAttributedString { trace_scope!("attributedSubstringForProposedRange:actualRange:"); - nil + ptr::null() } #[sel(characterIndexForPoint:)] @@ -588,278 +400,244 @@ declare_class!( _actual_range: *mut c_void, // *mut NSRange ) -> NSRect { trace_scope!("firstRectForCharacterRange:actualRange:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - let content_rect = NSWindow::contentRectForFrameRect_( - state.ns_window, - NSWindow::frame(state.ns_window), - ); - let base_x = content_rect.origin.x as f64; - let base_y = (content_rect.origin.y + content_rect.size.height) as f64; - let x = base_x + state.ime_position.x; - let y = base_y - state.ime_position.y; - // This is not ideal: We _should_ return a different position based on - // the currently selected character (which varies depending on the type - // and size of the character), but in the current `winit` API there is - // no way to express this. Same goes for the `NSSize`. - NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0)) - } + let window = self.window(); + let content_rect = window.contentRectForFrameRect(window.frame()); + let base_x = content_rect.origin.x as f64; + let base_y = (content_rect.origin.y + content_rect.size.height) as f64; + let x = base_x + self.state.ime_position.x; + let y = base_y - self.state.ime_position.y; + // This is not ideal: We _should_ return a different position based on + // the currently selected character (which varies depending on the type + // and size of the character), but in the current `winit` API there is + // no way to express this. Same goes for the `NSSize`. + NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0)) } #[sel(insertText:replacementRange:)] - fn insert_text(&self, string: id, _replacement_range: NSRange) { + fn insert_text(&mut self, string: &NSObject, _replacement_range: NSRange) { trace_scope!("insertText:replacementRange:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - let string = id_to_string_lossy(string); - - let is_control = string.chars().next().map_or(false, |c| c.is_control()); + // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. + let string = if string.is_kind_of::() { + let string: *const NSObject = string; + let string: *const NSAttributedString = string.cast(); + unsafe { &*string }.string().to_string() + } else { + let string: *const NSObject = string; + let string: *const NSString = string.cast(); + unsafe { &*string }.to_string() + }; - // We don't need this now, but it's here if that changes. - //let event: id = msg_send![NSApp(), currentEvent]; + let is_control = string.chars().next().map_or(false, |c| c.is_control()); - if state.is_ime_enabled() && !is_control { - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Commit(string)), - })); - state.ime_state = ImeState::Commited; - } + if self.is_ime_enabled() && !is_control { + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::Ime(Ime::Commit(string)), + })); + self.state.ime_state = ImeState::Commited; } } + // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human + // readable" character happens, i.e. newlines, tabs, and Ctrl+C. #[sel(doCommandBySelector:)] - fn do_command_by_selector(&self, _command: Sel) { + fn do_command_by_selector(&mut self, _command: Sel) { trace_scope!("doCommandBySelector:"); - // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human - // readable" character happens, i.e. newlines, tabs, and Ctrl+C. - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - // We shouldn't forward any character from just commited text, since we'll end up sending - // it twice with some IMEs like Korean one. We'll also always send `Enter` in that case, - // which is not desired given it was used to confirm IME input. - if state.ime_state == ImeState::Commited { - return; - } + // We shouldn't forward any character from just commited text, since we'll end up sending + // it twice with some IMEs like Korean one. We'll also always send `Enter` in that case, + // which is not desired given it was used to confirm IME input. + if self.state.ime_state == ImeState::Commited { + return; + } - state.forward_key_to_app = true; + self.state.forward_key_to_app = true; - let has_marked_text = msg_send![self, hasMarkedText]; - if has_marked_text && state.ime_state == ImeState::Preedit { - // Leave preedit so that we also report the keyup for this key - state.ime_state = ImeState::Enabled; - } + if self.hasMarkedText() && self.state.ime_state == ImeState::Preedit { + // Leave preedit so that we also report the keyup for this key + self.state.ime_state = ImeState::Enabled; } } } unsafe impl WinitView { #[sel(keyDown:)] - fn key_down(&self, event: id) { + fn key_down(&mut self, event: &NSEvent) { trace_scope!("keyDown:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - let window_id = WindowId(get_window_id(state.ns_window)); - - let input_source = self.current_input_source(); - if state.input_source != input_source && state.is_ime_enabled() { - state.ime_state = ImeState::Disabled; - state.input_source = input_source; - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Disabled), - })); - } - let was_in_preedit = state.ime_state == ImeState::Preedit; - - let characters = get_characters(event, false); - state.forward_key_to_app = false; - - // The `interpretKeyEvents` function might call - // `setMarkedText`, `insertText`, and `doCommandBySelector`. - // It's important that we call this before queuing the KeyboardInput, because - // we must send the `KeyboardInput` event during IME if it triggered - // `doCommandBySelector`. (doCommandBySelector means that the keyboard input - // is not handled by IME and should be handled by the application) - let mut text_commited = false; - if state.ime_allowed { - let events_for_nsview: id = msg_send![class!(NSArray), arrayWithObject: event]; - let _: () = msg_send![self, interpretKeyEvents: events_for_nsview]; - - // Using a compiler fence because `interpretKeyEvents` might call - // into functions that modify the `ViewState`, but the compiler - // doesn't know this. Without the fence, the compiler may think that - // some of the reads (eg `state.ime_state`) that happen after this - // point are not needed. - compiler_fence(Ordering::SeqCst); - - // If the text was commited we must treat the next keyboard event as IME related. - if state.ime_state == ImeState::Commited { - state.ime_state = ImeState::Enabled; - text_commited = true; - } + let window_id = self.window_id(); + + let input_source = self.current_input_source(); + if self.state.input_source != input_source && self.is_ime_enabled() { + self.state.ime_state = ImeState::Disabled; + self.state.input_source = input_source; + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::Ime(Ime::Disabled), + })); + } + let was_in_preedit = self.state.ime_state == ImeState::Preedit; + + let characters = get_characters(event, false); + self.state.forward_key_to_app = false; + + // The `interpretKeyEvents` function might call + // `setMarkedText`, `insertText`, and `doCommandBySelector`. + // It's important that we call this before queuing the KeyboardInput, because + // we must send the `KeyboardInput` event during IME if it triggered + // `doCommandBySelector`. (doCommandBySelector means that the keyboard input + // is not handled by IME and should be handled by the application) + let mut text_commited = false; + if self.state.ime_allowed { + let events_for_nsview = NSArray::from_slice(&[event.copy()]); + unsafe { self.interpretKeyEvents(&events_for_nsview) }; + + // If the text was commited we must treat the next keyboard event as IME related. + if self.state.ime_state == ImeState::Commited { + self.state.ime_state = ImeState::Enabled; + text_commited = true; } + } - let now_in_preedit = state.ime_state == ImeState::Preedit; + let now_in_preedit = self.state.ime_state == ImeState::Preedit; - let scancode = get_scancode(event) as u32; - let virtual_keycode = retrieve_keycode(event); + let scancode = event.scancode() as u32; + let virtual_keycode = retrieve_keycode(event); - update_potentially_stale_modifiers(state, event); + self.update_potentially_stale_modifiers(event); - let ime_related = was_in_preedit || now_in_preedit || text_commited; + let ime_related = was_in_preedit || now_in_preedit || text_commited; - if !ime_related || state.forward_key_to_app || !state.ime_allowed { - #[allow(deprecated)] - let window_event = Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Pressed, - scancode, - virtual_keycode, - modifiers: event_mods(event), - }, - is_synthetic: false, + if !ime_related || self.state.forward_key_to_app || !self.state.ime_allowed { + #[allow(deprecated)] + let window_event = Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Pressed, + scancode, + virtual_keycode, + modifiers: event_mods(event), }, - }; + is_synthetic: false, + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); - for character in characters.chars().filter(|c| !is_corporate_character(*c)) { - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event: WindowEvent::ReceivedCharacter(character), - })); - } + for character in characters.chars().filter(|c| !is_corporate_character(*c)) { + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::ReceivedCharacter(character), + })); } } } #[sel(keyUp:)] - fn key_up(&self, event: id) { + fn key_up(&mut self, event: &NSEvent) { trace_scope!("keyUp:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let scancode = get_scancode(event) as u32; - let virtual_keycode = retrieve_keycode(event); - - update_potentially_stale_modifiers(state, event); - - // We want to send keyboard input when we are not currently in preedit - if state.ime_state != ImeState::Preedit { - #[allow(deprecated)] - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Released, - scancode, - virtual_keycode, - modifiers: event_mods(event), - }, - is_synthetic: false, + let scancode = event.scancode() as u32; + let virtual_keycode = retrieve_keycode(event); + + self.update_potentially_stale_modifiers(event); + + // We want to send keyboard input when we are not currently in preedit + if self.state.ime_state != ImeState::Preedit { + #[allow(deprecated)] + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Released, + scancode, + virtual_keycode, + modifiers: event_mods(event), }, - }; + is_synthetic: false, + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } } #[sel(flagsChanged:)] - fn flags_changed(&self, event: id) { + fn flags_changed(&mut self, event: &NSEvent) { trace_scope!("flagsChanged:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let mut events = VecDeque::with_capacity(4); - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSShiftKeyMask, - state.modifiers.shift(), - ) { - state.modifiers.toggle(ModifiersState::SHIFT); - events.push_back(window_event); - } + let mut events = VecDeque::with_capacity(4); - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSControlKeyMask, - state.modifiers.ctrl(), - ) { - state.modifiers.toggle(ModifiersState::CTRL); - events.push_back(window_event); - } + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSShiftKeyMask, + self.state.modifiers.shift(), + ) { + self.state.modifiers.toggle(ModifiersState::SHIFT); + events.push_back(window_event); + } - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSCommandKeyMask, - state.modifiers.logo(), - ) { - state.modifiers.toggle(ModifiersState::LOGO); - events.push_back(window_event); - } + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSControlKeyMask, + self.state.modifiers.ctrl(), + ) { + self.state.modifiers.toggle(ModifiersState::CTRL); + events.push_back(window_event); + } - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSAlternateKeyMask, - state.modifiers.alt(), - ) { - state.modifiers.toggle(ModifiersState::ALT); - events.push_back(window_event); - } + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSCommandKeyMask, + self.state.modifiers.logo(), + ) { + self.state.modifiers.toggle(ModifiersState::LOGO); + events.push_back(window_event); + } - let window_id = WindowId(get_window_id(state.ns_window)); + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSAlternateKeyMask, + self.state.modifiers.alt(), + ) { + self.state.modifiers.toggle(ModifiersState::ALT); + events.push_back(window_event); + } - for event in events { - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event, - })); - } + let window_id = self.window_id(); + for event in events { AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(state.modifiers), + event, })); } + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(self.state.modifiers), + })); } #[sel(insertTab:)] - fn insert_tab(&self, _sender: id) { + fn insert_tab(&self, _sender: *const Object) { trace_scope!("insertTab:"); - unsafe { - let window: id = msg_send![self, window]; - let first_responder: id = msg_send![window, firstResponder]; - let self_ptr = self as *const _ as *mut _; - if first_responder == self_ptr { - let _: () = msg_send![window, selectNextKeyView: self]; + let window = self.window(); + if let Some(first_responder) = window.firstResponder() { + if *first_responder == ***self { + window.selectNextKeyView(Some(self)) } } } #[sel(insertBackTab:)] - fn insert_back_tab(&self, _sender: id) { + fn insert_back_tab(&self, _sender: *const Object) { trace_scope!("insertBackTab:"); - unsafe { - let window: id = msg_send![self, window]; - let first_responder: id = msg_send![window, firstResponder]; - let self_ptr = self as *const _ as *mut _; - if first_responder == self_ptr { - let _: () = msg_send![window, selectPreviousKeyView: self]; + let window = self.window(); + if let Some(first_responder) = window.firstResponder() { + if *first_responder == ***self { + window.selectPreviousKeyView(Some(self)) } } } @@ -867,303 +645,267 @@ declare_class!( // Allows us to receive Cmd-. (the shortcut for closing a dialog) // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 #[sel(cancelOperation:)] - fn cancel_operation(&self, _sender: id) { + fn cancel_operation(&mut self, _sender: *const Object) { trace_scope!("cancelOperation:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let scancode = 0x2f; - let virtual_keycode = scancode_to_keycode(scancode); - debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period)); + let scancode = 0x2f; + let virtual_keycode = scancode_to_keycode(scancode); + debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period)); - let event: id = msg_send![NSApp(), currentEvent]; + let event = NSApp() + .currentEvent() + .expect("could not find current event"); - update_potentially_stale_modifiers(state, event); + self.update_potentially_stale_modifiers(&event); - #[allow(deprecated)] - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Pressed, - scancode: scancode as _, - virtual_keycode, - modifiers: event_mods(event), - }, - is_synthetic: false, + #[allow(deprecated)] + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Pressed, + scancode: scancode as _, + virtual_keycode, + modifiers: event_mods(&event), }, - }; + is_synthetic: false, + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } #[sel(mouseDown:)] - fn mouse_down(&self, event: id) { + fn mouse_down(&mut self, event: &NSEvent) { trace_scope!("mouseDown:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Left, ElementState::Pressed); + self.mouse_motion(event); + self.mouse_click(event, MouseButton::Left, ElementState::Pressed); } #[sel(mouseUp:)] - fn mouse_up(&self, event: id) { + fn mouse_up(&mut self, event: &NSEvent) { trace_scope!("mouseUp:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Left, ElementState::Released); + self.mouse_motion(event); + self.mouse_click(event, MouseButton::Left, ElementState::Released); } #[sel(rightMouseDown:)] - fn right_mouse_down(&self, event: id) { + fn right_mouse_down(&mut self, event: &NSEvent) { trace_scope!("rightMouseDown:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Right, ElementState::Pressed); + self.mouse_motion(event); + self.mouse_click(event, MouseButton::Right, ElementState::Pressed); } #[sel(rightMouseUp:)] - fn right_mouse_up(&self, event: id) { + fn right_mouse_up(&mut self, event: &NSEvent) { trace_scope!("rightMouseUp:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Right, ElementState::Released); + self.mouse_motion(event); + self.mouse_click(event, MouseButton::Right, ElementState::Released); } #[sel(otherMouseDown:)] - fn other_mouse_down(&self, event: id) { + fn other_mouse_down(&mut self, event: &NSEvent) { trace_scope!("otherMouseDown:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Middle, ElementState::Pressed); + self.mouse_motion(event); + self.mouse_click(event, MouseButton::Middle, ElementState::Pressed); } #[sel(otherMouseUp:)] - fn other_mouse_up(&self, event: id) { + fn other_mouse_up(&mut self, event: &NSEvent) { trace_scope!("otherMouseUp:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Middle, ElementState::Released); + self.mouse_motion(event); + self.mouse_click(event, MouseButton::Middle, ElementState::Released); } // No tracing on these because that would be overly verbose #[sel(mouseMoved:)] - fn mouse_moved(&self, event: id) { - mouse_motion(self, event); + fn mouse_moved(&mut self, event: &NSEvent) { + self.mouse_motion(event); } #[sel(mouseDragged:)] - fn mouse_dragged(&self, event: id) { - mouse_motion(self, event); + fn mouse_dragged(&mut self, event: &NSEvent) { + self.mouse_motion(event); } #[sel(rightMouseDragged:)] - fn right_mouse_dragged(&self, event: id) { - mouse_motion(self, event); + fn right_mouse_dragged(&mut self, event: &NSEvent) { + self.mouse_motion(event); } #[sel(otherMouseDragged:)] - fn other_mouse_dragged(&self, event: id) { - mouse_motion(self, event); + fn other_mouse_dragged(&mut self, event: &NSEvent) { + self.mouse_motion(event); } #[sel(mouseEntered:)] - fn mouse_entered(&self, _event: id) { + fn mouse_entered(&self, _event: &NSEvent) { trace_scope!("mouseEntered:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let enter_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::CursorEntered { - device_id: DEVICE_ID, - }, - }; + let enter_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::CursorEntered { + device_id: DEVICE_ID, + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(enter_event)); - } + AppState::queue_event(EventWrapper::StaticEvent(enter_event)); } #[sel(mouseExited:)] - fn mouse_exited(&self, _event: id) { + fn mouse_exited(&self, _event: &NSEvent) { trace_scope!("mouseExited:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::CursorLeft { - device_id: DEVICE_ID, - }, - }; + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::CursorLeft { + device_id: DEVICE_ID, + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } #[sel(scrollWheel:)] - fn scroll_wheel(&self, event: id) { + fn scroll_wheel(&mut self, event: &NSEvent) { trace_scope!("scrollWheel:"); - mouse_motion(self, event); + self.mouse_motion(event); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let delta = { - let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); - if Bool::from_raw(event.hasPreciseScrollingDeltas()).as_bool() { - let delta = - LogicalPosition::new(x, y).to_physical(state.get_scale_factor()); - MouseScrollDelta::PixelDelta(delta) - } else { - MouseScrollDelta::LineDelta(x as f32, y as f32) - } - }; - - // The "momentum phase," if any, has higher priority than touch phase (the two should - // be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum - // phase is recorded (or rather, the started/ended cases of the momentum phase) then we - // report the touch phase. - let phase = - match event.momentumPhase() { - NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => { - TouchPhase::Started - } - NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => { - TouchPhase::Ended - } - _ => match event.phase() { - NSEventPhase::NSEventPhaseMayBegin - | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, - NSEventPhase::NSEventPhaseEnded - | NSEventPhase::NSEventPhaseCancelled => TouchPhase::Ended, - _ => TouchPhase::Moved, - }, - }; + let delta = { + let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); + if event.hasPreciseScrollingDeltas() { + let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor()); + MouseScrollDelta::PixelDelta(delta) + } else { + MouseScrollDelta::LineDelta(x as f32, y as f32) + } + }; - let device_event = Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::MouseWheel { delta }, - }; + // The "momentum phase," if any, has higher priority than touch phase (the two should + // be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum + // phase is recorded (or rather, the started/ended cases of the momentum phase) then we + // report the touch phase. + let phase = match event.momentumPhase() { + NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => { + TouchPhase::Started + } + NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => { + TouchPhase::Ended + } + _ => match event.phase() { + NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => { + TouchPhase::Started + } + NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => { + TouchPhase::Ended + } + _ => TouchPhase::Moved, + }, + }; - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); + let device_event = Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::MouseWheel { delta }, + }; - update_potentially_stale_modifiers(state, event); + self.update_potentially_stale_modifiers(event); - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, - delta, - phase, - modifiers: event_mods(event), - }, - }; + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta, + phase, + modifiers: event_mods(event), + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(device_event)); - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } + AppState::queue_event(EventWrapper::StaticEvent(device_event)); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } #[sel(magnifyWithEvent:)] - fn magnify_with_event(&self, event: id) { + fn magnify_with_event(&self, event: &NSEvent) { trace_scope!("magnifyWithEvent:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let delta = event.magnification(); - let phase = match event.phase() { - NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, - NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, - NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, - NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, - _ => return, - }; + let delta = event.magnification(); + let phase = match event.phase() { + NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, + NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => return, + }; - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::TouchpadMagnify { - device_id: DEVICE_ID, - delta, - phase, - }, - }; + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::TouchpadMagnify { + device_id: DEVICE_ID, + delta, + phase, + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } #[sel(rotateWithEvent:)] - fn rotate_with_event(&self, event: id) { + fn rotate_with_event(&self, event: &NSEvent) { trace_scope!("rotateWithEvent:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let delta = event.rotation(); - let phase = match event.phase() { - NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, - NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, - NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, - NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, - _ => return, - }; + let delta = event.rotation(); + let phase = match event.phase() { + NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, + NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => return, + }; - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::TouchpadRotate { - device_id: DEVICE_ID, - delta, - phase, - }, - }; + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::TouchpadRotate { + device_id: DEVICE_ID, + delta, + phase, + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } #[sel(pressureChangeWithEvent:)] - fn pressure_change_with_event(&self, event: id) { + fn pressure_change_with_event(&mut self, event: &NSEvent) { trace_scope!("pressureChangeWithEvent:"); - mouse_motion(self, event); - - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); + self.mouse_motion(event); - let pressure = event.pressure(); - let stage = event.stage(); + let pressure = event.pressure(); + let stage = event.stage(); - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::TouchpadPressure { - device_id: DEVICE_ID, - pressure, - stage: stage as i64, - }, - }; + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::TouchpadPressure { + device_id: DEVICE_ID, + pressure, + stage: stage as i64, + }, + }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } // Allows us to receive Ctrl-Tab and Ctrl-Esc. // Note that this *doesn't* help with any missing Cmd inputs. // https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 #[sel(_wantsKeyDownForEvent:)] - fn wants_key_down_for_event(&self, _event: id) -> bool { + fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool { trace_scope!("_wantsKeyDownForEvent:"); true } #[sel(acceptsFirstMouse:)] - fn accepts_first_mouse(&self, _event: id) -> bool { + fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { trace_scope!("acceptsFirstMouse:"); true } @@ -1171,9 +913,131 @@ declare_class!( ); impl WinitView { + pub(super) fn new(window_id: *mut Object) -> IdRef { + let obj: Id<_, Owned> = + unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithId: window_id] }; + let mut obj = ManuallyDrop::new(obj); + IdRef::new(Id::as_mut_ptr(&mut obj)) + } + + fn window(&self) -> Id { + // TODO: Simply use `window` property on `NSView`. + // That only returns a window _after_ the view has been attached though! + // (which is incompatible with `frameDidChange:`) + // + // unsafe { msg_send_id![self, window] } + (*self._ns_window).clone() + } + + fn window_id(&self) -> WindowId { + let ptr = Id::as_ptr(&*self._ns_window); + WindowId(get_window_id(ptr as _)) + } + + fn scale_factor(&self) -> f64 { + self.window().backingScaleFactor() as f64 + } + + fn is_ime_enabled(&self) -> bool { + !matches!(self.state.ime_state, ImeState::Disabled) + } + fn current_input_source(&self) -> String { - let input_context: id = unsafe { msg_send![self, inputContext] }; - let input_source: id = unsafe { msg_send![input_context, selectedKeyboardInputSource] }; - unsafe { id_to_string_lossy(input_source) } + self.inputContext() + .expect("input context") + .selectedKeyboardInputSource() + .map(|input_source| input_source.to_string()) + .unwrap_or_else(String::new) + } + + pub(super) fn set_ime_allowed(&mut self, ime_allowed: bool) { + if self.state.ime_allowed == ime_allowed { + return; + } + self.state.ime_allowed = ime_allowed; + if self.state.ime_allowed { + return; + } + + // Clear markedText + *self.marked_text = NSMutableAttributedString::new(); + + if self.state.ime_state != ImeState::Disabled { + self.state.ime_state = ImeState::Disabled; + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::Ime(Ime::Disabled), + })); + } + } + + pub(super) fn set_ime_position(&mut self, position: LogicalPosition) { + self.state.ime_position = position; + let input_context = self.inputContext().expect("input context"); + input_context.invalidateCharacterCoordinates(); + } + + // Update `state.modifiers` if `event` has something different + fn update_potentially_stale_modifiers(&mut self, event: &NSEvent) { + let event_modifiers = event_mods(event); + if self.state.modifiers != event_modifiers { + self.state.modifiers = event_modifiers; + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::ModifiersChanged(self.state.modifiers), + })); + } + } + + fn mouse_click(&mut self, event: &NSEvent, button: MouseButton, button_state: ElementState) { + self.update_potentially_stale_modifiers(event); + + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::MouseInput { + device_id: DEVICE_ID, + state: button_state, + button, + modifiers: event_mods(event), + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + + fn mouse_motion(&mut self, event: &NSEvent) { + let window_point = event.locationInWindow(); + let view_point = self.convertPoint_fromView(window_point, None); + let view_rect = self.frame(); + + if view_point.x.is_sign_negative() + || view_point.y.is_sign_negative() + || view_point.x > view_rect.size.width + || view_point.y > view_rect.size.height + { + let mouse_buttons_down = NSEvent::pressedMouseButtons(); + if mouse_buttons_down == 0 { + // Point is outside of the client area (view) and no buttons are pressed + return; + } + } + + let x = view_point.x as f64; + let y = view_rect.size.height as f64 - view_point.y as f64; + let logical_position = LogicalPosition::new(x, y); + + self.update_potentially_stale_modifiers(event); + + let window_event = Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::CursorMoved { + device_id: DEVICE_ID, + position: logical_position.to_physical(self.scale_factor()), + modifiers: event_mods(event), + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 0395482e6e..754d48a689 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -26,7 +26,7 @@ use crate::{ ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, - view::{self, new_view, ViewState}, + view::{ViewState, WinitView}, window_delegate::new_delegate, OsError, }, @@ -108,34 +108,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes { } } -unsafe fn create_view( - ns_window: id, - pl_attribs: &PlatformSpecificWindowBuilderAttributes, -) -> Option { - let ns_view = new_view(ns_window); - ns_view.non_nil().map(|ns_view| { - // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until - // macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid - // always the default system value in favour of the user's code - if !pl_attribs.disallow_hidpi { - ns_view.setWantsBestResolutionOpenGLSurface_(Bool::YES.as_raw()); - } else { - ns_view.setWantsBestResolutionOpenGLSurface_(Bool::NO.as_raw()); - } - - // On Mojave, views automatically become layer-backed shortly after being added to - // a window. Changing the layer-backedness of a view breaks the association between - // the view and its associated OpenGL context. To work around this, on Mojave we - // explicitly make the view layer-backed up front so that AppKit doesn't do it - // itself and break the association with its context. - if f64::floor(appkit::NSAppKitVersionNumber) > appkit::NSAppKitVersionNumber10_12 { - ns_view.setWantsLayer(Bool::YES.as_raw()); - } - - ns_view - }) -} - fn create_window( attrs: &WindowAttributes, pl_attrs: &PlatformSpecificWindowBuilderAttributes, @@ -392,8 +364,26 @@ impl UnownedWindow { let ns_window = create_window(&win_attribs, &pl_attribs) .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; - let ns_view = unsafe { create_view(*ns_window, &pl_attribs) } - .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSView`")))?; + let ns_view = WinitView::new(*ns_window); + + // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until + // macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid + // always the default system value in favour of the user's code + if !pl_attribs.disallow_hidpi { + unsafe { ns_view.setWantsBestResolutionOpenGLSurface_(Bool::YES.as_raw()) }; + } else { + unsafe { ns_view.setWantsBestResolutionOpenGLSurface_(Bool::NO.as_raw()) }; + } + + // On Mojave, views automatically become layer-backed shortly after being added to + // a window. Changing the layer-backedness of a view breaks the association between + // the view and its associated OpenGL context. To work around this, on Mojave we + // explicitly make the view layer-backed up front so that AppKit doesn't do it + // itself and break the association with its context. + if f64::floor(unsafe { appkit::NSAppKitVersionNumber }) > appkit::NSAppKitVersionNumber10_12 + { + unsafe { ns_view.setWantsLayer(Bool::YES.as_raw()) }; + } // Configure the new view as the "key view" for the window unsafe { @@ -611,7 +601,7 @@ impl UnownedWindow { pub fn set_cursor_icon(&self, icon: CursorIcon) { let view_state: &ViewState = unsafe { let ns_view: &Object = (*self.ns_view).as_ref().expect("failed to deref"); - let state_ptr: *const c_void = *ns_view.ivar("winitState"); + let state_ptr: *const c_void = *ns_view.ivar("state"); &*(state_ptr as *const ViewState) }; let mut cursor_state = view_state.cursor_state.lock().unwrap(); @@ -644,7 +634,7 @@ impl UnownedWindow { pub fn set_cursor_visible(&self, visible: bool) { let view_state: &ViewState = unsafe { let ns_view: &Object = (*self.ns_view).as_ref().expect("failed to deref"); - let state_ptr: *const c_void = *ns_view.ivar("winitState"); + let state_ptr: *const c_void = *ns_view.ivar("state"); &*(state_ptr as *const ViewState) }; let mut cursor_state = view_state.cursor_state.lock().unwrap(); @@ -1059,14 +1049,14 @@ impl UnownedWindow { pub fn set_ime_position(&self, spot: Position) { let scale_factor = self.scale_factor(); let logical_spot = spot.to_logical(scale_factor); - unsafe { view::set_ime_position(*self.ns_view, logical_spot) }; + let view: *mut WinitView = self.ns_view.cast(); + unsafe { &mut *view }.set_ime_position(logical_spot); } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { - unsafe { - view::set_ime_allowed(*self.ns_view, allowed); - } + let view: *mut WinitView = self.ns_view.cast(); + unsafe { &mut *view }.set_ime_allowed(allowed); } #[inline] diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 73cf41c797..f16b64baa6 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -230,12 +230,12 @@ declare_class!( // easily fall out of synchrony with reality. This requires us to emit // a synthetic ModifiersChanged event when we lose focus. // - // Here we (very unsafely) acquire the winitState (a ViewState) from the + // Here we (very unsafely) acquire the state (a ViewState) from the // Object referenced by state.ns_view (an IdRef, which is dereferenced // to an id) let view_state: &mut ViewState = unsafe { let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref"); - let state_ptr: *mut c_void = *ns_view.ivar("winitState"); + let state_ptr: *mut c_void = *ns_view.ivar("state"); &mut *(state_ptr as *mut ViewState) }; From 1856fa8bf4707a620d744b9cbe0b2d7c60049e6a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 2 Sep 2022 23:57:06 +0200 Subject: [PATCH 3/8] Refactor Window to use much less unsafe --- src/platform/macos.rs | 3 +- src/platform_impl/macos/appkit/button.rs | 14 + src/platform_impl/macos/appkit/color.rs | 28 + src/platform_impl/macos/appkit/control.rs | 14 + src/platform_impl/macos/appkit/mod.rs | 16 +- src/platform_impl/macos/appkit/pasteboard.rs | 7 + src/platform_impl/macos/appkit/screen.rs | 64 ++ src/platform_impl/macos/appkit/view.rs | 7 +- src/platform_impl/macos/appkit/window.rs | 242 ++++- src/platform_impl/macos/ffi.rs | 36 - src/platform_impl/macos/mod.rs | 22 +- src/platform_impl/macos/monitor.rs | 62 +- src/platform_impl/macos/util/async.rs | 127 ++- src/platform_impl/macos/util/mod.rs | 30 +- src/platform_impl/macos/view.rs | 15 +- src/platform_impl/macos/window.rs | 886 ++++++++----------- src/platform_impl/macos/window_delegate.rs | 97 +- 17 files changed, 925 insertions(+), 745 deletions(-) create mode 100644 src/platform_impl/macos/appkit/button.rs create mode 100644 src/platform_impl/macos/appkit/color.rs create mode 100644 src/platform_impl/macos/appkit/control.rs create mode 100644 src/platform_impl/macos/appkit/pasteboard.rs create mode 100644 src/platform_impl/macos/appkit/screen.rs diff --git a/src/platform/macos.rs b/src/platform/macos.rs index d7fe84a257..309dbda0e3 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -1,3 +1,4 @@ +use objc2::rc::Id; use std::os::raw::c_void; use crate::{ @@ -249,7 +250,7 @@ impl MonitorHandleExtMacOS for MonitorHandle { } fn ns_screen(&self) -> Option<*mut c_void> { - self.inner.ns_screen().map(|s| s as *mut c_void) + self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _) } } diff --git a/src/platform_impl/macos/appkit/button.rs b/src/platform_impl/macos/appkit/button.rs new file mode 100644 index 0000000000..c4a61ca1aa --- /dev/null +++ b/src/platform_impl/macos/appkit/button.rs @@ -0,0 +1,14 @@ +use objc2::foundation::NSObject; +use objc2::{extern_class, ClassType}; + +use super::{NSControl, NSResponder, NSView}; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSButton; + + unsafe impl ClassType for NSButton { + #[inherits(NSView, NSResponder, NSObject)] + type Super = NSControl; + } +); diff --git a/src/platform_impl/macos/appkit/color.rs b/src/platform_impl/macos/appkit/color.rs new file mode 100644 index 0000000000..59399731e0 --- /dev/null +++ b/src/platform_impl/macos/appkit/color.rs @@ -0,0 +1,28 @@ +use objc2::foundation::NSObject; +use objc2::rc::{Id, Shared}; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +extern_class!( + /// An object that stores color data and sometimes opacity (alpha value). + /// + /// + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSColor; + + unsafe impl ClassType for NSColor { + type Super = NSObject; + } +); + +// SAFETY: Documentation clearly states: +// > Color objects are immutable and thread-safe +unsafe impl Send for NSColor {} +unsafe impl Sync for NSColor {} + +extern_methods!( + unsafe impl NSColor { + pub fn clear() -> Id { + unsafe { msg_send_id![Self::class(), clearColor] } + } + } +); diff --git a/src/platform_impl/macos/appkit/control.rs b/src/platform_impl/macos/appkit/control.rs new file mode 100644 index 0000000000..2dd2290288 --- /dev/null +++ b/src/platform_impl/macos/appkit/control.rs @@ -0,0 +1,14 @@ +use objc2::foundation::NSObject; +use objc2::{extern_class, ClassType}; + +use super::{NSResponder, NSView}; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSControl; + + unsafe impl ClassType for NSControl { + #[inherits(NSResponder, NSObject)] + type Super = NSView; + } +); diff --git a/src/platform_impl/macos/appkit/mod.rs b/src/platform_impl/macos/appkit/mod.rs index 003e7869cc..96d6da8574 100644 --- a/src/platform_impl/macos/appkit/mod.rs +++ b/src/platform_impl/macos/appkit/mod.rs @@ -12,19 +12,33 @@ #![allow(non_upper_case_globals)] mod application; +mod button; +mod color; +mod control; mod cursor; mod event; mod image; +mod pasteboard; mod responder; +mod screen; mod text_input_context; mod view; mod window; pub(crate) use self::application::{NSApp, NSApplication}; +pub(crate) use self::button::NSButton; +pub(crate) use self::color::NSColor; +pub(crate) use self::control::NSControl; pub(crate) use self::cursor::NSCursor; pub(crate) use self::event::{NSEvent, NSEventModifierFlags, NSEventPhase}; pub(crate) use self::image::NSImage; +pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboardType}; pub(crate) use self::responder::NSResponder; +#[allow(unused_imports)] +pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen}; pub(crate) use self::text_input_context::NSTextInputContext; pub(crate) use self::view::{NSTrackingRectTag, NSView}; -pub(crate) use self::window::NSWindow; +pub(crate) use self::window::{ + NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState, NSWindowStyleMask, + NSWindowTitleVisibility, +}; diff --git a/src/platform_impl/macos/appkit/pasteboard.rs b/src/platform_impl/macos/appkit/pasteboard.rs new file mode 100644 index 0000000000..65093b8da1 --- /dev/null +++ b/src/platform_impl/macos/appkit/pasteboard.rs @@ -0,0 +1,7 @@ +use objc2::foundation::NSString; + +pub type NSPasteboardType = NSString; + +extern "C" { + pub static NSFilenamesPboardType: &'static NSPasteboardType; +} diff --git a/src/platform_impl/macos/appkit/screen.rs b/src/platform_impl/macos/appkit/screen.rs new file mode 100644 index 0000000000..fbc9f7d05a --- /dev/null +++ b/src/platform_impl/macos/appkit/screen.rs @@ -0,0 +1,64 @@ +use objc2::foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::Object; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSScreen; + + unsafe impl ClassType for NSScreen { + type Super = NSObject; + } +); + +// TODO: Main thread marker! + +extern_methods!( + unsafe impl NSScreen { + /// The application object must have been created. + pub fn main() -> Option> { + unsafe { msg_send_id![Self::class(), mainScreen] } + } + + /// The application object must have been created. + pub fn screens() -> Id, Shared> { + unsafe { msg_send_id![Self::class(), screens] } + } + + #[sel(frame)] + pub fn frame(&self) -> NSRect; + + #[sel(visibleFrame)] + pub fn visibleFrame(&self) -> NSRect; + + pub fn deviceDescription( + &self, + ) -> Id, Shared> { + unsafe { msg_send_id![self, deviceDescription] } + } + + pub fn display_id(&self) -> u32 { + let device_description = self.deviceDescription(); + + // Retrieve the CGDirectDisplayID associated with this screen + // + // SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed + // to be an NSNumber. See documentation for `deviceDescription` for details: + // + let obj = device_description + .get(ns_string!("NSScreenNumber")) + .expect("failed getting screen display id from device description"); + let obj: *const Object = obj; + let obj: *const NSNumber = obj.cast(); + let obj: &NSNumber = unsafe { &*obj }; + + obj.as_u32() + } + + #[sel(backingScaleFactor)] + pub fn backingScaleFactor(&self) -> CGFloat; + } +); + +pub type NSDeviceDescriptionKey = NSString; diff --git a/src/platform_impl/macos/appkit/view.rs b/src/platform_impl/macos/appkit/view.rs index 3a0f2be4f8..e0e379c9b1 100644 --- a/src/platform_impl/macos/appkit/view.rs +++ b/src/platform_impl/macos/appkit/view.rs @@ -56,10 +56,10 @@ extern_methods!( unsafe impl NSView { #[sel(setWantsBestResolutionOpenGLSurface:)] - pub fn setWantsBestResolutionOpenGLSurface(&mut self, value: bool); + pub fn setWantsBestResolutionOpenGLSurface(&self, value: bool); #[sel(setWantsLayer:)] - pub fn setWantsLayer(&mut self, wants_layer: bool); + pub fn setWantsLayer(&self, wants_layer: bool); #[sel(setPostsFrameChangedNotifications:)] pub fn setPostsFrameChangedNotifications(&mut self, value: bool); @@ -85,6 +85,9 @@ extern_methods!( #[sel(addCursorRect:cursor:)] // NSCursor safe to take by shared reference since it is already immutable pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor); + + #[sel(setHidden:)] + pub fn setHidden(&self, hidden: bool); } ); diff --git a/src/platform_impl/macos/appkit/window.rs b/src/platform_impl/macos/appkit/window.rs index 49a59396bb..c745581bc2 100644 --- a/src/platform_impl/macos/appkit/window.rs +++ b/src/platform_impl/macos/appkit/window.rs @@ -1,9 +1,12 @@ -use objc2::foundation::{CGFloat, NSObject, NSRect, NSSize}; +use objc2::encode::{Encode, Encoding}; +use objc2::foundation::{ + CGFloat, NSArray, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, NSUInteger, +}; use objc2::rc::{Id, Shared}; use objc2::runtime::Object; use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; -use super::NSResponder; +use super::{NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView}; extern_class!( /// Main-Thread-Only! @@ -33,30 +36,90 @@ extern_methods!( #[sel(backingScaleFactor)] pub fn backingScaleFactor(&self) -> CGFloat; + pub fn contentView(&self) -> Id { + unsafe { msg_send_id![self, contentView] } + } + + #[sel(setContentView:)] + pub fn setContentView(&self, view: &NSView); + + #[sel(setInitialFirstResponder:)] + pub fn setInitialFirstResponder(&self, view: &NSView); + + #[sel(makeFirstResponder:)] + #[must_use] + pub fn makeFirstResponder(&self, responder: Option<&NSResponder>) -> bool; + #[sel(contentRectForFrameRect:)] pub fn contentRectForFrameRect(&self, windowFrame: NSRect) -> NSRect; + pub fn screen(&self) -> Option> { + unsafe { msg_send_id![self, screen] } + } + #[sel(setContentSize:)] pub fn setContentSize(&self, contentSize: NSSize); + #[sel(setFrameTopLeftPoint:)] + pub fn setFrameTopLeftPoint(&self, point: NSPoint); + #[sel(setMinSize:)] pub fn setMinSize(&self, minSize: NSSize); #[sel(setMaxSize:)] pub fn setMaxSize(&self, maxSize: NSSize); + #[sel(setResizeIncrements:)] + pub fn setResizeIncrements(&self, increments: NSSize); + #[sel(setFrame:display:)] pub fn setFrame_display(&self, frameRect: NSRect, flag: bool); #[sel(setMovable:)] pub fn setMovable(&self, movable: bool); + #[sel(setOpaque:)] + pub fn setOpaque(&self, opaque: bool); + + #[sel(hasShadow)] + pub fn hasShadow(&self) -> bool; + + #[sel(setHasShadow:)] + pub fn setHasShadow(&self, has_shadow: bool); + + #[sel(setIgnoresMouseEvents:)] + pub fn setIgnoresMouseEvents(&self, ignores: bool); + + #[sel(setBackgroundColor:)] + pub fn setBackgroundColor(&self, color: &NSColor); + + #[sel(styleMask)] + pub fn styleMask(&self) -> NSWindowStyleMask; + + #[sel(setStyleMask:)] + pub fn setStyleMask(&self, mask: NSWindowStyleMask); + + #[sel(registerForDraggedTypes:)] + pub fn registerForDraggedTypes(&self, types: &NSArray); + + #[sel(makeKeyAndOrderFront:)] + pub fn makeKeyAndOrderFront(&self, sender: Option<&Object>); + #[sel(miniaturize:)] pub fn miniaturize(&self, sender: Option<&Object>); #[sel(sender:)] pub fn deminiaturize(&self, sender: Option<&Object>); + #[sel(toggleFullScreen:)] + pub fn toggleFullScreen(&self, sender: Option<&Object>); + + #[sel(orderOut:)] + pub fn orderOut(&self, sender: Option<&Object>); + + #[sel(zoom:)] + pub fn zoom(&self, sender: Option<&Object>); + #[sel(selectNextKeyView:)] pub fn selectNextKeyView(&self, sender: Option<&Object>); @@ -66,5 +129,180 @@ extern_methods!( pub fn firstResponder(&self) -> Option> { unsafe { msg_send_id![self, firstResponder] } } + + pub fn standardWindowButton(&self, kind: NSWindowButton) -> Option> { + unsafe { msg_send_id![self, standardWindowButton: kind] } + } + + #[sel(setTitle:)] + pub fn setTitle(&self, title: &NSString); + + #[sel(setReleasedWhenClosed:)] + pub fn setReleasedWhenClosed(&self, val: bool); + + #[sel(setAcceptsMouseMovedEvents:)] + pub fn setAcceptsMouseMovedEvents(&self, val: bool); + + #[sel(setTitlebarAppearsTransparent:)] + pub fn setTitlebarAppearsTransparent(&self, val: bool); + + #[sel(setTitleVisibility:)] + pub fn setTitleVisibility(&self, visibility: NSWindowTitleVisibility); + + #[sel(setMovableByWindowBackground:)] + pub fn setMovableByWindowBackground(&self, val: bool); + + #[sel(setLevel:)] + pub fn setLevel(&self, level: NSWindowLevel); + + #[sel(occlusionState)] + pub fn occlusionState(&self) -> NSWindowOcclusionState; + + #[sel(center)] + pub fn center(&self); + + #[sel(isResizable)] + pub fn isResizable(&self) -> bool; + + #[sel(isMiniaturized)] + pub fn isMiniaturized(&self) -> bool; + + #[sel(isVisible)] + pub fn isVisible(&self) -> bool; + + #[sel(isZoomed)] + pub fn isZoomed(&self) -> bool; + + #[sel(close)] + pub fn close(&self); + + #[sel(performWindowDragWithEvent:)] + pub fn performWindowDragWithEvent(&self, event: &NSEvent); + + #[sel(invalidateCursorRectsForView:)] + pub fn invalidateCursorRectsForView(&self, view: &NSView); + + #[sel(setDelegate:)] + pub fn setDelegate(&self, delegate: Option<&NSObject>); } ); + +#[allow(dead_code)] +#[repr(isize)] // NSInteger +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NSWindowTitleVisibility { + #[doc(alias = "NSWindowTitleVisible")] + Visible = 0, + #[doc(alias = "NSWindowTitleHidden")] + Hidden = 1, +} + +unsafe impl Encode for NSWindowTitleVisibility { + const ENCODING: Encoding = NSInteger::ENCODING; +} + +#[allow(dead_code)] +#[repr(usize)] // NSUInteger +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NSWindowButton { + #[doc(alias = "NSWindowCloseButton")] + Close = 0, + #[doc(alias = "NSWindowMiniaturizeButton")] + Miniaturize = 1, + #[doc(alias = "NSWindowZoomButton")] + Zoom = 2, + #[doc(alias = "NSWindowToolbarButton")] + Toolbar = 3, + #[doc(alias = "NSWindowDocumentIconButton")] + DocumentIcon = 4, + #[doc(alias = "NSWindowDocumentVersionsButton")] + DocumentVersions = 6, + #[doc(alias = "NSWindowFullScreenButton")] + #[deprecated = "Deprecated since macOS 10.12"] + FullScreen = 7, +} + +unsafe impl Encode for NSWindowButton { + const ENCODING: Encoding = NSUInteger::ENCODING; +} + +#[allow(dead_code)] +mod window_level_key { + use objc2::foundation::NSInteger; + pub const kCGBaseWindowLevelKey: NSInteger = 0; + pub const kCGMinimumWindowLevelKey: NSInteger = 1; + pub const kCGDesktopWindowLevelKey: NSInteger = 2; + pub const kCGBackstopMenuLevelKey: NSInteger = 3; + pub const kCGNormalWindowLevelKey: NSInteger = 4; + pub const kCGFloatingWindowLevelKey: NSInteger = 5; + pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6; + pub const kCGDockWindowLevelKey: NSInteger = 7; + pub const kCGMainMenuWindowLevelKey: NSInteger = 8; + pub const kCGStatusWindowLevelKey: NSInteger = 9; + pub const kCGModalPanelWindowLevelKey: NSInteger = 10; + pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11; + pub const kCGDraggingWindowLevelKey: NSInteger = 12; + pub const kCGScreenSaverWindowLevelKey: NSInteger = 13; + pub const kCGMaximumWindowLevelKey: NSInteger = 14; + pub const kCGOverlayWindowLevelKey: NSInteger = 15; + pub const kCGHelpWindowLevelKey: NSInteger = 16; + pub const kCGUtilityWindowLevelKey: NSInteger = 17; + pub const kCGDesktopIconWindowLevelKey: NSInteger = 18; + pub const kCGCursorWindowLevelKey: NSInteger = 19; + pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; +} +use window_level_key::*; + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(isize)] +pub enum NSWindowLevel { + #[doc(alias = "NSNormalWindowLevel")] + Normal = kCGBaseWindowLevelKey, + #[doc(alias = "NSFloatingWindowLevel")] + Floating = kCGFloatingWindowLevelKey, + #[doc(alias = "NSTornOffMenuWindowLevel")] + TornOffMenu = kCGTornOffMenuWindowLevelKey, + #[doc(alias = "NSModalPanelWindowLevel")] + ModalPanel = kCGModalPanelWindowLevelKey, + #[doc(alias = "NSMainMenuWindowLevel")] + MainMenu = kCGMainMenuWindowLevelKey, + #[doc(alias = "NSStatusWindowLevel")] + Status = kCGStatusWindowLevelKey, + #[doc(alias = "NSPopUpMenuWindowLevel")] + PopUpMenu = kCGPopUpMenuWindowLevelKey, + #[doc(alias = "NSScreenSaverWindowLevel")] + ScreenSaver = kCGScreenSaverWindowLevelKey, +} + +unsafe impl Encode for NSWindowLevel { + const ENCODING: Encoding = NSInteger::ENCODING; +} + +bitflags! { + pub struct NSWindowOcclusionState: NSUInteger { + const NSWindowOcclusionStateVisible = 1 << 1; + } +} + +unsafe impl Encode for NSWindowOcclusionState { + const ENCODING: Encoding = NSUInteger::ENCODING; +} + +bitflags! { + pub struct NSWindowStyleMask: NSUInteger { + const NSBorderlessWindowMask = 0; + const NSTitledWindowMask = 1 << 0; + const NSClosableWindowMask = 1 << 1; + const NSMiniaturizableWindowMask = 1 << 2; + const NSResizableWindowMask = 1 << 3; + const NSTexturedBackgroundWindowMask = 1 << 8; + const NSUnifiedTitleAndToolbarWindowMask = 1 << 12; + const NSFullScreenWindowMask = 1 << 14; + const NSFullSizeContentViewWindowMask = 1 << 15; + } +} + +unsafe impl Encode for NSWindowStyleMask { + const ENCODING: Encoding = NSUInteger::ENCODING; +} diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index aba07535c2..5a02abc226 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -15,42 +15,6 @@ use objc2::foundation::NSInteger; pub const NSNotFound: NSInteger = NSInteger::max_value(); -pub const kCGBaseWindowLevelKey: NSInteger = 0; -pub const kCGMinimumWindowLevelKey: NSInteger = 1; -pub const kCGDesktopWindowLevelKey: NSInteger = 2; -pub const kCGBackstopMenuLevelKey: NSInteger = 3; -pub const kCGNormalWindowLevelKey: NSInteger = 4; -pub const kCGFloatingWindowLevelKey: NSInteger = 5; -pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6; -pub const kCGDockWindowLevelKey: NSInteger = 7; -pub const kCGMainMenuWindowLevelKey: NSInteger = 8; -pub const kCGStatusWindowLevelKey: NSInteger = 9; -pub const kCGModalPanelWindowLevelKey: NSInteger = 10; -pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11; -pub const kCGDraggingWindowLevelKey: NSInteger = 12; -pub const kCGScreenSaverWindowLevelKey: NSInteger = 13; -pub const kCGMaximumWindowLevelKey: NSInteger = 14; -pub const kCGOverlayWindowLevelKey: NSInteger = 15; -pub const kCGHelpWindowLevelKey: NSInteger = 16; -pub const kCGUtilityWindowLevelKey: NSInteger = 17; -pub const kCGDesktopIconWindowLevelKey: NSInteger = 18; -pub const kCGCursorWindowLevelKey: NSInteger = 19; -pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; - -#[derive(Debug, Clone, Copy)] -#[repr(isize)] -#[allow(clippy::enum_variant_names)] -pub enum NSWindowLevel { - NSNormalWindowLevel = kCGBaseWindowLevelKey as _, - NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _, - NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _, - NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _, - NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _, - NSStatusWindowLevel = kCGStatusWindowLevelKey as _, - NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _, - NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, -} - pub type CGDisplayFadeInterval = f32; pub type CGDisplayReservationInterval = f32; pub type CGDisplayBlendFraction = f32; diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 8b2788856a..2efb064d95 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -18,19 +18,20 @@ mod view; mod window; mod window_delegate; -use std::{fmt, ops::Deref, sync::Arc}; +use std::{fmt, ops::Deref}; +use self::window::WinitWindow; pub(crate) use self::{ event_loop::{ EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, }, monitor::{MonitorHandle, VideoMode}, - window::{PlatformSpecificWindowBuilderAttributes, UnownedWindow, WindowId}, + window::{PlatformSpecificWindowBuilderAttributes, WindowId}, }; use crate::{ error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes, }; -use objc::rc::autoreleasepool; +use objc2::rc::{autoreleasepool, Id, Shared}; pub(crate) use crate::icon::NoIcon as PlatformIcon; @@ -46,12 +47,19 @@ impl DeviceId { // Constant device ID; to be removed when if backend is updated to report real device IDs. pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); -pub struct Window { - window: Arc, +pub(crate) struct Window { + pub(crate) window: Id, // We keep this around so that it doesn't get dropped until the window does. _delegate: util::IdRef, } +impl Drop for Window { + fn drop(&mut self) { + // Ensure the window is closed + util::close_async(Id::into_super(self.window.clone())); + } +} + #[derive(Debug)] pub enum OsError { CGError(core_graphics::base::CGError), @@ -62,7 +70,7 @@ unsafe impl Send for Window {} unsafe impl Sync for Window {} impl Deref for Window { - type Target = UnownedWindow; + type Target = WinitWindow; #[inline] fn deref(&self) -> &Self::Target { &*self.window @@ -75,7 +83,7 @@ impl Window { attributes: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { - let (window, _delegate) = autoreleasepool(|_| UnownedWindow::new(attributes, pl_attribs))?; + let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?; Ok(Window { window, _delegate }) } } diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 00a4d69d16..64ebe57652 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -1,21 +1,19 @@ use std::{collections::VecDeque, fmt}; -use super::{ffi, util}; -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, -}; -use cocoa::{ - appkit::NSScreen, - base::{id, nil}, -}; use core_foundation::{ array::{CFArrayGetCount, CFArrayGetValueAtIndex}, base::{CFRelease, TCFType}, string::CFString, }; use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}; -use objc::foundation::NSUInteger; +use objc2::rc::{Id, Shared}; + +use super::appkit::NSScreen; +use super::ffi; +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, +}; #[derive(Clone)] pub struct VideoMode { @@ -213,11 +211,10 @@ impl MonitorHandle { } pub fn scale_factor(&self) -> f64 { - let screen = match self.ns_screen() { - Some(screen) => screen, - None => return 1.0, // default to 1.0 when we can't find the screen - }; - unsafe { NSScreen::backingScaleFactor(screen) as f64 } + match self.ns_screen() { + Some(screen) => screen.backingScaleFactor() as f64, + None => 1.0, // default to 1.0 when we can't find the screen + } } pub fn refresh_rate_millihertz(&self) -> Option { @@ -298,26 +295,19 @@ impl MonitorHandle { } } - pub(crate) fn ns_screen(&self) -> Option { - unsafe { - let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0); - let screens = NSScreen::screens(nil); - let count: NSUInteger = msg_send![screens, count]; - let key = util::ns_string_id_ref("NSScreenNumber"); - for i in 0..count { - let screen = msg_send![screens, objectAtIndex: i as NSUInteger]; - let device_description = NSScreen::deviceDescription(screen); - let value: id = msg_send![device_description, objectForKey:*key]; - if value != nil { - let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue]; - let other_uuid = - ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID); - if uuid == other_uuid { - return Some(screen); - } - } - } - None - } + pub(crate) fn ns_screen(&self) -> Option> { + let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) }; + NSScreen::screens() + .into_iter() + .find(|screen| { + let other_native_id = screen.display_id(); + let other_uuid = unsafe { + ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID) + }; + uuid == other_uuid + }) + .map(|screen| unsafe { + Id::retain(screen as *const NSScreen as *mut NSScreen).unwrap() + }) } } diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index c221f08b21..db933e54c3 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -1,23 +1,16 @@ -use std::{ - ops::Deref, - sync::{Mutex, Weak}, -}; +use std::mem; +use std::ops::Deref; +use std::sync::{Mutex, Weak}; -use cocoa::{ - appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask}, - base::{id, nil}, - foundation::{NSPoint, NSSize, NSString}, -}; use dispatch::Queue; -use objc::foundation::is_main_thread; -use objc::rc::autoreleasepool; -use objc::runtime::Bool; +use objc2::foundation::{is_main_thread, CGFloat, NSPoint, NSSize, NSString}; +use objc2::rc::{autoreleasepool, Id, Shared}; use crate::{ dpi::LogicalSize, platform_impl::platform::{ + appkit::{NSScreen, NSWindow, NSWindowLevel, NSWindowStyleMask}, ffi, - util::IdRef, window::{SharedState, SharedStateMutexGuard}, }, }; @@ -37,91 +30,88 @@ impl Deref for MainThreadSafe { } } -unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { - ns_window.setStyleMask_(mask); +fn set_style_mask(window: &NSWindow, mask: NSWindowStyleMask) { + window.setStyleMask(mask); // If we don't do this, key handling will break // (at least until the window is clicked again/etc.) - ns_window.makeFirstResponder_(ns_view); + let _ = window.makeFirstResponder(Some(&window.contentView())); } // Always use this function instead of trying to modify `styleMask` directly! // `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch. // Otherwise, this would vomit out errors about not being on the main thread // and fail to do anything. -pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { - let ns_window = MainThreadSafe(ns_window); - let ns_view = MainThreadSafe(ns_view); +pub(crate) fn set_style_mask_async(window: &NSWindow, mask: NSWindowStyleMask) { + // TODO(madsmtm): Remove this 'static hack! + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; Queue::main().exec_async(move || { - set_style_mask(*ns_window, *ns_view, mask); + set_style_mask(*window, mask); }); } -pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { +pub(crate) fn set_style_mask_sync(window: &NSWindow, mask: NSWindowStyleMask) { if is_main_thread() { - set_style_mask(ns_window, ns_view, mask); + set_style_mask(window, mask); } else { - let ns_window = MainThreadSafe(ns_window); - let ns_view = MainThreadSafe(ns_view); + let window = MainThreadSafe(window); Queue::main().exec_sync(move || { - set_style_mask(*ns_window, *ns_view, mask); + set_style_mask(*window, mask); }) } } // `setContentSize:` isn't thread-safe either, though it doesn't log any errors // and just fails silently. Anyway, GCD to the rescue! -pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize) { - let ns_window = MainThreadSafe(ns_window); +pub(crate) fn set_content_size_async(window: &NSWindow, size: LogicalSize) { + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; Queue::main().exec_async(move || { - ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat)); + window.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat)); }); } // `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy // to log errors. -pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) { - let ns_window = MainThreadSafe(ns_window); +pub(crate) fn set_frame_top_left_point_async(window: &NSWindow, point: NSPoint) { + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; Queue::main().exec_async(move || { - ns_window.setFrameTopLeftPoint_(point); + window.setFrameTopLeftPoint(point); }); } // `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. -pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) { - let ns_window = MainThreadSafe(ns_window); +pub(crate) fn set_level_async(window: &NSWindow, level: NSWindowLevel) { + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; Queue::main().exec_async(move || { - ns_window.setLevel_(level as _); + window.setLevel(level); }); } // `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently. -pub unsafe fn set_ignore_mouse_events(ns_window: id, ignore: bool) { - let ns_window = MainThreadSafe(ns_window); +pub(crate) fn set_ignore_mouse_events(window: &NSWindow, ignore: bool) { + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; Queue::main().exec_async(move || { - ns_window.setIgnoresMouseEvents_(Bool::from(ignore).as_raw()); + window.setIgnoresMouseEvents(ignore); }); } // `toggleFullScreen` is thread-safe, but our additional logic to account for // window styles isn't. -pub unsafe fn toggle_full_screen_async( - ns_window: id, - ns_view: id, +pub(crate) fn toggle_full_screen_async( + window: &NSWindow, not_fullscreen: bool, shared_state: Weak>, ) { - let ns_window = MainThreadSafe(ns_window); - let ns_view = MainThreadSafe(ns_view); + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; let shared_state = MainThreadSafe(shared_state); Queue::main().exec_async(move || { // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we // set a normal style temporarily. The previous state will be // restored in `WindowDelegate::window_did_exit_fullscreen`. if not_fullscreen { - let curr_mask = ns_window.styleMask(); + let curr_mask = window.styleMask(); let required = NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; if !curr_mask.contains(required) { - set_style_mask(*ns_window, *ns_view, required); + set_style_mask(*window, required); if let Some(shared_state) = shared_state.upgrade() { let mut shared_state_lock = SharedStateMutexGuard::new( shared_state.lock().unwrap(), @@ -134,12 +124,12 @@ pub unsafe fn toggle_full_screen_async( // Window level must be restored from `CGShieldingWindowLevel() // + 1` back to normal in order for `toggleFullScreen` to do // anything - ns_window.setLevel_(0); - ns_window.toggleFullScreen_(nil); + window.setLevel(NSWindowLevel::Normal); + window.toggleFullScreen(None); }); } -pub unsafe fn restore_display_mode_async(ns_screen: u32) { +pub(crate) unsafe fn restore_display_mode_async(ns_screen: u32) { Queue::main().exec_async(move || { ffi::CGRestorePermanentDisplayConfiguration(); assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess); @@ -147,13 +137,13 @@ pub unsafe fn restore_display_mode_async(ns_screen: u32) { } // `setMaximized` is not thread-safe -pub unsafe fn set_maximized_async( - ns_window: id, +pub(crate) fn set_maximized_async( + window: &NSWindow, is_zoomed: bool, maximized: bool, shared_state: Weak>, ) { - let ns_window = MainThreadSafe(ns_window); + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; let shared_state = MainThreadSafe(shared_state); Queue::main().exec_async(move || { if let Some(shared_state) = shared_state.upgrade() { @@ -162,7 +152,7 @@ pub unsafe fn set_maximized_async( // Save the standard frame sized if it is not zoomed if !is_zoomed { - shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window)); + shared_state_lock.standard_frame = Some(window.frame()); } shared_state_lock.maximized = maximized; @@ -172,21 +162,21 @@ pub unsafe fn set_maximized_async( return; } - if ns_window + if window .styleMask() .contains(NSWindowStyleMask::NSResizableWindowMask) { // Just use the native zoom if resizable - ns_window.zoom_(nil); + window.zoom(None); } else { // if it's not resizable, we set the frame directly let new_rect = if maximized { - let screen = NSScreen::mainScreen(nil); - NSScreen::visibleFrame(screen) + let screen = NSScreen::main().expect("no screen found"); + screen.visibleFrame() } else { shared_state_lock.saved_standard_frame() }; - ns_window.setFrame_display_(new_rect, Bool::NO.as_raw()); + window.setFrame_display(new_rect, false); } } }); @@ -194,30 +184,29 @@ pub unsafe fn set_maximized_async( // `orderOut:` isn't thread-safe. Calling it from another thread actually works, // but with an odd delay. -pub unsafe fn order_out_async(ns_window: id) { - let ns_window = MainThreadSafe(ns_window); +pub(crate) fn order_out_async(window: &NSWindow) { + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; Queue::main().exec_async(move || { - ns_window.orderOut_(nil); + window.orderOut(None); }); } // `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread // actually works, but with an odd delay. -pub unsafe fn make_key_and_order_front_async(ns_window: id) { - let ns_window = MainThreadSafe(ns_window); +pub(crate) fn make_key_and_order_front_async(window: &NSWindow) { + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; Queue::main().exec_async(move || { - ns_window.makeKeyAndOrderFront_(nil); + window.makeKeyAndOrderFront(None); }); } // `setTitle:` isn't thread-safe. Calling it from another thread invalidates the // window drag regions, which throws an exception when not done in the main // thread -pub unsafe fn set_title_async(ns_window: id, title: String) { - let ns_window = MainThreadSafe(ns_window); +pub(crate) fn set_title_async(window: &NSWindow, title: String) { + let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) }; Queue::main().exec_async(move || { - let title = IdRef::new(NSString::alloc(nil).init_str(&title)); - ns_window.setTitle_(*title); + window.setTitle(&NSString::from_str(&title)); }); } @@ -226,11 +215,11 @@ pub unsafe fn set_title_async(ns_window: id, title: String) { // // ArturKovacs: It's important that this operation keeps the underlying window alive // through the `IdRef` because otherwise it would dereference free'd memory -pub unsafe fn close_async(ns_window: IdRef) { - let ns_window = MainThreadSafe(ns_window); +pub(crate) fn close_async(window: Id) { + let window = MainThreadSafe(window); Queue::main().exec_async(move || { autoreleasepool(move |_| { - ns_window.close(); + window.close(); }); }); } diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 7b39c36b14..b1245fbb1f 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -1,16 +1,16 @@ mod r#async; -pub use self::r#async::*; +pub(crate) use self::r#async::*; use std::ops::{BitAnd, Deref}; use cocoa::{ - appkit::{CGFloat, NSApp, NSWindowStyleMask}, + appkit::NSApp, base::{id, nil}, - foundation::{NSPoint, NSRect, NSString}, + foundation::NSString, }; use core_graphics::display::CGDisplay; -use objc2::foundation::{NSRange, NSUInteger}; +use objc2::foundation::{CGFloat, NSPoint, NSRange, NSRect, NSUInteger}; use crate::dpi::LogicalPosition; use crate::platform_impl::platform::ffi; @@ -45,14 +45,6 @@ impl IdRef { } IdRef(inner) } - - pub fn non_nil(self) -> Option { - if self.0 == nil { - None - } else { - Some(self) - } - } } impl Drop for IdRef { @@ -145,17 +137,3 @@ pub unsafe fn app_name() -> Option { pub unsafe fn open_emoji_picker() { let _: () = msg_send![NSApp(), orderFrontCharacterPalette: nil]; } - -pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) { - use cocoa::appkit::NSWindow; - - let current_style_mask = window.styleMask(); - if on { - window.setStyleMask_(current_style_mask | mask); - } else { - window.setStyleMask_(current_style_mask & (!mask)); - } - - // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! - window.makeFirstResponder_(view); -} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index be4bb7119d..3c8b199493 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -1,6 +1,4 @@ -use std::{ - boxed::Box, collections::VecDeque, mem::ManuallyDrop, os::raw::*, ptr, str, sync::Mutex, -}; +use std::{boxed::Box, collections::VecDeque, os::raw::*, ptr, str, sync::Mutex}; use objc2::declare::{Ivar, IvarDrop}; use objc2::foundation::{ @@ -27,7 +25,7 @@ use crate::{ char_to_keycode, check_function_keys, event_mods, modifier_event, scancode_to_keycode, EventWrapper, }, - util::{self, IdRef}, + util, window::get_window_id, DEVICE_ID, }, @@ -136,7 +134,7 @@ declare_class!( #[allow(non_snake_case)] pub(super) struct WinitView { _ns_window: IvarDrop>, - state: IvarDrop>, + pub(super) state: IvarDrop>, marked_text: IvarDrop>, } @@ -913,11 +911,8 @@ declare_class!( ); impl WinitView { - pub(super) fn new(window_id: *mut Object) -> IdRef { - let obj: Id<_, Owned> = - unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithId: window_id] }; - let mut obj = ManuallyDrop::new(obj); - IdRef::new(Id::as_mut_ptr(&mut obj)) + pub(super) fn new(window: &NSWindow) -> Id { + unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithId: window] } } fn window(&self) -> Id { diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 754d48a689..5936874adb 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1,6 +1,5 @@ use std::{ collections::VecDeque, - convert::TryInto, f64, ops, os::raw::c_void, sync::{ @@ -26,7 +25,7 @@ use crate::{ ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, - view::{ViewState, WinitView}, + view::WinitView, window_delegate::new_delegate, OsError, }, @@ -37,19 +36,24 @@ use crate::{ }; use cocoa::{ appkit::{ - self, CGFloat, NSApp, NSApplication, NSApplicationPresentationOptions, NSColor, - NSRequestUserAttentionType, NSScreen, NSView, NSWindow, NSWindowButton, NSWindowStyleMask, + self, NSApp, NSApplication, NSApplicationPresentationOptions, NSRequestUserAttentionType, }, - base::{id, nil}, - foundation::{NSDictionary, NSPoint, NSRect, NSSize}, + base::id, }; use core_graphics::display::{CGDisplay, CGDisplayMode}; -use objc2::foundation::{is_main_thread, NSObject, NSUInteger}; -use objc2::rc::autoreleasepool; +use objc2::declare::{Ivar, IvarDrop}; +use objc2::foundation::{ + is_main_thread, CGFloat, NSArray, NSCopying, NSObject, NSPoint, NSRect, NSSize, NSString, +}; +use objc2::rc::{autoreleasepool, Id, Owned, Shared}; use objc2::runtime::{Bool, Object}; use objc2::{declare_class, ClassType}; -use super::appkit::{NSCursor, NSResponder, NSWindow as NSWindowClass}; +use super::appkit::{ + NSColor, NSCursor, NSEvent, NSFilenamesPboardType, NSResponder, NSScreen, + NSWindow as NSWindowClass, NSWindowButton, NSWindowLevel, NSWindowStyleMask, + NSWindowTitleVisibility, +}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub usize); @@ -108,132 +112,13 @@ impl Default for PlatformSpecificWindowBuilderAttributes { } } -fn create_window( - attrs: &WindowAttributes, - pl_attrs: &PlatformSpecificWindowBuilderAttributes, -) -> Option { - autoreleasepool(|_| unsafe { - let screen = match attrs.fullscreen { - Some(Fullscreen::Borderless(Some(RootMonitorHandle { inner: ref monitor }))) - | Some(Fullscreen::Exclusive(RootVideoMode { - video_mode: VideoMode { ref monitor, .. }, - })) => { - let monitor_screen = monitor.ns_screen(); - Some(monitor_screen.unwrap_or_else(|| appkit::NSScreen::mainScreen(nil))) - } - Some(Fullscreen::Borderless(None)) => Some(appkit::NSScreen::mainScreen(nil)), - None => None, - }; - let frame = match screen { - Some(screen) => NSScreen::frame(screen), - None => { - let screen = NSScreen::mainScreen(nil); - let scale_factor = NSScreen::backingScaleFactor(screen) as f64; - let (width, height) = match attrs.inner_size { - Some(size) => { - let logical = size.to_logical(scale_factor); - (logical.width, logical.height) - } - None => (800.0, 600.0), - }; - let (left, bottom) = match attrs.position { - Some(position) => { - let logical = util::window_position(position.to_logical(scale_factor)); - // macOS wants the position of the bottom left corner, - // but caller is setting the position of top left corner - (logical.x, logical.y - height) - } - // This value is ignored by calling win.center() below - None => (0.0, 0.0), - }; - NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height)) - } - }; - - let mut masks = if (!attrs.decorations && screen.is_none()) || pl_attrs.titlebar_hidden { - // Resizable UnownedWindow without a titlebar or borders - // if decorations is set to false, ignore pl_attrs - // - // if the titlebar is hidden, ignore other pl_attrs - NSWindowStyleMask::NSBorderlessWindowMask - | NSWindowStyleMask::NSResizableWindowMask - | NSWindowStyleMask::NSMiniaturizableWindowMask - } else { - // default case, resizable window with titlebar and titlebar buttons - NSWindowStyleMask::NSClosableWindowMask - | NSWindowStyleMask::NSMiniaturizableWindowMask - | NSWindowStyleMask::NSResizableWindowMask - | NSWindowStyleMask::NSTitledWindowMask - }; - - if !attrs.resizable { - masks &= !NSWindowStyleMask::NSResizableWindowMask; - } - - if pl_attrs.fullsize_content_view { - masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; - } - - let ns_window: id = msg_send![WinitWindow::class(), alloc]; - let ns_window = IdRef::new(ns_window.initWithContentRect_styleMask_backing_defer_( - frame, - masks, - appkit::NSBackingStoreBuffered, - Bool::NO.as_raw(), - )); - - ns_window.non_nil().map(|ns_window| { - let title = util::ns_string_id_ref(&attrs.title); - ns_window.setReleasedWhenClosed_(Bool::NO.as_raw()); - ns_window.setTitle_(*title); - ns_window.setAcceptsMouseMovedEvents_(Bool::YES.as_raw()); - - if pl_attrs.titlebar_transparent { - ns_window.setTitlebarAppearsTransparent_(Bool::YES.as_raw()); - } - if pl_attrs.title_hidden { - ns_window.setTitleVisibility_(appkit::NSWindowTitleVisibility::NSWindowTitleHidden); - } - if pl_attrs.titlebar_buttons_hidden { - for titlebar_button in &[ - NSWindowButton::NSWindowFullScreenButton, - NSWindowButton::NSWindowMiniaturizeButton, - NSWindowButton::NSWindowCloseButton, - NSWindowButton::NSWindowZoomButton, - ] { - let button = ns_window.standardWindowButton_(*titlebar_button); - let _: () = msg_send![button, setHidden: true]; - } - } - if pl_attrs.movable_by_window_background { - ns_window.setMovableByWindowBackground_(Bool::YES.as_raw()); - } - - if attrs.always_on_top { - let _: () = msg_send![*ns_window, setLevel: ffi::kCGFloatingWindowLevelKey]; - } - - if let Some(increments) = pl_attrs.resize_increments { - let (x, y) = (increments.width, increments.height); - if x >= 1.0 && y >= 1.0 { - let size = NSSize::new(x as CGFloat, y as CGFloat); - ns_window.setResizeIncrements_(size); - } - } - - if !pl_attrs.has_shadow { - ns_window.setHasShadow_(Bool::NO.as_raw()); - } - if attrs.position.is_none() { - ns_window.center(); - } - ns_window - }) - }) -} - declare_class!( - struct WinitWindow {} + pub(crate) struct WinitWindow { + // TODO: Fix unnecessary boxing here + // SAFETY: These are initialized in WinitWindow::new, right after it is created. + shared_state: IvarDrop>>>, + decorations: IvarDrop>, + } unsafe impl ClassType for WinitWindow { #[inherits(NSResponder, NSObject)] @@ -258,6 +143,8 @@ declare_class!( #[derive(Default)] pub struct SharedState { pub resizable: bool, + /// This field tracks the current fullscreen state of the window + /// (as seen by `WindowDelegate`). pub fullscreen: Option, // This is true between windowWillEnterFullScreen and windowDidEnterFullScreen // or windowWillExitFullScreen and windowDidExitFullScreen. @@ -286,23 +173,6 @@ impl SharedState { } } -impl From for SharedState { - fn from(attribs: WindowAttributes) -> Self { - SharedState { - resizable: attribs.resizable, - // This fullscreen field tracks the current state of the window - // (as seen by `WindowDelegate`), and since the window hasn't - // actually been fullscreened yet, we can't set it yet. This is - // necessary for state transitions to work right, since otherwise - // the initial value and the first `set_fullscreen` call would be - // identical, resulting in a no-op. - fullscreen: None, - maximized: attribs.maximized, - ..Default::default() - } - } -} - pub(crate) struct SharedStateMutexGuard<'a> { guard: MutexGuard<'a, SharedState>, called_from_fn: &'static str, @@ -341,39 +211,164 @@ impl Drop for SharedStateMutexGuard<'_> { } } -pub struct UnownedWindow { - pub ns_window: IdRef, // never changes - pub ns_view: IdRef, // never changes - shared_state: Arc>, - decorations: AtomicBool, -} - -unsafe impl Send for UnownedWindow {} -unsafe impl Sync for UnownedWindow {} - -impl UnownedWindow { +impl WinitWindow { pub(crate) fn new( - mut win_attribs: WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result<(Arc, IdRef), RootOsError> { + attrs: WindowAttributes, + pl_attrs: PlatformSpecificWindowBuilderAttributes, + ) -> Result<(Id, IdRef), RootOsError> { + trace_scope!("WinitWindow::new"); + if !is_main_thread() { panic!("Windows can only be created on the main thread on macOS"); } - trace!("Creating new window"); - let ns_window = create_window(&win_attribs, &pl_attribs) - .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; + let this = autoreleasepool(|_| { + let screen = match attrs.fullscreen { + Some(Fullscreen::Borderless(Some(RootMonitorHandle { inner: ref monitor }))) + | Some(Fullscreen::Exclusive(RootVideoMode { + video_mode: VideoMode { ref monitor, .. }, + })) => monitor.ns_screen().or_else(NSScreen::main), + Some(Fullscreen::Borderless(None)) => NSScreen::main(), + None => None, + }; + let frame = match &screen { + Some(screen) => screen.frame(), + None => { + let scale_factor = NSScreen::main() + .map(|screen| screen.backingScaleFactor() as f64) + .unwrap_or(1.0); + let (width, height) = match attrs.inner_size { + Some(size) => { + let logical = size.to_logical(scale_factor); + (logical.width, logical.height) + } + None => (800.0, 600.0), + }; + let (left, bottom) = match attrs.position { + Some(position) => { + let logical = util::window_position(position.to_logical(scale_factor)); + // macOS wants the position of the bottom left corner, + // but caller is setting the position of top left corner + (logical.x, logical.y - height) + } + // This value is ignored by calling win.center() below + None => (0.0, 0.0), + }; + NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height)) + } + }; + + let mut masks = if (!attrs.decorations && screen.is_none()) || pl_attrs.titlebar_hidden + { + // Resizable without a titlebar or borders + // if decorations is set to false, ignore pl_attrs + // + // if the titlebar is hidden, ignore other pl_attrs + NSWindowStyleMask::NSBorderlessWindowMask + | NSWindowStyleMask::NSResizableWindowMask + | NSWindowStyleMask::NSMiniaturizableWindowMask + } else { + // default case, resizable window with titlebar and titlebar buttons + NSWindowStyleMask::NSClosableWindowMask + | NSWindowStyleMask::NSMiniaturizableWindowMask + | NSWindowStyleMask::NSResizableWindowMask + | NSWindowStyleMask::NSTitledWindowMask + }; + + if !attrs.resizable { + masks &= !NSWindowStyleMask::NSResizableWindowMask; + } + + if pl_attrs.fullsize_content_view { + masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; + } + + let this: Option> = unsafe { + msg_send_id![ + msg_send_id![WinitWindow::class(), alloc], + initWithContentRect: frame, + styleMask: masks, + backing: appkit::NSBackingStoreBuffered, + defer: false, + ] + }; + + this.map(|mut this| { + // Properly initialize the window's variables + // + // Ideally this should be done in an `init` method, + // but for convenience we do it here instead. + let state = SharedState { + resizable: attrs.resizable, + maximized: attrs.maximized, + ..Default::default() + }; + Ivar::write( + &mut this.shared_state, + Box::new(Arc::new(Mutex::new(state))), + ); + Ivar::write( + &mut this.decorations, + Box::new(AtomicBool::new(attrs.decorations)), + ); + + this.setReleasedWhenClosed(false); + this.setTitle(&NSString::from_str(&attrs.title)); + this.setAcceptsMouseMovedEvents(true); + + if pl_attrs.titlebar_transparent { + this.setTitlebarAppearsTransparent(true); + } + if pl_attrs.title_hidden { + this.setTitleVisibility(NSWindowTitleVisibility::Hidden); + } + if pl_attrs.titlebar_buttons_hidden { + for titlebar_button in &[ + #[allow(deprecated)] + NSWindowButton::FullScreen, + NSWindowButton::Miniaturize, + NSWindowButton::Close, + NSWindowButton::Zoom, + ] { + if let Some(button) = this.standardWindowButton(*titlebar_button) { + button.setHidden(true); + } + } + } + if pl_attrs.movable_by_window_background { + this.setMovableByWindowBackground(true); + } + + if attrs.always_on_top { + this.setLevel(NSWindowLevel::Floating); + } + + if let Some(increments) = pl_attrs.resize_increments { + let (x, y) = (increments.width, increments.height); + if x >= 1.0 && y >= 1.0 { + let size = objc2::foundation::NSSize::new(x as CGFloat, y as CGFloat); + this.setResizeIncrements(size); + } + } + + if !pl_attrs.has_shadow { + this.setHasShadow(false); + } + if attrs.position.is_none() { + this.center(); + } + + Id::into_shared(this) + }) + }) + .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; - let ns_view = WinitView::new(*ns_window); + let view = WinitView::new(&this); // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until // macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid // always the default system value in favour of the user's code - if !pl_attribs.disallow_hidpi { - unsafe { ns_view.setWantsBestResolutionOpenGLSurface_(Bool::YES.as_raw()) }; - } else { - unsafe { ns_view.setWantsBestResolutionOpenGLSurface_(Bool::NO.as_raw()) }; - } + view.setWantsBestResolutionOpenGLSurface(!pl_attrs.disallow_hidpi); // On Mojave, views automatically become layer-backed shortly after being added to // a window. Changing the layer-backedness of a view breaks the association between @@ -382,76 +377,53 @@ impl UnownedWindow { // itself and break the association with its context. if f64::floor(unsafe { appkit::NSAppKitVersionNumber }) > appkit::NSAppKitVersionNumber10_12 { - unsafe { ns_view.setWantsLayer(Bool::YES.as_raw()) }; + view.setWantsLayer(true); } // Configure the new view as the "key view" for the window - unsafe { - ns_window.setContentView_(*ns_view); - ns_window.setInitialFirstResponder_(*ns_view); - } - - let scale_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 }; + this.setContentView(&view); + this.setInitialFirstResponder(&view); - unsafe { - if win_attribs.transparent { - ns_window.setOpaque_(Bool::NO.as_raw()); - ns_window.setBackgroundColor_(NSColor::clearColor(nil)); - } - - if let Some(dim) = win_attribs.min_inner_size { - let logical_dim = dim.to_logical(scale_factor); - set_min_inner_size(*ns_window, logical_dim); - } - if let Some(dim) = win_attribs.max_inner_size { - let logical_dim = dim.to_logical(scale_factor); - set_max_inner_size(*ns_window, logical_dim); - } + if attrs.transparent { + this.setOpaque(false); + this.setBackgroundColor(&NSColor::clear()); + } - use cocoa::foundation::NSArray; - // register for drag and drop operations. - let _: () = msg_send![ - *ns_window, - registerForDraggedTypes: - NSArray::arrayWithObject(nil, appkit::NSFilenamesPboardType) - ]; + if let Some(dim) = attrs.min_inner_size { + this.set_min_inner_size(Some(dim)); + } + if let Some(dim) = attrs.max_inner_size { + this.set_max_inner_size(Some(dim)); } - // Since `win_attribs` is put into a mutex below, we'll just copy these - // attributes now instead of bothering to lock it later. - // Also, `SharedState` doesn't carry `fullscreen` over; it's set - // indirectly by us calling `set_fullscreen` below, causing handlers in - // `WindowDelegate` to update the state. - let fullscreen = win_attribs.fullscreen.take(); - let maximized = win_attribs.maximized; - let visible = win_attribs.visible; - let decorations = win_attribs.decorations; - - let window = Arc::new(UnownedWindow { - ns_view, - ns_window, - shared_state: Arc::new(Mutex::new(win_attribs.into())), - decorations: AtomicBool::new(decorations), - }); + // register for drag and drop operations. + this.registerForDraggedTypes(&NSArray::from_slice(&[ + unsafe { NSFilenamesPboardType }.copy() + ])); - let delegate = new_delegate(&window, fullscreen.is_some()); + let delegate = new_delegate(this.clone(), attrs.fullscreen.is_some()); // Set fullscreen mode after we setup everything - window.set_fullscreen(fullscreen); + this.set_fullscreen(attrs.fullscreen); // Setting the window as key has to happen *after* we set the fullscreen // state, since otherwise we'll briefly see the window at normal size // before it transitions. - if visible { + if attrs.visible { // Tightly linked with `app_state::window_activation_hack` - unsafe { window.ns_window.makeKeyAndOrderFront_(nil) }; + this.makeKeyAndOrderFront(None); } - if maximized { - window.set_maximized(maximized); + if attrs.maximized { + this.set_maximized(attrs.maximized); } - trace!("Done unowned window::new"); - Ok((window, delegate)) + + Ok((this, delegate)) + } + + pub(super) fn view(&self) -> Id { + // SAFETY: The view inside WinitWindow is always `WinitView` + unsafe { Id::cast(self.contentView()) } } #[track_caller] @@ -463,34 +435,31 @@ impl UnownedWindow { } fn set_style_mask_async(&self, mask: NSWindowStyleMask) { - unsafe { util::set_style_mask_async(*self.ns_window, *self.ns_view, mask) }; + util::set_style_mask_async(self, mask); } fn set_style_mask_sync(&self, mask: NSWindowStyleMask) { - unsafe { util::set_style_mask_sync(*self.ns_window, *self.ns_view, mask) }; + util::set_style_mask_sync(self, mask); } pub fn id(&self) -> WindowId { - get_window_id(*self.ns_window) + get_window_id(self as *const Self as *mut Self as _) } pub fn set_title(&self, title: &str) { - unsafe { - util::set_title_async(*self.ns_window, title.to_string()); - } + util::set_title_async(self, title.to_string()); } pub fn set_visible(&self, visible: bool) { match visible { - true => unsafe { util::make_key_and_order_front_async(*self.ns_window) }, - false => unsafe { util::order_out_async(*self.ns_window) }, + true => util::make_key_and_order_front_async(self), + false => util::order_out_async(self), } } #[inline] pub fn is_visible(&self) -> Option { - let is_visible = unsafe { msg_send![*self.ns_window, isVisible] }; - Some(is_visible) + Some(self.isVisible()) } pub fn request_redraw(&self) { @@ -498,7 +467,7 @@ impl UnownedWindow { } pub fn outer_position(&self) -> Result, NotSupportedError> { - let frame_rect = unsafe { NSWindow::frame(*self.ns_window) }; + let frame_rect = self.frame(); let position = LogicalPosition::new( frame_rect.origin.x as f64, util::bottom_left_to_top_left(frame_rect), @@ -508,9 +477,7 @@ impl UnownedWindow { } pub fn inner_position(&self) -> Result, NotSupportedError> { - let content_rect = unsafe { - NSWindow::contentRectForFrameRect_(*self.ns_window, NSWindow::frame(*self.ns_window)) - }; + let content_rect = self.contentRectForFrameRect(self.frame()); let position = LogicalPosition::new( content_rect.origin.x as f64, util::bottom_left_to_top_left(content_rect), @@ -522,56 +489,87 @@ impl UnownedWindow { pub fn set_outer_position(&self, position: Position) { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); - unsafe { - util::set_frame_top_left_point_async(*self.ns_window, util::window_position(position)); - } + util::set_frame_top_left_point_async(self, util::window_position(position)); } #[inline] pub fn inner_size(&self) -> PhysicalSize { - let view_frame = unsafe { NSView::frame(*self.ns_view) }; - let logical: LogicalSize = - (view_frame.size.width as f64, view_frame.size.height as f64).into(); + let frame = self.contentView().frame(); + let logical: LogicalSize = (frame.size.width as f64, frame.size.height as f64).into(); let scale_factor = self.scale_factor(); logical.to_physical(scale_factor) } #[inline] pub fn outer_size(&self) -> PhysicalSize { - let view_frame = unsafe { NSWindow::frame(*self.ns_window) }; - let logical: LogicalSize = - (view_frame.size.width as f64, view_frame.size.height as f64).into(); + let frame = self.frame(); + let logical: LogicalSize = (frame.size.width as f64, frame.size.height as f64).into(); let scale_factor = self.scale_factor(); logical.to_physical(scale_factor) } #[inline] pub fn set_inner_size(&self, size: Size) { - unsafe { - let scale_factor = self.scale_factor(); - util::set_content_size_async(*self.ns_window, size.to_logical(scale_factor)); - } + let scale_factor = self.scale_factor(); + util::set_content_size_async(self, size.to_logical(scale_factor)); } pub fn set_min_inner_size(&self, dimensions: Option) { - unsafe { - let dimensions = dimensions.unwrap_or(Logical(LogicalSize { - width: 0.0, - height: 0.0, - })); - let scale_factor = self.scale_factor(); - set_min_inner_size(*self.ns_window, dimensions.to_logical(scale_factor)); + let dimensions = dimensions.unwrap_or(Logical(LogicalSize { + width: 0.0, + height: 0.0, + })); + let min_size = dimensions.to_logical::(self.scale_factor()); + + let mut current_rect = self.frame(); + let content_rect = self.contentRectForFrameRect(current_rect); + // Convert from client area size to window size + let min_size = NSSize::new( + min_size.width + (current_rect.size.width - content_rect.size.width), // this tends to be 0 + min_size.height + (current_rect.size.height - content_rect.size.height), + ); + self.setMinSize(min_size); + // If necessary, resize the window to match constraint + if current_rect.size.width < min_size.width { + current_rect.size.width = min_size.width; + self.setFrame_display(current_rect, false) + } + if current_rect.size.height < min_size.height { + // The origin point of a rectangle is at its bottom left in Cocoa. + // To ensure the window's top-left point remains the same: + current_rect.origin.y += current_rect.size.height - min_size.height; + current_rect.size.height = min_size.height; + self.setFrame_display(current_rect, false) } } pub fn set_max_inner_size(&self, dimensions: Option) { - unsafe { - let dimensions = dimensions.unwrap_or(Logical(LogicalSize { - width: std::f32::MAX as f64, - height: std::f32::MAX as f64, - })); - let scale_factor = self.scale_factor(); - set_max_inner_size(*self.ns_window, dimensions.to_logical(scale_factor)); + let dimensions = dimensions.unwrap_or(Logical(LogicalSize { + width: std::f32::MAX as f64, + height: std::f32::MAX as f64, + })); + let scale_factor = self.scale_factor(); + let max_size = dimensions.to_logical::(scale_factor); + + let mut current_rect = self.frame(); + let content_rect = self.contentRectForFrameRect(current_rect); + // Convert from client area size to window size + let max_size = NSSize::new( + max_size.width + (current_rect.size.width - content_rect.size.width), // this tends to be 0 + max_size.height + (current_rect.size.height - content_rect.size.height), + ); + self.setMaxSize(max_size); + // If necessary, resize the window to match constraint + if current_rect.size.width > max_size.width { + current_rect.size.width = max_size.width; + self.setFrame_display(current_rect, false) + } + if current_rect.size.height > max_size.height { + // The origin point of a rectangle is at its bottom left in Cocoa. + // To ensure the window's top-left point remains the same: + current_rect.origin.y += current_rect.size.height - max_size.height; + current_rect.size.height = max_size.height; + self.setFrame_display(current_rect, false) } } @@ -583,7 +581,7 @@ impl UnownedWindow { shared_state_lock.fullscreen.is_some() }; if !fullscreen { - let mut mask = unsafe { self.ns_window.styleMask() }; + let mut mask = self.styleMask(); if resizable { mask |= NSWindowStyleMask::NSResizableWindowMask; } else { @@ -595,24 +593,15 @@ impl UnownedWindow { #[inline] pub fn is_resizable(&self) -> bool { - unsafe { msg_send![*self.ns_window, isResizable] } + self.isResizable() } pub fn set_cursor_icon(&self, icon: CursorIcon) { - let view_state: &ViewState = unsafe { - let ns_view: &Object = (*self.ns_view).as_ref().expect("failed to deref"); - let state_ptr: *const c_void = *ns_view.ivar("state"); - &*(state_ptr as *const ViewState) - }; - let mut cursor_state = view_state.cursor_state.lock().unwrap(); + let view = self.view(); + let mut cursor_state = view.state.cursor_state.lock().unwrap(); cursor_state.cursor = NSCursor::from_icon(icon); drop(cursor_state); - unsafe { - let _: () = msg_send![ - *self.ns_window, - invalidateCursorRectsForView: *self.ns_view, - ]; - } + self.invalidateCursorRectsForView(&view); } #[inline] @@ -632,26 +621,18 @@ impl UnownedWindow { #[inline] pub fn set_cursor_visible(&self, visible: bool) { - let view_state: &ViewState = unsafe { - let ns_view: &Object = (*self.ns_view).as_ref().expect("failed to deref"); - let state_ptr: *const c_void = *ns_view.ivar("state"); - &*(state_ptr as *const ViewState) - }; - let mut cursor_state = view_state.cursor_state.lock().unwrap(); + let view = self.view(); + let mut cursor_state = view.state.cursor_state.lock().unwrap(); if visible != cursor_state.visible { cursor_state.visible = visible; drop(cursor_state); - unsafe { - let _: () = msg_send![*self.ns_window, - invalidateCursorRectsForView:*self.ns_view - ]; - } + self.invalidateCursorRectsForView(&view); } } #[inline] pub fn scale_factor(&self) -> f64 { - unsafe { NSWindow::backingScaleFactor(*self.ns_window) as _ } + self.backingScaleFactor() as f64 } #[inline] @@ -674,27 +655,21 @@ impl UnownedWindow { #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { - unsafe { - let event: id = msg_send![NSApp(), currentEvent]; - let _: () = msg_send![*self.ns_window, performWindowDragWithEvent: event]; - } - + let event: &NSEvent = unsafe { msg_send![NSApp(), currentEvent] }; + self.performWindowDragWithEvent(event); Ok(()) } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { - unsafe { - util::set_ignore_mouse_events(*self.ns_window, !hittest); - } - + util::set_ignore_mouse_events(self, !hittest); Ok(()) } pub(crate) fn is_zoomed(&self) -> bool { // because `isZoomed` doesn't work if the window's borderless, // we make it resizable temporalily. - let curr_mask = unsafe { self.ns_window.styleMask() }; + let curr_mask = self.styleMask(); let required = NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; @@ -703,7 +678,7 @@ impl UnownedWindow { self.set_style_mask_sync(required); } - let is_zoomed = unsafe { msg_send![*self.ns_window, isZoomed] }; + let is_zoomed = self.isZoomed(); // Roll back temp styles if needs_temp_mask { @@ -717,7 +692,7 @@ impl UnownedWindow { let base_mask = shared_state .saved_style .take() - .unwrap_or_else(|| unsafe { self.ns_window.styleMask() }); + .unwrap_or_else(|| self.styleMask()); if shared_state.resizable { base_mask | NSWindowStyleMask::NSResizableWindowMask } else { @@ -744,19 +719,15 @@ impl UnownedWindow { #[inline] pub fn set_minimized(&self, minimized: bool) { - let is_minimized: bool = unsafe { msg_send![*self.ns_window, isMiniaturized] }; + let is_minimized = self.isMiniaturized(); if is_minimized == minimized { return; } if minimized { - unsafe { - NSWindow::miniaturize_(*self.ns_window, *self.ns_window); - } + self.miniaturize(Some(self)); } else { - unsafe { - NSWindow::deminiaturize_(*self.ns_window, *self.ns_window); - } + self.deminiaturize(Some(self)); } } @@ -766,14 +737,12 @@ impl UnownedWindow { if is_zoomed == maximized { return; }; - unsafe { - util::set_maximized_async( - *self.ns_window, - is_zoomed, - maximized, - Arc::downgrade(&self.shared_state), - ); - } + util::set_maximized_async( + self, + is_zoomed, + maximized, + Arc::downgrade(&*self.shared_state), + ); } #[inline] @@ -823,15 +792,13 @@ impl UnownedWindow { .ns_screen() .unwrap(); - unsafe { - let old_screen = NSWindow::screen(*self.ns_window); - if old_screen != new_screen { - let mut screen_frame: NSRect = msg_send![new_screen, frame]; - // The coordinate system here has its origin at bottom-left - // and Y goes up - screen_frame.origin.y += screen_frame.size.height; - util::set_frame_top_left_point_async(*self.ns_window, screen_frame.origin); - } + let old_screen = self.screen().unwrap(); + if old_screen != new_screen { + let mut screen_frame = new_screen.frame(); + // The coordinate system here has its origin at bottom-left + // and Y goes up + screen_frame.origin.y += screen_frame.size.height; + util::set_frame_top_left_point_async(self, screen_frame.origin); } } @@ -911,33 +878,32 @@ impl UnownedWindow { shared_state_lock.fullscreen = fullscreen.clone(); match (&old_fullscreen, &fullscreen) { - (&None, &Some(_)) => unsafe { + (&None, &Some(_)) => { util::toggle_full_screen_async( - *self.ns_window, - *self.ns_view, + self, old_fullscreen.is_none(), - Arc::downgrade(&self.shared_state), + Arc::downgrade(&*self.shared_state), ); - }, - (&Some(Fullscreen::Borderless(_)), &None) => unsafe { + } + (&Some(Fullscreen::Borderless(_)), &None) => { // State is restored by `window_did_exit_fullscreen` util::toggle_full_screen_async( - *self.ns_window, - *self.ns_view, + self, old_fullscreen.is_none(), - Arc::downgrade(&self.shared_state), + Arc::downgrade(&*self.shared_state), ); - }, - (&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &None) => unsafe { - util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); + } + (&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &None) => { + unsafe { + util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()) + }; // Rest of the state is restored by `window_did_exit_fullscreen` util::toggle_full_screen_async( - *self.ns_window, - *self.ns_view, + self, old_fullscreen.is_none(), - Arc::downgrade(&self.shared_state), + Arc::downgrade(&*self.shared_state), ); - }, + } (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe { // If we're already in fullscreen mode, calling // `CGDisplayCapture` will place the shielding window on top of @@ -956,7 +922,7 @@ impl UnownedWindow { | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; app.setPresentationOptions_(presentation_options); - let _: () = msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1]; + let _: () = msg_send![self, setLevel: ffi::CGShieldingWindowLevel() + 1]; }, ( &Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), @@ -974,7 +940,7 @@ impl UnownedWindow { // Restore the normal window level following the Borderless fullscreen // `CGShieldingWindowLevel() + 1` hack. - let _: () = msg_send![*self.ns_window, setLevel: ffi::kCGBaseWindowLevelKey]; + self.setLevel(NSWindowLevel::Normal); }, _ => {} }; @@ -1026,11 +992,11 @@ impl UnownedWindow { #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { let level = if always_on_top { - ffi::NSWindowLevel::NSFloatingWindowLevel + NSWindowLevel::Floating } else { - ffi::NSWindowLevel::NSNormalWindowLevel + NSWindowLevel::Normal }; - unsafe { util::set_level_async(*self.ns_window, level) }; + util::set_level_async(self, level); } #[inline] @@ -1049,25 +1015,25 @@ impl UnownedWindow { pub fn set_ime_position(&self, spot: Position) { let scale_factor = self.scale_factor(); let logical_spot = spot.to_logical(scale_factor); - let view: *mut WinitView = self.ns_view.cast(); - unsafe { &mut *view }.set_ime_position(logical_spot); + // TODO(madsmtm): Remove the need for this + unsafe { Id::from_shared(self.view()) }.set_ime_position(logical_spot); } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { - let view: *mut WinitView = self.ns_view.cast(); - unsafe { &mut *view }.set_ime_allowed(allowed); + // TODO(madsmtm): Remove the need for this + unsafe { Id::from_shared(self.view()) }.set_ime_allowed(allowed); } #[inline] pub fn focus_window(&self) { - let is_minimized: bool = unsafe { msg_send![*self.ns_window, isMiniaturized] }; - let is_visible: bool = unsafe { msg_send![*self.ns_window, isVisible] }; + let is_minimized = self.isMiniaturized(); + let is_visible = self.isVisible(); if !is_minimized && is_visible { unsafe { NSApp().activateIgnoringOtherApps_(Bool::YES.as_raw()); - util::make_key_and_order_front_async(*self.ns_window); + util::make_key_and_order_front_async(self); } } } @@ -1088,15 +1054,9 @@ impl UnownedWindow { #[inline] // Allow directly accessing the current monitor internally without unwrapping. pub(crate) fn current_monitor_inner(&self) -> RootMonitorHandle { - unsafe { - let screen: id = msg_send![*self.ns_window, screen]; - let desc = NSScreen::deviceDescription(screen); - let key = util::ns_string_id_ref("NSScreenNumber"); - let value = NSDictionary::valueForKey_(desc, *key); - let display_id: NSUInteger = msg_send![value, unsignedIntegerValue]; - RootMonitorHandle { - inner: MonitorHandle::new(display_id.try_into().unwrap()), - } + let display_id = self.screen().expect("expected screen").display_id(); + RootMonitorHandle { + inner: MonitorHandle::new(display_id), } } @@ -1119,8 +1079,8 @@ impl UnownedWindow { #[inline] pub fn raw_window_handle(&self) -> RawWindowHandle { let mut window_handle = AppKitWindowHandle::empty(); - window_handle.ns_window = *self.ns_window as *mut _; - window_handle.ns_view = *self.ns_view as *mut _; + window_handle.ns_window = self.ns_window(); + window_handle.ns_view = self.ns_view(); RawWindowHandle::AppKit(window_handle) } @@ -1128,17 +1088,26 @@ impl UnownedWindow { pub fn raw_display_handle(&self) -> RawDisplayHandle { RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()) } + + fn toggle_style_mask(&self, mask: NSWindowStyleMask, on: bool) { + let current_style_mask = self.styleMask(); + if on { + util::set_style_mask_sync(self, current_style_mask | mask); + } else { + util::set_style_mask_sync(self, current_style_mask & (!mask)); + } + } } -impl WindowExtMacOS for UnownedWindow { +impl WindowExtMacOS for WinitWindow { #[inline] fn ns_window(&self) -> *mut c_void { - *self.ns_window as *mut _ + self as *const Self as *mut _ } #[inline] fn ns_view(&self) -> *mut c_void { - *self.ns_view as *mut _ + Id::as_ptr(&self.contentView()) as *mut _ } #[inline] @@ -1151,152 +1120,71 @@ impl WindowExtMacOS for UnownedWindow { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { let mut shared_state_lock = self.shared_state.lock().unwrap(); - unsafe { - let app = NSApp(); - let is_native_fullscreen = shared_state_lock.fullscreen.is_some(); - let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen; - - // Do nothing if native fullscreen is active. - if is_native_fullscreen - || (fullscreen && is_simple_fullscreen) - || (!fullscreen && !is_simple_fullscreen) - { - return false; - } + let app = unsafe { NSApp() }; + let is_native_fullscreen = shared_state_lock.fullscreen.is_some(); + let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen; - if fullscreen { - // Remember the original window's settings - // Exclude title bar - shared_state_lock.standard_frame = Some(NSWindow::contentRectForFrameRect_( - *self.ns_window, - NSWindow::frame(*self.ns_window), - )); - shared_state_lock.saved_style = Some(self.ns_window.styleMask()); - shared_state_lock.save_presentation_opts = Some(app.presentationOptions_()); + // Do nothing if native fullscreen is active. + if is_native_fullscreen + || (fullscreen && is_simple_fullscreen) + || (!fullscreen && !is_simple_fullscreen) + { + return false; + } - // Tell our window's state that we're in fullscreen - shared_state_lock.is_simple_fullscreen = true; + if fullscreen { + // Remember the original window's settings + // Exclude title bar + shared_state_lock.standard_frame = Some(self.contentRectForFrameRect(self.frame())); + shared_state_lock.saved_style = Some(self.styleMask()); + shared_state_lock.save_presentation_opts = Some(unsafe { app.presentationOptions_() }); - // Simulate pre-Lion fullscreen by hiding the dock and menu bar - let presentation_options = - NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | - NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; - app.setPresentationOptions_(presentation_options); + // Tell our window's state that we're in fullscreen + shared_state_lock.is_simple_fullscreen = true; - // Hide the titlebar - util::toggle_style_mask( - *self.ns_window, - *self.ns_view, - NSWindowStyleMask::NSTitledWindowMask, - false, - ); + // Simulate pre-Lion fullscreen by hiding the dock and menu bar + let presentation_options = + NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; + unsafe { app.setPresentationOptions_(presentation_options) }; - // Set the window frame to the screen frame size - let screen = self.ns_window.screen(); - let screen_frame = NSScreen::frame(screen); - NSWindow::setFrame_display_(*self.ns_window, screen_frame, Bool::YES.as_raw()); - - // Fullscreen windows can't be resized, minimized, or moved - util::toggle_style_mask( - *self.ns_window, - *self.ns_view, - NSWindowStyleMask::NSMiniaturizableWindowMask, - false, - ); - util::toggle_style_mask( - *self.ns_window, - *self.ns_view, - NSWindowStyleMask::NSResizableWindowMask, - false, - ); - NSWindow::setMovable_(*self.ns_window, Bool::NO.as_raw()); + // Hide the titlebar + self.toggle_style_mask(NSWindowStyleMask::NSTitledWindowMask, false); - true - } else { - let new_mask = self.saved_style(&mut *shared_state_lock); - self.set_style_mask_async(new_mask); - shared_state_lock.is_simple_fullscreen = false; + // Set the window frame to the screen frame size + let screen = self.screen().expect("expected screen to be available"); + self.setFrame_display(screen.frame(), true); - if let Some(presentation_opts) = shared_state_lock.save_presentation_opts { - app.setPresentationOptions_(presentation_opts); - } + // Fullscreen windows can't be resized, minimized, or moved + self.toggle_style_mask(NSWindowStyleMask::NSMiniaturizableWindowMask, false); + self.toggle_style_mask(NSWindowStyleMask::NSResizableWindowMask, false); + self.setMovable(false); - let frame = shared_state_lock.saved_standard_frame(); - NSWindow::setFrame_display_(*self.ns_window, frame, Bool::YES.as_raw()); - NSWindow::setMovable_(*self.ns_window, Bool::YES.as_raw()); + true + } else { + let new_mask = self.saved_style(&mut *shared_state_lock); + self.set_style_mask_async(new_mask); + shared_state_lock.is_simple_fullscreen = false; - true + if let Some(presentation_opts) = shared_state_lock.save_presentation_opts { + unsafe { app.setPresentationOptions_(presentation_opts) }; } + + let frame = shared_state_lock.saved_standard_frame(); + self.setFrame_display(frame, true); + self.setMovable(true); + + true } } #[inline] fn has_shadow(&self) -> bool { - unsafe { Bool::from_raw(self.ns_window.hasShadow()).as_bool() } + self.hasShadow() } #[inline] fn set_has_shadow(&self, has_shadow: bool) { - unsafe { self.ns_window.setHasShadow_(Bool::new(has_shadow).as_raw()) } - } -} - -impl Drop for UnownedWindow { - fn drop(&mut self) { - trace!("Dropping `UnownedWindow` ({:?})", self as *mut _); - // Close the window if it has not yet been closed. - if *self.ns_window != nil { - unsafe { util::close_async(self.ns_window.clone()) }; - } - } -} - -unsafe fn set_min_inner_size(window: V, mut min_size: LogicalSize) { - let mut current_rect = NSWindow::frame(window); - let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); - // Convert from client area size to window size - min_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 - min_size.height += (current_rect.size.height - content_rect.size.height) as f64; - let min_size = NSSize { - width: min_size.width as CGFloat, - height: min_size.height as CGFloat, - }; - window.setMinSize_(min_size); - // If necessary, resize the window to match constraint - if current_rect.size.width < min_size.width { - current_rect.size.width = min_size.width; - window.setFrame_display_(current_rect, Bool::NO.as_raw()) - } - if current_rect.size.height < min_size.height { - // The origin point of a rectangle is at its bottom left in Cocoa. - // To ensure the window's top-left point remains the same: - current_rect.origin.y += current_rect.size.height - min_size.height; - current_rect.size.height = min_size.height; - window.setFrame_display_(current_rect, Bool::NO.as_raw()) - } -} - -unsafe fn set_max_inner_size(window: V, mut max_size: LogicalSize) { - let mut current_rect = NSWindow::frame(window); - let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); - // Convert from client area size to window size - max_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 - max_size.height += (current_rect.size.height - content_rect.size.height) as f64; - let max_size = NSSize { - width: max_size.width as CGFloat, - height: max_size.height as CGFloat, - }; - window.setMaxSize_(max_size); - // If necessary, resize the window to match constraint - if current_rect.size.width > max_size.width { - current_rect.size.width = max_size.width; - window.setFrame_display_(current_rect, Bool::NO.as_raw()) - } - if current_rect.size.height > max_size.height { - // The origin point of a rectangle is at its bottom left in Cocoa. - // To ensure the window's top-left point remains the same: - current_rect.origin.y += current_rect.size.height - max_size.height; - current_rect.size.height = max_size.height; - window.setFrame_display_(current_rect, Bool::NO.as_raw()) + self.setHasShadow(has_shadow) } } diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index f16b64baa6..e6b249a2cd 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,18 +1,14 @@ -use std::{ - f64, - os::raw::c_void, - sync::{Arc, Weak}, -}; +use std::os::raw::c_void; use cocoa::{ - appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow, NSWindowOcclusionState}, + appkit::{self, NSApplicationPresentationOptions}, base::{id, nil}, }; use objc2::foundation::{NSObject, NSUInteger}; -use objc2::rc::autoreleasepool; -use objc2::runtime::Object; +use objc2::rc::{autoreleasepool, Id, Shared}; use objc2::{declare_class, ClassType}; +use super::appkit::NSWindowOcclusionState; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{Event, ModifiersState, WindowEvent}, @@ -20,17 +16,13 @@ use crate::{ app_state::AppState, event::{EventProxy, EventWrapper}, util::{self, IdRef}, - view::ViewState, - window::{get_window_id, UnownedWindow}, + window::WinitWindow, }, window::{Fullscreen, WindowId}, }; struct WindowDelegateState { - ns_window: IdRef, // never changes - ns_view: IdRef, // never changes - - window: Weak, + window: Id, // TODO: It's possible for delegate methods to be called asynchronously, // causing data races / `RefCell` panics. @@ -47,12 +39,10 @@ struct WindowDelegateState { } impl WindowDelegateState { - fn new(window: &Arc, initial_fullscreen: bool) -> Self { + fn new(window: Id, initial_fullscreen: bool) -> Self { let scale_factor = window.scale_factor(); let mut delegate_state = WindowDelegateState { - ns_window: window.ns_window.clone(), - ns_view: window.ns_view.clone(), - window: Arc::downgrade(window), + window, initial_fullscreen, previous_position: None, previous_scale_factor: scale_factor, @@ -65,16 +55,17 @@ impl WindowDelegateState { delegate_state } + // TODO: Remove this fn with_window(&mut self, callback: F) -> Option where - F: FnOnce(&UnownedWindow) -> T, + F: FnOnce(&WinitWindow) -> T, { - self.window.upgrade().map(|ref window| callback(window)) + Some(callback(&self.window.clone())) } fn emit_event(&mut self, event: WindowEvent<'static>) { let event = Event::WindowEvent { - window_id: WindowId(get_window_id(*self.ns_window)), + window_id: WindowId(self.window.id()), event, }; AppState::queue_event(EventWrapper::StaticEvent(event)); @@ -87,8 +78,9 @@ impl WindowDelegateState { }; self.previous_scale_factor = scale_factor; + let ns_window: *const WinitWindow = &*self.window; let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { - ns_window: IdRef::retain(*self.ns_window), + ns_window: IdRef::retain(ns_window as _), suggested_size: self.view_size(), scale_factor, }); @@ -96,7 +88,7 @@ impl WindowDelegateState { } fn emit_move_event(&mut self) { - let rect = unsafe { NSWindow::frame(*self.ns_window) }; + let rect = self.window.frame(); let x = rect.origin.x as f64; let y = util::bottom_left_to_top_left(rect); let moved = self.previous_position != Some((x, y)); @@ -109,16 +101,16 @@ impl WindowDelegateState { } fn get_scale_factor(&self) -> f64 { - (unsafe { NSWindow::backingScaleFactor(*self.ns_window) }) as f64 + self.window.scale_factor() } fn view_size(&self) -> LogicalSize { - let ns_size = unsafe { NSView::frame(*self.ns_view).size }; - LogicalSize::new(ns_size.width as f64, ns_size.height as f64) + let size = self.window.contentView().frame().size; + LogicalSize::new(size.width as f64, size.height as f64) } } -pub fn new_delegate(window: &Arc, initial_fullscreen: bool) -> IdRef { +pub(crate) fn new_delegate(window: Id, initial_fullscreen: bool) -> IdRef { let state = WindowDelegateState::new(window, initial_fullscreen); unsafe { // This is free'd in `dealloc` @@ -152,7 +144,7 @@ declare_class!( this.map(|this| { *this.state = state; this.with_state(|state| { - let _: () = unsafe { msg_send![*state.ns_window, setDelegate: &*this] }; + state.window.setDelegate(Some(&*this)); }); this }) @@ -171,12 +163,12 @@ declare_class!( #[sel(windowWillClose:)] fn window_will_close(&self, _: id) { trace_scope!("windowWillClose:"); - self.with_state(|state| unsafe { + self.with_state(|state| { // `setDelegate:` retains the previous value and then autoreleases it autoreleasepool(|_| { // Since El Capitan, we need to be careful that delegate methods can't // be called after the window closes. - let _: () = msg_send![*state.ns_window, setDelegate: nil]; + state.window.setDelegate(None); }); state.emit_event(WindowEvent::Destroyed); }); @@ -229,20 +221,14 @@ declare_class!( // NSWindowDelegate, and as a result a tracked modifiers state can quite // easily fall out of synchrony with reality. This requires us to emit // a synthetic ModifiersChanged event when we lose focus. - // - // Here we (very unsafely) acquire the state (a ViewState) from the - // Object referenced by state.ns_view (an IdRef, which is dereferenced - // to an id) - let view_state: &mut ViewState = unsafe { - let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref"); - let state_ptr: *mut c_void = *ns_view.ivar("state"); - &mut *(state_ptr as *mut ViewState) - }; + + // TODO(madsmtm): Remove the need for this unsafety + let mut view = unsafe { Id::from_shared(state.window.view()) }; // Both update the state and emit a ModifiersChanged event. - if !view_state.modifiers.is_empty() { - view_state.modifiers = ModifiersState::empty(); - state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers)); + if !view.state.modifiers.is_empty() { + view.state.modifiers = ModifiersState::empty(); + state.emit_event(WindowEvent::ModifiersChanged(view.state.modifiers)); } state.emit_event(WindowEvent::Focused(false)); @@ -468,10 +454,11 @@ declare_class!( }); if state.initial_fullscreen { unsafe { - let _: () = msg_send![*state.ns_window, - performSelector:sel!(toggleFullScreen:) - withObject:nil - afterDelay: 0.5 + let _: () = msg_send![ + &*state.window, + performSelector: sel!(toggleFullScreen:), + withObject: nil, + afterDelay: 0.5, ]; }; } else { @@ -484,16 +471,14 @@ declare_class!( #[sel(windowDidChangeOcclusionState:)] fn window_did_change_occlusion_state(&self, _: id) { trace_scope!("windowDidChangeOcclusionState:"); - unsafe { - self.with_state(|state| { - state.emit_event(WindowEvent::Occluded( - !state - .ns_window - .occlusionState() - .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), - )) - }); - } + self.with_state(|state| { + state.emit_event(WindowEvent::Occluded( + !state + .window + .occlusionState() + .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), + )) + }); } } ); From 639237818e5f4e7408bb474196796e9ceae207bf Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 3 Sep 2022 00:28:32 +0200 Subject: [PATCH 4/8] Refactor NSApplication usage to have much less unsafe --- src/platform_impl/macos/app.rs | 56 +++++------ src/platform_impl/macos/app_delegate.rs | 2 +- src/platform_impl/macos/app_state.rs | 85 +++++++---------- src/platform_impl/macos/appkit/application.rs | 88 ++++++++++++++++- src/platform_impl/macos/appkit/event.rs | 59 +++++++++++- src/platform_impl/macos/appkit/mod.rs | 11 ++- src/platform_impl/macos/appkit/window.rs | 6 +- src/platform_impl/macos/event.rs | 16 ++-- src/platform_impl/macos/event_loop.rs | 95 ++++++------------- src/platform_impl/macos/menu.rs | 8 +- src/platform_impl/macos/util/mod.rs | 6 -- src/platform_impl/macos/window.rs | 75 +++++++-------- src/platform_impl/macos/window_delegate.rs | 20 ++-- 13 files changed, 300 insertions(+), 227 deletions(-) diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index 2b6e1ecfca..07627545fb 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -1,13 +1,9 @@ use std::collections::VecDeque; -use cocoa::{ - appkit::{self, NSEvent}, - base::id, -}; use objc2::foundation::NSObject; use objc2::{declare_class, ClassType}; -use super::appkit::{NSApplication, NSResponder}; +use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; use crate::event::{DeviceEvent, ElementState, Event}; @@ -25,37 +21,33 @@ declare_class!( // Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) // Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) #[sel(sendEvent:)] - fn send_event(&self, event: id) { - unsafe { - // For posterity, there are some undocumented event types - // (https://github.com/servo/cocoa-rs/issues/155) - // but that doesn't really matter here. - let event_type = event.eventType(); - let modifier_flags = event.modifierFlags(); - if event_type == appkit::NSKeyUp - && util::has_flag( - modifier_flags, - appkit::NSEventModifierFlags::NSCommandKeyMask, - ) - { - let key_window: id = msg_send![self, keyWindow]; - let _: () = msg_send![key_window, sendEvent: event]; - } else { - maybe_dispatch_device_event(event); - let _: () = msg_send![super(self), sendEvent: event]; + fn send_event(&self, event: &NSEvent) { + // For posterity, there are some undocumented event types + // (https://github.com/servo/cocoa-rs/issues/155) + // but that doesn't really matter here. + let event_type = event.type_(); + let modifier_flags = event.modifierFlags(); + if event_type == NSEventType::NSKeyUp + && util::has_flag(modifier_flags, NSEventModifierFlags::NSCommandKeyMask) + { + if let Some(key_window) = self.keyWindow() { + unsafe { key_window.sendEvent(event) }; } + } else { + maybe_dispatch_device_event(event); + let _: () = unsafe { msg_send![super(self), sendEvent: event] }; } } } ); -unsafe fn maybe_dispatch_device_event(event: id) { - let event_type = event.eventType(); +fn maybe_dispatch_device_event(event: &NSEvent) { + let event_type = event.type_(); match event_type { - appkit::NSMouseMoved - | appkit::NSLeftMouseDragged - | appkit::NSOtherMouseDragged - | appkit::NSRightMouseDragged => { + NSEventType::NSMouseMoved + | NSEventType::NSLeftMouseDragged + | NSEventType::NSOtherMouseDragged + | NSEventType::NSRightMouseDragged => { let mut events = VecDeque::with_capacity(3); let delta_x = event.deltaX() as f64; @@ -92,7 +84,9 @@ unsafe fn maybe_dispatch_device_event(event: id) { AppState::queue_events(events); } - appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => { + NSEventType::NSLeftMouseDown + | NSEventType::NSRightMouseDown + | NSEventType::NSOtherMouseDown => { let mut events = VecDeque::with_capacity(1); events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { @@ -105,7 +99,7 @@ unsafe fn maybe_dispatch_device_event(event: id) { AppState::queue_events(events); } - appkit::NSLeftMouseUp | appkit::NSRightMouseUp | appkit::NSOtherMouseUp => { + NSEventType::NSLeftMouseUp | NSEventType::NSRightMouseUp | NSEventType::NSOtherMouseUp => { let mut events = VecDeque::with_capacity(1); events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index 681327417e..841a9a9035 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -1,10 +1,10 @@ -use cocoa::appkit::NSApplicationActivationPolicy; use objc2::foundation::NSObject; use objc2::rc::{Id, Shared}; use objc2::runtime::Object; use objc2::{declare_class, ClassType}; use super::app_state::AppState; +use super::appkit::NSApplicationActivationPolicy; declare_class!( #[derive(Debug)] diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index e9b9f6e2ea..7cfd945852 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -2,7 +2,6 @@ use std::{ cell::{RefCell, RefMut}, collections::VecDeque, fmt::{self, Debug}, - hint::unreachable_unchecked, mem, rc::{Rc, Weak}, sync::{ @@ -12,27 +11,22 @@ use std::{ time::Instant, }; -use cocoa::{ - appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSWindow}, - base::{id, nil}, - foundation::NSSize, -}; -use objc::foundation::is_main_thread; -use objc::rc::autoreleasepool; -use objc::runtime::Bool; +use objc2::foundation::{is_main_thread, NSSize}; +use objc2::rc::autoreleasepool; use once_cell::sync::Lazy; +use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent}; use crate::{ dpi::LogicalSize, event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, platform_impl::platform::{ event::{EventProxy, EventWrapper}, - event_loop::{post_dummy_event, PanicInfo}, + event_loop::PanicInfo, menu, observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker}, - util::{IdRef, Never}, - window::get_window_id, + util::Never, + window::WinitWindow, }, window::WindowId, }; @@ -44,7 +38,7 @@ impl<'a, Never> Event<'a, Never> { self.map_nonuser_event() // `Never` can't be constructed, so the `UserEvent` variant can't // be present here. - .unwrap_or_else(|_| unsafe { unreachable_unchecked() }) + .unwrap_or_else(|_| unreachable!()) } } @@ -217,14 +211,14 @@ impl Handler { fn handle_scale_factor_changed_event( &self, callback: &mut Box, - ns_window: IdRef, + window: &WinitWindow, suggested_size: LogicalSize, scale_factor: f64, ) { let mut size = suggested_size.to_physical(scale_factor); let new_inner_size = &mut size; let event = Event::WindowEvent { - window_id: WindowId(get_window_id(*ns_window)), + window_id: WindowId(window.id()), event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size, @@ -236,18 +230,18 @@ impl Handler { let physical_size = *new_inner_size; let logical_size = physical_size.to_logical(scale_factor); let size = NSSize::new(logical_size.width, logical_size.height); - unsafe { NSWindow::setContentSize_(*ns_window, size) }; + window.setContentSize(size); } fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box) { match proxy { EventProxy::DpiChangedProxy { - ns_window, + window, suggested_size, scale_factor, } => self.handle_scale_factor_changed_event( callback, - ns_window, + &window, suggested_size, scale_factor, ), @@ -255,7 +249,7 @@ impl Handler { } } -pub enum AppState {} +pub(crate) enum AppState {} impl AppState { pub fn set_callback(callback: Weak>, window_target: Rc>) { @@ -278,18 +272,16 @@ impl AppState { } pub fn launched(activation_policy: NSApplicationActivationPolicy, create_default_menu: bool) { - unsafe { - let ns_app = NSApp(); + let app = NSApp(); + // We need to delay setting the activation policy and activating the app + // until `applicationDidFinishLaunching` has been called. Otherwise the + // menu bar is initially unresponsive on macOS 10.15. + app.setActivationPolicy(activation_policy); - // We need to delay setting the activation policy and activating the app - // until `applicationDidFinishLaunching` has been called. Otherwise the - // menu bar is initially unresponsive on macOS 10.15. - ns_app.setActivationPolicy_(activation_policy); + window_activation_hack(&app); + // TODO: Consider allowing the user to specify they don't want their application activated + app.activateIgnoringOtherApps(true); - window_activation_hack(ns_app); - // TODO: Consider allowing the user to specify they don't want their application activated - ns_app.activateIgnoringOtherApps_(Bool::YES.as_raw()); - }; HANDLER.set_ready(); HANDLER.waker().start(); if create_default_menu { @@ -397,15 +389,12 @@ impl AppState { HANDLER.set_in_callback(false); if HANDLER.should_exit() { - unsafe { - let app: id = NSApp(); - - autoreleasepool(|_| { - let _: () = msg_send![app, stop: nil]; - // To stop event loop immediately, we need to post some event here. - post_dummy_event(app); - }); - }; + let app = NSApp(); + autoreleasepool(|_| { + app.stop(None); + // To stop event loop immediately, we need to post some event here. + app.postEvent_atStart(&NSEvent::dummy(), true); + }); } HANDLER.update_start_time(); match HANDLER.get_old_and_new_control_flow() { @@ -426,25 +415,17 @@ impl AppState { /// /// If this becomes too bothersome to maintain, it can probably be removed /// without too much damage. -unsafe fn window_activation_hack(ns_app: id) { - // Get the application's windows +fn window_activation_hack(app: &NSApplication) { // TODO: Proper ordering of the windows - let ns_windows: id = msg_send![ns_app, windows]; - let ns_enumerator: id = msg_send![ns_windows, objectEnumerator]; - loop { - // Enumerate over the windows - let ns_window: id = msg_send![ns_enumerator, nextObject]; - if ns_window == nil { - break; - } - // And call `makeKeyAndOrderFront` if it was called on the window in `UnownedWindow::new` + app.windows().into_iter().for_each(|window| { + // Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::new` // This way we preserve the user's desired initial visiblity status // TODO: Also filter on the type/"level" of the window, and maybe other things? - if Bool::from_raw(ns_window.isVisible()).as_bool() { + if window.isVisible() { trace!("Activating visible window"); - ns_window.makeKeyAndOrderFront_(nil); + window.makeKeyAndOrderFront(None); } else { trace!("Skipping activating invisible window"); } - } + }) } diff --git a/src/platform_impl/macos/appkit/application.rs b/src/platform_impl/macos/appkit/application.rs index 1681c4d5c5..353d9fefe4 100644 --- a/src/platform_impl/macos/appkit/application.rs +++ b/src/platform_impl/macos/appkit/application.rs @@ -1,9 +1,10 @@ -use objc2::foundation::{MainThreadMarker, NSObject}; +use objc2::foundation::{MainThreadMarker, NSArray, NSInteger, NSObject, NSUInteger}; use objc2::rc::{Id, Shared}; use objc2::runtime::Object; use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; +use objc2::{Encode, Encoding}; -use super::{NSEvent, NSResponder}; +use super::{NSEvent, NSResponder, NSWindow}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] @@ -16,8 +17,8 @@ extern_class!( ); pub(crate) fn NSApp() -> Id { - let msg = "tried to access NSApp on something that was not the main thread"; - NSApplication::shared(MainThreadMarker::new().expect(msg)) + // TODO: Only allow access from main thread + NSApplication::shared(unsafe { MainThreadMarker::new_unchecked() }) } extern_methods!( @@ -35,13 +36,34 @@ extern_methods!( unsafe { msg_send_id![self, currentEvent] } } + #[sel(postEvent:atStart:)] + pub fn postEvent_atStart(&self, event: &NSEvent, front_of_queue: bool); + + #[sel(presentationOptions)] + pub fn presentationOptions(&self) -> NSApplicationPresentationOptions; + + pub fn windows(&self) -> Id, Shared> { + unsafe { msg_send_id![self, windows] } + } + + pub fn keyWindow(&self) -> Option> { + unsafe { msg_send_id![self, keyWindow] } + } + // TODO: NSApplicationDelegate #[sel(setDelegate:)] - pub unsafe fn setDelegate(&self, delegate: &Object); + pub fn setDelegate(&self, delegate: &Object); + + #[sel(setPresentationOptions:)] + pub fn setPresentationOptions(&self, options: NSApplicationPresentationOptions); #[sel(hide:)] pub fn hide(&self, sender: Option<&Object>); + #[sel(orderFrontCharacterPalette:)] + #[allow(dead_code)] + pub fn orderFrontCharacterPalette(&self, sender: Option<&Object>); + #[sel(hideOtherApplications:)] pub fn hideOtherApplications(&self, sender: Option<&Object>); @@ -51,7 +73,63 @@ extern_methods!( #[sel(activateIgnoringOtherApps:)] pub fn activateIgnoringOtherApps(&self, ignore: bool); + #[sel(requestUserAttention:)] + pub fn requestUserAttention(&self, type_: NSRequestUserAttentionType) -> NSInteger; + + #[sel(setActivationPolicy:)] + pub fn setActivationPolicy(&self, policy: NSApplicationActivationPolicy) -> bool; + + #[sel(setMainMenu:)] + pub fn setMainMenu(&self, menu: *mut Object); + #[sel(run)] pub unsafe fn run(&self); } ); + +#[allow(dead_code)] +#[repr(isize)] // NSInteger +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NSApplicationActivationPolicy { + NSApplicationActivationPolicyRegular = 0, + NSApplicationActivationPolicyAccessory = 1, + NSApplicationActivationPolicyProhibited = 2, + NSApplicationActivationPolicyERROR = -1, +} + +unsafe impl Encode for NSApplicationActivationPolicy { + const ENCODING: Encoding = NSInteger::ENCODING; +} + +bitflags! { + pub struct NSApplicationPresentationOptions: NSUInteger { + const NSApplicationPresentationDefault = 0; + const NSApplicationPresentationAutoHideDock = 1 << 0; + const NSApplicationPresentationHideDock = 1 << 1; + const NSApplicationPresentationAutoHideMenuBar = 1 << 2; + const NSApplicationPresentationHideMenuBar = 1 << 3; + const NSApplicationPresentationDisableAppleMenu = 1 << 4; + const NSApplicationPresentationDisableProcessSwitching = 1 << 5; + const NSApplicationPresentationDisableForceQuit = 1 << 6; + const NSApplicationPresentationDisableSessionTermination = 1 << 7; + const NSApplicationPresentationDisableHideApplication = 1 << 8; + const NSApplicationPresentationDisableMenuBarTransparency = 1 << 9; + const NSApplicationPresentationFullScreen = 1 << 10; + const NSApplicationPresentationAutoHideToolbar = 1 << 11; + } +} + +unsafe impl Encode for NSApplicationPresentationOptions { + const ENCODING: Encoding = NSUInteger::ENCODING; +} + +#[repr(usize)] // NSUInteger +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NSRequestUserAttentionType { + NSCriticalRequest = 0, + NSInformationalRequest = 10, +} + +unsafe impl Encode for NSRequestUserAttentionType { + const ENCODING: Encoding = NSUInteger::ENCODING; +} diff --git a/src/platform_impl/macos/appkit/event.rs b/src/platform_impl/macos/appkit/event.rs index 6ba04c3d87..a952157644 100644 --- a/src/platform_impl/macos/appkit/event.rs +++ b/src/platform_impl/macos/appkit/event.rs @@ -1,7 +1,9 @@ use std::os::raw::c_ushort; use objc2::encode::{Encode, Encoding}; -use objc2::foundation::{CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSUInteger}; +use objc2::foundation::{ + CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSTimeInterval, NSUInteger, +}; use objc2::rc::{Id, Shared}; use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; @@ -22,6 +24,49 @@ extern_class!( extern_methods!( unsafe impl NSEvent { + unsafe fn otherEventWithType( + type_: NSEventType, + location: NSPoint, + flags: NSEventModifierFlags, + time: NSTimeInterval, + window_num: NSInteger, + context: Option<&NSObject>, // NSGraphicsContext + subtype: NSEventSubtype, + data1: NSInteger, + data2: NSInteger, + ) -> Id { + unsafe { + msg_send_id![ + Self::class(), + otherEventWithType: type_, + location: location, + modifierFlags: flags, + timestamp: time, + windowNumber: window_num, + context: context, + subtype: subtype, + data1: data1, + data2: data2, + ] + } + } + + pub fn dummy() -> Id { + unsafe { + Self::otherEventWithType( + NSEventType::NSApplicationDefined, + NSPoint::new(0.0, 0.0), + NSEventModifierFlags::empty(), + 0.0, + 0, + None, + NSEventSubtype::NSWindowExposedEventType, + 0, + 0, + ) + } + } + #[sel(locationInWindow)] pub fn locationInWindow(&self) -> NSPoint; @@ -32,6 +77,9 @@ extern_methods!( #[sel(modifierFlags)] pub fn modifierFlags(&self) -> NSEventModifierFlags; + #[sel(type)] + pub fn type_(&self) -> NSEventType; + // In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character, // and there is no easy way to navtively retrieve the layout-dependent character. // In winit, we use keycode to refer to the key's character, and so this function aligns @@ -48,6 +96,15 @@ extern_methods!( #[sel(momentumPhase)] pub fn momentumPhase(&self) -> NSEventPhase; + #[sel(deltaX)] + pub fn deltaX(&self) -> CGFloat; + + #[sel(deltaY)] + pub fn deltaY(&self) -> CGFloat; + + #[sel(buttonNumber)] + pub fn buttonNumber(&self) -> NSInteger; + #[sel(scrollingDeltaX)] pub fn scrollingDeltaX(&self) -> CGFloat; diff --git a/src/platform_impl/macos/appkit/mod.rs b/src/platform_impl/macos/appkit/mod.rs index 96d6da8574..0cd1584d87 100644 --- a/src/platform_impl/macos/appkit/mod.rs +++ b/src/platform_impl/macos/appkit/mod.rs @@ -8,6 +8,7 @@ // Objective-C methods have different conventions, and it's much easier to // understand if we just use the same names #![allow(non_snake_case)] +#![allow(clippy::too_many_arguments)] #![allow(clippy::enum_variant_names)] #![allow(non_upper_case_globals)] @@ -25,12 +26,18 @@ mod text_input_context; mod view; mod window; -pub(crate) use self::application::{NSApp, NSApplication}; +pub(crate) use self::application::{ + NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions, + NSRequestUserAttentionType, +}; pub(crate) use self::button::NSButton; pub(crate) use self::color::NSColor; pub(crate) use self::control::NSControl; pub(crate) use self::cursor::NSCursor; -pub(crate) use self::event::{NSEvent, NSEventModifierFlags, NSEventPhase}; +#[allow(unused_imports)] +pub(crate) use self::event::{ + NSEvent, NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSEventType, +}; pub(crate) use self::image::NSImage; pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboardType}; pub(crate) use self::responder::NSResponder; diff --git a/src/platform_impl/macos/appkit/window.rs b/src/platform_impl/macos/appkit/window.rs index c745581bc2..2e5eb8787b 100644 --- a/src/platform_impl/macos/appkit/window.rs +++ b/src/platform_impl/macos/appkit/window.rs @@ -177,13 +177,17 @@ extern_methods!( pub fn close(&self); #[sel(performWindowDragWithEvent:)] - pub fn performWindowDragWithEvent(&self, event: &NSEvent); + // TODO: Can this actually accept NULL? + pub fn performWindowDragWithEvent(&self, event: Option<&NSEvent>); #[sel(invalidateCursorRectsForView:)] pub fn invalidateCursorRectsForView(&self, view: &NSView); #[sel(setDelegate:)] pub fn setDelegate(&self, delegate: Option<&NSObject>); + + #[sel(sendEvent:)] + pub unsafe fn sendEvent(&self, event: &NSEvent); } ); diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 5e240ba7e4..2c5fe5a0c4 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -1,25 +1,25 @@ use std::os::raw::c_ushort; +use objc2::rc::{Id, Shared}; + use super::appkit::{NSEvent, NSEventModifierFlags}; +use super::window::WinitWindow; use crate::{ dpi::LogicalSize, event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}, - platform_impl::platform::{ - util::{IdRef, Never}, - DEVICE_ID, - }, + platform_impl::platform::{util::Never, DEVICE_ID}, }; #[derive(Debug)] -pub enum EventWrapper { +pub(crate) enum EventWrapper { StaticEvent(Event<'static, Never>), EventProxy(EventProxy), } -#[derive(Debug, PartialEq)] -pub enum EventProxy { +#[derive(Debug)] +pub(crate) enum EventProxy { DpiChangedProxy { - ns_window: IdRef, + window: Id, suggested_size: LogicalSize, scale_factor: f64, }, diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index ae253554d9..638dd7420d 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -11,16 +11,12 @@ use std::{ sync::mpsc, }; -use cocoa::{ - appkit::{NSApp, NSEventModifierFlags, NSEventSubtype, NSEventType::NSApplicationDefined}, - base::{id, nil}, - foundation::{NSPoint, NSTimeInterval}, -}; use objc2::foundation::is_main_thread; use objc2::rc::{autoreleasepool, Id, Shared}; use objc2::ClassType; use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; +use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent}; use crate::{ event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, @@ -95,15 +91,11 @@ impl EventLoopWindowTarget { impl EventLoopWindowTarget { pub(crate) fn hide_application(&self) { - let cls = objc::runtime::Class::get("NSApplication").unwrap(); - let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] }; - unsafe { msg_send![app, hide: 0] } + NSApp().hide(None) } pub(crate) fn hide_other_applications(&self) { - let cls = objc::runtime::Class::get("NSApplication").unwrap(); - let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] }; - unsafe { msg_send![app, hideOtherApplications: 0] } + NSApp().hideOtherApplications(None) } } @@ -141,31 +133,29 @@ impl Default for PlatformSpecificEventLoopAttributes { impl EventLoop { pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self { - let delegate = unsafe { - if !is_main_thread() { - panic!("On macOS, `EventLoop` must be created on the main thread!"); - } + if !is_main_thread() { + panic!("On macOS, `EventLoop` must be created on the main thread!"); + } - // This must be done before `NSApp()` (equivalent to sending - // `sharedApplication`) is called anywhere else, or we'll end up - // with the wrong `NSApplication` class and the wrong thread could - // be marked as main. - let app: id = msg_send![WinitApplication::class(), sharedApplication]; - - use cocoa::appkit::NSApplicationActivationPolicy::*; - let activation_policy = match attributes.activation_policy { - ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, - ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory, - ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited, - }; - let delegate = ApplicationDelegate::new(activation_policy, attributes.default_menu); - - autoreleasepool(|_| { - let _: () = msg_send![app, setDelegate: &*delegate]; - }); - - delegate + // This must be done before `NSApp()` (equivalent to sending + // `sharedApplication`) is called anywhere else, or we'll end up + // with the wrong `NSApplication` class and the wrong thread could + // be marked as main. + let app: &WinitApplication = + unsafe { msg_send![WinitApplication::class(), sharedApplication] }; + + use NSApplicationActivationPolicy::*; + let activation_policy = match attributes.activation_policy { + ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, + ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory, + ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited, }; + let delegate = ApplicationDelegate::new(activation_policy, attributes.default_menu); + + autoreleasepool(|_| { + app.setDelegate(&delegate); + }); + let panic_info: Rc = Default::default(); setup_control_flow_observers(Rc::downgrade(&panic_info)); EventLoop { @@ -208,9 +198,8 @@ impl EventLoop { self._callback = Some(Rc::clone(&callback)); - let exit_code = autoreleasepool(|_| unsafe { + let exit_code = autoreleasepool(|_| { let app = NSApp(); - assert_ne!(app, nil); // A bit of juggling with the callback references to make sure // that `self.callback` is the only owner of the callback. @@ -218,7 +207,7 @@ impl EventLoop { drop(callback); AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); - let _: () = msg_send![app, run]; + unsafe { app.run() }; if let Some(panic) = self.panic_info.take() { drop(self._callback.take()); @@ -236,24 +225,6 @@ impl EventLoop { } } -#[inline] -pub unsafe fn post_dummy_event(target: id) { - let event_class = class!(NSEvent); - let dummy_event: id = msg_send![ - event_class, - otherEventWithType: NSApplicationDefined - location: NSPoint::new(0.0, 0.0) - modifierFlags: NSEventModifierFlags::empty() - timestamp: 0 as NSTimeInterval - windowNumber: 0isize - context: nil - subtype: NSEventSubtype::NSWindowExposedEventType - data1: 0isize - data2: 0isize - ]; - let _: () = msg_send![target, postEvent: dummy_event, atStart: true]; -} - /// Catches panics that happen inside `f` and when a panic /// happens, stops the `sharedApplication` #[inline] @@ -272,15 +243,11 @@ pub fn stop_app_on_panic R + UnwindSafe, R>( let panic_info = panic_info.upgrade().unwrap(); panic_info.set_panic(e); } - unsafe { - let app_class = class!(NSApplication); - let app: id = msg_send![app_class, sharedApplication]; - let _: () = msg_send![app, stop: nil]; - - // Posting a dummy event to get `stop` to take effect immediately. - // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752 - post_dummy_event(app); - } + let app = NSApp(); + app.stop(None); + // Posting a dummy event to get `stop` to take effect immediately. + // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752 + app.postEvent_atStart(&NSEvent::dummy(), true); None } } diff --git a/src/platform_impl/macos/menu.rs b/src/platform_impl/macos/menu.rs index a791869277..5a20316015 100644 --- a/src/platform_impl/macos/menu.rs +++ b/src/platform_impl/macos/menu.rs @@ -1,5 +1,5 @@ use super::util::IdRef; -use cocoa::appkit::{NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}; +use cocoa::appkit::{NSMenu, NSMenuItem}; use cocoa::base::{nil, selector}; use cocoa::foundation::{NSProcessInfo, NSString}; use objc::{ @@ -7,6 +7,8 @@ use objc::{ runtime::{Object, Sel}, }; +use super::appkit::{NSApp, NSEventModifierFlags}; + struct KeyEquivalent<'a> { key: &'a str, masks: Option, @@ -18,7 +20,7 @@ pub fn initialize() { let app_menu_item = IdRef::new(NSMenuItem::new(nil)); menubar.addItem_(*app_menu_item); let app = NSApp(); - app.setMainMenu_(*menubar); + app.setMainMenu(*menubar); let app_menu = NSMenu::new(nil); let process_name = NSProcessInfo::processInfo(nil).processName(); @@ -107,7 +109,7 @@ fn menu_item( }; let item = NSMenuItem::alloc(nil).initWithTitle_action_keyEquivalent_(title, selector, key); if let Some(masks) = masks { - item.setKeyEquivalentModifierMask_(masks) + item.setKeyEquivalentModifierMask_(cocoa::appkit::NSEventModifierFlags::from_bits(masks.bits() as _).unwrap()) } item diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index b1245fbb1f..716923f366 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -5,7 +5,6 @@ pub(crate) use self::r#async::*; use std::ops::{BitAnd, Deref}; use cocoa::{ - appkit::NSApp, base::{id, nil}, foundation::NSString, }; @@ -132,8 +131,3 @@ pub unsafe fn app_name() -> Option { None } } - -#[allow(dead_code)] -pub unsafe fn open_emoji_picker() { - let _: () = msg_send![NSApp(), orderFrontCharacterPalette: nil]; -} diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 5936874adb..cef9b47558 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -34,25 +34,20 @@ use crate::{ WindowId as RootWindowId, }, }; -use cocoa::{ - appkit::{ - self, NSApp, NSApplication, NSApplicationPresentationOptions, NSRequestUserAttentionType, - }, - base::id, -}; -use core_graphics::display::{CGDisplay, CGDisplayMode}; +use cocoa::{appkit, base::id}; +use core_graphics::display::CGDisplay; use objc2::declare::{Ivar, IvarDrop}; use objc2::foundation::{ is_main_thread, CGFloat, NSArray, NSCopying, NSObject, NSPoint, NSRect, NSSize, NSString, }; use objc2::rc::{autoreleasepool, Id, Owned, Shared}; -use objc2::runtime::{Bool, Object}; +use objc2::runtime::Object; use objc2::{declare_class, ClassType}; use super::appkit::{ - NSColor, NSCursor, NSEvent, NSFilenamesPboardType, NSResponder, NSScreen, - NSWindow as NSWindowClass, NSWindowButton, NSWindowLevel, NSWindowStyleMask, - NSWindowTitleVisibility, + NSApp, NSApplicationPresentationOptions, NSColor, NSCursor, NSFilenamesPboardType, + NSRequestUserAttentionType, NSResponder, NSScreen, NSWindow, NSWindowButton, NSWindowLevel, + NSWindowStyleMask, NSWindowTitleVisibility, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -113,6 +108,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { } declare_class!( + #[derive(Debug)] pub(crate) struct WinitWindow { // TODO: Fix unnecessary boxing here // SAFETY: These are initialized in WinitWindow::new, right after it is created. @@ -122,7 +118,7 @@ declare_class!( unsafe impl ClassType for WinitWindow { #[inherits(NSResponder, NSObject)] - type Super = NSWindowClass; + type Super = NSWindow; } unsafe impl WinitWindow { @@ -140,7 +136,7 @@ declare_class!( } ); -#[derive(Default)] +#[derive(Debug, Default)] pub struct SharedState { pub resizable: bool, /// This field tracks the current fullscreen state of the window @@ -163,7 +159,6 @@ pub struct SharedState { /// bar in exclusive fullscreen but want to restore the original options when /// transitioning back to borderless fullscreen. save_presentation_opts: Option, - pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, } impl SharedState { @@ -655,8 +650,8 @@ impl WinitWindow { #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { - let event: &NSEvent = unsafe { msg_send![NSApp(), currentEvent] }; - self.performWindowDragWithEvent(event); + let event = NSApp().currentEvent(); + self.performWindowDragWithEvent(event.as_deref()); Ok(()) } @@ -820,11 +815,9 @@ impl WinitWindow { let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken; if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) { - unsafe { - let app = NSApp(); - let mut shared_state_lock = self.lock_shared_state("set_fullscreen"); - shared_state_lock.save_presentation_opts = Some(app.presentationOptions_()); - } + let app = NSApp(); + let mut shared_state_lock = self.lock_shared_state("set_fullscreen"); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); } unsafe { @@ -904,7 +897,7 @@ impl WinitWindow { Arc::downgrade(&*self.shared_state), ); } - (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe { + (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => { // If we're already in fullscreen mode, calling // `CGDisplayCapture` will place the shielding window on top of // our window, which results in a black display and is not what @@ -914,34 +907,36 @@ impl WinitWindow { // that the menu bar is disabled. This is done in the window // delegate in `window:willUseFullScreenPresentationOptions:`. let app = NSApp(); - shared_state_lock.save_presentation_opts = Some(app.presentationOptions_()); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; - app.setPresentationOptions_(presentation_options); + app.setPresentationOptions(presentation_options); - let _: () = msg_send![self, setLevel: ffi::CGShieldingWindowLevel() + 1]; - }, + let _: () = unsafe { msg_send![self, setLevel: ffi::CGShieldingWindowLevel() + 1] }; + } ( &Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &Some(Fullscreen::Borderless(_)), - ) => unsafe { + ) => { let presentation_options = shared_state_lock.save_presentation_opts.unwrap_or_else(|| { NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar }); - NSApp().setPresentationOptions_(presentation_options); + NSApp().setPresentationOptions(presentation_options); - util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); + unsafe { + util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()) + }; // Restore the normal window level following the Borderless fullscreen // `CGShieldingWindowLevel() + 1` hack. self.setLevel(NSWindowLevel::Normal); - }, + } _ => {} }; } @@ -1031,10 +1026,8 @@ impl WinitWindow { let is_visible = self.isVisible(); if !is_minimized && is_visible { - unsafe { - NSApp().activateIgnoringOtherApps_(Bool::YES.as_raw()); - util::make_key_and_order_front_async(self); - } + NSApp().activateIgnoringOtherApps(true); + util::make_key_and_order_front_async(self); } } @@ -1044,10 +1037,8 @@ impl WinitWindow { UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest, }); - unsafe { - if let Some(ty) = ns_request_type { - NSApp().requestUserAttention_(ty); - } + if let Some(ty) = ns_request_type { + NSApp().requestUserAttention(ty); } } @@ -1120,7 +1111,7 @@ impl WindowExtMacOS for WinitWindow { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { let mut shared_state_lock = self.shared_state.lock().unwrap(); - let app = unsafe { NSApp() }; + let app = NSApp(); let is_native_fullscreen = shared_state_lock.fullscreen.is_some(); let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen; @@ -1137,7 +1128,7 @@ impl WindowExtMacOS for WinitWindow { // Exclude title bar shared_state_lock.standard_frame = Some(self.contentRectForFrameRect(self.frame())); shared_state_lock.saved_style = Some(self.styleMask()); - shared_state_lock.save_presentation_opts = Some(unsafe { app.presentationOptions_() }); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); // Tell our window's state that we're in fullscreen shared_state_lock.is_simple_fullscreen = true; @@ -1146,7 +1137,7 @@ impl WindowExtMacOS for WinitWindow { let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; - unsafe { app.setPresentationOptions_(presentation_options) }; + app.setPresentationOptions(presentation_options); // Hide the titlebar self.toggle_style_mask(NSWindowStyleMask::NSTitledWindowMask, false); @@ -1167,7 +1158,7 @@ impl WindowExtMacOS for WinitWindow { shared_state_lock.is_simple_fullscreen = false; if let Some(presentation_opts) = shared_state_lock.save_presentation_opts { - unsafe { app.setPresentationOptions_(presentation_opts) }; + app.setPresentationOptions(presentation_opts); } let frame = shared_state_lock.saved_standard_frame(); diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index e6b249a2cd..00d991a09f 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,14 +1,14 @@ use std::os::raw::c_void; use cocoa::{ - appkit::{self, NSApplicationPresentationOptions}, + appkit, base::{id, nil}, }; -use objc2::foundation::{NSObject, NSUInteger}; +use objc2::foundation::NSObject; use objc2::rc::{autoreleasepool, Id, Shared}; use objc2::{declare_class, ClassType}; -use super::appkit::NSWindowOcclusionState; +use super::appkit::{NSApplicationPresentationOptions, NSWindowOcclusionState}; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{Event, ModifiersState, WindowEvent}, @@ -78,9 +78,8 @@ impl WindowDelegateState { }; self.previous_scale_factor = scale_factor; - let ns_window: *const WinitWindow = &*self.window; let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { - ns_window: IdRef::retain(ns_window as _), + window: self.window.clone(), suggested_size: self.view_size(), scale_factor, }); @@ -361,8 +360,8 @@ declare_class!( fn window_will_use_fullscreen_presentation_options( &self, _: id, - proposed_options: NSUInteger, - ) -> NSUInteger { + proposed_options: NSApplicationPresentationOptions, + ) -> NSApplicationPresentationOptions { trace_scope!("window:willUseFullScreenPresentationOptions:"); // Generally, games will want to disable the menu bar and the dock. Ideally, // this would be configurable by the user. Unfortunately because of our @@ -372,16 +371,15 @@ declare_class!( // still want to make this configurable for borderless fullscreen. Right now // we don't, for consistency. If we do, it should be documented that the // user-provided options are ignored in exclusive fullscreen. - let mut options: NSUInteger = proposed_options; + let mut options = proposed_options; self.with_state(|state| { state.with_window(|window| { let shared_state = window.lock_shared_state("window_will_use_fullscreen_presentation_options"); if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { - options = (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen + options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar) - .bits() as NSUInteger; + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; } }) }); From f4ea1ebee8a6422fc8c5557dca57798123e802e8 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 2 Sep 2022 21:16:00 +0200 Subject: [PATCH 5/8] Remove cocoa dependency --- Cargo.toml | 7 +- src/platform_impl/macos/app.rs | 4 +- src/platform_impl/macos/appkit/application.rs | 4 +- src/platform_impl/macos/appkit/menu.rs | 25 +++ src/platform_impl/macos/appkit/menu_item.rs | 48 +++++ src/platform_impl/macos/appkit/mod.rs | 15 +- src/platform_impl/macos/appkit/pasteboard.rs | 21 ++- src/platform_impl/macos/appkit/version.rs | 62 +++++++ src/platform_impl/macos/appkit/window.rs | 13 ++ src/platform_impl/macos/ffi.rs | 3 - src/platform_impl/macos/menu.rs | 169 ++++++++---------- src/platform_impl/macos/mod.rs | 3 +- src/platform_impl/macos/util/async.rs | 2 +- src/platform_impl/macos/util/mod.rs | 74 +------- src/platform_impl/macos/view.rs | 15 +- src/platform_impl/macos/window.rs | 34 ++-- src/platform_impl/macos/window_delegate.rs | 125 ++++++------- 17 files changed, 341 insertions(+), 283 deletions(-) create mode 100644 src/platform_impl/macos/appkit/menu.rs create mode 100644 src/platform_impl/macos/appkit/menu_item.rs create mode 100644 src/platform_impl/macos/appkit/version.rs diff --git a/Cargo.toml b/Cargo.toml index 22a237eb81..7753bbd809 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,11 +65,8 @@ ndk-glue = "0.7.0" objc = { version = "=0.3.0-beta.3", package = "objc2" } [target.'cfg(target_os = "macos")'.dependencies] -# Branch: objc2 -# TODO: Use non-git versions before we release -cocoa = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" } -core-foundation = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" } -core-graphics = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" } +core-foundation = "0.9.3" +core-graphics = "0.22.3" dispatch = "0.2.0" [target.'cfg(target_os = "windows")'.dependencies.windows-sys] diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index 07627545fb..a82275cd61 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -4,7 +4,7 @@ use objc2::foundation::NSObject; use objc2::{declare_class, ClassType}; use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; -use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; +use super::{app_state::AppState, event::EventWrapper, DEVICE_ID}; use crate::event::{DeviceEvent, ElementState, Event}; declare_class!( @@ -28,7 +28,7 @@ declare_class!( let event_type = event.type_(); let modifier_flags = event.modifierFlags(); if event_type == NSEventType::NSKeyUp - && util::has_flag(modifier_flags, NSEventModifierFlags::NSCommandKeyMask) + && modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask) { if let Some(key_window) = self.keyWindow() { unsafe { key_window.sendEvent(event) }; diff --git a/src/platform_impl/macos/appkit/application.rs b/src/platform_impl/macos/appkit/application.rs index 353d9fefe4..f75b9a2e48 100644 --- a/src/platform_impl/macos/appkit/application.rs +++ b/src/platform_impl/macos/appkit/application.rs @@ -4,7 +4,7 @@ use objc2::runtime::Object; use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; use objc2::{Encode, Encoding}; -use super::{NSEvent, NSResponder, NSWindow}; +use super::{NSEvent, NSMenu, NSResponder, NSWindow}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] @@ -80,7 +80,7 @@ extern_methods!( pub fn setActivationPolicy(&self, policy: NSApplicationActivationPolicy) -> bool; #[sel(setMainMenu:)] - pub fn setMainMenu(&self, menu: *mut Object); + pub fn setMainMenu(&self, menu: &NSMenu); #[sel(run)] pub unsafe fn run(&self); diff --git a/src/platform_impl/macos/appkit/menu.rs b/src/platform_impl/macos/appkit/menu.rs new file mode 100644 index 0000000000..cb8b47facb --- /dev/null +++ b/src/platform_impl/macos/appkit/menu.rs @@ -0,0 +1,25 @@ +use objc2::foundation::NSObject; +use objc2::rc::{Id, Shared}; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +use super::NSMenuItem; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSMenu; + + unsafe impl ClassType for NSMenu { + type Super = NSObject; + } +); + +extern_methods!( + unsafe impl NSMenu { + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new] } + } + + #[sel(addItem:)] + pub fn addItem(&self, item: &NSMenuItem); + } +); diff --git a/src/platform_impl/macos/appkit/menu_item.rs b/src/platform_impl/macos/appkit/menu_item.rs new file mode 100644 index 0000000000..6daed88ee3 --- /dev/null +++ b/src/platform_impl/macos/appkit/menu_item.rs @@ -0,0 +1,48 @@ +use objc2::foundation::{NSObject, NSString}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::Sel; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +use super::{NSEventModifierFlags, NSMenu}; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSMenuItem; + + unsafe impl ClassType for NSMenuItem { + type Super = NSObject; + } +); + +extern_methods!( + unsafe impl NSMenuItem { + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new] } + } + + pub fn newWithTitle( + title: &NSString, + action: Sel, + key_equivalent: &NSString, + ) -> Id { + unsafe { + msg_send_id![ + msg_send_id![Self::class(), alloc], + initWithTitle: title, + action: action, + keyEquivalent: key_equivalent, + ] + } + } + + pub fn separatorItem() -> Id { + unsafe { msg_send_id![Self::class(), separatorItem] } + } + + #[sel(setKeyEquivalentModifierMask:)] + pub fn setKeyEquivalentModifierMask(&self, mask: NSEventModifierFlags); + + #[sel(setSubmenu:)] + pub fn setSubmenu(&self, submenu: &NSMenu); + } +); diff --git a/src/platform_impl/macos/appkit/mod.rs b/src/platform_impl/macos/appkit/mod.rs index 0cd1584d87..829234d31a 100644 --- a/src/platform_impl/macos/appkit/mod.rs +++ b/src/platform_impl/macos/appkit/mod.rs @@ -19,10 +19,13 @@ mod control; mod cursor; mod event; mod image; +mod menu; +mod menu_item; mod pasteboard; mod responder; mod screen; mod text_input_context; +mod version; mod view; mod window; @@ -39,13 +42,19 @@ pub(crate) use self::event::{ NSEvent, NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSEventType, }; pub(crate) use self::image::NSImage; -pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboardType}; +pub(crate) use self::menu::NSMenu; +pub(crate) use self::menu_item::NSMenuItem; +pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboard, NSPasteboardType}; pub(crate) use self::responder::NSResponder; #[allow(unused_imports)] pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen}; pub(crate) use self::text_input_context::NSTextInputContext; +pub(crate) use self::version::NSAppKitVersion; pub(crate) use self::view::{NSTrackingRectTag, NSView}; pub(crate) use self::window::{ - NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState, NSWindowStyleMask, - NSWindowTitleVisibility, + NSBackingStoreType, NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState, + NSWindowStyleMask, NSWindowTitleVisibility, }; + +#[link(name = "AppKit", kind = "framework")] +extern "C" {} diff --git a/src/platform_impl/macos/appkit/pasteboard.rs b/src/platform_impl/macos/appkit/pasteboard.rs index 65093b8da1..6564540dc6 100644 --- a/src/platform_impl/macos/appkit/pasteboard.rs +++ b/src/platform_impl/macos/appkit/pasteboard.rs @@ -1,4 +1,23 @@ -use objc2::foundation::NSString; +use objc2::foundation::{NSObject, NSString}; +use objc2::rc::{Id, Shared}; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSPasteboard; + + unsafe impl ClassType for NSPasteboard { + type Super = NSObject; + } +); + +extern_methods!( + unsafe impl NSPasteboard { + pub fn propertyListForType(&self, type_: &NSPasteboardType) -> Id { + unsafe { msg_send_id![self, propertyListForType: type_] } + } + } +); pub type NSPasteboardType = NSString; diff --git a/src/platform_impl/macos/appkit/version.rs b/src/platform_impl/macos/appkit/version.rs new file mode 100644 index 0000000000..c121e0dc9d --- /dev/null +++ b/src/platform_impl/macos/appkit/version.rs @@ -0,0 +1,62 @@ +#[repr(transparent)] +#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)] +pub struct NSAppKitVersion(f64); + +#[allow(dead_code)] +#[allow(non_upper_case_globals)] +impl NSAppKitVersion { + pub fn current() -> Self { + extern "C" { + static NSAppKitVersionNumber: NSAppKitVersion; + } + + unsafe { NSAppKitVersionNumber } + } + + pub fn floor(self) -> Self { + Self(self.0.floor()) + } + + pub const NSAppKitVersionNumber10_0: Self = Self(577.0); + pub const NSAppKitVersionNumber10_1: Self = Self(620.0); + pub const NSAppKitVersionNumber10_2: Self = Self(663.0); + pub const NSAppKitVersionNumber10_2_3: Self = Self(663.6); + pub const NSAppKitVersionNumber10_3: Self = Self(743.0); + pub const NSAppKitVersionNumber10_3_2: Self = Self(743.14); + pub const NSAppKitVersionNumber10_3_3: Self = Self(743.2); + pub const NSAppKitVersionNumber10_3_5: Self = Self(743.24); + pub const NSAppKitVersionNumber10_3_7: Self = Self(743.33); + pub const NSAppKitVersionNumber10_3_9: Self = Self(743.36); + pub const NSAppKitVersionNumber10_4: Self = Self(824.0); + pub const NSAppKitVersionNumber10_4_1: Self = Self(824.1); + pub const NSAppKitVersionNumber10_4_3: Self = Self(824.23); + pub const NSAppKitVersionNumber10_4_4: Self = Self(824.33); + pub const NSAppKitVersionNumber10_4_7: Self = Self(824.41); + pub const NSAppKitVersionNumber10_5: Self = Self(949.0); + pub const NSAppKitVersionNumber10_5_2: Self = Self(949.27); + pub const NSAppKitVersionNumber10_5_3: Self = Self(949.33); + pub const NSAppKitVersionNumber10_6: Self = Self(1038.0); + pub const NSAppKitVersionNumber10_7: Self = Self(1138.0); + pub const NSAppKitVersionNumber10_7_2: Self = Self(1138.23); + pub const NSAppKitVersionNumber10_7_3: Self = Self(1138.32); + pub const NSAppKitVersionNumber10_7_4: Self = Self(1138.47); + pub const NSAppKitVersionNumber10_8: Self = Self(1187.0); + pub const NSAppKitVersionNumber10_9: Self = Self(1265.0); + pub const NSAppKitVersionNumber10_10: Self = Self(1343.0); + pub const NSAppKitVersionNumber10_10_2: Self = Self(1344.0); + pub const NSAppKitVersionNumber10_10_3: Self = Self(1347.0); + pub const NSAppKitVersionNumber10_10_4: Self = Self(1348.0); + pub const NSAppKitVersionNumber10_10_5: Self = Self(1348.0); + pub const NSAppKitVersionNumber10_10_Max: Self = Self(1349.0); + pub const NSAppKitVersionNumber10_11: Self = Self(1404.0); + pub const NSAppKitVersionNumber10_11_1: Self = Self(1404.13); + pub const NSAppKitVersionNumber10_11_2: Self = Self(1404.34); + pub const NSAppKitVersionNumber10_11_3: Self = Self(1404.34); + pub const NSAppKitVersionNumber10_12: Self = Self(1504.0); + pub const NSAppKitVersionNumber10_12_1: Self = Self(1504.60); + pub const NSAppKitVersionNumber10_12_2: Self = Self(1504.76); + pub const NSAppKitVersionNumber10_13: Self = Self(1561.0); + pub const NSAppKitVersionNumber10_13_1: Self = Self(1561.1); + pub const NSAppKitVersionNumber10_13_2: Self = Self(1561.2); + pub const NSAppKitVersionNumber10_13_4: Self = Self(1561.4); +} diff --git a/src/platform_impl/macos/appkit/window.rs b/src/platform_impl/macos/appkit/window.rs index 2e5eb8787b..8c2e8fa692 100644 --- a/src/platform_impl/macos/appkit/window.rs +++ b/src/platform_impl/macos/appkit/window.rs @@ -310,3 +310,16 @@ bitflags! { unsafe impl Encode for NSWindowStyleMask { const ENCODING: Encoding = NSUInteger::ENCODING; } + +#[allow(dead_code)] +#[repr(usize)] // NSUInteger +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NSBackingStoreType { + NSBackingStoreRetained = 0, + NSBackingStoreNonretained = 1, + NSBackingStoreBuffered = 2, +} + +unsafe impl Encode for NSBackingStoreType { + const ENCODING: Encoding = NSUInteger::ENCODING; +} diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 5a02abc226..f080331058 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -11,9 +11,6 @@ use core_graphics::{ base::CGError, display::{CGDirectDisplayID, CGDisplayConfigRef}, }; -use objc2::foundation::NSInteger; - -pub const NSNotFound: NSInteger = NSInteger::max_value(); pub type CGDisplayFadeInterval = f32; pub type CGDisplayReservationInterval = f32; diff --git a/src/platform_impl/macos/menu.rs b/src/platform_impl/macos/menu.rs index 5a20316015..96fae90f42 100644 --- a/src/platform_impl/macos/menu.rs +++ b/src/platform_impl/macos/menu.rs @@ -1,117 +1,98 @@ -use super::util::IdRef; -use cocoa::appkit::{NSMenu, NSMenuItem}; -use cocoa::base::{nil, selector}; -use cocoa::foundation::{NSProcessInfo, NSString}; -use objc::{ - rc::autoreleasepool, - runtime::{Object, Sel}, -}; +use objc2::foundation::{NSProcessInfo, NSString}; +use objc2::ns_string; +use objc2::rc::{Id, Shared}; +use objc2::runtime::Sel; -use super::appkit::{NSApp, NSEventModifierFlags}; +use super::appkit::{NSApp, NSEventModifierFlags, NSMenu, NSMenuItem}; struct KeyEquivalent<'a> { - key: &'a str, + key: &'a NSString, masks: Option, } pub fn initialize() { - autoreleasepool(|_| unsafe { - let menubar = IdRef::new(NSMenu::new(nil)); - let app_menu_item = IdRef::new(NSMenuItem::new(nil)); - menubar.addItem_(*app_menu_item); - let app = NSApp(); - app.setMainMenu(*menubar); + let menubar = NSMenu::new(); + let app_menu_item = NSMenuItem::new(); + menubar.addItem(&app_menu_item); - let app_menu = NSMenu::new(nil); - let process_name = NSProcessInfo::processInfo(nil).processName(); + let app_menu = NSMenu::new(); + let process_name = NSProcessInfo::process_info().process_name(); - // About menu item - let about_item_prefix = NSString::alloc(nil).init_str("About "); - let about_item_title = about_item_prefix.stringByAppendingString_(process_name); - let about_item = menu_item( - about_item_title, - selector("orderFrontStandardAboutPanel:"), - None, - ); + // About menu item + let about_item_title = ns_string!("About ").concat(&*process_name); + let about_item = menu_item(&about_item_title, sel!(orderFrontStandardAboutPanel:), None); - // Seperator menu item - let sep_first = NSMenuItem::separatorItem(nil); + // Seperator menu item + let sep_first = NSMenuItem::separatorItem(); - // Hide application menu item - let hide_item_prefix = NSString::alloc(nil).init_str("Hide "); - let hide_item_title = hide_item_prefix.stringByAppendingString_(process_name); - let hide_item = menu_item( - hide_item_title, - selector("hide:"), - Some(KeyEquivalent { - key: "h", - masks: None, - }), - ); + // Hide application menu item + let hide_item_title = ns_string!("Hide ").concat(&*process_name); + let hide_item = menu_item( + &hide_item_title, + sel!(hide:), + Some(KeyEquivalent { + key: ns_string!("h"), + masks: None, + }), + ); - // Hide other applications menu item - let hide_others_item_title = NSString::alloc(nil).init_str("Hide Others"); - let hide_others_item = menu_item( - hide_others_item_title, - selector("hideOtherApplications:"), - Some(KeyEquivalent { - key: "h", - masks: Some( - NSEventModifierFlags::NSAlternateKeyMask - | NSEventModifierFlags::NSCommandKeyMask, - ), - }), - ); + // Hide other applications menu item + let hide_others_item_title = ns_string!("Hide Others"); + let hide_others_item = menu_item( + hide_others_item_title, + sel!(hideOtherApplications:), + Some(KeyEquivalent { + key: ns_string!("h"), + masks: Some( + NSEventModifierFlags::NSAlternateKeyMask | NSEventModifierFlags::NSCommandKeyMask, + ), + }), + ); - // Show applications menu item - let show_all_item_title = NSString::alloc(nil).init_str("Show All"); - let show_all_item = menu_item( - show_all_item_title, - selector("unhideAllApplications:"), - None, - ); + // Show applications menu item + let show_all_item_title = ns_string!("Show All"); + let show_all_item = menu_item(show_all_item_title, sel!(unhideAllApplications:), None); - // Seperator menu item - let sep = NSMenuItem::separatorItem(nil); + // Seperator menu item + let sep = NSMenuItem::separatorItem(); - // Quit application menu item - let quit_item_prefix = NSString::alloc(nil).init_str("Quit "); - let quit_item_title = quit_item_prefix.stringByAppendingString_(process_name); - let quit_item = menu_item( - quit_item_title, - selector("terminate:"), - Some(KeyEquivalent { - key: "q", - masks: None, - }), - ); + // Quit application menu item + let quit_item_title = ns_string!("Quit ").concat(&*process_name); + let quit_item = menu_item( + &quit_item_title, + sel!(terminate:), + Some(KeyEquivalent { + key: ns_string!("q"), + masks: None, + }), + ); - app_menu.addItem_(about_item); - app_menu.addItem_(sep_first); - app_menu.addItem_(hide_item); - app_menu.addItem_(hide_others_item); - app_menu.addItem_(show_all_item); - app_menu.addItem_(sep); - app_menu.addItem_(quit_item); - app_menu_item.setSubmenu_(app_menu); - }); + app_menu.addItem(&about_item); + app_menu.addItem(&sep_first); + app_menu.addItem(&hide_item); + app_menu.addItem(&hide_others_item); + app_menu.addItem(&show_all_item); + app_menu.addItem(&sep); + app_menu.addItem(&quit_item); + app_menu_item.setSubmenu(&app_menu); + + let app = NSApp(); + app.setMainMenu(&menubar); } fn menu_item( - title: *mut Object, + title: &NSString, selector: Sel, key_equivalent: Option>, -) -> *mut Object { - unsafe { - let (key, masks) = match key_equivalent { - Some(ke) => (NSString::alloc(nil).init_str(ke.key), ke.masks), - None => (NSString::alloc(nil).init_str(""), None), - }; - let item = NSMenuItem::alloc(nil).initWithTitle_action_keyEquivalent_(title, selector, key); - if let Some(masks) = masks { - item.setKeyEquivalentModifierMask_(cocoa::appkit::NSEventModifierFlags::from_bits(masks.bits() as _).unwrap()) - } - - item +) -> Id { + let (key, masks) = match key_equivalent { + Some(ke) => (ke.key, ke.masks), + None => (ns_string!(""), None), + }; + let item = NSMenuItem::newWithTitle(title, selector, key); + if let Some(masks) = masks { + item.setKeyEquivalentModifierMask(masks) } + + item } diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 2efb064d95..eb59ede58a 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -21,6 +21,7 @@ mod window_delegate; use std::{fmt, ops::Deref}; use self::window::WinitWindow; +use self::window_delegate::WinitWindowDelegate; pub(crate) use self::{ event_loop::{ EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, @@ -50,7 +51,7 @@ pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); pub(crate) struct Window { pub(crate) window: Id, // We keep this around so that it doesn't get dropped until the window does. - _delegate: util::IdRef, + _delegate: Id, } impl Drop for Window { diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index db933e54c3..d8ceba791b 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -214,7 +214,7 @@ pub(crate) fn set_title_async(window: &NSWindow, title: String) { // thread. Though, it's a good idea to look into that more... // // ArturKovacs: It's important that this operation keeps the underlying window alive -// through the `IdRef` because otherwise it would dereference free'd memory +// through the `Id` because otherwise it would dereference free'd memory pub(crate) fn close_async(window: Id) { let window = MainThreadSafe(window); Queue::main().exec_async(move || { diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 716923f366..98713ac7ed 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -2,73 +2,20 @@ mod r#async; pub(crate) use self::r#async::*; -use std::ops::{BitAnd, Deref}; - -use cocoa::{ - base::{id, nil}, - foundation::NSString, -}; use core_graphics::display::CGDisplay; -use objc2::foundation::{CGFloat, NSPoint, NSRange, NSRect, NSUInteger}; +use objc2::foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger}; use crate::dpi::LogicalPosition; -use crate::platform_impl::platform::ffi; // Replace with `!` once stable #[derive(Debug)] pub enum Never {} -pub fn has_flag(bitset: T, flag: T) -> bool -where - T: Copy + PartialEq + BitAnd, -{ - bitset & flag == flag -} - pub const EMPTY_RANGE: NSRange = NSRange { - location: ffi::NSNotFound as NSUInteger, + location: NSNotFound as NSUInteger, length: 0, }; -#[derive(Debug, PartialEq, Eq)] -pub struct IdRef(id); - -impl IdRef { - pub fn new(inner: id) -> IdRef { - IdRef(inner) - } - - pub fn retain(inner: id) -> IdRef { - if inner != nil { - let _: id = unsafe { msg_send![inner, retain] }; - } - IdRef(inner) - } -} - -impl Drop for IdRef { - fn drop(&mut self) { - if self.0 != nil { - unsafe { - let _: () = msg_send![self.0, release]; - }; - } - } -} - -impl Deref for IdRef { - type Target = id; - fn deref(&self) -> &id { - &self.0 - } -} - -impl Clone for IdRef { - fn clone(&self) -> IdRef { - IdRef::retain(self.0) - } -} - macro_rules! trace_scope { ($s:literal) => { let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s); @@ -114,20 +61,3 @@ pub fn window_position(position: LogicalPosition) -> NSPoint { CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat, ) } - -pub unsafe fn ns_string_id_ref(s: &str) -> IdRef { - IdRef::new(NSString::alloc(nil).init_str(s)) -} - -#[allow(dead_code)] // In case we want to use this function in the future -pub unsafe fn app_name() -> Option { - let bundle: id = msg_send![class!(NSBundle), mainBundle]; - let dict: id = msg_send![bundle, infoDictionary]; - let key = ns_string_id_ref("CFBundleName"); - let app_name: id = msg_send![dict, objectForKey:*key]; - if app_name != nil { - Some(app_name) - } else { - None - } -} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 3c8b199493..3a07881930 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -11,7 +11,7 @@ use objc2::{declare_class, ClassType}; use super::appkit::{ NSApp, NSCursor, NSEvent, NSEventModifierFlags, NSEventPhase, NSResponder, NSTrackingRectTag, - NSView, NSWindow, + NSView, }; use crate::{ dpi::{LogicalPosition, LogicalSize}, @@ -26,7 +26,7 @@ use crate::{ EventWrapper, }, util, - window::get_window_id, + window::WinitWindow, DEVICE_ID, }, window::WindowId, @@ -133,7 +133,7 @@ declare_class!( #[derive(Debug)] #[allow(non_snake_case)] pub(super) struct WinitView { - _ns_window: IvarDrop>, + _ns_window: IvarDrop>, pub(super) state: IvarDrop>, marked_text: IvarDrop>, } @@ -145,7 +145,7 @@ declare_class!( unsafe impl WinitView { #[sel(initWithId:)] - fn init_with_id(&mut self, window: *mut NSWindow) -> Option<&mut Self> { + fn init_with_id(&mut self, window: *mut WinitWindow) -> Option<&mut Self> { let this: Option<&mut Self> = unsafe { msg_send![super(self), init] }; this.map(|this| { let state = ViewState { @@ -911,11 +911,11 @@ declare_class!( ); impl WinitView { - pub(super) fn new(window: &NSWindow) -> Id { + pub(super) fn new(window: &WinitWindow) -> Id { unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithId: window] } } - fn window(&self) -> Id { + fn window(&self) -> Id { // TODO: Simply use `window` property on `NSView`. // That only returns a window _after_ the view has been attached though! // (which is incompatible with `frameDidChange:`) @@ -925,8 +925,7 @@ impl WinitView { } fn window_id(&self) -> WindowId { - let ptr = Id::as_ptr(&*self._ns_window); - WindowId(get_window_id(ptr as _)) + WindowId(self._ns_window.id()) } fn scale_factor(&self) -> f64 { diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index cef9b47558..abd12e5f45 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -24,9 +24,9 @@ use crate::{ app_state::AppState, ffi, monitor::{self, MonitorHandle, VideoMode}, - util::{self, IdRef}, + util, view::WinitView, - window_delegate::new_delegate, + window_delegate::WinitWindowDelegate, OsError, }, window::{ @@ -34,20 +34,18 @@ use crate::{ WindowId as RootWindowId, }, }; -use cocoa::{appkit, base::id}; -use core_graphics::display::CGDisplay; +use core_graphics::display::{CGDisplay, CGPoint}; use objc2::declare::{Ivar, IvarDrop}; use objc2::foundation::{ is_main_thread, CGFloat, NSArray, NSCopying, NSObject, NSPoint, NSRect, NSSize, NSString, }; use objc2::rc::{autoreleasepool, Id, Owned, Shared}; -use objc2::runtime::Object; use objc2::{declare_class, ClassType}; use super::appkit::{ - NSApp, NSApplicationPresentationOptions, NSColor, NSCursor, NSFilenamesPboardType, - NSRequestUserAttentionType, NSResponder, NSScreen, NSWindow, NSWindowButton, NSWindowLevel, - NSWindowStyleMask, NSWindowTitleVisibility, + NSApp, NSAppKitVersion, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, + NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen, NSWindow, + NSWindowButton, NSWindowLevel, NSWindowStyleMask, NSWindowTitleVisibility, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -71,12 +69,6 @@ impl From for WindowId { } } -// Convert the `cocoa::base::id` associated with a window to a usize to use as a unique identifier -// for the window. -pub fn get_window_id(window_cocoa_id: id) -> WindowId { - WindowId(window_cocoa_id as *const Object as _) -} - #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub movable_by_window_background: bool, @@ -207,10 +199,11 @@ impl Drop for SharedStateMutexGuard<'_> { } impl WinitWindow { + #[allow(clippy::type_complexity)] pub(crate) fn new( attrs: WindowAttributes, pl_attrs: PlatformSpecificWindowBuilderAttributes, - ) -> Result<(Id, IdRef), RootOsError> { + ) -> Result<(Id, Id), RootOsError> { trace_scope!("WinitWindow::new"); if !is_main_thread() { @@ -283,7 +276,7 @@ impl WinitWindow { msg_send_id![WinitWindow::class(), alloc], initWithContentRect: frame, styleMask: masks, - backing: appkit::NSBackingStoreBuffered, + backing: NSBackingStoreType::NSBackingStoreBuffered, defer: false, ] }; @@ -370,8 +363,7 @@ impl WinitWindow { // the view and its associated OpenGL context. To work around this, on Mojave we // explicitly make the view layer-backed up front so that AppKit doesn't do it // itself and break the association with its context. - if f64::floor(unsafe { appkit::NSAppKitVersionNumber }) > appkit::NSAppKitVersionNumber10_12 - { + if NSAppKitVersion::current().floor() > NSAppKitVersion::NSAppKitVersionNumber10_12 { view.setWantsLayer(true); } @@ -396,7 +388,7 @@ impl WinitWindow { unsafe { NSFilenamesPboardType }.copy() ])); - let delegate = new_delegate(this.clone(), attrs.fullscreen.is_some()); + let delegate = WinitWindowDelegate::new(this.clone(), attrs.fullscreen.is_some()); // Set fullscreen mode after we setup everything this.set_fullscreen(attrs.fullscreen); @@ -438,7 +430,7 @@ impl WinitWindow { } pub fn id(&self) -> WindowId { - get_window_id(self as *const Self as *mut Self as _) + WindowId(self as *const Self as usize) } pub fn set_title(&self, title: &str) { @@ -636,7 +628,7 @@ impl WinitWindow { let scale_factor = self.scale_factor(); let window_position = physical_window_position.to_logical::(scale_factor); let logical_cursor_position = cursor_position.to_logical::(scale_factor); - let point = appkit::CGPoint { + let point = CGPoint { x: logical_cursor_position.x + window_position.x, y: logical_cursor_position.y + window_position.y, }; diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 00d991a09f..db1bd896f4 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,21 +1,21 @@ use std::os::raw::c_void; +use std::ptr; -use cocoa::{ - appkit, - base::{id, nil}, -}; -use objc2::foundation::NSObject; +use objc2::foundation::{NSArray, NSObject, NSString}; use objc2::rc::{autoreleasepool, Id, Shared}; +use objc2::runtime::Object; use objc2::{declare_class, ClassType}; -use super::appkit::{NSApplicationPresentationOptions, NSWindowOcclusionState}; +use super::appkit::{ + NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState, +}; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{Event, ModifiersState, WindowEvent}, platform_impl::platform::{ app_state::AppState, event::{EventProxy, EventWrapper}, - util::{self, IdRef}, + util, window::WinitWindow, }, window::{Fullscreen, WindowId}, @@ -109,19 +109,9 @@ impl WindowDelegateState { } } -pub(crate) fn new_delegate(window: Id, initial_fullscreen: bool) -> IdRef { - let state = WindowDelegateState::new(window, initial_fullscreen); - unsafe { - // This is free'd in `dealloc` - let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; - let delegate: id = msg_send![WinitWindowDelegate::class(), alloc]; - IdRef::new(msg_send![delegate, initWithWinit: state_ptr]) - } -} - declare_class!( #[derive(Debug)] - struct WinitWindowDelegate { + pub(crate) struct WinitWindowDelegate { state: *mut c_void, } @@ -153,14 +143,14 @@ declare_class!( // NSWindowDelegate + NSDraggingDestination protocols unsafe impl WinitWindowDelegate { #[sel(windowShouldClose:)] - fn window_should_close(&self, _: id) -> bool { + fn window_should_close(&self, _: Option<&Object>) -> bool { trace_scope!("windowShouldClose:"); self.with_state(|state| state.emit_event(WindowEvent::CloseRequested)); false } #[sel(windowWillClose:)] - fn window_will_close(&self, _: id) { + fn window_will_close(&self, _: Option<&Object>) { trace_scope!("windowWillClose:"); self.with_state(|state| { // `setDelegate:` retains the previous value and then autoreleases it @@ -174,7 +164,7 @@ declare_class!( } #[sel(windowDidResize:)] - fn window_did_resize(&self, _: id) { + fn window_did_resize(&self, _: Option<&Object>) { trace_scope!("windowDidResize:"); self.with_state(|state| { // NOTE: WindowEvent::Resized is reported in frameDidChange. @@ -184,7 +174,7 @@ declare_class!( // This won't be triggered if the move was part of a resize. #[sel(windowDidMove:)] - fn window_did_move(&self, _: id) { + fn window_did_move(&self, _: Option<&Object>) { trace_scope!("windowDidMove:"); self.with_state(|state| { state.emit_move_event(); @@ -192,7 +182,7 @@ declare_class!( } #[sel(windowDidChangeBackingProperties:)] - fn window_did_change_backing_properties(&self, _: id) { + fn window_did_change_backing_properties(&self, _: Option<&Object>) { trace_scope!("windowDidChangeBackingProperties:"); self.with_state(|state| { state.emit_static_scale_factor_changed_event(); @@ -200,7 +190,7 @@ declare_class!( } #[sel(windowDidBecomeKey:)] - fn window_did_become_key(&self, _: id) { + fn window_did_become_key(&self, _: Option<&Object>) { trace_scope!("windowDidBecomeKey:"); self.with_state(|state| { // TODO: center the cursor if the window had mouse grab when it @@ -210,7 +200,7 @@ declare_class!( } #[sel(windowDidResignKey:)] - fn window_did_resign_key(&self, _: id) { + fn window_did_resign_key(&self, _: Option<&Object>) { trace_scope!("windowDidResignKey:"); self.with_state(|state| { // It happens rather often, e.g. when the user is Cmd+Tabbing, that the @@ -236,85 +226,71 @@ declare_class!( /// Invoked when the dragged image enters destination bounds or frame #[sel(draggingEntered:)] - fn dragging_entered(&self, sender: id) -> bool { + fn dragging_entered(&self, sender: *mut Object) -> bool { trace_scope!("draggingEntered:"); - use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration}; use std::path::PathBuf; - let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = - unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; - - for file in unsafe { filenames.iter() } { - use cocoa::foundation::NSString; - use std::ffi::CStr; + let pb: Id = unsafe { msg_send_id![sender, draggingPasteboard] }; + let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }); + let filenames: Id, Shared> = unsafe { Id::cast(filenames) }; - unsafe { - let f = NSString::UTF8String(file); - let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + filenames.into_iter().for_each(|file| { + let path = PathBuf::from(file.to_string()); - self.with_state(|state| { - state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path))); - }); - } - } + self.with_state(|state| { + state.emit_event(WindowEvent::HoveredFile(path)); + }); + }); true } /// Invoked when the image is released #[sel(prepareForDragOperation:)] - fn prepare_for_drag_operation(&self, _: id) -> bool { + fn prepare_for_drag_operation(&self, _: Option<&Object>) -> bool { trace_scope!("prepareForDragOperation:"); true } /// Invoked after the released image has been removed from the screen #[sel(performDragOperation:)] - fn perform_drag_operation(&self, sender: id) -> bool { + fn perform_drag_operation(&self, sender: *mut Object) -> bool { trace_scope!("performDragOperation:"); - use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration}; use std::path::PathBuf; - let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = - unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; - - for file in unsafe { filenames.iter() } { - use cocoa::foundation::NSString; - use std::ffi::CStr; + let pb: Id = unsafe { msg_send_id![sender, draggingPasteboard] }; + let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }); + let filenames: Id, Shared> = unsafe { Id::cast(filenames) }; - unsafe { - let f = NSString::UTF8String(file); - let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + filenames.into_iter().for_each(|file| { + let path = PathBuf::from(file.to_string()); - self.with_state(|state| { - state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path))); - }); - } - } + self.with_state(|state| { + state.emit_event(WindowEvent::DroppedFile(path)); + }); + }); true } /// Invoked when the dragging operation is complete #[sel(concludeDragOperation:)] - fn conclude_drag_operation(&self, _: id) { + fn conclude_drag_operation(&self, _: Option<&Object>) { trace_scope!("concludeDragOperation:"); } /// Invoked when the dragging operation is cancelled #[sel(draggingExited:)] - fn dragging_exited(&self, _: id) { + fn dragging_exited(&self, _: Option<&Object>) { trace_scope!("draggingExited:"); self.with_state(|state| state.emit_event(WindowEvent::HoveredFileCancelled)); } /// Invoked when before enter fullscreen #[sel(windowWillEnterFullscreen:)] - fn window_will_enter_fullscreen(&self, _: id) { + fn window_will_enter_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowWillEnterFullscreen:"); self.with_state(|state| { @@ -345,7 +321,7 @@ declare_class!( /// Invoked when before exit fullscreen #[sel(windowWillExitFullScreen:)] - fn window_will_exit_fullscreen(&self, _: id) { + fn window_will_exit_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowWillExitFullScreen:"); self.with_state(|state| { @@ -359,7 +335,7 @@ declare_class!( #[sel(window:willUseFullScreenPresentationOptions:)] fn window_will_use_fullscreen_presentation_options( &self, - _: id, + _: Option<&Object>, proposed_options: NSApplicationPresentationOptions, ) -> NSApplicationPresentationOptions { trace_scope!("window:willUseFullScreenPresentationOptions:"); @@ -389,7 +365,7 @@ declare_class!( /// Invoked when entered fullscreen #[sel(windowDidEnterFullscreen:)] - fn window_did_enter_fullscreen(&self, _: id) { + fn window_did_enter_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowDidEnterFullscreen:"); self.with_state(|state| { state.initial_fullscreen = false; @@ -407,7 +383,7 @@ declare_class!( /// Invoked when exited fullscreen #[sel(windowDidExitFullscreen:)] - fn window_did_exit_fullscreen(&self, _: id) { + fn window_did_exit_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowDidExitFullscreen:"); self.with_state(|state| { @@ -441,7 +417,7 @@ declare_class!( /// This method indicates that there was an error, and you should clean up any /// work you may have done to prepare to enter full-screen mode. #[sel(windowDidFailToEnterFullscreen:)] - fn window_did_fail_to_enter_fullscreen(&self, _: id) { + fn window_did_fail_to_enter_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowDidFailToEnterFullscreen:"); self.with_state(|state| { state.with_window(|window| { @@ -455,7 +431,7 @@ declare_class!( let _: () = msg_send![ &*state.window, performSelector: sel!(toggleFullScreen:), - withObject: nil, + withObject: ptr::null::(), afterDelay: 0.5, ]; }; @@ -467,7 +443,7 @@ declare_class!( // Invoked when the occlusion state of the window changes #[sel(windowDidChangeOcclusionState:)] - fn window_did_change_occlusion_state(&self, _: id) { + fn window_did_change_occlusion_state(&self, _: Option<&Object>) { trace_scope!("windowDidChangeOcclusionState:"); self.with_state(|state| { state.emit_event(WindowEvent::Occluded( @@ -482,6 +458,15 @@ declare_class!( ); impl WinitWindowDelegate { + pub fn new(window: Id, initial_fullscreen: bool) -> Id { + let state = WindowDelegateState::new(window, initial_fullscreen); + unsafe { + // This is free'd in `dealloc` + let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; + msg_send_id![msg_send_id![Self::class(), alloc], initWithWinit: state_ptr] + } + } + // This function is definitely unsafe (&self -> &mut state), but labeling that // would increase boilerplate and wouldn't really clarify anything... fn with_state T, T>(&self, callback: F) { From a407e553a04da478d92785f45536089f145ead5a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 3 Sep 2022 00:23:14 +0200 Subject: [PATCH 6/8] Enable `deny(unsafe_op_in_unsafe_fn)` on macOS Also re-enable clippy `let_unit_value` lint --- src/platform_impl/macos/app.rs | 2 +- src/platform_impl/macos/appkit/mod.rs | 1 - src/platform_impl/macos/mod.rs | 3 +-- src/platform_impl/macos/observer.rs | 24 ++++++++++++---------- src/platform_impl/macos/util/async.rs | 7 +++++-- src/platform_impl/macos/view.rs | 10 +++++---- src/platform_impl/macos/window.rs | 5 ++++- src/platform_impl/macos/window_delegate.rs | 1 + 8 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index a82275cd61..cc7f632db8 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -35,7 +35,7 @@ declare_class!( } } else { maybe_dispatch_device_event(event); - let _: () = unsafe { msg_send![super(self), sendEvent: event] }; + unsafe { msg_send![super(self), sendEvent: event] } } } } diff --git a/src/platform_impl/macos/appkit/mod.rs b/src/platform_impl/macos/appkit/mod.rs index 829234d31a..7aee6335b5 100644 --- a/src/platform_impl/macos/appkit/mod.rs +++ b/src/platform_impl/macos/appkit/mod.rs @@ -4,7 +4,6 @@ //! In the future, these should probably live in another crate like `cacao`. //! //! TODO: Main thread safety. -#![deny(unsafe_op_in_unsafe_fn)] // Objective-C methods have different conventions, and it's much easier to // understand if we just use the same names #![allow(non_snake_case)] diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index eb59ede58a..2ffa4f88b6 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -1,5 +1,4 @@ -#![cfg(target_os = "macos")] -#![allow(clippy::let_unit_value)] +#![deny(unsafe_op_in_unsafe_fn)] #[macro_use] mod util; diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index 2ced84ef5f..a51a9d1014 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -130,7 +130,7 @@ unsafe fn control_flow_handler(panic_info: *mut c_void, f: F) where F: FnOnce(Weak) + UnwindSafe, { - let info_from_raw = Weak::from_raw(panic_info as *mut PanicInfo); + let info_from_raw = unsafe { Weak::from_raw(panic_info as *mut PanicInfo) }; // Asserting unwind safety on this type should be fine because `PanicInfo` is // `RefUnwindSafe` and `Rc` is `UnwindSafe` if `T` is `RefUnwindSafe`. let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw)); @@ -195,7 +195,7 @@ struct RunLoop(CFRunLoopRef); impl RunLoop { unsafe fn get() -> Self { - RunLoop(CFRunLoopGetMain()) + RunLoop(unsafe { CFRunLoopGetMain() }) } unsafe fn add_observer( @@ -205,15 +205,17 @@ impl RunLoop { handler: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, ) { - let observer = CFRunLoopObserverCreate( - ptr::null_mut(), - flags, - ffi::TRUE, // Indicates we want this to run repeatedly - priority, // The lower the value, the sooner this will run - handler, - context, - ); - CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes); + let observer = unsafe { + CFRunLoopObserverCreate( + ptr::null_mut(), + flags, + ffi::TRUE, // Indicates we want this to run repeatedly + priority, // The lower the value, the sooner this will run + handler, + context, + ) + }; + unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) }; } } diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index d8ceba791b..59f8e2ed54 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -131,8 +131,11 @@ pub(crate) fn toggle_full_screen_async( pub(crate) unsafe fn restore_display_mode_async(ns_screen: u32) { Queue::main().exec_async(move || { - ffi::CGRestorePermanentDisplayConfiguration(); - assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess); + unsafe { ffi::CGRestorePermanentDisplayConfiguration() }; + assert_eq!( + unsafe { ffi::CGDisplayRelease(ns_screen) }, + ffi::kCGErrorSuccess + ); }); } diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 3a07881930..304e2d01d6 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -173,15 +173,16 @@ declare_class!( // About frame change let frame_did_change_notification_name = NSString::from_str("NSViewFrameDidChangeNotification"); - let _: () = unsafe { - msg_send![ + #[allow(clippy::let_unit_value)] + unsafe { + let _: () = msg_send![ notification_center, addObserver: &*this, selector: sel!(frameDidChange:), name: &*frame_did_change_notification_name, object: &*this, - ] - }; + ]; + } this.state.input_source = this.current_input_source(); this @@ -230,6 +231,7 @@ declare_class!( AppState::handle_redraw(self.window_id()); + #[allow(clippy::let_unit_value)] unsafe { let _: () = msg_send![super(self), drawRect: rect]; } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index abd12e5f45..29f80b7b41 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -907,7 +907,10 @@ impl WinitWindow { | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; app.setPresentationOptions(presentation_options); - let _: () = unsafe { msg_send![self, setLevel: ffi::CGShieldingWindowLevel() + 1] }; + #[allow(clippy::let_unit_value)] + unsafe { + let _: () = msg_send![self, setLevel: ffi::CGShieldingWindowLevel() + 1]; + } } ( &Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index db1bd896f4..fba3237bc7 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -427,6 +427,7 @@ declare_class!( shared_state.target_fullscreen = None; }); if state.initial_fullscreen { + #[allow(clippy::let_unit_value)] unsafe { let _: () = msg_send![ &*state.window, From 95a87fa1f338efc59687dd09598b5fbbefa84ef2 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 2 Sep 2022 23:43:42 +0200 Subject: [PATCH 7/8] Remove #[macro_use] on macOS --- src/lib.rs | 2 +- src/platform_impl/macos/app.rs | 2 +- src/platform_impl/macos/app_delegate.rs | 2 +- src/platform_impl/macos/appkit/cursor.rs | 2 +- src/platform_impl/macos/appkit/screen.rs | 2 +- src/platform_impl/macos/event_loop.rs | 6 +++--- src/platform_impl/macos/menu.rs | 2 +- src/platform_impl/macos/view.rs | 2 +- src/platform_impl/macos/window.rs | 2 +- src/platform_impl/macos/window_delegate.rs | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 30fea96bce..26a7fdc7cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ extern crate log; extern crate serde; #[macro_use] extern crate bitflags; -#[cfg(any(target_os = "macos", target_os = "ios"))] +#[cfg(target_os = "ios")] #[macro_use] extern crate objc; #[cfg(target_os = "macos")] diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index cc7f632db8..621c87429d 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; use objc2::foundation::NSObject; -use objc2::{declare_class, ClassType}; +use objc2::{declare_class, msg_send, ClassType}; use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use super::{app_state::AppState, event::EventWrapper, DEVICE_ID}; diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index 841a9a9035..8cdeeeb54d 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -1,7 +1,7 @@ use objc2::foundation::NSObject; use objc2::rc::{Id, Shared}; use objc2::runtime::Object; -use objc2::{declare_class, ClassType}; +use objc2::{declare_class, msg_send, msg_send_id, ClassType}; use super::app_state::AppState; use super::appkit::NSApplicationActivationPolicy; diff --git a/src/platform_impl/macos/appkit/cursor.rs b/src/platform_impl/macos/appkit/cursor.rs index 2ec700dbbc..0461b3acfe 100644 --- a/src/platform_impl/macos/appkit/cursor.rs +++ b/src/platform_impl/macos/appkit/cursor.rs @@ -3,7 +3,7 @@ use once_cell::sync::Lazy; use objc2::foundation::{NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSString}; use objc2::rc::{DefaultId, Id, Shared}; use objc2::runtime::Sel; -use objc2::{extern_class, extern_methods, msg_send_id, ns_string, ClassType}; +use objc2::{extern_class, extern_methods, msg_send_id, ns_string, sel, ClassType}; use super::NSImage; use crate::window::CursorIcon; diff --git a/src/platform_impl/macos/appkit/screen.rs b/src/platform_impl/macos/appkit/screen.rs index fbc9f7d05a..9b76ecbbb2 100644 --- a/src/platform_impl/macos/appkit/screen.rs +++ b/src/platform_impl/macos/appkit/screen.rs @@ -1,7 +1,7 @@ use objc2::foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString}; use objc2::rc::{Id, Shared}; use objc2::runtime::Object; -use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; +use objc2::{extern_class, extern_methods, msg_send_id, ns_string, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 638dd7420d..cfb4d15d6e 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -13,7 +13,7 @@ use std::{ use objc2::foundation::is_main_thread; use objc2::rc::{autoreleasepool, Id, Shared}; -use objc2::ClassType; +use objc2::{msg_send_id, ClassType}; use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent}; @@ -141,8 +141,8 @@ impl EventLoop { // `sharedApplication`) is called anywhere else, or we'll end up // with the wrong `NSApplication` class and the wrong thread could // be marked as main. - let app: &WinitApplication = - unsafe { msg_send![WinitApplication::class(), sharedApplication] }; + let app: Id = + unsafe { msg_send_id![WinitApplication::class(), sharedApplication] }; use NSApplicationActivationPolicy::*; let activation_policy = match attributes.activation_policy { diff --git a/src/platform_impl/macos/menu.rs b/src/platform_impl/macos/menu.rs index 96fae90f42..66ff0fffa1 100644 --- a/src/platform_impl/macos/menu.rs +++ b/src/platform_impl/macos/menu.rs @@ -1,7 +1,7 @@ use objc2::foundation::{NSProcessInfo, NSString}; -use objc2::ns_string; use objc2::rc::{Id, Shared}; use objc2::runtime::Sel; +use objc2::{ns_string, sel}; use super::appkit::{NSApp, NSEventModifierFlags, NSMenu, NSMenuItem}; diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 304e2d01d6..25f5851876 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -7,7 +7,7 @@ use objc2::foundation::{ }; use objc2::rc::{Id, Owned, Shared}; use objc2::runtime::{Object, Sel}; -use objc2::{declare_class, ClassType}; +use objc2::{class, declare_class, msg_send, msg_send_id, sel, ClassType}; use super::appkit::{ NSApp, NSCursor, NSEvent, NSEventModifierFlags, NSEventPhase, NSResponder, NSTrackingRectTag, diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 29f80b7b41..de1bd7c8e3 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -40,7 +40,7 @@ use objc2::foundation::{ is_main_thread, CGFloat, NSArray, NSCopying, NSObject, NSPoint, NSRect, NSSize, NSString, }; use objc2::rc::{autoreleasepool, Id, Owned, Shared}; -use objc2::{declare_class, ClassType}; +use objc2::{declare_class, msg_send, msg_send_id, ClassType}; use super::appkit::{ NSApp, NSAppKitVersion, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index fba3237bc7..cff65cbd3a 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -4,7 +4,7 @@ use std::ptr; use objc2::foundation::{NSArray, NSObject, NSString}; use objc2::rc::{autoreleasepool, Id, Shared}; use objc2::runtime::Object; -use objc2::{declare_class, ClassType}; +use objc2::{declare_class, msg_send, msg_send_id, sel, ClassType}; use super::appkit::{ NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState, From 00a809e44b303bb69648f3bc47f067c6eb41ebe6 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 2 Sep 2022 22:07:46 +0200 Subject: [PATCH 8/8] Refactor window delegate to use much less unsafe --- src/platform_impl/macos/window.rs | 2 +- src/platform_impl/macos/window_delegate.rs | 452 +++++++++------------ 2 files changed, 189 insertions(+), 265 deletions(-) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index de1bd7c8e3..2452d54c15 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -388,7 +388,7 @@ impl WinitWindow { unsafe { NSFilenamesPboardType }.copy() ])); - let delegate = WinitWindowDelegate::new(this.clone(), attrs.fullscreen.is_some()); + let delegate = WinitWindowDelegate::new(&this, attrs.fullscreen.is_some()); // Set fullscreen mode after we setup everything this.set_fullscreen(attrs.fullscreen); diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index cff65cbd3a..a173359d61 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,6 +1,6 @@ -use std::os::raw::c_void; use std::ptr; +use objc2::declare::{Ivar, IvarDrop}; use objc2::foundation::{NSArray, NSObject, NSString}; use objc2::rc::{autoreleasepool, Id, Shared}; use objc2::runtime::Object; @@ -21,98 +21,24 @@ use crate::{ window::{Fullscreen, WindowId}, }; -struct WindowDelegateState { - window: Id, - - // TODO: It's possible for delegate methods to be called asynchronously, - // causing data races / `RefCell` panics. - - // This is set when WindowBuilder::with_fullscreen was set, - // see comments of `window_did_fail_to_enter_fullscreen` - initial_fullscreen: bool, - - // During `windowDidResize`, we use this to only send Moved if the position changed. - previous_position: Option<(f64, f64)>, - - // Used to prevent redundant events. - previous_scale_factor: f64, -} - -impl WindowDelegateState { - fn new(window: Id, initial_fullscreen: bool) -> Self { - let scale_factor = window.scale_factor(); - let mut delegate_state = WindowDelegateState { - window, - initial_fullscreen, - previous_position: None, - previous_scale_factor: scale_factor, - }; - - if scale_factor != 1.0 { - delegate_state.emit_static_scale_factor_changed_event(); - } - - delegate_state - } - - // TODO: Remove this - fn with_window(&mut self, callback: F) -> Option - where - F: FnOnce(&WinitWindow) -> T, - { - Some(callback(&self.window.clone())) - } - - fn emit_event(&mut self, event: WindowEvent<'static>) { - let event = Event::WindowEvent { - window_id: WindowId(self.window.id()), - event, - }; - AppState::queue_event(EventWrapper::StaticEvent(event)); - } - - fn emit_static_scale_factor_changed_event(&mut self) { - let scale_factor = self.get_scale_factor(); - if scale_factor == self.previous_scale_factor { - return; - }; - - self.previous_scale_factor = scale_factor; - let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { - window: self.window.clone(), - suggested_size: self.view_size(), - scale_factor, - }); - AppState::queue_event(wrapper); - } +declare_class!( + #[derive(Debug)] + pub(crate) struct WinitWindowDelegate { + window: IvarDrop>, - fn emit_move_event(&mut self) { - let rect = self.window.frame(); - let x = rect.origin.x as f64; - let y = util::bottom_left_to_top_left(rect); - let moved = self.previous_position != Some((x, y)); - if moved { - self.previous_position = Some((x, y)); - let scale_factor = self.get_scale_factor(); - let physical_pos = LogicalPosition::::from((x, y)).to_physical(scale_factor); - self.emit_event(WindowEvent::Moved(physical_pos)); - } - } + // TODO: It's possible for delegate methods to be called asynchronously, + // causing data races / `RefCell` panics. - fn get_scale_factor(&self) -> f64 { - self.window.scale_factor() - } + // This is set when WindowBuilder::with_fullscreen was set, + // see comments of `window_did_fail_to_enter_fullscreen` + initial_fullscreen: bool, - fn view_size(&self) -> LogicalSize { - let size = self.window.contentView().frame().size; - LogicalSize::new(size.width as f64, size.height as f64) - } -} + // During `windowDidResize`, we use this to only send Moved if the position changed. + // TODO: Remove unnecessary boxing here + previous_position: IvarDrop>>, -declare_class!( - #[derive(Debug)] - pub(crate) struct WinitWindowDelegate { - state: *mut c_void, + // Used to prevent redundant events. + previous_scale_factor: f64, } unsafe impl ClassType for WinitWindowDelegate { @@ -120,21 +46,28 @@ declare_class!( } unsafe impl WinitWindowDelegate { - #[sel(dealloc)] - fn dealloc(&mut self) { - self.with_state(|state| unsafe { - drop(Box::from_raw(state as *mut WindowDelegateState)); - }); - } - - #[sel(initWithWinit:)] - fn init_with_winit(&mut self, state: *mut c_void) -> Option<&mut Self> { + #[sel(initWithWindow:initialFullscreen:)] + fn init_with_winit( + &mut self, + window: &WinitWindow, + initial_fullscreen: bool, + ) -> Option<&mut Self> { let this: Option<&mut Self> = unsafe { msg_send![self, init] }; this.map(|this| { - *this.state = state; - this.with_state(|state| { - state.window.setDelegate(Some(&*this)); + let scale_factor = window.scale_factor(); + + Ivar::write(&mut this.window, unsafe { + let window: *const WinitWindow = window; + Id::retain(window as *mut WinitWindow).unwrap() }); + Ivar::write(&mut this.initial_fullscreen, initial_fullscreen); + Ivar::write(&mut this.previous_position, None); + Ivar::write(&mut this.previous_scale_factor, scale_factor); + + if scale_factor != 1.0 { + this.emit_static_scale_factor_changed_event(); + } + this.window.setDelegate(Some(this)); this }) } @@ -145,83 +78,71 @@ declare_class!( #[sel(windowShouldClose:)] fn window_should_close(&self, _: Option<&Object>) -> bool { trace_scope!("windowShouldClose:"); - self.with_state(|state| state.emit_event(WindowEvent::CloseRequested)); + self.emit_event(WindowEvent::CloseRequested); false } #[sel(windowWillClose:)] fn window_will_close(&self, _: Option<&Object>) { trace_scope!("windowWillClose:"); - self.with_state(|state| { - // `setDelegate:` retains the previous value and then autoreleases it - autoreleasepool(|_| { - // Since El Capitan, we need to be careful that delegate methods can't - // be called after the window closes. - state.window.setDelegate(None); - }); - state.emit_event(WindowEvent::Destroyed); + // `setDelegate:` retains the previous value and then autoreleases it + autoreleasepool(|_| { + // Since El Capitan, we need to be careful that delegate methods can't + // be called after the window closes. + self.window.setDelegate(None); }); + self.emit_event(WindowEvent::Destroyed); } #[sel(windowDidResize:)] - fn window_did_resize(&self, _: Option<&Object>) { + fn window_did_resize(&mut self, _: Option<&Object>) { trace_scope!("windowDidResize:"); - self.with_state(|state| { - // NOTE: WindowEvent::Resized is reported in frameDidChange. - state.emit_move_event(); - }); + // NOTE: WindowEvent::Resized is reported in frameDidChange. + self.emit_move_event(); } // This won't be triggered if the move was part of a resize. #[sel(windowDidMove:)] - fn window_did_move(&self, _: Option<&Object>) { + fn window_did_move(&mut self, _: Option<&Object>) { trace_scope!("windowDidMove:"); - self.with_state(|state| { - state.emit_move_event(); - }); + self.emit_move_event(); } #[sel(windowDidChangeBackingProperties:)] - fn window_did_change_backing_properties(&self, _: Option<&Object>) { + fn window_did_change_backing_properties(&mut self, _: Option<&Object>) { trace_scope!("windowDidChangeBackingProperties:"); - self.with_state(|state| { - state.emit_static_scale_factor_changed_event(); - }); + self.emit_static_scale_factor_changed_event(); } #[sel(windowDidBecomeKey:)] fn window_did_become_key(&self, _: Option<&Object>) { trace_scope!("windowDidBecomeKey:"); - self.with_state(|state| { - // TODO: center the cursor if the window had mouse grab when it - // lost focus - state.emit_event(WindowEvent::Focused(true)); - }); + // TODO: center the cursor if the window had mouse grab when it + // lost focus + self.emit_event(WindowEvent::Focused(true)); } #[sel(windowDidResignKey:)] fn window_did_resign_key(&self, _: Option<&Object>) { trace_scope!("windowDidResignKey:"); - self.with_state(|state| { - // It happens rather often, e.g. when the user is Cmd+Tabbing, that the - // NSWindowDelegate will receive a didResignKey event despite no event - // being received when the modifiers are released. This is because - // flagsChanged events are received by the NSView instead of the - // NSWindowDelegate, and as a result a tracked modifiers state can quite - // easily fall out of synchrony with reality. This requires us to emit - // a synthetic ModifiersChanged event when we lose focus. - - // TODO(madsmtm): Remove the need for this unsafety - let mut view = unsafe { Id::from_shared(state.window.view()) }; - - // Both update the state and emit a ModifiersChanged event. - if !view.state.modifiers.is_empty() { - view.state.modifiers = ModifiersState::empty(); - state.emit_event(WindowEvent::ModifiersChanged(view.state.modifiers)); - } - - state.emit_event(WindowEvent::Focused(false)); - }); + // It happens rather often, e.g. when the user is Cmd+Tabbing, that the + // NSWindowDelegate will receive a didResignKey event despite no event + // being received when the modifiers are released. This is because + // flagsChanged events are received by the NSView instead of the + // NSWindowDelegate, and as a result a tracked modifiers state can quite + // easily fall out of synchrony with reality. This requires us to emit + // a synthetic ModifiersChanged event when we lose focus. + + // TODO(madsmtm): Remove the need for this unsafety + let mut view = unsafe { Id::from_shared(self.window.view()) }; + + // Both update the state and emit a ModifiersChanged event. + if !view.state.modifiers.is_empty() { + view.state.modifiers = ModifiersState::empty(); + self.emit_event(WindowEvent::ModifiersChanged(view.state.modifiers)); + } + + self.emit_event(WindowEvent::Focused(false)); } /// Invoked when the dragged image enters destination bounds or frame @@ -237,10 +158,7 @@ declare_class!( filenames.into_iter().for_each(|file| { let path = PathBuf::from(file.to_string()); - - self.with_state(|state| { - state.emit_event(WindowEvent::HoveredFile(path)); - }); + self.emit_event(WindowEvent::HoveredFile(path)); }); true @@ -266,10 +184,7 @@ declare_class!( filenames.into_iter().for_each(|file| { let path = PathBuf::from(file.to_string()); - - self.with_state(|state| { - state.emit_event(WindowEvent::DroppedFile(path)); - }); + self.emit_event(WindowEvent::DroppedFile(path)); }); true @@ -285,7 +200,7 @@ declare_class!( #[sel(draggingExited:)] fn dragging_exited(&self, _: Option<&Object>) { trace_scope!("draggingExited:"); - self.with_state(|state| state.emit_event(WindowEvent::HoveredFileCancelled)); + self.emit_event(WindowEvent::HoveredFileCancelled); } /// Invoked when before enter fullscreen @@ -293,30 +208,28 @@ declare_class!( fn window_will_enter_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowWillEnterFullscreen:"); - self.with_state(|state| { - state.with_window(|window| { - let mut shared_state = window.lock_shared_state("window_will_enter_fullscreen"); - shared_state.maximized = window.is_zoomed(); - let fullscreen = shared_state.fullscreen.as_ref(); - match fullscreen { - // Exclusive mode sets the state in `set_fullscreen` as the user - // can't enter exclusive mode by other means (like the - // fullscreen button on the window decorations) - Some(Fullscreen::Exclusive(_)) => (), - // `window_will_enter_fullscreen` was triggered and we're already - // in fullscreen, so we must've reached here by `set_fullscreen` - // as it updates the state - Some(Fullscreen::Borderless(_)) => (), - // Otherwise, we must've reached fullscreen by the user clicking - // on the green fullscreen button. Update state! - None => { - let current_monitor = Some(window.current_monitor_inner()); - shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) - } - } - shared_state.in_fullscreen_transition = true; - }) - }); + let mut shared_state = self + .window + .lock_shared_state("window_will_enter_fullscreen"); + shared_state.maximized = self.window.is_zoomed(); + let fullscreen = shared_state.fullscreen.as_ref(); + match fullscreen { + // Exclusive mode sets the state in `set_fullscreen` as the user + // can't enter exclusive mode by other means (like the + // fullscreen button on the window decorations) + Some(Fullscreen::Exclusive(_)) => (), + // `window_will_enter_fullscreen` was triggered and we're already + // in fullscreen, so we must've reached here by `set_fullscreen` + // as it updates the state + Some(Fullscreen::Borderless(_)) => (), + // Otherwise, we must've reached fullscreen by the user clicking + // on the green fullscreen button. Update state! + None => { + let current_monitor = Some(self.window.current_monitor_inner()); + shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) + } + } + shared_state.in_fullscreen_transition = true; } /// Invoked when before exit fullscreen @@ -324,12 +237,8 @@ declare_class!( fn window_will_exit_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowWillExitFullScreen:"); - self.with_state(|state| { - state.with_window(|window| { - let mut shared_state = window.lock_shared_state("window_will_exit_fullscreen"); - shared_state.in_fullscreen_transition = true; - }); - }); + let mut shared_state = self.window.lock_shared_state("window_will_exit_fullscreen"); + shared_state.in_fullscreen_transition = true; } #[sel(window:willUseFullScreenPresentationOptions:)] @@ -348,37 +257,30 @@ declare_class!( // we don't, for consistency. If we do, it should be documented that the // user-provided options are ignored in exclusive fullscreen. let mut options = proposed_options; - self.with_state(|state| { - state.with_window(|window| { - let shared_state = - window.lock_shared_state("window_will_use_fullscreen_presentation_options"); - if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { - options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen - | NSApplicationPresentationOptions::NSApplicationPresentationHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; - } - }) - }); + let shared_state = self + .window + .lock_shared_state("window_will_use_fullscreen_presentation_options"); + if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { + options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen + | NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; + } options } /// Invoked when entered fullscreen #[sel(windowDidEnterFullscreen:)] - fn window_did_enter_fullscreen(&self, _: Option<&Object>) { + fn window_did_enter_fullscreen(&mut self, _: Option<&Object>) { trace_scope!("windowDidEnterFullscreen:"); - self.with_state(|state| { - state.initial_fullscreen = false; - state.with_window(|window| { - let mut shared_state = window.lock_shared_state("window_did_enter_fullscreen"); - shared_state.in_fullscreen_transition = false; - let target_fullscreen = shared_state.target_fullscreen.take(); - drop(shared_state); - if let Some(target_fullscreen) = target_fullscreen { - window.set_fullscreen(target_fullscreen); - } - }); - }); + *self.initial_fullscreen = false; + let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen"); + shared_state.in_fullscreen_transition = false; + let target_fullscreen = shared_state.target_fullscreen.take(); + drop(shared_state); + if let Some(target_fullscreen) = target_fullscreen { + self.window.set_fullscreen(target_fullscreen); + } } /// Invoked when exited fullscreen @@ -386,18 +288,14 @@ declare_class!( fn window_did_exit_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowDidExitFullscreen:"); - self.with_state(|state| { - state.with_window(|window| { - window.restore_state_from_fullscreen(); - let mut shared_state = window.lock_shared_state("window_did_exit_fullscreen"); - shared_state.in_fullscreen_transition = false; - let target_fullscreen = shared_state.target_fullscreen.take(); - drop(shared_state); - if let Some(target_fullscreen) = target_fullscreen { - window.set_fullscreen(target_fullscreen); - } - }) - }); + self.window.restore_state_from_fullscreen(); + let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen"); + shared_state.in_fullscreen_transition = false; + let target_fullscreen = shared_state.target_fullscreen.take(); + drop(shared_state); + if let Some(target_fullscreen) = target_fullscreen { + self.window.set_fullscreen(target_fullscreen); + } } /// Invoked when fail to enter fullscreen @@ -419,62 +317,88 @@ declare_class!( #[sel(windowDidFailToEnterFullscreen:)] fn window_did_fail_to_enter_fullscreen(&self, _: Option<&Object>) { trace_scope!("windowDidFailToEnterFullscreen:"); - self.with_state(|state| { - state.with_window(|window| { - let mut shared_state = - window.lock_shared_state("window_did_fail_to_enter_fullscreen"); - shared_state.in_fullscreen_transition = false; - shared_state.target_fullscreen = None; - }); - if state.initial_fullscreen { - #[allow(clippy::let_unit_value)] - unsafe { - let _: () = msg_send![ - &*state.window, - performSelector: sel!(toggleFullScreen:), - withObject: ptr::null::(), - afterDelay: 0.5, - ]; - }; - } else { - state.with_window(|window| window.restore_state_from_fullscreen()); - } - }); + let mut shared_state = self + .window + .lock_shared_state("window_did_fail_to_enter_fullscreen"); + shared_state.in_fullscreen_transition = false; + shared_state.target_fullscreen = None; + if *self.initial_fullscreen { + #[allow(clippy::let_unit_value)] + unsafe { + let _: () = msg_send![ + &*self.window, + performSelector: sel!(toggleFullScreen:), + withObject: ptr::null::(), + afterDelay: 0.5, + ]; + }; + } else { + self.window.restore_state_from_fullscreen(); + } } // Invoked when the occlusion state of the window changes #[sel(windowDidChangeOcclusionState:)] fn window_did_change_occlusion_state(&self, _: Option<&Object>) { trace_scope!("windowDidChangeOcclusionState:"); - self.with_state(|state| { - state.emit_event(WindowEvent::Occluded( - !state - .window - .occlusionState() - .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), - )) - }); + self.emit_event(WindowEvent::Occluded( + !self + .window + .occlusionState() + .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), + )) } } ); impl WinitWindowDelegate { - pub fn new(window: Id, initial_fullscreen: bool) -> Id { - let state = WindowDelegateState::new(window, initial_fullscreen); + pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id { unsafe { - // This is free'd in `dealloc` - let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; - msg_send_id![msg_send_id![Self::class(), alloc], initWithWinit: state_ptr] + msg_send_id![ + msg_send_id![Self::class(), alloc], + initWithWindow: window, + initialFullscreen: initial_fullscreen, + ] } } - // This function is definitely unsafe (&self -> &mut state), but labeling that - // would increase boilerplate and wouldn't really clarify anything... - fn with_state T, T>(&self, callback: F) { - let state_ptr = unsafe { - let state_ptr: *mut c_void = *self.state; - &mut *(state_ptr as *mut WindowDelegateState) + fn emit_event(&self, event: WindowEvent<'static>) { + let event = Event::WindowEvent { + window_id: WindowId(self.window.id()), + event, + }; + AppState::queue_event(EventWrapper::StaticEvent(event)); + } + + fn emit_static_scale_factor_changed_event(&mut self) { + let scale_factor = self.window.scale_factor(); + if scale_factor == *self.previous_scale_factor { + return; }; - callback(state_ptr); + + *self.previous_scale_factor = scale_factor; + let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { + window: self.window.clone(), + suggested_size: self.view_size(), + scale_factor, + }); + AppState::queue_event(wrapper); + } + + fn emit_move_event(&mut self) { + let rect = self.window.frame(); + let x = rect.origin.x as f64; + let y = util::bottom_left_to_top_left(rect); + if self.previous_position.as_deref() != Some(&(x, y)) { + *self.previous_position = Some(Box::new((x, y))); + let scale_factor = self.window.scale_factor(); + let physical_pos = LogicalPosition::::from((x, y)).to_physical(scale_factor); + self.emit_event(WindowEvent::Moved(physical_pos)); + } + } + + fn view_size(&self) -> LogicalSize { + let size = self.window.contentView().frame().size; + LogicalSize::new(size.width as f64, size.height as f64) } }