diff --git a/Cargo.toml b/Cargo.toml index e44ee1b084..e5ec6e62a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,14 +104,15 @@ ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false } # AppKit or UIKit [target.'cfg(target_vendor = "apple")'.dependencies] -block2 = "0.5.1" -core-foundation = "0.9.3" -objc2 = "0.5.2" +block2 = "0.6.0" +dispatch2 = { version = "0.2.0", default-features = false, features = ["std", "objc2"] } +objc2 = "0.6.0" # AppKit [target.'cfg(target_os = "macos")'.dependencies] -core-graphics = "0.23.1" -objc2-app-kit = { version = "0.2.2", features = [ +objc2-app-kit = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-foundation", "NSAppearance", "NSApplication", "NSBitmapImageRep", @@ -141,9 +142,37 @@ objc2-app-kit = { version = "0.2.2", features = [ "NSWindowScripting", "NSWindowTabGroup", ] } -objc2-foundation = { version = "0.2.2", features = [ +objc2-core-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "block2", + "CFBase", + "CFCGTypes", + "CFData", + "CFRunLoop", + "CFString", + "CFUUID", +] } +objc2-core-graphics = { version = "0.3.0", default-features = false, features = [ + "std", + "libc", + "CGDirectDisplay", + "CGDisplayConfiguration", + "CGDisplayFade", + "CGError", + "CGRemoteOperation", + "CGWindowLevel", +] } +objc2-core-video = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-graphics", + "CVBase", + "CVReturn", + "CVDisplayLink", +] } +objc2-foundation = { version = "0.3.0", default-features = false, features = [ + "std", "block2", - "dispatch", + "objc2-core-foundation", "NSArray", "NSAttributedString", "NSData", @@ -165,20 +194,29 @@ objc2-foundation = { version = "0.2.2", features = [ # UIKit [target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies] -objc2-foundation = { version = "0.2.2", features = [ +objc2-core-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "CFCGTypes", + "CFBase", + "CFRunLoop", + "CFString", +] } +objc2-foundation = { version = "0.3.0", default-features = false, features = [ + "std", "block2", - "dispatch", + "objc2-core-foundation", "NSArray", "NSEnumerator", "NSGeometry", "NSObjCRuntime", "NSOperation", "NSString", - "NSProcessInfo", "NSThread", "NSSet", ] } -objc2-ui-kit = { version = "0.2.2", features = [ +objc2-ui-kit = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-foundation", "UIApplication", "UIDevice", "UIEvent", diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index cf9c9288a4..79558b72d5 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -184,6 +184,7 @@ changelog entry. The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves whilst files are being dragged over the window. It doesn't contain any file paths, just the pointer position. +- Updated `objc2` to `v0.6`. ### Removed diff --git a/src/platform/ios.rs b/src/platform/ios.rs index f7e9f95346..5f5b221bb2 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -394,7 +394,7 @@ impl MonitorHandleExtIOS for MonitorHandle { #[inline] fn ui_screen(&self) -> *mut c_void { // SAFETY: The marker is only used to get the pointer of the screen - let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; + let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() }; objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void } diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 85f7081eba..bb0f8b1f32 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -21,26 +21,21 @@ #![cfg_attr(not(target_os = "macos"), doc = "```ignore")] //! use objc2::rc::Retained; //! use objc2::runtime::ProtocolObject; -//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; +//! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly}; //! use objc2_app_kit::{NSApplication, NSApplicationDelegate}; -//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol}; +//! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol}; //! use winit::event_loop::EventLoop; //! -//! declare_class!( +//! define_class!( +//! #[unsafe(super(NSObject))] +//! #[thread_kind = MainThreadOnly] +//! #[name = "AppDelegate"] //! struct AppDelegate; //! -//! unsafe impl ClassType for AppDelegate { -//! type Super = NSObject; -//! type Mutability = mutability::MainThreadOnly; -//! const NAME: &'static str = "MyAppDelegate"; -//! } -//! -//! impl DeclaredClass for AppDelegate {} -//! //! unsafe impl NSObjectProtocol for AppDelegate {} //! //! unsafe impl NSApplicationDelegate for AppDelegate { -//! #[method(application:openURLs:)] +//! #[unsafe(method(application:openURLs:))] //! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray) { //! // Note: To specifically get `application:openURLs:` to work, you _might_ //! // have to bundle your application. This is not done in this example. @@ -51,7 +46,7 @@ //! //! impl AppDelegate { //! fn new(mtm: MainThreadMarker) -> Retained { -//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] } +//! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] } //! } //! } //! @@ -518,7 +513,7 @@ impl MonitorHandleExtMacOS for MonitorHandle { fn ns_screen(&self) -> Option<*mut c_void> { // SAFETY: We only use the marker to get a pointer - let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; + let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() }; self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _) } } diff --git a/src/platform_impl/apple/appkit/app.rs b/src/platform_impl/apple/appkit/app.rs index 747a17b4c5..1e87da0a01 100644 --- a/src/platform_impl/apple/appkit/app.rs +++ b/src/platform_impl/apple/appkit/app.rs @@ -2,30 +2,23 @@ use std::rc::Rc; -use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass}; +use objc2::{define_class, msg_send, MainThreadMarker}; use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; -use objc2_foundation::{MainThreadMarker, NSObject}; +use objc2_foundation::NSObject; use super::app_state::AppState; use crate::event::{DeviceEvent, ElementState}; -declare_class!( +define_class!( + #[unsafe(super(NSApplication, NSResponder, NSObject))] + #[name = "WinitApplication"] pub(super) struct WinitApplication; - unsafe impl ClassType for WinitApplication { - #[inherits(NSResponder, NSObject)] - type Super = NSApplication; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitApplication"; - } - - impl DeclaredClass for WinitApplication {} - - unsafe impl WinitApplication { + impl WinitApplication { // Normally, holding Cmd + any key never sends us a `keyUp` event for that key. // 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) - #[method(sendEvent:)] + #[unsafe(method(sendEvent:))] fn send_event(&self, event: &NSEvent) { // For posterity, there are some undocumented event types // (https://github.com/servo/cocoa-rs/issues/155) @@ -33,7 +26,7 @@ declare_class!( let event_type = unsafe { event.r#type() }; let modifier_flags = unsafe { event.modifierFlags() }; if event_type == NSEventType::KeyUp - && modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand) + && modifier_flags.contains(NSEventModifierFlags::Command) { if let Some(key_window) = self.keyWindow() { key_window.sendEvent(event); diff --git a/src/platform_impl/apple/appkit/app_state.rs b/src/platform_impl/apple/appkit/app_state.rs index 9ff1e95720..e9be6032f2 100644 --- a/src/platform_impl/apple/appkit/app_state.rs +++ b/src/platform_impl/apple/appkit/app_state.rs @@ -5,8 +5,10 @@ use std::sync::atomic::Ordering as AtomicOrdering; use std::sync::Arc; use std::time::Instant; +use dispatch2::MainThreadBound; +use objc2::MainThreadMarker; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication}; -use objc2_foundation::{MainThreadMarker, NSNotification}; +use objc2_foundation::NSNotification; use super::super::event_handler::EventHandler; use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo}; @@ -45,22 +47,10 @@ pub(super) struct AppState { // as such should be careful to not add fields that, in turn, strongly reference those. } -// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s. -struct StaticMainThreadBound(T); - -impl StaticMainThreadBound { - const fn get(&self, _mtm: MainThreadMarker) -> &T { - &self.0 - } -} - -unsafe impl Send for StaticMainThreadBound {} -unsafe impl Sync for StaticMainThreadBound {} - -// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the +// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the // main thread. -static GLOBAL: StaticMainThreadBound>> = - StaticMainThreadBound(OnceCell::new()); +static GLOBAL: MainThreadBound>> = + MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() }); impl AppState { pub(super) fn setup_global( diff --git a/src/platform_impl/apple/appkit/cursor.rs b/src/platform_impl/apple/appkit/cursor.rs index b8befe1101..807f6b31cd 100644 --- a/src/platform_impl/apple/appkit/cursor.rs +++ b/src/platform_impl/apple/appkit/cursor.rs @@ -4,11 +4,10 @@ use std::sync::OnceLock; use objc2::rc::Retained; use objc2::runtime::Sel; -use objc2::{msg_send, msg_send_id, sel, ClassType}; +use objc2::{available, msg_send, sel, AllocAnyThread, ClassType}; use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage}; use objc2_foundation::{ - ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, - NSString, + ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, }; use crate::cursor::{CursorImage, OnlyCursorImageSource}; @@ -67,8 +66,8 @@ pub(crate) fn default_cursor() -> Retained { unsafe fn try_cursor_from_selector(sel: Sel) -> Option> { let cls = NSCursor::class(); - if msg_send![cls, respondsToSelector: sel] { - let cursor: Retained = unsafe { msg_send_id![cls, performSelector: sel] }; + if unsafe { msg_send![cls, respondsToSelector: sel] } { + let cursor: Retained = unsafe { msg_send![cls, performSelector: sel] }; Some(cursor) } else { tracing::warn!("cursor `{sel}` appears to be invalid"); @@ -130,25 +129,21 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Retained { // TODO: Handle PLists better let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist")); let info: Retained> = unsafe { - msg_send_id![ + msg_send![ >::class(), dictionaryWithContentsOfFile: &*info_path, ] }; let mut x = 0.0; - if let Some(n) = info.get(&*ns_string!("hotx")) { - if n.is_kind_of::() { - let ptr: *const NSObject = n; - let ptr: *const NSNumber = ptr.cast(); - x = unsafe { &*ptr }.as_cgfloat() + if let Some(n) = info.objectForKey(ns_string!("hotx")) { + if let Ok(n) = n.downcast::() { + x = n.as_cgfloat(); } } let mut y = 0.0; - if let Some(n) = info.get(&*ns_string!("hotx")) { - if n.is_kind_of::() { - let ptr: *const NSObject = n; - let ptr: *const NSNumber = ptr.cast(); - y = unsafe { &*ptr }.as_cgfloat() + if let Some(n) = info.objectForKey(ns_string!("hoty")) { + if let Ok(n) = n.downcast::() { + y = n.as_cgfloat(); } } @@ -188,6 +183,7 @@ pub(crate) fn invisible_cursor() -> Retained { CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone() } +#[allow(deprecated)] pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained { match icon { CursorIcon::Default => default_cursor(), @@ -208,7 +204,9 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained { CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(), CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(), CursorIcon::Help => _helpCursor(), + CursorIcon::ZoomIn if available!(macos = 15.0) => unsafe { NSCursor::zoomInCursor() }, CursorIcon::ZoomIn => _zoomInCursor(), + CursorIcon::ZoomOut if available!(macos = 15.0) => unsafe { NSCursor::zoomOutCursor() }, CursorIcon::ZoomOut => _zoomOutCursor(), CursorIcon::NeResize => _windowResizeNorthEastCursor(), CursorIcon::NwResize => _windowResizeNorthWestCursor(), diff --git a/src/platform_impl/apple/appkit/event.rs b/src/platform_impl/apple/appkit/event.rs index f562aceb84..0405088cb0 100644 --- a/src/platform_impl/apple/appkit/event.rs +++ b/src/platform_impl/apple/appkit/event.rs @@ -1,10 +1,10 @@ -use std::ffi::c_void; +use std::ptr::NonNull; -use core_foundation::base::CFRelease; -use core_foundation::data::{CFDataGetBytePtr, CFDataRef}; +use dispatch2::run_on_main; use objc2::rc::Retained; use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType}; -use objc2_foundation::{run_on_main, NSPoint}; +use objc2_core_foundation::{CFData, CFDataGetBytePtr, CFRetained}; +use objc2_foundation::NSPoint; use smol_str::SmolStr; use super::ffi; @@ -22,29 +22,27 @@ pub struct KeyEventExtra { /// Ignores ALL modifiers. pub fn get_modifierless_char(scancode: u16) -> Key { - let mut string = [0; 16]; - let input_source; - let layout; - unsafe { - input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource(); - if input_source.is_null() { - tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr"); - return Key::Unidentified(NativeKey::MacOS(scancode)); - } - let layout_data = - ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData); - if layout_data.is_null() { - CFRelease(input_source as *mut c_void); - tracing::error!("`TISGetInputSourceProperty` returned null ptr"); - return Key::Unidentified(NativeKey::MacOS(scancode)); - } - layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout; - } + let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else { + tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr"); + return Key::Unidentified(NativeKey::MacOS(scancode)); + }; + let input_source = unsafe { CFRetained::from_raw(ptr) }; + + let layout_data = unsafe { + ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData) + }; + let Some(layout_data) = (unsafe { layout_data.cast::().as_ref() }) else { + tracing::error!("`TISGetInputSourceProperty` returned null ptr"); + return Key::Unidentified(NativeKey::MacOS(scancode)); + }; + + let layout = unsafe { CFDataGetBytePtr(layout_data).cast() }; let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() }); let mut result_len = 0; let mut dead_keys = 0; let modifiers = 0; + let mut string = [0; 16]; let translate_result = unsafe { ffi::UCKeyTranslate( layout, @@ -59,9 +57,6 @@ pub fn get_modifierless_char(scancode: u16) -> Key { string.as_mut_ptr(), ) }; - unsafe { - CFRelease(input_source as *mut c_void); - } if translate_result != 0 { tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result); return Key::Unidentified(NativeKey::MacOS(scancode)); @@ -123,8 +118,8 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo let key_without_modifiers = get_modifierless_char(scancode); let modifiers = unsafe { ns_event.modifierFlags() }; - let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl); - let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand); + let has_ctrl = modifiers.contains(NSEventModifierFlags::Control); + let has_cmd = modifiers.contains(NSEventModifierFlags::Command); let logical_key = match text_with_all_modifiers.as_ref() { // Only checking for ctrl and cmd here, not checking for alt because we DO want to @@ -308,26 +303,19 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers { let mut state = ModifiersState::empty(); let mut pressed_mods = ModifiersKeys::empty(); - state - .set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift)); + state.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::Shift)); pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK)); pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK)); - state.set( - ModifiersState::CONTROL, - flags.contains(NSEventModifierFlags::NSEventModifierFlagControl), - ); + state.set(ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::Control)); pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK)); pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK)); - state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption)); + state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::Option)); pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK)); pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK)); - state.set( - ModifiersState::SUPER, - flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand), - ); + state.set(ModifiersState::SUPER, flags.contains(NSEventModifierFlags::Command)); pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK)); pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK)); diff --git a/src/platform_impl/apple/appkit/event_loop.rs b/src/platform_impl/apple/appkit/event_loop.rs index 4e8648d0f8..c9fc4d3562 100644 --- a/src/platform_impl/apple/appkit/event_loop.rs +++ b/src/platform_impl/apple/appkit/event_loop.rs @@ -8,18 +8,19 @@ use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use std::sync::Arc; use std::time::{Duration, Instant}; -use core_foundation::base::{CFIndex, CFRelease}; -use core_foundation::runloop::{ - kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext, - CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, -}; use objc2::rc::{autoreleasepool, Retained}; -use objc2::{msg_send_id, sel, ClassType}; +use objc2::runtime::ProtocolObject; +use objc2::{available, msg_send, ClassType, MainThreadMarker}; use objc2_app_kit::{ NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification, NSApplicationWillTerminateNotification, NSWindow, }; -use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol}; +use objc2_core_foundation::{ + kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoopAddSource, CFRunLoopGetMain, + CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceSignal, + CFRunLoopWakeUp, +}; +use objc2_foundation::{NSNotificationCenter, NSObjectProtocol}; use rwh_06::HasDisplayHandle; use super::super::notification_center::create_observer; @@ -129,7 +130,8 @@ impl RootActiveEventLoop for ActiveEventLoop { fn system_theme(&self) -> Option { let app = NSApplication::sharedApplication(self.mtm); - if app.respondsToSelector(sel!(effectiveAppearance)) { + // Dark appearance was introduced in macOS 10.14 + if available!(macos = 10.14) { Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) } else { Some(Theme::Light) @@ -183,8 +185,8 @@ pub struct EventLoop { // the system instead cleans it up next time it would have posted a notification to it. // // Though we do still need to keep the observers around to prevent them from being deallocated. - _did_finish_launching_observer: Retained, - _will_terminate_observer: Retained, + _did_finish_launching_observer: Retained>, + _will_terminate_observer: Retained>, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -208,9 +210,9 @@ impl EventLoop { .expect("on macOS, `EventLoop` must be created on the main thread!"); let app: Retained = - unsafe { msg_send_id![WinitApplication::class(), sharedApplication] }; + unsafe { msg_send![WinitApplication::class(), sharedApplication] }; - if !app.is_kind_of::() { + if !app.isKindOfClass(WinitApplication::class()) { panic!( "`winit` requires control over the principal class. You must create the event \ loop before other parts of your application initialize NSApplication" @@ -301,8 +303,8 @@ impl EventLoop { self.app_state.dispatch_init_events(); } - // SAFETY: We do not run the application re-entrantly - unsafe { self.app.run() }; + // NOTE: Make sure to not run the application re-entrantly, as that'd be confusing. + self.app.run(); // While the app is running it's possible that we catch a panic // to avoid unwinding across an objective-c ffi boundary, which @@ -333,8 +335,7 @@ impl EventLoop { debug_assert!(!self.app_state.is_running()); self.app_state.set_stop_on_launch(); - // SAFETY: We do not run the application re-entrantly - unsafe { self.app.run() }; + self.app.run(); // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application // has launched @@ -366,8 +367,7 @@ impl EventLoop { }, } self.app_state.set_stop_on_redraw(true); - // SAFETY: We do not run the application re-entrantly - unsafe { self.app.run() }; + self.app.run(); } // While the app is running it's possible that we catch a panic @@ -437,29 +437,21 @@ pub fn stop_app_on_panic R + UnwindSafe, R>( #[derive(Debug)] pub struct EventLoopProxy { pub(crate) wake_up: AtomicBool, - source: CFRunLoopSourceRef, + source: CFRetained, } unsafe impl Send for EventLoopProxy {} unsafe impl Sync for EventLoopProxy {} -impl Drop for EventLoopProxy { - fn drop(&mut self) { - unsafe { - CFRelease(self.source as _); - } - } -} - impl EventLoopProxy { pub(crate) fn new() -> Self { unsafe { // just wake up the eventloop - extern "C" fn event_loop_proxy_handler(_: *const c_void) {} + extern "C-unwind" fn event_loop_proxy_handler(_context: *mut c_void) {} // adding a Source to the main CFRunLoop lets us wake it up and // process user events through the normal OS EventLoop mechanisms. - let rl = CFRunLoopGetMain(); + let rl = CFRunLoopGetMain().unwrap(); let mut context = CFRunLoopSourceContext { version: 0, info: ptr::null_mut(), @@ -470,11 +462,11 @@ impl EventLoopProxy { hash: None, schedule: None, cancel: None, - perform: event_loop_proxy_handler, + perform: Some(event_loop_proxy_handler), }; - let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); - CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); - CFRunLoopWakeUp(rl); + let source = CFRunLoopSourceCreate(None, CFIndex::MAX - 1, &mut context).unwrap(); + CFRunLoopAddSource(&rl, Some(&source), kCFRunLoopCommonModes); + CFRunLoopWakeUp(&rl); EventLoopProxy { wake_up: AtomicBool::new(false), source } } @@ -486,9 +478,9 @@ impl EventLoopProxyProvider for EventLoopProxy { self.wake_up.store(true, AtomicOrdering::Relaxed); unsafe { // Let the main thread know there's a new event. - CFRunLoopSourceSignal(self.source); - let rl = CFRunLoopGetMain(); - CFRunLoopWakeUp(rl); + CFRunLoopSourceSignal(&self.source); + let rl = CFRunLoopGetMain().unwrap(); + CFRunLoopWakeUp(&rl); } } } diff --git a/src/platform_impl/apple/appkit/ffi.rs b/src/platform_impl/apple/appkit/ffi.rs index 92d4874e4b..21241c6062 100644 --- a/src/platform_impl/apple/appkit/ffi.rs +++ b/src/platform_impl/apple/appkit/ffi.rs @@ -4,18 +4,10 @@ use std::ffi::c_void; -use core_foundation::array::CFArrayRef; -use core_foundation::dictionary::CFDictionaryRef; -use core_foundation::string::CFStringRef; -use core_foundation::uuid::CFUUIDRef; -use core_graphics::base::CGError; -use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef}; use objc2::ffi::NSInteger; use objc2::runtime::AnyObject; - -pub type CGDisplayFadeInterval = f32; -pub type CGDisplayReservationInterval = f32; -pub type CGDisplayBlendFraction = f32; +use objc2_core_foundation::{cf_type, CFString, CFUUID}; +use objc2_core_graphics::CGDirectDisplayID; pub const kCGDisplayBlendNormal: f32 = 0.0; pub const kCGDisplayBlendSolidColor: f32 = 1.0; @@ -23,22 +15,6 @@ pub const kCGDisplayBlendSolidColor: f32 = 1.0; pub type CGDisplayFadeReservationToken = u32; pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0; -pub type Boolean = u8; -pub const FALSE: Boolean = 0; -pub const TRUE: Boolean = 1; - -pub const kCGErrorSuccess: i32 = 0; -pub const kCGErrorFailure: i32 = 1000; -pub const kCGErrorIllegalArgument: i32 = 1001; -pub const kCGErrorInvalidConnection: i32 = 1002; -pub const kCGErrorInvalidContext: i32 = 1003; -pub const kCGErrorCannotComplete: i32 = 1004; -pub const kCGErrorNotImplemented: i32 = 1006; -pub const kCGErrorRangeCheck: i32 = 1007; -pub const kCGErrorTypeCheck: i32 = 1008; -pub const kCGErrorInvalidOperation: i32 = 1010; -pub const kCGErrorNoneAvailable: i32 = 1011; - pub const IO1BitIndexedPixels: &str = "P"; pub const IO2BitIndexedPixels: &str = "PP"; pub const IO4BitIndexedPixels: &str = "PPPP"; @@ -55,9 +31,6 @@ pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32"; pub const IOYUV422Pixels: &str = "Y4U2V2"; pub const IO8BitOverlayPixels: &str = "O8"; -pub type CGWindowLevel = i32; -pub type CGDisplayModeRef = *mut c_void; - // `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework. // However, that framework was only introduced "publicly" in macOS 10.13. // @@ -67,54 +40,11 @@ pub type CGDisplayModeRef = *mut c_void; // https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG #[link(name = "ApplicationServices", kind = "framework")] extern "C" { - pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID; } #[link(name = "CoreGraphics", kind = "framework")] extern "C" { - pub fn CGRestorePermanentDisplayConfiguration(); - pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError; - pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError; - pub fn CGConfigureDisplayFadeEffect( - config: CGDisplayConfigRef, - fadeOutSeconds: CGDisplayFadeInterval, - fadeInSeconds: CGDisplayFadeInterval, - fadeRed: f32, - fadeGreen: f32, - fadeBlue: f32, - ) -> CGError; - pub fn CGAcquireDisplayFadeReservation( - seconds: CGDisplayReservationInterval, - token: *mut CGDisplayFadeReservationToken, - ) -> CGError; - pub fn CGDisplayFade( - token: CGDisplayFadeReservationToken, - duration: CGDisplayFadeInterval, - startBlend: CGDisplayBlendFraction, - endBlend: CGDisplayBlendFraction, - redBlend: f32, - greenBlend: f32, - blueBlend: f32, - synchronous: Boolean, - ) -> CGError; - pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError; - pub fn CGShieldingWindowLevel() -> CGWindowLevel; - pub fn CGDisplaySetDisplayMode( - display: CGDirectDisplayID, - mode: CGDisplayModeRef, - options: CFDictionaryRef, - ) -> CGError; - pub fn CGDisplayCopyAllDisplayModes( - display: CGDirectDisplayID, - options: CFDictionaryRef, - ) -> CFArrayRef; - pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize; - pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize; - pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64; - pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef; - pub fn CGDisplayModeRetain(mode: CGDisplayModeRef); - pub fn CGDisplayModeRelease(mode: CGDisplayModeRef); - // Wildly used private APIs; Apple uses them for their Terminal.app. pub fn CGSMainConnectionID() -> *mut AnyObject; pub fn CGSSetWindowBackgroundBlurRadius( @@ -124,50 +54,13 @@ extern "C" { ) -> i32; } -mod core_video { - use super::*; - - #[link(name = "CoreVideo", kind = "framework")] - extern "C" {} - - // CVBase.h - - pub type CVTimeFlags = i32; // int32_t - pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0; - - #[repr(C)] - #[derive(Debug, Clone)] - pub struct CVTime { - pub time_value: i64, // int64_t - pub time_scale: i32, // int32_t - pub flags: i32, // int32_t - } - - // CVReturn.h - - pub type CVReturn = i32; // int32_t - pub const kCVReturnSuccess: CVReturn = 0; - - // CVDisplayLink.h - - pub type CVDisplayLinkRef = *mut c_void; - - extern "C" { - pub fn CVDisplayLinkCreateWithCGDisplay( - displayID: CGDirectDisplayID, - displayLinkOut: *mut CVDisplayLinkRef, - ) -> CVReturn; - pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod( - displayLink: CVDisplayLinkRef, - ) -> CVTime; - pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef); - } -} - -pub use core_video::*; #[repr(transparent)] pub struct TISInputSource(std::ffi::c_void); -pub type TISInputSourceRef = *mut TISInputSource; + +cf_type!( + #[encoding_name = "__TISInputSource"] + unsafe impl TISInputSource {} +); #[repr(transparent)] pub struct UCKeyboardLayout(std::ffi::c_void); @@ -184,15 +77,15 @@ pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1; #[link(name = "Carbon", kind = "framework")] extern "C" { - pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef; + pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString; #[allow(non_snake_case)] pub fn TISGetInputSourceProperty( - inputSource: TISInputSourceRef, - propertyKey: CFStringRef, + inputSource: &TISInputSource, + propertyKey: &CFString, ) -> *mut c_void; - pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef; + pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource; pub fn LMGetKbdType() -> u8; diff --git a/src/platform_impl/apple/appkit/menu.rs b/src/platform_impl/apple/appkit/menu.rs index cdebea0b9d..f99dc6d357 100644 --- a/src/platform_impl/apple/appkit/menu.rs +++ b/src/platform_impl/apple/appkit/menu.rs @@ -1,8 +1,8 @@ use objc2::rc::Retained; use objc2::runtime::Sel; -use objc2::sel; +use objc2::{sel, MainThreadMarker}; use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}; -use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString}; +use objc2_foundation::{ns_string, NSProcessInfo, NSString}; struct KeyEquivalent<'a> { key: &'a NSString, @@ -48,10 +48,7 @@ pub fn initialize(app: &NSApplication) { Some(sel!(hideOtherApplications:)), Some(KeyEquivalent { key: ns_string!("h"), - masks: Some( - NSEventModifierFlags::NSEventModifierFlagOption - | NSEventModifierFlags::NSEventModifierFlagCommand, - ), + masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command), }), ); diff --git a/src/platform_impl/apple/appkit/monitor.rs b/src/platform_impl/apple/appkit/monitor.rs index c654dc1aee..af4c2fc518 100644 --- a/src/platform_impl/apple/appkit/monitor.rs +++ b/src/platform_impl/apple/appkit/monitor.rs @@ -1,22 +1,33 @@ #![allow(clippy::unnecessary_cast)] use std::collections::VecDeque; -use std::fmt; use std::num::{NonZeroU16, NonZeroU32}; +use std::ptr::NonNull; +use std::{fmt, ptr}; -use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; -use core_foundation::base::{CFRelease, TCFType}; -use core_foundation::string::CFString; -use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUID}; -use core_graphics::display::{ - CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode, -}; +use dispatch2::run_on_main; use objc2::rc::Retained; -use objc2::runtime::AnyObject; +use objc2::MainThreadMarker; use objc2_app_kit::NSScreen; -use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect}; +use objc2_core_foundation::{ + CFArrayGetCount, CFArrayGetValueAtIndex, CFRetained, CFUUIDGetUUIDBytes, +}; +#[allow(deprecated)] +use objc2_core_graphics::{ + CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode, + CGDisplayMode, CGDisplayModeCopyPixelEncoding, CGDisplayModeGetPixelHeight, + CGDisplayModeGetPixelWidth, CGDisplayModeGetRefreshRate, CGDisplayModelNumber, + CGGetActiveDisplayList, CGMainDisplayID, +}; +#[allow(deprecated)] +use objc2_core_video::{ + kCVReturnSuccess, CVDisplayLinkCreateWithCGDisplay, + CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVTimeFlags, +}; +use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect}; use super::ffi; +use super::util::cgerr; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::monitor::VideoMode; @@ -50,28 +61,12 @@ impl std::fmt::Debug for VideoModeHandle { } } -pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef); +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NativeDisplayMode(pub CFRetained); unsafe impl Send for NativeDisplayMode {} unsafe impl Sync for NativeDisplayMode {} -impl Drop for NativeDisplayMode { - fn drop(&mut self) { - unsafe { - ffi::CGDisplayModeRelease(self.0); - } - } -} - -impl Clone for NativeDisplayMode { - fn clone(&self) -> Self { - unsafe { - ffi::CGDisplayModeRetain(self.0); - } - NativeDisplayMode(self.0) - } -} - impl VideoModeHandle { fn new( monitor: MonitorHandle, @@ -79,10 +74,9 @@ impl VideoModeHandle { refresh_rate_millihertz: Option, ) -> Self { unsafe { - let pixel_encoding = CFString::wrap_under_create_rule( - ffi::CGDisplayModeCopyPixelEncoding(native_mode.0), - ) - .to_string(); + #[allow(deprecated)] + let pixel_encoding = + CGDisplayModeCopyPixelEncoding(Some(&native_mode.0)).unwrap().to_string(); let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { 32 } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { @@ -95,8 +89,8 @@ impl VideoModeHandle { let mode = VideoMode { size: PhysicalSize::new( - ffi::CGDisplayModeGetPixelWidth(native_mode.0) as u32, - ffi::CGDisplayModeGetPixelHeight(native_mode.0) as u32, + CGDisplayModeGetPixelWidth(Some(&native_mode.0)) as u32, + CGDisplayModeGetPixelHeight(Some(&native_mode.0)) as u32, ), refresh_rate_millihertz, bit_depth: NonZeroU16::new(bit_depth), @@ -110,33 +104,12 @@ impl VideoModeHandle { #[derive(Clone)] pub struct MonitorHandle(CGDirectDisplayID); -type MonitorUuid = [u8; 16]; - impl MonitorHandle { /// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle. - fn uuid(&self) -> MonitorUuid { - let cf_uuid = unsafe { - CFUUID::wrap_under_create_rule(ffi::CGDisplayCreateUUIDFromDisplayID(self.0)) - }; - let uuid = unsafe { CFUUIDGetUUIDBytes(cf_uuid.as_concrete_TypeRef()) }; - MonitorUuid::from([ - uuid.byte0, - uuid.byte1, - uuid.byte2, - uuid.byte3, - uuid.byte4, - uuid.byte5, - uuid.byte6, - uuid.byte7, - uuid.byte8, - uuid.byte9, - uuid.byte10, - uuid.byte11, - uuid.byte12, - uuid.byte13, - uuid.byte14, - uuid.byte15, - ]) + fn uuid(&self) -> [u8; 16] { + let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) }; + let cf_uuid = unsafe { CFRetained::from_raw(NonNull::new(ptr).unwrap()) }; + unsafe { CFUUIDGetUUIDBytes(&cf_uuid) }.into() } } @@ -170,19 +143,32 @@ impl std::hash::Hash for MonitorHandle { } pub fn available_monitors() -> VecDeque { - if let Ok(displays) = CGDisplay::active_displays() { - let mut monitors = VecDeque::with_capacity(displays.len()); - for display in displays { - monitors.push_back(MonitorHandle(display)); - } - monitors - } else { - VecDeque::with_capacity(0) + let mut expected_count = 0; + let res = cgerr(unsafe { CGGetActiveDisplayList(0, ptr::null_mut(), &mut expected_count) }); + if res.is_err() { + return VecDeque::with_capacity(0); + } + + let mut displays: Vec = vec![0; expected_count as usize]; + let mut actual_count = 0; + let res = cgerr(unsafe { + CGGetActiveDisplayList(expected_count, displays.as_mut_ptr(), &mut actual_count) + }); + displays.truncate(actual_count as usize); + + if res.is_err() { + return VecDeque::with_capacity(0); + } + + let mut monitors = VecDeque::with_capacity(displays.len()); + for display in displays { + monitors.push_back(MonitorHandle(display)); } + monitors } pub fn primary_monitor() -> MonitorHandle { - MonitorHandle(CGDisplay::main().id) + MonitorHandle(unsafe { CGMainDisplayID() }) } impl fmt::Debug for MonitorHandle { @@ -204,8 +190,7 @@ impl MonitorHandle { // TODO: Be smarter about this: // pub fn name(&self) -> Option { - let MonitorHandle(display_id) = *self; - let screen_num = CGDisplay::new(display_id).model_number(); + let screen_num = unsafe { CGDisplayModelNumber(self.0) }; Some(format!("Monitor #{screen_num}")) } @@ -219,7 +204,7 @@ impl MonitorHandle { // This is already in screen coordinates. If we were using `NSScreen`, // then a conversion would've been needed: // flip_window_screen_coordinates(self.ns_screen(mtm)?.frame()) - let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; + let bounds = unsafe { CGDisplayBounds(self.0) }; let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y); Some(position.to_physical(self.scale_factor())) } @@ -235,12 +220,12 @@ impl MonitorHandle { fn refresh_rate_millihertz(&self) -> Option { let current_display_mode = - NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _); + NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap()); refresh_rate_millihertz(self.0, ¤t_display_mode) } pub fn current_video_mode(&self) -> Option { - let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _); + let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap()); let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode); Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode) } @@ -255,22 +240,20 @@ impl MonitorHandle { unsafe { let modes = { - let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null()); - assert!(!array.is_null(), "failed to get list of display modes"); - let array_count = CFArrayGetCount(array); + let array = CGDisplayCopyAllDisplayModes(self.0, None) + .expect("failed to get list of display modes"); + let array_count = CFArrayGetCount(&array); let modes: Vec<_> = (0..array_count) .map(move |i| { - let mode = CFArrayGetValueAtIndex(array, i) as *mut _; - ffi::CGDisplayModeRetain(mode); - mode + let mode = CFArrayGetValueAtIndex(&array, i) as *mut CGDisplayMode; + CFRetained::from_raw(NonNull::new(mode).unwrap()) }) .collect(); - CFRelease(array as *const _); modes }; modes.into_iter().map(move |mode| { - let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; + let cg_refresh_rate_hertz = CGDisplayModeGetRefreshRate(Some(&mode)).round() as i64; // CGDisplayModeGetRefreshRate returns 0.0 for any display that // isn't a CRT @@ -307,15 +290,14 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 { // 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: + // The value from @"NSScreenNumber" in deviceDescription is guaranteed + // to be an NSNumber. See documentation for details: // let obj = device_description - .get(key) - .expect("failed getting screen display id from device description"); - let obj: *const AnyObject = obj; - let obj: *const NSNumber = obj.cast(); - let obj: &NSNumber = unsafe { &*obj }; + .objectForKey(key) + .expect("failed getting screen display id from device description") + .downcast::() + .expect("NSScreenNumber must be NSNumber"); obj.as_u32() }) @@ -336,7 +318,7 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint { // It is intentional that we use `CGMainDisplayID` (as opposed to // `NSScreen::mainScreen`), because that's what the screen coordinates // are relative to, no matter which display the window is currently on. - let main_screen_height = CGDisplay::main().bounds().size.height; + let main_screen_height = unsafe { CGDisplayBounds(CGMainDisplayID()) }.size.height; let y = main_screen_height - frame.size.height - frame.origin.y; NSPoint::new(frame.origin.x, y) @@ -344,25 +326,29 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint { fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option { unsafe { - let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0); + let refresh_rate = CGDisplayModeGetRefreshRate(Some(&mode.0)); if refresh_rate > 0.0 { return NonZeroU32::new((refresh_rate * 1000.0).round() as u32); } let mut display_link = std::ptr::null_mut(); - if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess { + #[allow(deprecated)] + if CVDisplayLinkCreateWithCGDisplay(id, NonNull::from(&mut display_link)) + != kCVReturnSuccess + { return None; } - let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); - ffi::CVDisplayLinkRelease(display_link); + let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap()); + #[allow(deprecated)] + let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(&display_link); // This value is indefinite if an invalid display link was specified - if time.flags & ffi::kCVTimeIsIndefinite != 0 { + if time.flags & CVTimeFlags::IsIndefinite.0 != 0 { return None; } - (time.time_scale as i64) - .checked_div(time.time_value) + (time.timeScale as i64) + .checked_div(time.timeValue) .map(|v| (v * 1000) as u32) .and_then(NonZeroU32::new) } diff --git a/src/platform_impl/apple/appkit/observer.rs b/src/platform_impl/apple/appkit/observer.rs index 426b90bb96..f5e40c1c9b 100644 --- a/src/platform_impl/apple/appkit/observer.rs +++ b/src/platform_impl/apple/appkit/observer.rs @@ -9,22 +9,18 @@ use std::ptr; use std::rc::Weak; use std::time::Instant; -use block2::Block; -use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef}; -use core_foundation::date::CFAbsoluteTimeGetCurrent; -use core_foundation::runloop::{ - kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, - kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain, - CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate, - CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, - CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp, +use objc2::MainThreadMarker; +use objc2_core_foundation::{ + kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained, + CFRunLoop, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain, + CFRunLoopObserver, CFRunLoopObserverCallBack, CFRunLoopObserverContext, + CFRunLoopObserverCreate, CFRunLoopPerformBlock, CFRunLoopTimer, CFRunLoopTimerCreate, + CFRunLoopTimerInvalidate, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp, }; -use objc2_foundation::MainThreadMarker; use tracing::error; use super::app_state::AppState; use super::event_loop::{stop_app_on_panic, PanicInfo}; -use super::ffi; unsafe fn control_flow_handler(panic_info: *mut c_void, f: F) where @@ -48,8 +44,8 @@ where } // begin is queued with the highest priority to ensure it is processed before other observers -extern "C" fn control_flow_begin_handler( - _: CFRunLoopObserverRef, +extern "C-unwind" fn control_flow_begin_handler( + _: *mut CFRunLoopObserver, activity: CFRunLoopActivity, panic_info: *mut c_void, ) { @@ -57,7 +53,7 @@ extern "C" fn control_flow_begin_handler( control_flow_handler(panic_info, |panic_info| { #[allow(non_upper_case_globals)] match activity { - kCFRunLoopAfterWaiting => { + CFRunLoopActivity::AfterWaiting => { // trace!("Triggered `CFRunLoopAfterWaiting`"); AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info); // trace!("Completed `CFRunLoopAfterWaiting`"); @@ -70,8 +66,8 @@ extern "C" fn control_flow_begin_handler( // end is queued with the lowest priority to ensure it is processed after other observers // without that, LoopExiting would get sent after AboutToWait -extern "C" fn control_flow_end_handler( - _: CFRunLoopObserverRef, +extern "C-unwind" fn control_flow_end_handler( + _: *mut CFRunLoopObserver, activity: CFRunLoopActivity, panic_info: *mut c_void, ) { @@ -79,12 +75,12 @@ extern "C" fn control_flow_end_handler( control_flow_handler(panic_info, |panic_info| { #[allow(non_upper_case_globals)] match activity { - kCFRunLoopBeforeWaiting => { + CFRunLoopActivity::BeforeWaiting => { // trace!("Triggered `CFRunLoopBeforeWaiting`"); AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info); // trace!("Completed `CFRunLoopBeforeWaiting`"); }, - kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen + CFRunLoopActivity::Exit => (), /* unimplemented!(), // not expected to ever happen */ _ => unreachable!(), } }); @@ -92,44 +88,32 @@ extern "C" fn control_flow_end_handler( } #[derive(Debug)] -pub struct RunLoop(CFRunLoopRef); - -impl Default for RunLoop { - fn default() -> Self { - Self(ptr::null_mut()) - } -} +pub struct RunLoop(CFRetained); impl RunLoop { pub fn main(mtm: MainThreadMarker) -> Self { // SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so // scheduling (and scheduling a non-`Send` block) to that thread is allowed. let _ = mtm; - RunLoop(unsafe { CFRunLoopGetMain() }) + RunLoop(unsafe { CFRunLoopGetMain() }.unwrap()) } pub fn wakeup(&self) { - unsafe { CFRunLoopWakeUp(self.0) } + unsafe { CFRunLoopWakeUp(&self.0) } } unsafe fn add_observer( &self, - flags: CFOptionFlags, + flags: CFRunLoopActivity, + // The lower the value, the sooner this will run priority: CFIndex, handler: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, ) { - 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) }; + let observer = + unsafe { CFRunLoopObserverCreate(None, flags.0, true, priority, handler, context) } + .unwrap(); + unsafe { CFRunLoopAddObserver(&self.0, Some(&observer), kCFRunLoopCommonModes) }; } /// Submit a closure to run on the main thread as the next step in the run loop, before other @@ -166,10 +150,6 @@ impl RunLoop { /// put the event at the very front of the queue, to be handled as soon as possible after /// handling whatever event it's currently handling. pub fn queue_closure(&self, closure: impl FnOnce() + 'static) { - extern "C" { - fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block); - } - // Convert `FnOnce()` to `Block`. let closure = Cell::new(Some(closure)); let block = block2::RcBlock::new(move || { @@ -195,10 +175,10 @@ impl RunLoop { // and be delivered to the application afterwards. // // [#1779]: https://github.com/rust-windowing/winit/issues/1779 - let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef }; + let mode = unsafe { kCFRunLoopDefaultMode.unwrap() }; // SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`. - unsafe { CFRunLoopPerformBlock(self.0, mode, &block) } + unsafe { CFRunLoopPerformBlock(&self.0, Some(mode), Some(&block)) } } } @@ -213,15 +193,15 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak, /// An arbitrary instant in the past, that will trigger an immediate wake /// We save this as the `next_fire_date` for consistency so we can @@ -244,30 +224,28 @@ pub struct EventLoopWaker { impl Drop for EventLoopWaker { fn drop(&mut self) { - unsafe { - CFRunLoopTimerInvalidate(self.timer); - CFRelease(self.timer as _); - } + unsafe { CFRunLoopTimerInvalidate(&self.timer) }; } } impl EventLoopWaker { pub(crate) fn new() -> Self { - extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {} unsafe { // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // It is initially setup with a first fire time really far into the // future, but that gets changed to fire immediately in did_finish_launching let timer = CFRunLoopTimerCreate( - ptr::null_mut(), + None, f64::MAX, 0.000_000_1, 0, 0, - wakeup_main_loop, + Some(wakeup_main_loop), ptr::null_mut(), - ); - CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); + ) + .unwrap(); + CFRunLoopAddTimer(&CFRunLoopGetMain().unwrap(), Some(&timer), kCFRunLoopCommonModes); Self { timer, start_instant: Instant::now(), next_fire_date: None } } } @@ -275,14 +253,14 @@ impl EventLoopWaker { pub fn stop(&mut self) { if self.next_fire_date.is_some() { self.next_fire_date = None; - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } + unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) }; } } pub fn start(&mut self) { if self.next_fire_date != Some(self.start_instant) { self.next_fire_date = Some(self.start_instant); - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } + unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) }; } } @@ -300,7 +278,7 @@ impl EventLoopWaker { let duration = instant - now; let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; - CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs); } } }, diff --git a/src/platform_impl/apple/appkit/util.rs b/src/platform_impl/apple/appkit/util.rs index ae78b532bd..97ffca7f2d 100644 --- a/src/platform_impl/apple/appkit/util.rs +++ b/src/platform_impl/apple/appkit/util.rs @@ -1,5 +1,8 @@ +use objc2_core_graphics::CGError; use tracing::trace; +use crate::error::OsError; + macro_rules! trace_scope { ($s:literal) => { let _crate = @@ -26,3 +29,12 @@ impl Drop for TraceGuard { trace!(target = self.module_path, "Completed `{}`", self.called_from_fn); } } + +#[track_caller] +pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> { + if err == CGError::Success { + Ok(()) + } else { + Err(os_error!(format!("CGError {err:?}"))) + } +} diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index ddbf4dbe22..2707f85d84 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -6,15 +6,14 @@ use std::rc::Rc; use objc2::rc::Retained; use objc2::runtime::{AnyObject, Sel}; -use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; +use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker}; use objc2_app_kit::{ NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag, NSView, NSWindow, }; use objc2_foundation::{ - MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, - NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, - NSSize, NSString, NSUInteger, + NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, + NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; use super::app_state::AppState; @@ -138,28 +137,21 @@ pub struct ViewState { option_as_alt: Cell, } -declare_class!( +define_class!( + #[unsafe(super(NSView, NSResponder, NSObject))] + #[ivars = ViewState] + #[name = "WinitView"] pub(super) struct WinitView; - unsafe impl ClassType for WinitView { - #[inherits(NSResponder, NSObject)] - type Super = NSView; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitView"; - } - - impl DeclaredClass for WinitView { - type Ivars = ViewState; - } - - unsafe impl WinitView { - #[method(isFlipped)] + /// This documentation attribute makes rustfmt work for some reason? + impl WinitView { + #[unsafe(method(isFlipped))] fn is_flipped(&self) -> bool { // `winit` uses the upper-left corner as the origin. true } - #[method(viewDidMoveToWindow)] + #[unsafe(method(viewDidMoveToWindow))] fn view_did_move_to_window(&self) { trace_scope!("viewDidMoveToWindow"); if let Some(tracking_rect) = self.ivars().tracking_rect.take() { @@ -175,7 +167,7 @@ declare_class!( } // Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`. - #[method(viewFrameDidChangeNotification:)] + #[unsafe(method(viewFrameDidChangeNotification:))] fn frame_did_change(&self, _notification: Option<&AnyObject>) { trace_scope!("NSViewFrameDidChangeNotification"); if let Some(tracking_rect) = self.ivars().tracking_rect.take() { @@ -190,14 +182,16 @@ declare_class!( self.ivars().tracking_rect.set(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). + // 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()); self.queue_event(WindowEvent::SurfaceResized(size)); } - #[method(drawRect:)] + #[unsafe(method(drawRect:))] fn draw_rect(&self, _rect: NSRect) { trace_scope!("drawRect:"); @@ -206,22 +200,27 @@ declare_class!( // This is a direct subclass of NSView, no need to call superclass' drawRect: } - #[method(acceptsFirstResponder)] + #[unsafe(method(acceptsFirstResponder))] fn accepts_first_responder(&self) -> bool { trace_scope!("acceptsFirstResponder"); true } // This is necessary to prevent a beefy terminal error on MacBook Pros: - // IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem - // TODO: Add an API extension for using `NSTouchBar` - #[method_id(touchBar)] + // IMKInputSession [0x7fc573576ff0 + // presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self + // textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error + // Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this + // process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from + // this process.}, com.apple.inputmethod.EmojiFunctionRowItem TODO: Add an API + // extension for using `NSTouchBar` + #[unsafe(method_id(touchBar))] fn touch_bar(&self) -> Option> { trace_scope!("touchBar"); None } - #[method(resetCursorRects)] + #[unsafe(method(resetCursorRects))] fn reset_cursor_rects(&self) { trace_scope!("resetCursorRects"); let bounds = self.bounds(); @@ -236,13 +235,13 @@ declare_class!( } unsafe impl NSTextInputClient for WinitView { - #[method(hasMarkedText)] + #[unsafe(method(hasMarkedText))] fn has_marked_text(&self) -> bool { trace_scope!("hasMarkedText"); self.ivars().marked_text.borrow().length() > 0 } - #[method(markedRange)] + #[unsafe(method(markedRange))] fn marked_range(&self) -> NSRange { trace_scope!("markedRange"); let length = self.ivars().marked_text.borrow().length(); @@ -254,14 +253,14 @@ declare_class!( } } - #[method(selectedRange)] + #[unsafe(method(selectedRange))] fn selected_range(&self) -> NSRange { trace_scope!("selectedRange"); // Documented to return `{NSNotFound, 0}` if there is no selection. NSRange::new(NSNotFound as NSUInteger, 0) } - #[method(setMarkedText:selectedRange:replacementRange:)] + #[unsafe(method(setMarkedText:selectedRange:replacementRange:))] fn set_marked_text( &self, string: &NSObject, @@ -271,23 +270,15 @@ declare_class!( // TODO: Use _replacement_range, requires changing the event to report surrounding text. trace_scope!("setMarkedText:selectedRange:replacementRange:"); - // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. - let (marked_text, 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(), - ) + let (marked_text, string) = if let Some(string) = + string.downcast_ref::() + { + (NSMutableAttributedString::from_attributed_nsstring(string), string.string()) + } else if let Some(string) = string.downcast_ref::() { + (NSMutableAttributedString::from_nsstring(string), string.copy()) } else { - let string: *const NSObject = string; - let string: *const NSString = string.cast(); - let string = unsafe { &*string }; - ( - NSMutableAttributedString::from_nsstring(string), - string.copy(), - ) + // This method is guaranteed to get either a `NSString` or a `NSAttributedString`. + panic!("unexpected text {string:?}") }; // Update marked text. @@ -323,7 +314,7 @@ declare_class!( self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range))); } - #[method(unmarkText)] + #[unsafe(method(unmarkText))] fn unmark_text(&self) { trace_scope!("unmarkText"); *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); @@ -340,13 +331,13 @@ declare_class!( } } - #[method_id(validAttributesForMarkedText)] + #[unsafe(method_id(validAttributesForMarkedText))] fn valid_attributes_for_marked_text(&self) -> Retained> { trace_scope!("validAttributesForMarkedText"); NSArray::new() } - #[method_id(attributedSubstringForProposedRange:actualRange:)] + #[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))] fn attributed_substring_for_proposed_range( &self, _range: NSRange, @@ -356,42 +347,36 @@ declare_class!( None } - #[method(characterIndexForPoint:)] + #[unsafe(method(characterIndexForPoint:))] fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { trace_scope!("characterIndexForPoint:"); 0 } - #[method(firstRectForCharacterRange:actualRange:)] + #[unsafe(method(firstRectForCharacterRange:actualRange:))] fn first_rect_for_character_range( &self, _range: NSRange, _actual_range: *mut NSRange, ) -> NSRect { trace_scope!("firstRectForCharacterRange:actualRange:"); - let rect = NSRect::new( - self.ivars().ime_position.get(), - self.ivars().ime_size.get() - ); + let rect = NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get()); // Return value is expected to be in screen coordinates, so we need a conversion here - self.window() - .convertRectToScreen(self.convertRect_toView(rect, None)) + self.window().convertRectToScreen(self.convertRect_toView(rect, None)) } - #[method(insertText:replacementRange:)] + #[unsafe(method(insertText:replacementRange:))] fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) { // TODO: Use _replacement_range, requires changing the event to report surrounding text. trace_scope!("insertText:replacementRange:"); - // 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() + let string = if let Some(string) = string.downcast_ref::() { + string.string().to_string() + } else if let Some(string) = string.downcast_ref::() { + string.to_string() } else { - let string: *const NSObject = string; - let string: *const NSString = string.cast(); - unsafe { &*string }.to_string() + // This method is guaranteed to get either a `NSString` or a `NSAttributedString`. + panic!("unexpected text {string:?}") }; let is_control = string.chars().next().is_some_and(|c| c.is_control()); @@ -404,15 +389,16 @@ declare_class!( } } - // 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. - #[method(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(method(doCommandBySelector:))] fn do_command_by_selector(&self, command: Sel) { trace_scope!("doCommandBySelector:"); - // We shouldn't forward any character from just committed 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. + // We shouldn't forward any character from just committed 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.ivars().ime_state.get() == ImeState::Committed { return; } @@ -429,7 +415,11 @@ declare_class!( let window_id = window_id(&self.window()); self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { if let Some(handler) = app.macos_handler() { - handler.standard_key_binding(event_loop, window_id, command.name()); + handler.standard_key_binding( + event_loop, + window_id, + command.name().to_str().unwrap(), + ); } }); @@ -439,8 +429,9 @@ declare_class!( } } - unsafe impl WinitView { - #[method(keyDown:)] + /// This documentation attribute makes rustfmt work for some reason? + impl WinitView { + #[unsafe(method(keyDown:))] fn key_down(&self, event: &NSEvent) { trace_scope!("keyDown:"); { @@ -483,7 +474,7 @@ declare_class!( // Allow normal input after the commit. self.ivars().ime_state.set(ImeState::Ground); true - } + }, ImeState::Preedit => true, // `key_down` could result in preedit clear, so compare old and current state. _ => old_ime_state != self.ivars().ime_state.get(), @@ -499,7 +490,7 @@ declare_class!( } } - #[method(keyUp:)] + #[unsafe(method(keyUp:))] fn key_up(&self, event: &NSEvent) { trace_scope!("keyUp:"); @@ -507,10 +498,7 @@ declare_class!( self.update_modifiers(&event, false); // We want to send keyboard input when we are currently in the ground state. - if matches!( - self.ivars().ime_state.get(), - ImeState::Ground | ImeState::Disabled - ) { + if matches!(self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled) { self.queue_event(WindowEvent::KeyboardInput { device_id: None, event: create_key_event(&event, false, false), @@ -519,14 +507,14 @@ declare_class!( } } - #[method(flagsChanged:)] + #[unsafe(method(flagsChanged:))] fn flags_changed(&self, event: &NSEvent) { trace_scope!("flagsChanged:"); self.update_modifiers(event, true); } - #[method(insertTab:)] + #[unsafe(method(insertTab:))] fn insert_tab(&self, _sender: Option<&AnyObject>) { trace_scope!("insertTab:"); let window = self.window(); @@ -537,7 +525,7 @@ declare_class!( } } - #[method(insertBackTab:)] + #[unsafe(method(insertBackTab:))] fn insert_back_tab(&self, _sender: Option<&AnyObject>) { trace_scope!("insertBackTab:"); let window = self.window(); @@ -550,7 +538,7 @@ declare_class!( // Allows us to receive Cmd-. (the shortcut for closing a dialog) // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 - #[method(cancelOperation:)] + #[unsafe(method(cancelOperation:))] fn cancel_operation(&self, _sender: Option<&AnyObject>) { let mtm = MainThreadMarker::from(self); trace_scope!("cancelOperation:"); @@ -580,42 +568,42 @@ declare_class!( // // See https://github.com/rust-windowing/winit/pull/1490 for history. - #[method(mouseDown:)] + #[unsafe(method(mouseDown:))] fn mouse_down(&self, event: &NSEvent) { trace_scope!("mouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } - #[method(mouseUp:)] + #[unsafe(method(mouseUp:))] fn mouse_up(&self, event: &NSEvent) { trace_scope!("mouseUp:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Released); } - #[method(rightMouseDown:)] + #[unsafe(method(rightMouseDown:))] fn right_mouse_down(&self, event: &NSEvent) { trace_scope!("rightMouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } - #[method(rightMouseUp:)] + #[unsafe(method(rightMouseUp:))] fn right_mouse_up(&self, event: &NSEvent) { trace_scope!("rightMouseUp:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Released); } - #[method(otherMouseDown:)] + #[unsafe(method(otherMouseDown:))] fn other_mouse_down(&self, event: &NSEvent) { trace_scope!("otherMouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } - #[method(otherMouseUp:)] + #[unsafe(method(otherMouseUp:))] fn other_mouse_up(&self, event: &NSEvent) { trace_scope!("otherMouseUp:"); self.mouse_motion(event); @@ -624,27 +612,27 @@ declare_class!( // No tracing on these because that would be overly verbose - #[method(mouseMoved:)] + #[unsafe(method(mouseMoved:))] fn mouse_moved(&self, event: &NSEvent) { self.mouse_motion(event); } - #[method(mouseDragged:)] + #[unsafe(method(mouseDragged:))] fn mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } - #[method(rightMouseDragged:)] + #[unsafe(method(rightMouseDragged:))] fn right_mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } - #[method(otherMouseDragged:)] + #[unsafe(method(otherMouseDragged:))] fn other_mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } - #[method(mouseEntered:)] + #[unsafe(method(mouseEntered:))] fn mouse_entered(&self, event: &NSEvent) { trace_scope!("mouseEntered:"); @@ -658,7 +646,7 @@ declare_class!( }); } - #[method(mouseExited:)] + #[unsafe(method(mouseExited:))] fn mouse_exited(&self, event: &NSEvent) { trace_scope!("mouseExited:"); @@ -672,7 +660,7 @@ declare_class!( }); } - #[method(scrollWheel:)] + #[unsafe(method(scrollWheel:))] fn scroll_wheel(&self, event: &NSEvent) { trace_scope!("scrollWheel:"); @@ -689,9 +677,9 @@ declare_class!( }; // 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. + // 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. #[allow(non_upper_case_globals)] let phase = match unsafe { event.momentumPhase() } { NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, @@ -705,17 +693,13 @@ declare_class!( self.update_modifiers(event, false); - self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| + self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta }) - ); - self.queue_event(WindowEvent::MouseWheel { - device_id: None, - delta, - phase, }); + self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase }); } - #[method(magnifyWithEvent:)] + #[unsafe(method(magnifyWithEvent:))] fn magnify_with_event(&self, event: &NSEvent) { trace_scope!("magnifyWithEvent:"); @@ -737,18 +721,16 @@ declare_class!( }); } - #[method(smartMagnifyWithEvent:)] + #[unsafe(method(smartMagnifyWithEvent:))] fn smart_magnify_with_event(&self, event: &NSEvent) { trace_scope!("smartMagnifyWithEvent:"); self.mouse_motion(event); - self.queue_event(WindowEvent::DoubleTapGesture { - device_id: None, - }); + self.queue_event(WindowEvent::DoubleTapGesture { device_id: None }); } - #[method(rotateWithEvent:)] + #[unsafe(method(rotateWithEvent:))] fn rotate_with_event(&self, event: &NSEvent) { trace_scope!("rotateWithEvent:"); @@ -770,7 +752,7 @@ declare_class!( }); } - #[method(pressureChangeWithEvent:)] + #[unsafe(method(pressureChangeWithEvent:))] fn pressure_change_with_event(&self, event: &NSEvent) { trace_scope!("pressureChangeWithEvent:"); @@ -784,13 +766,13 @@ declare_class!( // 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 - #[method(_wantsKeyDownForEvent:)] + #[unsafe(method(_wantsKeyDownForEvent:))] fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool { trace_scope!("_wantsKeyDownForEvent:"); true } - #[method(acceptsFirstMouse:)] + #[unsafe(method(acceptsFirstMouse:))] fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { trace_scope!("acceptsFirstMouse:"); self.ivars().accepts_first_mouse @@ -821,7 +803,7 @@ impl WinitView { accepts_first_mouse, option_as_alt: Cell::new(option_as_alt), }); - let this: Retained = unsafe { msg_send_id![super(this), init] }; + let this: Retained = unsafe { msg_send![super(this), init] }; *this.ivars().input_source.borrow_mut() = this.current_input_source(); diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs index 03c35052d3..5317c5eeda 100644 --- a/src/platform_impl/apple/appkit/window.rs +++ b/src/platform_impl/apple/appkit/window.rs @@ -1,10 +1,11 @@ #![allow(clippy::unnecessary_cast)] +use dispatch2::MainThreadBound; use dpi::{Position, Size}; use objc2::rc::{autoreleasepool, Retained}; -use objc2::{declare_class, mutability, ClassType, DeclaredClass}; +use objc2::{define_class, MainThreadMarker, Message}; use objc2_app_kit::{NSPanel, NSResponder, NSWindow}; -use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject}; +use objc2_foundation::NSObject; use super::event_loop::ActiveEventLoop; use super::window_delegate::WindowDelegate; @@ -332,27 +333,21 @@ impl CoreWindow for Window { } } -declare_class!( +define_class!( + #[unsafe(super(NSWindow, NSResponder, NSObject))] + #[name = "WinitWindow"] #[derive(Debug)] pub struct WinitWindow; - unsafe impl ClassType for WinitWindow { - #[inherits(NSResponder, NSObject)] - type Super = NSWindow; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitWindow"; - } - - impl DeclaredClass for WinitWindow {} - - unsafe impl WinitWindow { - #[method(canBecomeMainWindow)] + /// This documentation attribute makes rustfmt work for some reason? + impl WinitWindow { + #[unsafe(method(canBecomeMainWindow))] fn can_become_main_window(&self) -> bool { trace_scope!("canBecomeMainWindow"); true } - #[method(canBecomeKeyWindow)] + #[unsafe(method(canBecomeKeyWindow))] fn can_become_key_window(&self) -> bool { trace_scope!("canBecomeKeyWindow"); true @@ -360,23 +355,17 @@ declare_class!( } ); -declare_class!( +define_class!( + #[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))] + #[name = "WinitPanel"] #[derive(Debug)] pub struct WinitPanel; - unsafe impl ClassType for WinitPanel { - #[inherits(NSWindow, NSResponder, NSObject)] - type Super = NSPanel; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitPanel"; - } - - impl DeclaredClass for WinitPanel {} - - unsafe impl WinitPanel { + /// This documentation attribute makes rustfmt work for some reason? + impl WinitPanel { // although NSPanel can become key window // it doesn't if window doesn't have NSWindowStyleMask::Titled - #[method(canBecomeKeyWindow)] + #[unsafe(method(canBecomeKeyWindow))] fn can_become_key_window(&self) -> bool { trace_scope!("canBecomeKeyWindow"); true diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 926ff03018..7ef8de4127 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -6,10 +6,12 @@ use std::ptr; use std::rc::Rc; use std::sync::{Arc, Mutex}; -use core_graphics::display::CGDisplay; use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::{AnyObject, ProtocolObject}; -use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; +use objc2::{ + available, define_class, msg_send, sel, ClassType, DefinedClass, MainThreadMarker, + MainThreadOnly, Message, +}; use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, @@ -19,11 +21,17 @@ use objc2_app_kit::{ NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle, }; +use objc2_core_foundation::{CGFloat, CGPoint}; +use objc2_core_graphics::{ + CGAcquireDisplayFadeReservation, CGAssociateMouseAndMouseCursorPosition, CGDisplayCapture, + CGDisplayFade, CGDisplayRelease, CGDisplaySetDisplayMode, CGReleaseDisplayFadeReservation, + CGRestorePermanentDisplayConfiguration, CGShieldingWindowLevel, CGWarpMouseCursorPosition, +}; use objc2_foundation::{ - ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets, - NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, - NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming, - NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString, + ns_string, NSArray, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey, + NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSNotificationCenter, NSObject, + NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, + NSRect, NSSize, NSString, }; use tracing::{trace, warn}; @@ -31,6 +39,7 @@ use super::app_state::AppState; use super::cursor::cursor_from_icon; use super::monitor::{self, flip_window_screen_coordinates, get_display_id}; use super::observer::RunLoop; +use super::util::cgerr; use super::view::WinitView; use super::window::{window_id, WinitPanel, WinitWindow}; use super::{ffi, Fullscreen, MonitorHandle}; @@ -133,30 +142,24 @@ pub(crate) struct State { is_borderless_game: Cell, } -declare_class!( +define_class!( + #[unsafe(super(NSObject))] + #[thread_kind = MainThreadOnly] + #[name = "WinitWindowDelegate"] + #[ivars = State] pub(crate) struct WindowDelegate; - unsafe impl ClassType for WindowDelegate { - type Super = NSObject; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitWindowDelegate"; - } - - impl DeclaredClass for WindowDelegate { - type Ivars = State; - } - unsafe impl NSObjectProtocol for WindowDelegate {} unsafe impl NSWindowDelegate for WindowDelegate { - #[method(windowShouldClose:)] + #[unsafe(method(windowShouldClose:))] fn window_should_close(&self, _: Option<&AnyObject>) -> bool { trace_scope!("windowShouldClose:"); self.queue_event(WindowEvent::CloseRequested); false } - #[method(windowWillClose:)] + #[unsafe(method(windowWillClose:))] fn window_will_close(&self, _: Option<&AnyObject>) { trace_scope!("windowWillClose:"); // `setDelegate:` retains the previous value and then autoreleases it @@ -168,14 +171,14 @@ declare_class!( self.queue_event(WindowEvent::Destroyed); } - #[method(windowDidResize:)] + #[unsafe(method(windowDidResize:))] fn window_did_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidResize:"); // NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification. self.emit_move_event(); } - #[method(windowWillStartLiveResize:)] + #[unsafe(method(windowWillStartLiveResize:))] fn window_will_start_live_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowWillStartLiveResize:"); @@ -183,20 +186,20 @@ declare_class!( self.set_resize_increments_inner(increments); } - #[method(windowDidEndLiveResize:)] + #[unsafe(method(windowDidEndLiveResize:))] fn window_did_end_live_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidEndLiveResize:"); self.set_resize_increments_inner(NSSize::new(1., 1.)); } // This won't be triggered if the move was part of a resize. - #[method(windowDidMove:)] + #[unsafe(method(windowDidMove:))] fn window_did_move(&self, _: Option<&AnyObject>) { trace_scope!("windowDidMove:"); self.emit_move_event(); } - #[method(windowDidChangeBackingProperties:)] + #[unsafe(method(windowDidChangeBackingProperties:))] fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeBackingProperties:"); let scale_factor = self.scale_factor(); @@ -212,7 +215,7 @@ declare_class!( }); } - #[method(windowDidBecomeKey:)] + #[unsafe(method(windowDidBecomeKey:))] fn window_did_become_key(&self, _: Option<&AnyObject>) { trace_scope!("windowDidBecomeKey:"); // TODO: center the cursor if the window had mouse grab when it @@ -220,7 +223,7 @@ declare_class!( self.queue_event(WindowEvent::Focused(true)); } - #[method(windowDidResignKey:)] + #[unsafe(method(windowDidResignKey:))] fn window_did_resign_key(&self, _: Option<&AnyObject>) { trace_scope!("windowDidResignKey:"); // It happens rather often, e.g. when the user is Cmd+Tabbing, that the @@ -236,7 +239,7 @@ declare_class!( } /// Invoked when before enter fullscreen - #[method(windowWillEnterFullScreen:)] + #[unsafe(method(windowWillEnterFullScreen:))] fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowWillEnterFullScreen:"); @@ -246,7 +249,7 @@ declare_class!( // 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(_, _)) => (), + 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 @@ -262,14 +265,14 @@ declare_class!( } /// Invoked when before exit fullscreen - #[method(windowWillExitFullScreen:)] + #[unsafe(method(windowWillExitFullScreen:))] fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowWillExitFullScreen:"); self.ivars().in_fullscreen_transition.set(true); } - #[method(window:willUseFullScreenPresentationOptions:)] + #[unsafe(method(window:willUseFullScreenPresentationOptions:))] fn window_will_use_fullscreen_presentation_options( &self, _: Option<&AnyObject>, @@ -286,17 +289,17 @@ declare_class!( // user-provided options are ignored in exclusive fullscreen. let mut options = proposed_options; let fullscreen = self.ivars().fullscreen.borrow(); - if let Some(Fullscreen::Exclusive(_, _)) = &*fullscreen { - options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen - | NSApplicationPresentationOptions::NSApplicationPresentationHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; + if let Some(Fullscreen::Exclusive(..)) = &*fullscreen { + options = NSApplicationPresentationOptions::FullScreen + | NSApplicationPresentationOptions::HideDock + | NSApplicationPresentationOptions::HideMenuBar; } options } /// Invoked when entered fullscreen - #[method(windowDidEnterFullScreen:)] + #[unsafe(method(windowDidEnterFullScreen:))] fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidEnterFullScreen:"); self.ivars().initial_fullscreen.set(false); @@ -307,7 +310,7 @@ declare_class!( } /// Invoked when exited fullscreen - #[method(windowDidExitFullScreen:)] + #[unsafe(method(windowDidExitFullScreen:))] fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidExitFullScreen:"); @@ -334,7 +337,7 @@ declare_class!( /// due to being in the midst of handling some other animation or user gesture. /// 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. - #[method(windowDidFailToEnterFullScreen:)] + #[unsafe(method(windowDidFailToEnterFullScreen:))] fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidFailToEnterFullScreen:"); self.ivars().in_fullscreen_transition.set(false); @@ -353,14 +356,14 @@ declare_class!( } // Invoked when the occlusion state of the window changes - #[method(windowDidChangeOcclusionState:)] + #[unsafe(method(windowDidChangeOcclusionState:))] fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeOcclusionState:"); let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible); self.queue_event(WindowEvent::Occluded(!visible)); } - #[method(windowDidChangeScreen:)] + #[unsafe(method(windowDidChangeScreen:))] fn window_did_change_screen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeScreen:"); let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get(); @@ -374,44 +377,49 @@ declare_class!( unsafe impl NSDraggingDestination for WindowDelegate { /// Invoked when the dragged image enters destination bounds or frame - #[method(draggingEntered:)] + #[unsafe(method(draggingEntered:))] fn dragging_entered(&self, sender: &ProtocolObject) -> bool { trace_scope!("draggingEntered:"); use std::path::PathBuf; let pb = unsafe { sender.draggingPasteboard() }; - let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); - let filenames: Retained> = unsafe { Retained::cast(filenames) }; + let filenames = pb + .propertyListForType(unsafe { NSFilenamesPboardType }) + .unwrap() + .downcast::() + .unwrap(); let paths = filenames .into_iter() - .map(|file| PathBuf::from(file.to_string())) + .map(|file| PathBuf::from(file.downcast::().unwrap().to_string())) .collect(); let dl = unsafe { sender.draggingLocation() }; let dl = self.view().convertPoint_fromView(dl, None); - let position = LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); - + let position = + LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); self.queue_event(WindowEvent::DragEntered { paths, position }); true } - #[method(wantsPeriodicDraggingUpdates)] + #[unsafe(method(wantsPeriodicDraggingUpdates))] fn wants_periodic_dragging_updates(&self) -> bool { trace_scope!("wantsPeriodicDraggingUpdates:"); true } - /// Invoked periodically as the image is held within the destination area, allowing modification of the dragging operation or mouse-pointer position. - #[method(draggingUpdated:)] + /// Invoked periodically as the image is held within the destination area, allowing + /// modification of the dragging operation or mouse-pointer position. + #[unsafe(method(draggingUpdated:))] fn dragging_updated(&self, sender: &ProtocolObject) -> bool { trace_scope!("draggingUpdated:"); let dl = unsafe { sender.draggingLocation() }; let dl = self.view().convertPoint_fromView(dl, None); - let position = LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); + let position = + LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); self.queue_event(WindowEvent::DragMoved { position }); @@ -419,30 +427,34 @@ declare_class!( } /// Invoked when the image is released - #[method(prepareForDragOperation:)] + #[unsafe(method(prepareForDragOperation:))] fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool { trace_scope!("prepareForDragOperation:"); true } /// Invoked after the released image has been removed from the screen - #[method(performDragOperation:)] + #[unsafe(method(performDragOperation:))] fn perform_drag_operation(&self, sender: &ProtocolObject) -> bool { trace_scope!("performDragOperation:"); use std::path::PathBuf; let pb = unsafe { sender.draggingPasteboard() }; - let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); - let filenames: Retained> = unsafe { Retained::cast(filenames) }; + let filenames = pb + .propertyListForType(unsafe { NSFilenamesPboardType }) + .unwrap() + .downcast::() + .unwrap(); let paths = filenames .into_iter() - .map(|file| PathBuf::from(file.to_string())) + .map(|file| PathBuf::from(file.downcast::().unwrap().to_string())) .collect(); let dl = unsafe { sender.draggingLocation() }; let dl = self.view().convertPoint_fromView(dl, None); - let position = LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); + let position = + LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()); self.queue_event(WindowEvent::DragDropped { paths, position }); @@ -450,29 +462,29 @@ declare_class!( } /// Invoked when the dragging operation is complete - #[method(concludeDragOperation:)] + #[unsafe(method(concludeDragOperation:))] fn conclude_drag_operation(&self, _sender: Option<&NSObject>) { trace_scope!("concludeDragOperation:"); } /// Invoked when the dragging operation is cancelled - #[method(draggingExited:)] - fn dragging_exited(&self, info: Option<&ProtocolObject>) { + #[unsafe(method(draggingExited:))] + fn dragging_exited(&self, sender: Option<&ProtocolObject>) { trace_scope!("draggingExited:"); - let position = info.map(|info| { - let dl = unsafe { info.draggingLocation() }; + let position = sender.map(|sender| { + let dl = unsafe { sender.draggingLocation() }; let dl = self.view().convertPoint_fromView(dl, None); LogicalPosition::::from((dl.x, dl.y)).to_physical(self.scale_factor()) }); - self.queue_event(WindowEvent::DragLeft { position } ); + self.queue_event(WindowEvent::DragLeft { position }); } } - // Key-Value Observing - unsafe impl WindowDelegate { - #[method(observeValueForKeyPath:ofObject:change:context:)] + /// Key-Value Observing + impl WindowDelegate { + #[unsafe(method(observeValueForKeyPath:ofObject:change:context:))] fn observe_value( &self, key_path: Option<&NSString>, @@ -488,19 +500,15 @@ declare_class!( "requested a change dictionary in `addObserver`, but none was provided", ); let old = change - .get(unsafe { NSKeyValueChangeOldKey }) + .objectForKey(unsafe { NSKeyValueChangeOldKey }) .expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`"); let new = change - .get(unsafe { NSKeyValueChangeNewKey }) + .objectForKey(unsafe { NSKeyValueChangeNewKey }) .expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`"); - // SAFETY: The value of `effectiveAppearance` is `NSAppearance` - let old: *const AnyObject = old; - let old: *const NSAppearance = old.cast(); - let old: &NSAppearance = unsafe { &*old }; - let new: *const AnyObject = new; - let new: *const NSAppearance = new.cast(); - let new: &NSAppearance = unsafe { &*new }; + // The value of `effectiveAppearance` is `NSAppearance` + let old = old.downcast::().unwrap(); + let new = new.downcast::().unwrap(); trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed"); @@ -511,8 +519,8 @@ declare_class!( return; } - let old = appearance_to_theme(old); - let new = appearance_to_theme(new); + let old = appearance_to_theme(&old); + let new = appearance_to_theme(&new); // Check that the theme changed in Winit's terms (the theme might have changed on // other parameters, such as level of contrast, but the event should not be emitted // in those cases). @@ -627,11 +635,11 @@ fn new_window( masks |= NSWindowStyleMask::NonactivatingPanel; let window: Option> = unsafe { - msg_send_id![ + msg_send![ super(mtm.alloc().set_ivars(())), initWithContentRect: frame, styleMask: masks, - backing: NSBackingStoreType::NSBackingStoreBuffered, + backing: NSBackingStoreType::Buffered, defer: false, ] }; @@ -639,11 +647,11 @@ fn new_window( window?.as_super().as_super().retain() } else { let window: Option> = unsafe { - msg_send_id![ + msg_send![ super(mtm.alloc().set_ivars(())), initWithContentRect: frame, styleMask: masks, - backing: NSBackingStoreType::NSBackingStoreBuffered, + backing: NSBackingStoreType::Buffered, defer: false, ] }; @@ -665,22 +673,22 @@ fn new_window( } if attrs.content_protected { - window.setSharingType(NSWindowSharingType::NSWindowSharingNone); + window.setSharingType(NSWindowSharingType::None); } if attrs.platform_specific.titlebar_transparent { window.setTitlebarAppearsTransparent(true); } if attrs.platform_specific.title_hidden { - window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden); + window.setTitleVisibility(NSWindowTitleVisibility::Hidden); } if attrs.platform_specific.titlebar_buttons_hidden { for titlebar_button in &[ #[allow(deprecated)] NSWindowFullScreenButton, - NSWindowButton::NSWindowMiniaturizeButton, - NSWindowButton::NSWindowCloseButton, - NSWindowButton::NSWindowZoomButton, + NSWindowButton::MiniaturizeButton, + NSWindowButton::CloseButton, + NSWindowButton::ZoomButton, ] { if let Some(button) = window.standardWindowButton(*titlebar_button) { button.setHidden(true); @@ -700,7 +708,7 @@ fn new_window( } if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) { - if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) { + if let Some(button) = window.standardWindowButton(NSWindowButton::ZoomButton) { button.setEnabled(false); } } @@ -762,10 +770,7 @@ fn new_window( } // register for drag and drop operations. - window - .registerForDraggedTypes(&NSArray::from_id_slice(&[ - unsafe { NSFilenamesPboardType }.copy() - ])); + window.registerForDraggedTypes(&NSArray::from_slice(&[unsafe { NSFilenamesPboardType }])); Some(window) }) @@ -793,9 +798,7 @@ impl WindowDelegate { // SAFETY: We know that there are no parent -> child -> parent cycles since the only // place in `winit` where we allow making a window a child window is // right here, just after it's been created. - unsafe { - parent.addChildWindow_ordered(&window, NSWindowOrderingMode::NSWindowAbove) - }; + unsafe { parent.addChildWindow_ordered(&window, NSWindowOrderingMode::Above) }; }, Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"), None => (), @@ -836,7 +839,7 @@ impl WindowDelegate { saved_style: Cell::new(None), is_borderless_game: Cell::new(attrs.platform_specific.borderless_game), }); - let delegate: Retained = unsafe { msg_send_id![super(delegate), init] }; + let delegate: Retained = unsafe { msg_send![super(delegate), init] }; window.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); @@ -847,8 +850,7 @@ impl WindowDelegate { window.addObserver_forKeyPath_options_context( &delegate, ns_string!("effectiveAppearance"), - NSKeyValueObservingOptions::NSKeyValueObservingOptionNew - | NSKeyValueObservingOptions::NSKeyValueObservingOptionOld, + NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Old, ptr::null_mut(), ) }; @@ -892,8 +894,8 @@ impl WindowDelegate { #[track_caller] pub(super) fn view(&self) -> Retained { - // SAFETY: The view inside WinitWindow is always `WinitView` - unsafe { Retained::cast(self.window().contentView().unwrap()) } + // The view inside WinitWindow should always be set and be `WinitView`. + self.window().contentView().unwrap().downcast().unwrap() } #[track_caller] @@ -1213,8 +1215,7 @@ impl WindowDelegate { // We edit the button directly instead of using `NSResizableWindowMask`, // since that mask also affect the resizability of the window (which is // controllable by other means in `winit`). - if let Some(button) = self.window().standardWindowButton(NSWindowButton::NSWindowZoomButton) - { + if let Some(button) = self.window().standardWindowButton(NSWindowButton::ZoomButton) { button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE)); } } @@ -1227,7 +1228,7 @@ impl WindowDelegate { } if self .window() - .standardWindowButton(NSWindowButton::NSWindowZoomButton) + .standardWindowButton(NSWindowButton::ZoomButton) .map(|b| b.isEnabled()) .unwrap_or(true) { @@ -1266,8 +1267,9 @@ impl WindowDelegate { }; // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 - CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor) - .map_err(|status| os_error!(format!("CGError {status}")).into()) + cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(associate_mouse_cursor) })?; + + Ok(()) } #[inline] @@ -1289,14 +1291,12 @@ impl WindowDelegate { let content_rect = self.window().contentRectForFrameRect(self.window().frame()); let window_position = flip_window_screen_coordinates(content_rect); let cursor_position = cursor_position.to_logical::(self.scale_factor()); - let point = core_graphics::display::CGPoint { + let point = CGPoint { x: window_position.x + cursor_position.x, y: window_position.y + cursor_position.y, }; - CGDisplay::warp_mouse_cursor_position(point) - .map_err(|status| os_error!(format!("CGError {status}")))?; - CGDisplay::associate_mouse_and_mouse_cursor_position(true) - .map_err(|status| os_error!(format!("CGError {status}")))?; + cgerr(unsafe { CGWarpMouseCursorPosition(point) })?; + cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(true) })?; Ok(()) } @@ -1498,10 +1498,8 @@ impl WindowDelegate { unsafe { // Fade to black (and wait for the fade to complete) to hide the // flicker from capturing the display and switching display mode - if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token) - == ffi::kCGErrorSuccess - { - ffi::CGDisplayFade( + if cgerr(CGAcquireDisplayFadeReservation(5.0, &mut fade_token)).is_ok() { + CGDisplayFade( fade_token, 0.3, ffi::kCGDisplayBlendNormal, @@ -1509,11 +1507,11 @@ impl WindowDelegate { 0.0, 0.0, 0.0, - ffi::TRUE, + true, ); } - assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess); + cgerr(CGDisplayCapture(display_id)).unwrap(); } let video_mode = @@ -1523,17 +1521,13 @@ impl WindowDelegate { }; unsafe { - let result = ffi::CGDisplaySetDisplayMode( - display_id, - video_mode.native_mode.0, - std::ptr::null(), - ); - assert!(result == ffi::kCGErrorSuccess, "failed to set video mode"); + cgerr(CGDisplaySetDisplayMode(display_id, Some(&video_mode.native_mode.0), None)) + .expect("failed to set video mode"); // After the display has been configured, fade back in // asynchronously if fade_token != ffi::kCGDisplayFadeReservationInvalidToken { - ffi::CGDisplayFade( + CGDisplayFade( fade_token, 0.6, ffi::kCGDisplayBlendSolidColor, @@ -1541,9 +1535,9 @@ impl WindowDelegate { 0.0, 0.0, 0.0, - ffi::FALSE, + false, ); - ffi::CGReleaseDisplayFadeReservation(fade_token); + CGReleaseDisplayFadeReservation(fade_token); } } } @@ -1575,8 +1569,8 @@ impl WindowDelegate { // `window:willUseFullScreenPresentationOptions` because for some reason // the menu bar remains interactable despite being hidden. if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) { - let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; + let presentation_options = NSApplicationPresentationOptions::HideDock + | NSApplicationPresentationOptions::HideMenuBar; app.setPresentationOptions(presentation_options); } @@ -1601,20 +1595,19 @@ impl WindowDelegate { // delegate in `window:willUseFullScreenPresentationOptions:`. self.ivars().save_presentation_opts.set(Some(app.presentationOptions())); - let presentation_options = - NSApplicationPresentationOptions::NSApplicationPresentationFullScreen - | NSApplicationPresentationOptions::NSApplicationPresentationHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; + let presentation_options = NSApplicationPresentationOptions::FullScreen + | NSApplicationPresentationOptions::HideDock + | NSApplicationPresentationOptions::HideMenuBar; app.setPresentationOptions(presentation_options); - let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1; + let window_level = unsafe { CGShieldingWindowLevel() } as NSWindowLevel + 1; self.window().setLevel(window_level); }, (Some(Fullscreen::Exclusive(ref monitor, _)), Some(Fullscreen::Borderless(_))) => { let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or( - NSApplicationPresentationOptions::NSApplicationPresentationFullScreen - | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationOptions::FullScreen + | NSApplicationPresentationOptions::AutoHideDock + | NSApplicationPresentationOptions::AutoHideMenuBar, ); app.setPresentationOptions(presentation_options); @@ -1726,8 +1719,8 @@ impl WindowDelegate { pub fn request_user_attention(&self, request_type: Option) { let mtm = MainThreadMarker::from(self); let ns_request_type = request_type.map(|ty| match ty { - UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, - UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest, + UserAttentionType::Critical => NSRequestUserAttentionType::CriticalRequest, + UserAttentionType::Informational => NSRequestUserAttentionType::InformationalRequest, }); if let Some(ty) = ns_request_type { NSApplication::sharedApplication(mtm).requestUserAttention(ty); @@ -1787,7 +1780,7 @@ impl WindowDelegate { let mtm = MainThreadMarker::from(self); let app = NSApplication::sharedApplication(mtm); - if app.respondsToSelector(sel!(effectiveAppearance)) { + if available!(macos = 10.14) { Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) } else { Some(Theme::Light) @@ -1802,9 +1795,9 @@ impl WindowDelegate { #[inline] pub fn set_content_protected(&self, protected: bool) { self.window().setSharingType(if protected { - NSWindowSharingType::NSWindowSharingNone + NSWindowSharingType::None } else { - NSWindowSharingType::NSWindowSharingReadOnly + NSWindowSharingType::ReadOnly }) } @@ -1821,8 +1814,8 @@ fn restore_and_release_display(monitor: &MonitorHandle) { let available_monitors = monitor::available_monitors(); if available_monitors.contains(monitor) { unsafe { - ffi::CGRestorePermanentDisplayConfiguration(); - assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess); + CGRestorePermanentDisplayConfiguration(); + cgerr(CGDisplayRelease(monitor.native_identifier())).unwrap(); }; } else { warn!( @@ -1867,9 +1860,8 @@ impl WindowExtMacOS for WindowDelegate { self.ivars().is_simple_fullscreen.set(true); // Simulate pre-Lion fullscreen by hiding the dock and menu bar - let presentation_options = - NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; + let presentation_options = NSApplicationPresentationOptions::AutoHideDock + | NSApplicationPresentationOptions::AutoHideMenuBar; app.setPresentationOptions(presentation_options); // Hide the titlebar @@ -1950,10 +1942,14 @@ impl WindowExtMacOS for WindowDelegate { #[inline] fn select_tab_at_index(&self, index: usize) { + if !available!(macos = 10.13) { + tracing::warn!("window tab groups are only available on macOS 10.13+"); + return; + } if let Some(group) = self.window().tabGroup() { if let Some(windows) = unsafe { self.window().tabbedWindows() } { if index < windows.len() { - group.setSelectedWindow(Some(&windows[index])); + group.setSelectedWindow(Some(&windows.objectAtIndex(index))); } } } @@ -2026,9 +2022,9 @@ fn dark_appearance_name() -> &'static NSString { } pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme { - let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[ - unsafe { NSAppearanceNameAqua.copy() }, - dark_appearance_name().copy(), + let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_slice(&[ + unsafe { NSAppearanceNameAqua }, + dark_appearance_name(), ])); if let Some(best_match) = best_match { if *best_match == *dark_appearance_name() { diff --git a/src/platform_impl/apple/notification_center.rs b/src/platform_impl/apple/notification_center.rs index 652bf1d079..b240c5340e 100644 --- a/src/platform_impl/apple/notification_center.rs +++ b/src/platform_impl/apple/notification_center.rs @@ -2,7 +2,10 @@ use std::ptr::NonNull; use block2::RcBlock; use objc2::rc::Retained; -use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject}; +use objc2::runtime::ProtocolObject; +use objc2_foundation::{ + NSNotification, NSNotificationCenter, NSNotificationName, NSObjectProtocol, +}; /// Observe the given notification. /// @@ -12,7 +15,7 @@ pub fn create_observer( center: &NSNotificationCenter, name: &NSNotificationName, handler: impl Fn(&NSNotification) + 'static, -) -> Retained { +) -> Retained> { let block = RcBlock::new(move |notification: NonNull| { handler(unsafe { notification.as_ref() }); }); diff --git a/src/platform_impl/apple/uikit/app_state.rs b/src/platform_impl/apple/uikit/app_state.rs index 420504bf85..372a678c3e 100644 --- a/src/platform_impl/apple/uikit/app_state.rs +++ b/src/platform_impl/apple/uikit/app_state.rs @@ -4,23 +4,19 @@ use std::cell::{OnceCell, RefCell, RefMut}; use std::collections::HashSet; use std::os::raw::c_void; use std::sync::atomic::Ordering; -use std::sync::{Arc, Mutex, OnceLock}; +use std::sync::{Arc, Mutex}; use std::time::Instant; use std::{mem, ptr}; -use core_foundation::base::CFRelease; -use core_foundation::date::CFAbsoluteTimeGetCurrent; -use core_foundation::runloop::{ - kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, - CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, -}; +use dispatch2::MainThreadBound; use objc2::rc::Retained; -use objc2::sel; -use objc2_foundation::{ - CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion, - NSProcessInfo, +use objc2::MainThreadMarker; +use objc2_core_foundation::{ + kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopAddTimer, + CFRunLoopGetMain, CFRunLoopTimer, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, + CFRunLoopTimerSetNextFireDate, CGRect, CGSize, }; -use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow}; +use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView}; use super::super::event_handler::EventHandler; use super::window::WinitUIWindow; @@ -48,22 +44,10 @@ macro_rules! bug_assert { /// This is stored separately from AppState, since AppState needs to be accessible while the handler /// is executing. fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler { - // TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s. - struct StaticMainThreadBound(T); - - impl StaticMainThreadBound { - const fn get(&self, _mtm: MainThreadMarker) -> &T { - &self.0 - } - } - - unsafe impl Send for StaticMainThreadBound {} - unsafe impl Sync for StaticMainThreadBound {} - // SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept // of the main thread. - static GLOBAL: StaticMainThreadBound> = - StaticMainThreadBound(OnceCell::new()); + static GLOBAL: MainThreadBound> = + MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() }); GLOBAL.get(mtm).get_or_init(EventHandler::new) } @@ -131,7 +115,7 @@ impl AppState { #[inline(never)] #[cold] fn init_guard(guard: &mut RefMut<'static, Option>) { - let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() }); + let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain().unwrap() }); **guard = Some(AppState { app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }), control_flow: ControlFlow::default(), @@ -440,13 +424,7 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o let mut events = Vec::new(); #[allow(deprecated)] for window in application.windows().iter() { - if window.is_kind_of::() { - // SAFETY: We just checked that the window is a `winit` window - let window = unsafe { - let ptr: *const UIWindow = window; - let ptr: *const WinitUIWindow = ptr.cast(); - &*ptr - }; + if let Ok(window) = window.downcast::() { events.push(EventWrapper::Window { window_id: window.id(), event: WindowEvent::Occluded(occluded), @@ -510,13 +488,7 @@ pub(crate) fn terminated(application: &UIApplication) { let mut events = Vec::new(); #[allow(deprecated)] for window in application.windows().iter() { - if window.is_kind_of::() { - // SAFETY: We just checked that the window is a `winit` window - let window = unsafe { - let ptr: *const UIWindow = window; - let ptr: *const WinitUIWindow = ptr.cast(); - &*ptr - }; + if let Ok(window) = window.downcast::() { events.push(EventWrapper::Window { window_id: window.id(), event: WindowEvent::Destroyed, @@ -569,46 +541,46 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained, CGRec } struct EventLoopWaker { - timer: CFRunLoopTimerRef, + timer: CFRetained, } impl Drop for EventLoopWaker { fn drop(&mut self) { unsafe { - CFRunLoopTimerInvalidate(self.timer); - CFRelease(self.timer as _); + CFRunLoopTimerInvalidate(&self.timer); } } } impl EventLoopWaker { - fn new(rl: CFRunLoopRef) -> EventLoopWaker { - extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + fn new(rl: CFRetained) -> EventLoopWaker { + extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {} unsafe { // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // It is initially setup with a first fire time really far into the // future, but that gets changed to fire immediately in did_finish_launching let timer = CFRunLoopTimerCreate( - ptr::null_mut(), + None, f64::MAX, 0.000_000_1, 0, 0, - wakeup_main_loop, + Some(wakeup_main_loop), ptr::null_mut(), - ); - CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes); + ) + .unwrap(); + CFRunLoopAddTimer(&rl, Some(&timer), kCFRunLoopCommonModes); EventLoopWaker { timer } } } fn stop(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } + unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) } } fn start(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } + unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) } } fn start_at(&mut self, instant: Instant) { @@ -621,94 +593,8 @@ impl EventLoopWaker { let duration = instant - now; let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; - CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs); } } } } - -macro_rules! os_capabilities { - ( - $( - $(#[$attr:meta])* - $error_name:ident: $objc_call:literal, - $name:ident: $major:literal-$minor:literal - ),* - $(,)* - ) => { - #[derive(Clone, Debug)] - pub struct OSCapabilities { - $( - pub $name: bool, - )* - - os_version: NSOperatingSystemVersion, - } - - impl OSCapabilities { - fn from_os_version(os_version: NSOperatingSystemVersion) -> Self { - $(let $name = meets_requirements(os_version, $major, $minor);)* - Self { $($name,)* os_version, } - } - } - - impl OSCapabilities {$( - $(#[$attr])* - pub fn $error_name(&self, extra_msg: &str) { - tracing::warn!( - concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"), - $major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion, - extra_msg - ) - } - )*} - }; -} - -os_capabilities! { - /// - #[allow(unused)] // error message unused - safe_area_err_msg: "-[UIView safeAreaInsets]", - safe_area: 11-0, - /// - home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]", - home_indicator_hidden: 11-0, - /// - defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]", - defer_system_gestures: 11-0, - /// - maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]", - maximum_frames_per_second: 10-3, - /// - #[allow(unused)] // error message unused - force_touch_err_msg: "-[UITouch force]", - force_touch: 9-0, -} - -fn meets_requirements( - version: NSOperatingSystemVersion, - required_major: NSInteger, - required_minor: NSInteger, -) -> bool { - (version.majorVersion, version.minorVersion) >= (required_major, required_minor) -} - -fn get_version() -> NSOperatingSystemVersion { - let process_info = NSProcessInfo::processInfo(); - let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion)); - // Winit requires atleast iOS 8 because no one has put the time into supporting earlier os - // versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does - // not support debugging on devices with an iOS version of less than 8. Another example, in - // order to use an iOS simulator older than iOS 8, you must download an older version of Xcode - // (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might? - // - // The minimum required iOS version is likely to grow in the future. - assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater"); - process_info.operatingSystemVersion() -} - -pub fn os_capabilities() -> OSCapabilities { - // Cache the version lookup for efficiency - static OS_CAPABILITIES: OnceLock = OnceLock::new(); - OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone() -} diff --git a/src/platform_impl/apple/uikit/event_loop.rs b/src/platform_impl/apple/uikit/event_loop.rs index ba7377ce66..99a97e7226 100644 --- a/src/platform_impl/apple/uikit/event_loop.rs +++ b/src/platform_impl/apple/uikit/event_loop.rs @@ -3,16 +3,16 @@ use std::ptr::{self, NonNull}; use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use std::sync::Arc; -use core_foundation::base::{CFIndex, CFRelease}; -use core_foundation::runloop::{ - kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, - kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, - CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, - CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, -}; use objc2::rc::Retained; -use objc2::{msg_send_id, ClassType}; -use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject}; +use objc2::runtime::ProtocolObject; +use objc2::{msg_send, ClassType, MainThreadMarker}; +use objc2_core_foundation::{ + kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFIndex, CFRetained, CFRunLoopActivity, + CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopObserver, + CFRunLoopObserverCreate, CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate, + CFRunLoopSourceInvalidate, CFRunLoopSourceSignal, CFRunLoopWakeUp, +}; +use objc2_foundation::{NSNotificationCenter, NSObjectProtocol}; use objc2_ui_kit::{ UIApplication, UIApplicationDidBecomeActiveNotification, UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification, @@ -128,13 +128,13 @@ pub struct EventLoop { // system instead cleans it up next time it would have posted a notification to it. // // Though we do still need to keep the observers around to prevent them from being deallocated. - _did_finish_launching_observer: Retained, - _did_become_active_observer: Retained, - _will_resign_active_observer: Retained, - _will_enter_foreground_observer: Retained, - _did_enter_background_observer: Retained, - _will_terminate_observer: Retained, - _did_receive_memory_warning_observer: Retained, + _did_finish_launching_observer: Retained>, + _did_become_active_observer: Retained>, + _will_resign_active_observer: Retained>, + _will_enter_foreground_observer: Retained>, + _did_enter_background_observer: Retained>, + _will_terminate_observer: Retained>, + _did_receive_memory_warning_observer: Retained>, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -189,9 +189,9 @@ impl EventLoop { let app = unsafe { notification.object() }.expect( "UIApplicationWillEnterForegroundNotification to have application object", ); - // SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is - // documented to be `UIApplication`. - let app: Retained = unsafe { Retained::cast(app) }; + // The `object` in `UIApplicationWillEnterForegroundNotification` is documented to + // be `UIApplication`. + let app = app.downcast::().unwrap(); send_occluded_event_for_all_windows(&app, false); }, ); @@ -203,9 +203,9 @@ impl EventLoop { let app = unsafe { notification.object() }.expect( "UIApplicationDidEnterBackgroundNotification to have application object", ); - // SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is - // documented to be `UIApplication`. - let app: Retained = unsafe { Retained::cast(app) }; + // The `object` in `UIApplicationDidEnterBackgroundNotification` is documented to be + // `UIApplication`. + let app = app.downcast::().unwrap(); send_occluded_event_for_all_windows(&app, true); }, ); @@ -216,9 +216,9 @@ impl EventLoop { move |notification| { let app = unsafe { notification.object() } .expect("UIApplicationWillTerminateNotification to have application object"); - // SAFETY: The `object` in `UIApplicationWillTerminateNotification` is - // (somewhat) documented to be `UIApplication`. - let app: Retained = unsafe { Retained::cast(app) }; + // The `object` in `UIApplicationWillTerminateNotification` is (somewhat) documented + // to be `UIApplication`. + let app = app.downcast::().unwrap(); app_state::terminated(&app); }, ); @@ -244,7 +244,7 @@ impl EventLoop { pub fn run_app(self, mut app: A) -> ! { let application: Option> = - unsafe { msg_send_id![UIApplication::class(), sharedApplication] }; + unsafe { msg_send![UIApplication::class(), sharedApplication] }; assert!( application.is_none(), "\ @@ -279,7 +279,7 @@ impl EventLoop { pub struct EventLoopProxy { pub(crate) wake_up: AtomicBool, - source: CFRunLoopSourceRef, + source: CFRetained, } unsafe impl Send for EventLoopProxy {} @@ -287,10 +287,7 @@ unsafe impl Sync for EventLoopProxy {} impl Drop for EventLoopProxy { fn drop(&mut self) { - unsafe { - CFRunLoopSourceInvalidate(self.source); - CFRelease(self.source as _); - } + unsafe { CFRunLoopSourceInvalidate(&self.source) }; } } @@ -298,11 +295,11 @@ impl EventLoopProxy { pub(crate) fn new() -> EventLoopProxy { unsafe { // just wake up the eventloop - extern "C" fn event_loop_proxy_handler(_: *const c_void) {} + extern "C-unwind" fn event_loop_proxy_handler(_: *mut c_void) {} // adding a Source to the main CFRunLoop lets us wake it up and // process user events through the normal OS EventLoop mechanisms. - let rl = CFRunLoopGetMain(); + let rl = CFRunLoopGetMain().unwrap(); let mut context = CFRunLoopSourceContext { version: 0, info: ptr::null_mut(), @@ -313,11 +310,11 @@ impl EventLoopProxy { hash: None, schedule: None, cancel: None, - perform: event_loop_proxy_handler, + perform: Some(event_loop_proxy_handler), }; - let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); - CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); - CFRunLoopWakeUp(rl); + let source = CFRunLoopSourceCreate(None, CFIndex::MAX - 1, &mut context).unwrap(); + CFRunLoopAddSource(&rl, Some(&source), kCFRunLoopCommonModes); + CFRunLoopWakeUp(&rl); EventLoopProxy { wake_up: AtomicBool::new(false), source } } @@ -329,9 +326,9 @@ impl EventLoopProxyProvider for EventLoopProxy { self.wake_up.store(true, AtomicOrdering::Relaxed); unsafe { // let the main thread know there's a new event - CFRunLoopSourceSignal(self.source); - let rl = CFRunLoopGetMain(); - CFRunLoopWakeUp(rl); + CFRunLoopSourceSignal(&self.source); + let rl = CFRunLoopGetMain().unwrap(); + CFRunLoopWakeUp(&rl); } } } @@ -340,15 +337,15 @@ fn setup_control_flow_observers() { unsafe { // begin is queued with the highest priority to ensure it is processed before other // observers - extern "C" fn control_flow_begin_handler( - _: CFRunLoopObserverRef, + extern "C-unwind" fn control_flow_begin_handler( + _: *mut CFRunLoopObserver, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { - kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm), + CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm), _ => unreachable!(), } } @@ -364,65 +361,68 @@ fn setup_control_flow_observers() { // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. // // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. - extern "C" fn control_flow_main_end_handler( - _: CFRunLoopObserverRef, + extern "C-unwind" fn control_flow_main_end_handler( + _: *mut CFRunLoopObserver, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { - kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm), - kCFRunLoopExit => {}, // may happen when running on macOS + CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm), + CFRunLoopActivity::Exit => {}, // may happen when running on macOS _ => unreachable!(), } } // end is queued with the lowest priority to ensure it is processed after other observers - extern "C" fn control_flow_end_handler( - _: CFRunLoopObserverRef, + extern "C-unwind" fn control_flow_end_handler( + _: *mut CFRunLoopObserver, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { - kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm), - kCFRunLoopExit => {}, // may happen when running on macOS + CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm), + CFRunLoopActivity::Exit => {}, // may happen when running on macOS _ => unreachable!(), } } - let main_loop = CFRunLoopGetMain(); + let main_loop = CFRunLoopGetMain().unwrap(); let begin_observer = CFRunLoopObserverCreate( - ptr::null_mut(), - kCFRunLoopAfterWaiting, - 1, // repeat = true + None, + CFRunLoopActivity::AfterWaiting.0, + true, CFIndex::MIN, - control_flow_begin_handler, + Some(control_flow_begin_handler), ptr::null_mut(), - ); - CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); + ) + .unwrap(); + CFRunLoopAddObserver(&main_loop, Some(&begin_observer), kCFRunLoopDefaultMode); let main_end_observer = CFRunLoopObserverCreate( - ptr::null_mut(), - kCFRunLoopExit | kCFRunLoopBeforeWaiting, - 1, // repeat = true + None, + (CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0, + true, 0, // see comment on `control_flow_main_end_handler` - control_flow_main_end_handler, + Some(control_flow_main_end_handler), ptr::null_mut(), - ); - CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode); + ) + .unwrap(); + CFRunLoopAddObserver(&main_loop, Some(&main_end_observer), kCFRunLoopDefaultMode); let end_observer = CFRunLoopObserverCreate( - ptr::null_mut(), - kCFRunLoopExit | kCFRunLoopBeforeWaiting, - 1, // repeat = true + None, + (CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0, + true, CFIndex::MAX, - control_flow_end_handler, + Some(control_flow_end_handler), ptr::null_mut(), - ); - CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); + ) + .unwrap(); + CFRunLoopAddObserver(&main_loop, Some(&end_observer), kCFRunLoopDefaultMode); } } diff --git a/src/platform_impl/apple/uikit/monitor.rs b/src/platform_impl/apple/uikit/monitor.rs index ec3fca9614..81fe054d03 100644 --- a/src/platform_impl/apple/uikit/monitor.rs +++ b/src/platform_impl/apple/uikit/monitor.rs @@ -4,13 +4,12 @@ use std::collections::VecDeque; use std::num::NonZeroU32; use std::{fmt, hash, ptr}; -use objc2::mutability::IsRetainable; +use dispatch2::{run_on_main, MainThreadBound}; use objc2::rc::Retained; -use objc2::Message; -use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger}; +use objc2::{available, MainThreadMarker, Message}; +use objc2_foundation::NSInteger; use objc2_ui_kit::{UIScreen, UIScreenMode}; -use super::app_state; use crate::dpi::PhysicalPosition; use crate::monitor::VideoMode; @@ -18,13 +17,13 @@ use crate::monitor::VideoMode; #[derive(Debug)] struct MainThreadBoundDelegateImpls(MainThreadBound>); -impl Clone for MainThreadBoundDelegateImpls { +impl Clone for MainThreadBoundDelegateImpls { fn clone(&self) -> Self { Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm))) } } -impl hash::Hash for MainThreadBoundDelegateImpls { +impl hash::Hash for MainThreadBoundDelegateImpls { fn hash(&self, state: &mut H) { // SAFETY: Marker only used to get the pointer let mtm = unsafe { MainThreadMarker::new_unchecked() }; @@ -32,7 +31,7 @@ impl hash::Hash for MainThreadBoundDelegateImpls { } } -impl PartialEq for MainThreadBoundDelegateImpls { +impl PartialEq for MainThreadBoundDelegateImpls { fn eq(&self, other: &Self) -> bool { // SAFETY: Marker only used to get the pointer let mtm = unsafe { MainThreadMarker::new_unchecked() }; @@ -40,7 +39,7 @@ impl PartialEq for MainThreadBoundDelegateImpls { } } -impl Eq for MainThreadBoundDelegateImpls {} +impl Eq for MainThreadBoundDelegateImpls {} #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct VideoModeHandle { @@ -150,7 +149,7 @@ impl MonitorHandle { #[allow(deprecated)] UIScreen::screens(mtm) .iter() - .position(|rhs| rhs == &**self.ui_screen(mtm)) + .position(|rhs| rhs == *self.ui_screen(mtm)) .map(|idx| idx.to_string()) } }) @@ -211,8 +210,7 @@ impl MonitorHandle { fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option { let refresh_rate_millihertz: NSInteger = { - let os_capabilities = app_state::os_capabilities(); - if os_capabilities.maximum_frames_per_second { + if available!(ios = 10.3, tvos = 10.2) { uiscreen.maximumFramesPerSecond() } else { // https://developer.apple.com/library/archive/technotes/tn2460/_index.html @@ -225,7 +223,9 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option { // // FIXME: earlier OSs could calculate the refresh rate using // `-[CADisplayLink duration]`. - os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps"); + tracing::warn!( + "`maximumFramesPerSecond` requires iOS 10.3+ or tvOS 10.2+. Defaulting to 60 fps" + ); 60 } }; @@ -254,7 +254,7 @@ mod tests { assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm))); let main = UIScreen::mainScreen(mtm); - assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main))); + assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(&*screen, &*main))); assert!(unsafe { NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm)) diff --git a/src/platform_impl/apple/uikit/view.rs b/src/platform_impl/apple/uikit/view.rs index 9330a9cc5c..711ea13793 100644 --- a/src/platform_impl/apple/uikit/view.rs +++ b/src/platform_impl/apple/uikit/view.rs @@ -3,8 +3,9 @@ use std::cell::{Cell, RefCell}; use objc2::rc::Retained; use objc2::runtime::{NSObjectProtocol, ProtocolObject}; -use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass}; -use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString}; +use objc2::{available, define_class, msg_send, sel, DefinedClass, MainThreadMarker}; +use objc2_core_foundation::{CGFloat, CGPoint, CGRect}; +use objc2_foundation::{NSObject, NSSet, NSString}; use objc2_ui_kit::{ UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer, @@ -39,36 +40,26 @@ pub struct WinitViewState { fingers: Cell, } -declare_class!( +define_class!( + #[unsafe(super(UIView, UIResponder, NSObject))] + #[name = "WinitUIView"] + #[ivars = WinitViewState] pub(crate) struct WinitView; - unsafe impl ClassType for WinitView { - #[inherits(UIResponder, NSObject)] - type Super = UIView; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitUIView"; - } - - impl DeclaredClass for WinitView { - type Ivars = WinitViewState; - } - - unsafe impl WinitView { - #[method(drawRect:)] + /// This documentation attribute makes rustfmt work for some reason? + impl WinitView { + #[unsafe(method(drawRect:))] fn draw_rect(&self, rect: CGRect) { let mtm = MainThreadMarker::new().unwrap(); let window = self.window().unwrap(); - app_state::handle_nonuser_event( - mtm, - EventWrapper::Window { - window_id: window.id(), - event: WindowEvent::RedrawRequested, - }, - ); + app_state::handle_nonuser_event(mtm, EventWrapper::Window { + window_id: window.id(), + event: WindowEvent::RedrawRequested, + }); let _: () = unsafe { msg_send![super(self), drawRect: rect] }; } - #[method(layoutSubviews)] + #[unsafe(method(layoutSubviews))] fn layout_subviews(&self) { let mtm = MainThreadMarker::new().unwrap(); let _: () = unsafe { msg_send![super(self), layoutSubviews] }; @@ -82,16 +73,13 @@ declare_class!( .to_physical(scale_factor); let window = self.window().unwrap(); - app_state::handle_nonuser_event( - mtm, - EventWrapper::Window { - window_id: window.id(), - event: WindowEvent::SurfaceResized(size), - }, - ); + app_state::handle_nonuser_event(mtm, EventWrapper::Window { + window_id: window.id(), + event: WindowEvent::SurfaceResized(size), + }); } - #[method(setContentScaleFactor:)] + #[unsafe(method(setContentScaleFactor:))] fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { let mtm = MainThreadMarker::new().unwrap(); let _: () = @@ -124,49 +112,46 @@ declare_class!( let window_id = window.id(); app_state::handle_nonuser_events( mtm, - std::iter::once(EventWrapper::ScaleFactorChanged( - app_state::ScaleFactorChanged { - window, - scale_factor, - suggested_size: size.to_physical(scale_factor), - }, - )) + std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged { + window, + scale_factor, + suggested_size: size.to_physical(scale_factor), + })) .chain(std::iter::once(EventWrapper::Window { - window_id, - event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)), - }, - )), + window_id, + event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)), + })), ); } - #[method(safeAreaInsetsDidChange)] + #[unsafe(method(safeAreaInsetsDidChange))] fn safe_area_changed(&self) { debug!("safeAreaInsetsDidChange was called, requesting redraw"); // When the safe area changes we want to make sure to emit a redraw event self.setNeedsDisplay(); } - #[method(touchesBegan:withEvent:)] + #[unsafe(method(touchesBegan:withEvent:))] fn touches_began(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } - #[method(touchesMoved:withEvent:)] + #[unsafe(method(touchesMoved:withEvent:))] fn touches_moved(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } - #[method(touchesEnded:withEvent:)] + #[unsafe(method(touchesEnded:withEvent:))] fn touches_ended(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } - #[method(touchesCancelled:withEvent:)] + #[unsafe(method(touchesCancelled:withEvent:))] fn touches_cancelled(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } - #[method(pinchGesture:)] + #[unsafe(method(pinchGesture:))] fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) { let window = self.window().unwrap(); @@ -174,46 +159,40 @@ declare_class!( UIGestureRecognizerState::Began => { self.ivars().pinch_last_delta.set(recognizer.scale()); (TouchPhase::Started, 0.0) - } + }, UIGestureRecognizerState::Changed => { let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale()); (TouchPhase::Moved, recognizer.scale() - last_scale) - } + }, UIGestureRecognizerState::Ended => { let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0); (TouchPhase::Moved, recognizer.scale() - last_scale) - } + }, UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { self.ivars().rotation_last_delta.set(0.0); // Pass -delta so that action is reversed (TouchPhase::Cancelled, -recognizer.scale()) - } + }, state => panic!("unexpected recognizer state: {state:?}"), }; let gesture_event = EventWrapper::Window { window_id: window.id(), - event: WindowEvent::PinchGesture { - device_id: None, - delta: delta as f64, - phase, - }, + event: WindowEvent::PinchGesture { device_id: None, delta: delta as f64, phase }, }; let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); } - #[method(doubleTapGesture:)] + #[unsafe(method(doubleTapGesture:))] fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) { let window = self.window().unwrap(); if recognizer.state() == UIGestureRecognizerState::Ended { let gesture_event = EventWrapper::Window { window_id: window.id(), - event: WindowEvent::DoubleTapGesture { - device_id: None, - }, + event: WindowEvent::DoubleTapGesture { device_id: None }, }; let mtm = MainThreadMarker::new().unwrap(); @@ -221,7 +200,7 @@ declare_class!( } } - #[method(rotationGesture:)] + #[unsafe(method(rotationGesture:))] fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) { let window = self.window().unwrap(); @@ -230,23 +209,24 @@ declare_class!( self.ivars().rotation_last_delta.set(0.0); (TouchPhase::Started, 0.0) - } + }, UIGestureRecognizerState::Changed => { - let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation()); + let last_rotation = + self.ivars().rotation_last_delta.replace(recognizer.rotation()); (TouchPhase::Moved, recognizer.rotation() - last_rotation) - } + }, UIGestureRecognizerState::Ended => { let last_rotation = self.ivars().rotation_last_delta.replace(0.0); (TouchPhase::Ended, recognizer.rotation() - last_rotation) - } + }, UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { self.ivars().rotation_last_delta.set(0.0); // Pass -delta so that action is reversed (TouchPhase::Cancelled, -recognizer.rotation()) - } + }, state => panic!("unexpected recognizer state: {state:?}"), }; @@ -264,7 +244,7 @@ declare_class!( app_state::handle_nonuser_event(mtm, gesture_event); } - #[method(panGesture:)] + #[unsafe(method(panGesture:))] fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) { let window = self.window().unwrap(); @@ -275,7 +255,7 @@ declare_class!( self.ivars().pan_last_delta.set(translation); (TouchPhase::Started, 0.0, 0.0) - } + }, UIGestureRecognizerState::Changed => { let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation); @@ -283,25 +263,26 @@ declare_class!( let dy = translation.y - last_pan.y; (TouchPhase::Moved, dx, dy) - } + }, UIGestureRecognizerState::Ended => { - let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0}); + let last_pan: CGPoint = + self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 }); let dx = translation.x - last_pan.x; let dy = translation.y - last_pan.y; (TouchPhase::Ended, dx, dy) - } + }, UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { - let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0}); + let last_pan: CGPoint = + self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 }); // Pass -delta so that action is reversed (TouchPhase::Cancelled, -last_pan.x, -last_pan.y) - } + }, state => panic!("unexpected recognizer state: {state:?}"), }; - let gesture_event = EventWrapper::Window { window_id: window.id(), event: WindowEvent::PanGesture { @@ -315,7 +296,7 @@ declare_class!( app_state::handle_nonuser_event(mtm, gesture_event); } - #[method(canBecomeFirstResponder)] + #[unsafe(method(canBecomeFirstResponder))] fn can_become_first_responder(&self) -> bool { true } @@ -324,27 +305,30 @@ declare_class!( unsafe impl NSObjectProtocol for WinitView {} unsafe impl UIGestureRecognizerDelegate for WinitView { - #[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)] - fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool { + #[unsafe(method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:))] + fn should_recognize_simultaneously( + &self, + _gesture_recognizer: &UIGestureRecognizer, + _other_gesture_recognizer: &UIGestureRecognizer, + ) -> bool { true } } - unsafe impl UITextInputTraits for WinitView { - } + unsafe impl UITextInputTraits for WinitView {} unsafe impl UIKeyInput for WinitView { - #[method(hasText)] + #[unsafe(method(hasText))] fn has_text(&self) -> bool { true } - #[method(insertText:)] + #[unsafe(method(insertText:))] fn insert_text(&self, text: &NSString) { self.handle_insert_text(text) } - #[method(deleteBackward)] + #[unsafe(method(deleteBackward))] fn delete_backward(&self) { self.handle_delete_backward() } @@ -370,7 +354,7 @@ impl WinitView { primary_finger: Cell::new(None), fingers: Cell::new(0), }); - let this: Retained = unsafe { msg_send_id![super(this), initWithFrame: frame] }; + let this: Retained = unsafe { msg_send![super(this), initWithFrame: frame] }; this.setMultipleTouchEnabled(true); @@ -382,8 +366,8 @@ impl WinitView { } fn window(&self) -> Option> { - // SAFETY: `WinitView`s are always installed in a `WinitUIWindow` - (**self).window().map(|window| unsafe { Retained::cast(window) }) + // `WinitView`s should always be installed in a `WinitUIWindow` + (**self).window().map(|window| window.downcast().unwrap()) } pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) { @@ -478,13 +462,12 @@ impl WinitView { fn handle_touches(&self, touches: &NSSet) { let window = self.window().unwrap(); let mut touch_events = Vec::new(); - let os_supports_force = app_state::os_capabilities().force_touch; for touch in touches { let logical_location = touch.locationInView(None); let touch_type = touch.r#type(); let force = if let UITouchType::Pencil = touch_type { None - } else if os_supports_force { + } else if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) { let trait_collection = self.traitCollection(); let touch_capability = trait_collection.forceTouchCapability(); // Both the OS _and_ the device need to be checked for force touch support. @@ -501,7 +484,7 @@ impl WinitView { } else { None }; - let touch_id = touch as *const UITouch as usize; + let touch_id = Retained::as_ptr(&touch) as usize; let phase = touch.phase(); let position = { let scale_factor = self.contentScaleFactor(); diff --git a/src/platform_impl/apple/uikit/view_controller.rs b/src/platform_impl/apple/uikit/view_controller.rs index dd658682e2..f860838bd2 100644 --- a/src/platform_impl/apple/uikit/view_controller.rs +++ b/src/platform_impl/apple/uikit/view_controller.rs @@ -1,14 +1,13 @@ use std::cell::Cell; use objc2::rc::Retained; -use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; -use objc2_foundation::{MainThreadMarker, NSObject}; +use objc2::{available, define_class, msg_send, DefinedClass, MainThreadMarker}; +use objc2_foundation::NSObject; use objc2_ui_kit::{ UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle, UIUserInterfaceIdiom, UIView, UIViewController, }; -use super::app_state::{self}; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; use crate::window::WindowAttributes; @@ -20,51 +19,42 @@ pub struct ViewControllerState { preferred_screen_edges_deferring_system_gestures: Cell, } -declare_class!( +define_class!( + #[unsafe(super(UIViewController, UIResponder, NSObject))] + #[name = "WinitUIViewController"] + #[ivars = ViewControllerState] pub(crate) struct WinitViewController; - unsafe impl ClassType for WinitViewController { - #[inherits(UIResponder, NSObject)] - type Super = UIViewController; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitUIViewController"; - } - - impl DeclaredClass for WinitViewController { - type Ivars = ViewControllerState; - } - - unsafe impl WinitViewController { - #[method(shouldAutorotate)] + /// This documentation attribute makes rustfmt work for some reason? + impl WinitViewController { + #[unsafe(method(shouldAutorotate))] fn should_autorotate(&self) -> bool { true } - #[method(prefersStatusBarHidden)] + #[unsafe(method(prefersStatusBarHidden))] fn prefers_status_bar_hidden(&self) -> bool { self.ivars().prefers_status_bar_hidden.get() } - #[method(preferredStatusBarStyle)] + #[unsafe(method(preferredStatusBarStyle))] fn preferred_status_bar_style(&self) -> UIStatusBarStyle { self.ivars().preferred_status_bar_style.get() } - #[method(prefersHomeIndicatorAutoHidden)] + #[unsafe(method(prefersHomeIndicatorAutoHidden))] fn prefers_home_indicator_auto_hidden(&self) -> bool { self.ivars().prefers_home_indicator_auto_hidden.get() } - #[method(supportedInterfaceOrientations)] + #[unsafe(method(supportedInterfaceOrientations))] fn supported_orientations(&self) -> UIInterfaceOrientationMask { self.ivars().supported_orientations.get() } - #[method(preferredScreenEdgesDeferringSystemGestures)] + #[unsafe(method(preferredScreenEdgesDeferringSystemGestures))] fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { - self.ivars() - .preferred_screen_edges_deferring_system_gestures - .get() + self.ivars().preferred_screen_edges_deferring_system_gestures.get() } } ); @@ -87,11 +77,13 @@ impl WinitViewController { pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) { self.ivars().prefers_home_indicator_auto_hidden.set(val); - let os_capabilities = app_state::os_capabilities(); - if os_capabilities.home_indicator_hidden { + if available!(ios = 11.0, visionos = 1.0) { self.setNeedsUpdateOfHomeIndicatorAutoHidden(); } else { - os_capabilities.home_indicator_hidden_err_msg("ignoring") + tracing::warn!( + "`setNeedsUpdateOfHomeIndicatorAutoHidden` requires iOS 11.0+ or visionOS. \ + Ignoring" + ); } } @@ -101,11 +93,13 @@ impl WinitViewController { UIRectEdge(val.bits().into()) }; self.ivars().preferred_screen_edges_deferring_system_gestures.set(val); - let os_capabilities = app_state::os_capabilities(); - if os_capabilities.defer_system_gestures { + if available!(ios = 11.0, visionos = 1.0) { self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures(); } else { - os_capabilities.defer_system_gestures_err_msg("ignoring") + tracing::warn!( + "`setNeedsUpdateOfScreenEdgesDeferringSystemGestures` requires iOS 11.0+ or \ + visionOS. Ignoring" + ); } } @@ -146,7 +140,7 @@ impl WinitViewController { supported_orientations: Cell::new(UIInterfaceOrientationMask::All), preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()), }); - let this: Retained = unsafe { msg_send_id![super(this), init] }; + let this: Retained = unsafe { msg_send![super(this), init] }; this.set_prefers_status_bar_hidden( window_attributes.platform_specific.prefers_status_bar_hidden, diff --git a/src/platform_impl/apple/uikit/window.rs b/src/platform_impl/apple/uikit/window.rs index 381b7d9cc9..2bf9e0fe83 100644 --- a/src/platform_impl/apple/uikit/window.rs +++ b/src/platform_impl/apple/uikit/window.rs @@ -2,11 +2,11 @@ use std::collections::VecDeque; +use dispatch2::MainThreadBound; use objc2::rc::Retained; -use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass}; -use objc2_foundation::{ - CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObject, NSObjectProtocol, -}; +use objc2::{available, class, define_class, msg_send, MainThreadMarker}; +use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize}; +use objc2_foundation::{NSObject, NSObjectProtocol}; use objc2_ui_kit::{ UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen, UIScreenOverscanCompensation, UIViewController, UIWindow, @@ -32,43 +32,31 @@ use crate::window::{ WindowAttributes, WindowButtons, WindowId, WindowLevel, }; -declare_class!( +define_class!( + #[unsafe(super(UIWindow, UIResponder, NSObject))] + #[name = "WinitUIWindow"] #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct WinitUIWindow; - unsafe impl ClassType for WinitUIWindow { - #[inherits(UIResponder, NSObject)] - type Super = UIWindow; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitUIWindow"; - } - - impl DeclaredClass for WinitUIWindow {} - - unsafe impl WinitUIWindow { - #[method(becomeKeyWindow)] + /// This documentation attribute makes rustfmt work for some reason? + impl WinitUIWindow { + #[unsafe(method(becomeKeyWindow))] fn become_key_window(&self) { let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event( - mtm, - EventWrapper::Window { - window_id: self.id(), - event: WindowEvent::Focused(true), - }, - ); + app_state::handle_nonuser_event(mtm, EventWrapper::Window { + window_id: self.id(), + event: WindowEvent::Focused(true), + }); let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; } - #[method(resignKeyWindow)] + #[unsafe(method(resignKeyWindow))] fn resign_key_window(&self) { let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event( - mtm, - EventWrapper::Window { - window_id: self.id(), - event: WindowEvent::Focused(false), - }, - ); + app_state::handle_nonuser_event(mtm, EventWrapper::Window { + window_id: self.id(), + event: WindowEvent::Focused(false), + }); let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; } } @@ -86,7 +74,7 @@ impl WinitUIWindow { // into very confusing issues with the window not being properly activated. // // Winit ensures this by not allowing access to `ActiveEventLoop` before handling events. - let this: Retained = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] }; + let this: Retained = unsafe { msg_send![mtm.alloc(), initWithFrame: frame] }; this.setRootViewController(Some(view_controller)); @@ -208,8 +196,7 @@ impl Inner { } pub fn safe_area(&self) -> PhysicalInsets { - // Only available on iOS 11.0 - let insets = if app_state::os_capabilities().safe_area { + let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) { self.view.safeAreaInsets() } else { // Assume the status bar frame is the only thing that obscures the view diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index 944918d3a3..8f1f74d40b 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -27,7 +27,10 @@ use sctk::seat::pointer::{ use sctk::seat::SeatState; use crate::dpi::{LogicalPosition, PhysicalPosition}; -use crate::event::{ElementState, MouseButton, MouseScrollDelta, PointerSource, PointerKind, TouchPhase, WindowEvent}; +use crate::event::{ + ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase, + WindowEvent, +}; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, WindowId};