From a469b4ea0d97159d913aa9ebc8548664d7369316 Mon Sep 17 00:00:00 2001 From: paravozz Date: Sat, 25 Apr 2026 13:40:55 +0100 Subject: [PATCH] macos: migrate from objc to objc2 Replaces objc 0.2.7 runtime types and macros with objc2 0.6 across src/macos/ and src/gl/macos.rs. Keeps cocoa = "0.24" for framework type bindings (NSEvent, NSView, NSWindow, etc.); pure macros migration, no public-API or behavioral change. Drops #![allow(unexpected_cfgs)] from src/macos/mod.rs and src/gl/macos.rs (no longer needed: objc2's macros don't expand the buggy cargo-clippy cfg, so the warning that justified it goes silent at the source). Resolves the unexpected_cfgs lint failure that was making CI fail under RUSTFLAGS=-D warnings. Direct replacements: - objc::msg_send! -> objc2::msg_send! (~54 sites) - objc::class! -> objc2::class! (3 sites) - objc::sel! -> objc2::sel! (26 sites) - objc::declare::ClassDecl -> objc2::runtime::ClassBuilder - objc::runtime::Object/Class/Sel/Protocol -> objc2 AnyObject/AnyClass/Sel/AnyProtocol - objc::{Encode, Encoding} -> objc2::{Encode, Encoding} - objc::runtime::objc_disposeClassPair -> objc2::ffi::objc_disposeClassPair Less-mechanical patterns required by objc2's stricter API: - Callbacks use *const AnyObject / *mut AnyObject instead of &AnyObject / &mut AnyObject. objc2 0.6's MethodImplementation trait impls aren't HRT-compatible; raw pointers carry no lifetime, dodge the issue, and faithfully match the objc_msgSend ABI. - Callbacks marked extern "C-unwind" rather than extern "C". Plain extern "C" is undefined behavior if a foreign exception unwinds through it. - bool is not objc2::Encode, so BOOL-returning callbacks return objc2::runtime::Bool with .as_bool() at comparison sites. - Local CgPoint/CgSize/CgRect #[repr(C)] wrappers for geometric types used as msg_send! returns, since cocoa's NSPoint/NSSize/NSRect are external types and can't impl objc2's external Encode trait. - ClassBuilder::new and AnyClass::get / AnyProtocol::get take &CStr (was &str in objc 0.2). Also fixes several latent bugs surfaced by objc2's stricter unwinding (extern "C-unwind") and stricter @encode verification (debug_assertions): - Gate -[NSEvent isARepeat] on event_type == NSKeyDown to avoid raising NSInternalInconsistencyException on flagsChanged events. Under extern "C" + objc 0.2 the foreign exception's behavior on macOS aarch64 happened to be survivable; under extern "C-unwind" the unwind silently swallows the modifier-state KeyboardEvent for every Alt/Cmd/Shift press, breaking downstream modifier tracking. - Gate -[NSEvent keyCode] on event-type filter (mirrors isARepeat). Same exception class; the existing _ => return None arm in the event_type match was dead code because keyCode() ran before it. - Guard NSArray::objectAtIndex in update_tracking_areas with a count check (NSRangeException) for defense-in-depth, mirroring the guard already present in view_will_move_to_window. - Fix three return-type vs Apple @encode mismatches: view.rs registerForDraggedTypes: declared @ -> void window.rs addSubview: declared @ -> void gl/macos.rs retain declared () -> instancetype (@) These four fix categories ship together with the migration because they are entangled with the migration's stricter checks (the fixes become observable only once objc2's verification runs). Test plan, pattern rationale, and Apple doc citations in the PR description. Related to #229. Supersedes #231. --- Cargo.toml | 2 +- src/gl/macos.rs | 22 +-- src/macos/keyboard.rs | 36 +++- src/macos/mod.rs | 4 - src/macos/view.rs | 419 +++++++++++++++++++++++++++--------------- src/macos/window.rs | 67 ++++--- 6 files changed, 349 insertions(+), 201 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d65e460..dc3ff217 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ uuid = { version = "0.8", features = ["v4"], optional = true } [target.'cfg(target_os="macos")'.dependencies] cocoa = "0.24.0" core-foundation = "0.9.1" -objc = "0.2.7" +objc2 = "0.6" uuid = { version = "0.8", features = ["v4"] } [dev-dependencies] diff --git a/src/gl/macos.rs b/src/gl/macos.rs index f68b796a..32942222 100644 --- a/src/gl/macos.rs +++ b/src/gl/macos.rs @@ -1,7 +1,3 @@ -// This is required because the objc crate is causing a lot of warnings: https://github.com/SSheldon/rust-objc/issues/125 -// Eventually we should migrate to the objc2 crate and remove this. -#![allow(unexpected_cfgs)] - use std::ffi::c_void; use std::str::FromStr; @@ -21,7 +17,8 @@ use core_foundation::base::TCFType; use core_foundation::bundle::{CFBundleGetBundleWithIdentifier, CFBundleGetFunctionPointerForName}; use core_foundation::string::CFString; -use objc::{msg_send, sel, sel_impl}; +use objc2::msg_send; +use objc2::runtime::AnyObject; use super::{GlConfig, GlError, Profile}; @@ -98,15 +95,16 @@ impl GlContext { NSOpenGLView::display_(view); parent_view.addSubview_(view); - let context: id = msg_send![view, openGLContext]; - let () = msg_send![context, retain]; + let context_any: *mut AnyObject = msg_send![view as *mut AnyObject, openGLContext]; + let _: *mut AnyObject = msg_send![context_any, retain]; + let context: id = context_any as id; context.setValues_forParameter_( &(config.vsync as i32), NSOpenGLContextParameter::NSOpenGLCPSwapInterval, ); - let () = msg_send![pixel_format, release]; + let () = msg_send![pixel_format as *mut AnyObject, release]; Ok(GlContext { view, context }) } @@ -131,7 +129,7 @@ impl GlContext { pub fn swap_buffers(&self) { unsafe { self.context.flushBuffer(); - let () = msg_send![self.view, setNeedsDisplay: YES]; + let () = msg_send![self.view as *mut AnyObject, setNeedsDisplay: YES]; } } @@ -139,7 +137,7 @@ impl GlContext { pub(crate) fn resize(&self, size: NSSize) { unsafe { NSView::setFrameSize(self.view, size) }; unsafe { - let _: () = msg_send![self.view, setNeedsDisplay: YES]; + let _: () = msg_send![self.view as *mut AnyObject, setNeedsDisplay: YES]; } } } @@ -147,8 +145,8 @@ impl GlContext { impl Drop for GlContext { fn drop(&mut self) { unsafe { - let () = msg_send![self.context, release]; - let () = msg_send![self.view, release]; + let () = msg_send![self.context as *mut AnyObject, release]; + let () = msg_send![self.view as *mut AnyObject, release]; } } } diff --git a/src/macos/keyboard.rs b/src/macos/keyboard.rs index 18f3bfe5..1830f534 100644 --- a/src/macos/keyboard.rs +++ b/src/macos/keyboard.rs @@ -24,7 +24,8 @@ use cocoa::appkit::{NSEvent, NSEventModifierFlags, NSEventType}; use cocoa::base::id; use cocoa::foundation::NSString; use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Modifiers}; -use objc::{msg_send, sel, sel_impl}; +use objc2::msg_send; +use objc2::runtime::AnyObject; use crate::keyboard::code_to_location; @@ -282,10 +283,23 @@ impl KeyboardState { pub(crate) fn process_native_event(&self, event: id) -> Option { unsafe { let event_type = event.eventType(); - let key_code = event.keyCode(); + let raw_mods = event.modifierFlags(); + // `-[NSEvent keyCode]` is documented to raise + // `NSInternalInconsistencyException` when sent to non-key events. + // AppKit occasionally dispatches non-key events into + // `keyDown:` / `keyUp:` / `flagsChanged:` selectors + // (e.g. `NSAppKitDefined`, `NSSystemDefined`, or sync events + // around Cmd-Tab / input-source switches), so gate the call. + // Without this gate, the exception unwinds out of the + // `extern "C-unwind"` callback and silently swallows the event. + let key_code = match event_type { + NSEventType::NSKeyDown | NSEventType::NSKeyUp | NSEventType::NSFlagsChanged => { + event.keyCode() + } + _ => return None, + }; let code = key_code_to_code(key_code); let location = code_to_location(code); - let raw_mods = event.modifierFlags(); let modifiers = make_modifiers(raw_mods); let state = match event_type { NSEventType::NSKeyDown => KeyState::Down, @@ -311,10 +325,24 @@ impl KeyboardState { return None; } } + // Already filtered above; reachable only via newly-introduced + // event types we haven't taught the layer above about. _ => unreachable!(), }; let is_composing = false; - let repeat: bool = event_type == NSEventType::NSKeyDown && msg_send![event, isARepeat]; + // `-[NSEvent isARepeat]` is documented to raise + // `NSInternalInconsistencyException` when sent to anything other + // than keyDown/keyUp. Gate the query on the event types that + // actually carry a meaningful repeat flag — without this gate, + // the exception unwinds out of the `extern "C-unwind"` callback + // on every `flagsChanged:` press, dropping the modifier-state + // KeyboardEvent that downstream layers (e.g. baseview consumers + // that track Alt/Cmd/Shift via flagsChanged) rely on. + let repeat: bool = if event_type == NSEventType::NSKeyDown { + msg_send![event as *mut AnyObject, isARepeat] + } else { + false + }; let key = if let Some(key) = code_to_key(code) { key } else { diff --git a/src/macos/mod.rs b/src/macos/mod.rs index d5e7f591..02a0e36f 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -1,7 +1,3 @@ -// This is required because the objc crate is causing a lot of warnings: https://github.com/SSheldon/rust-objc/issues/125 -// Eventually we should migrate to the objc2 crate and remove this. -#![allow(unexpected_cfgs)] - mod keyboard; mod view; mod window; diff --git a/src/macos/view.rs b/src/macos/view.rs index 063ba24c..8f5cda91 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -1,18 +1,67 @@ -use std::ffi::c_void; +use std::ffi::{c_void, CStr, CString}; use cocoa::appkit::{NSEvent, NSFilenamesPboardType, NSView, NSWindow}; -use cocoa::base::{id, nil, BOOL, NO, YES}; +use cocoa::base::{id, nil, NO}; use cocoa::foundation::{NSArray, NSPoint, NSRect, NSSize, NSUInteger}; -use objc::{ - class, - declare::ClassDecl, - msg_send, - runtime::{Class, Object, Sel}, - sel, sel_impl, +use objc2::{ + class, msg_send, + runtime::{AnyClass, AnyObject, Bool as ObjcBool, ClassBuilder, Sel}, + sel, Encode, Encoding, }; use uuid::Uuid; +/// `CGPoint`/`CGSize`/`CGRect` clones carrying an `objc2::Encode` impl. Layout-identical to +/// cocoa's `NSPoint`/`NSSize`/`NSRect`, so `From` is a field-wise copy. We need these because +/// cocoa's types are external and can't implement objc2's `Encode` trait. +#[repr(C)] +#[derive(Clone, Copy, Debug)] +struct CgPoint { + x: f64, + y: f64, +} +#[repr(C)] +#[derive(Clone, Copy, Debug)] +struct CgSize { + width: f64, + height: f64, +} +#[repr(C)] +#[derive(Clone, Copy, Debug)] +struct CgRect { + origin: CgPoint, + size: CgSize, +} + +unsafe impl Encode for CgPoint { + const ENCODING: Encoding = + Encoding::Struct("CGPoint", &[::ENCODING, ::ENCODING]); +} +unsafe impl Encode for CgSize { + const ENCODING: Encoding = + Encoding::Struct("CGSize", &[::ENCODING, ::ENCODING]); +} +unsafe impl Encode for CgRect { + const ENCODING: Encoding = + Encoding::Struct("CGRect", &[::ENCODING, ::ENCODING]); +} + +impl From for NSRect { + fn from(r: CgRect) -> Self { + NSRect::new(NSPoint::new(r.origin.x, r.origin.y), NSSize::new(r.size.width, r.size.height)) + } +} +impl From for CgPoint { + fn from(p: NSPoint) -> Self { + CgPoint { x: p.x, y: p.y } + } +} +impl From for NSPoint { + fn from(p: CgPoint) -> Self { + NSPoint::new(p.x, p.y) + } +} + use crate::MouseEvent::{ButtonPressed, ButtonReleased}; use crate::{ DropData, DropEffect, Event, EventStatus, MouseButton, MouseEvent, Point, ScrollDelta, Size, @@ -38,15 +87,15 @@ extern "C" { macro_rules! add_simple_mouse_class_method { ($class:ident, $sel:ident, $event:expr) => { #[allow(non_snake_case)] - extern "C" fn $sel(this: &Object, _: Sel, _: id){ - let state = unsafe { WindowState::from_view(this) }; + extern "C-unwind" fn $sel(this: *const AnyObject, _: Sel, _: *mut AnyObject){ + let state = unsafe { WindowState::from_view(&*this) }; state.trigger_event(Event::Mouse($event)); } $class.add_method( sel!($sel:), - $sel as extern "C" fn(&Object, Sel, id), + $sel as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), ); }; } @@ -56,10 +105,10 @@ macro_rules! add_simple_mouse_class_method { macro_rules! add_mouse_button_class_method { ($class:ident, $sel:ident, $event_ty:ident, $button:expr) => { #[allow(non_snake_case)] - extern "C" fn $sel(this: &Object, _: Sel, event: id){ - let state = unsafe { WindowState::from_view(this) }; + extern "C-unwind" fn $sel(this: *const AnyObject, _: Sel, event: *mut AnyObject){ + let state = unsafe { WindowState::from_view(&*this) }; - let modifiers = unsafe { NSEvent::modifierFlags(event) }; + let modifiers = unsafe { NSEvent::modifierFlags(event as id) }; state.trigger_event(Event::Mouse($event_ty { button: $button, @@ -69,7 +118,7 @@ macro_rules! add_mouse_button_class_method { $class.add_method( sel!($sel:), - $sel as extern "C" fn(&Object, Sel, id), + $sel as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), ); }; } @@ -77,17 +126,17 @@ macro_rules! add_mouse_button_class_method { macro_rules! add_simple_keyboard_class_method { ($class:ident, $sel:ident) => { #[allow(non_snake_case)] - extern "C" fn $sel(this: &Object, _: Sel, event: id){ - let state = unsafe { WindowState::from_view(this) }; + extern "C-unwind" fn $sel(this: *const AnyObject, _: Sel, event: *mut AnyObject){ + let state = unsafe { WindowState::from_view(&*this) }; if let Some(key_event) = state.process_native_key_event(event){ let status = state.trigger_event(Event::Keyboard(key_event)); if let EventStatus::Ignored = status { unsafe { - let superclass = msg_send![this, superclass]; + let superclass: &AnyClass = msg_send![this, superclass]; - let () = msg_send![super(this, superclass), $sel:event]; + let () = msg_send![super(&*this, superclass), $sel:event]; } } } @@ -95,27 +144,29 @@ macro_rules! add_simple_keyboard_class_method { $class.add_method( sel!($sel:), - $sel as extern "C" fn(&Object, Sel, id), + $sel as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), ); }; } unsafe fn register_notification(observer: id, notification_name: id, object: id) { - let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter]; + let notification_center: *mut AnyObject = + msg_send![class!(NSNotificationCenter), defaultCenter]; let _: () = msg_send![ notification_center, - addObserver:observer - selector:sel!(handleNotification:) - name:notification_name - object:object + addObserver: observer as *mut AnyObject, + selector: sel!(handleNotification:), + name: notification_name as *mut AnyObject, + object: object as *mut AnyObject, ]; } pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id { let class = create_view_class(); - let view: id = msg_send![class, alloc]; + let view_any: *mut AnyObject = msg_send![class, alloc]; + let view: id = view_any as id; let size = window_options.size; @@ -124,91 +175,118 @@ pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id { register_notification(view, NSWindowDidBecomeKeyNotification, nil); register_notification(view, NSWindowDidResignKeyNotification, nil); - let _: id = msg_send![ - view, - registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType]) - ]; + let drag_types = NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType]) as *mut AnyObject; + let _: () = msg_send![view as *mut AnyObject, registerForDraggedTypes: drag_types]; view } -unsafe fn create_view_class() -> &'static Class { +unsafe fn create_view_class() -> &'static AnyClass { // Use unique class names so that there are no conflicts between different // instances. The class is deleted when the view is released. Previously, // the class was stored in a OnceCell after creation. This way, we didn't // have to recreate it each time a view was opened, but now we don't leave // any class definitions lying around when the plugin is closed. - let class_name = format!("BaseviewNSView_{}", Uuid::new_v4().to_simple()); - let mut class = ClassDecl::new(&class_name, class!(NSView)).unwrap(); + let class_name = + CString::new(format!("BaseviewNSView_{}", Uuid::new_v4().to_simple())).unwrap(); + let mut class = ClassBuilder::new(&class_name, class!(NSView)).unwrap(); class.add_method( sel!(acceptsFirstResponder), - property_yes as extern "C" fn(&Object, Sel) -> BOOL, + property_yes as extern "C-unwind" fn(*const AnyObject, Sel) -> ObjcBool, ); class.add_method( sel!(becomeFirstResponder), - become_first_responder as extern "C" fn(&Object, Sel) -> BOOL, + become_first_responder as extern "C-unwind" fn(*const AnyObject, Sel) -> ObjcBool, ); class.add_method( sel!(resignFirstResponder), - resign_first_responder as extern "C" fn(&Object, Sel) -> BOOL, + resign_first_responder as extern "C-unwind" fn(*const AnyObject, Sel) -> ObjcBool, + ); + class.add_method( + sel!(isFlipped), + property_yes as extern "C-unwind" fn(*const AnyObject, Sel) -> ObjcBool, ); - class.add_method(sel!(isFlipped), property_yes as extern "C" fn(&Object, Sel) -> BOOL); class.add_method( sel!(preservesContentInLiveResize), - property_no as extern "C" fn(&Object, Sel) -> BOOL, + property_no as extern "C-unwind" fn(*const AnyObject, Sel) -> ObjcBool, ); class.add_method( sel!(acceptsFirstMouse:), - accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL, + accepts_first_mouse + as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject) -> ObjcBool, ); class.add_method( sel!(windowShouldClose:), - window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, + window_should_close + as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject) -> ObjcBool, ); - class.add_method(sel!(dealloc), dealloc as extern "C" fn(&mut Object, Sel)); + class.add_method(sel!(dealloc), dealloc as extern "C-unwind" fn(*mut AnyObject, Sel)); class.add_method( sel!(viewWillMoveToWindow:), - view_will_move_to_window as extern "C" fn(&Object, Sel, id), + view_will_move_to_window as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), ); class.add_method( sel!(updateTrackingAreas:), - update_tracking_areas as extern "C" fn(&Object, Sel, id), + update_tracking_areas as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), ); - class.add_method(sel!(mouseMoved:), mouse_moved as extern "C" fn(&Object, Sel, id)); - class.add_method(sel!(mouseDragged:), mouse_moved as extern "C" fn(&Object, Sel, id)); - class.add_method(sel!(rightMouseDragged:), mouse_moved as extern "C" fn(&Object, Sel, id)); - class.add_method(sel!(otherMouseDragged:), mouse_moved as extern "C" fn(&Object, Sel, id)); + class.add_method( + sel!(mouseMoved:), + mouse_moved as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), + ); + class.add_method( + sel!(mouseDragged:), + mouse_moved as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), + ); + class.add_method( + sel!(rightMouseDragged:), + mouse_moved as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), + ); + class.add_method( + sel!(otherMouseDragged:), + mouse_moved as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), + ); - class.add_method(sel!(scrollWheel:), scroll_wheel as extern "C" fn(&Object, Sel, id)); + class.add_method( + sel!(scrollWheel:), + scroll_wheel as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), + ); class.add_method( sel!(viewDidChangeBackingProperties:), - view_did_change_backing_properties as extern "C" fn(&Object, Sel, id), + view_did_change_backing_properties + as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), ); class.add_method( sel!(draggingEntered:), - dragging_entered as extern "C" fn(&Object, Sel, id) -> NSUInteger, + dragging_entered + as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject) -> NSUInteger, ); class.add_method( sel!(prepareForDragOperation:), - prepare_for_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, + prepare_for_drag_operation + as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject) -> ObjcBool, ); class.add_method( sel!(performDragOperation:), - perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, + perform_drag_operation + as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject) -> ObjcBool, ); class.add_method( sel!(draggingUpdated:), - dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger, + dragging_updated + as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject) -> NSUInteger, + ); + class.add_method( + sel!(draggingExited:), + dragging_exited as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), ); - class.add_method(sel!(draggingExited:), dragging_exited as extern "C" fn(&Object, Sel, id)); class.add_method( sel!(handleNotification:), - handle_notification as extern "C" fn(&Object, Sel, id), + handle_notification as extern "C-unwind" fn(*const AnyObject, Sel, *mut AnyObject), ); add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left); @@ -224,30 +302,33 @@ unsafe fn create_view_class() -> &'static Class { add_simple_keyboard_class_method!(class, keyUp); add_simple_keyboard_class_method!(class, flagsChanged); - class.add_ivar::<*mut c_void>(BASEVIEW_STATE_IVAR); + let ivar_name = CString::new(BASEVIEW_STATE_IVAR).unwrap(); + class.add_ivar::<*mut c_void>(&ivar_name); class.register() } -extern "C" fn property_yes(_this: &Object, _sel: Sel) -> BOOL { - YES +extern "C-unwind" fn property_yes(_this: *const AnyObject, _sel: Sel) -> ObjcBool { + ObjcBool::YES } -extern "C" fn property_no(_this: &Object, _sel: Sel) -> BOOL { - NO +extern "C-unwind" fn property_no(_this: *const AnyObject, _sel: Sel) -> ObjcBool { + ObjcBool::NO } -extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL { - YES +extern "C-unwind" fn accepts_first_mouse( + _this: *const AnyObject, _sel: Sel, _event: *mut AnyObject, +) -> ObjcBool { + ObjcBool::YES } -extern "C" fn become_first_responder(this: &Object, _sel: Sel) -> BOOL { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn become_first_responder(this: *const AnyObject, _sel: Sel) -> ObjcBool { + let state = unsafe { WindowState::from_view(&*this) }; let is_key_window = unsafe { - let window: id = msg_send![this, window]; - if window != nil { - let is_key_window: BOOL = msg_send![window, isKeyWindow]; - is_key_window == YES + let window: *mut AnyObject = msg_send![this, window]; + if !window.is_null() { + let is_key_window: ObjcBool = msg_send![window, isKeyWindow]; + is_key_window.as_bool() } else { false } @@ -255,47 +336,52 @@ extern "C" fn become_first_responder(this: &Object, _sel: Sel) -> BOOL { if is_key_window { state.trigger_deferrable_event(Event::Window(WindowEvent::Focused)); } - YES + ObjcBool::YES } -extern "C" fn resign_first_responder(this: &Object, _sel: Sel) -> BOOL { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn resign_first_responder(this: *const AnyObject, _sel: Sel) -> ObjcBool { + let state = unsafe { WindowState::from_view(&*this) }; state.trigger_deferrable_event(Event::Window(WindowEvent::Unfocused)); - YES + ObjcBool::YES } -extern "C" fn window_should_close(this: &Object, _: Sel, _sender: id) -> BOOL { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn window_should_close( + this: *const AnyObject, _: Sel, _sender: *mut AnyObject, +) -> ObjcBool { + let state = unsafe { WindowState::from_view(&*this) }; state.trigger_event(Event::Window(WindowEvent::WillClose)); state.window_inner.close(); - NO + ObjcBool::NO } -extern "C" fn dealloc(this: &mut Object, _sel: Sel) { +extern "C-unwind" fn dealloc(this: *mut AnyObject, _sel: Sel) { unsafe { - let class = msg_send![this, class]; + let class: *const AnyClass = msg_send![this, class]; - let superclass = msg_send![this, superclass]; - let () = msg_send![super(this, superclass), dealloc]; + let superclass: &AnyClass = msg_send![this, superclass]; + let () = msg_send![super(&mut *this, superclass), dealloc]; // Delete class - ::objc::runtime::objc_disposeClassPair(class); + objc2::ffi::objc_disposeClassPair(class as *mut _); } } -extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel, _: id) { +extern "C-unwind" fn view_did_change_backing_properties( + this: *const AnyObject, _: Sel, _: *mut AnyObject, +) { unsafe { - let ns_window: *mut Object = msg_send![this, window]; + let ns_window: *mut AnyObject = msg_send![this, window]; let scale_factor: f64 = - if ns_window.is_null() { 1.0 } else { NSWindow::backingScaleFactor(ns_window) }; + if ns_window.is_null() { 1.0 } else { NSWindow::backingScaleFactor(ns_window as id) }; - let state = WindowState::from_view(this); + let state = WindowState::from_view(&*this); - let bounds: NSRect = msg_send![this, bounds]; + let bounds_raw: CgRect = msg_send![this, bounds]; + let bounds: NSRect = bounds_raw.into(); let new_window_info = WindowInfo::from_logical_size( Size::new(bounds.size.width, bounds.size.height), @@ -319,7 +405,7 @@ extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel, _: id) { /// https://developer.apple.com/documentation/appkit/nstrackingarea /// https://developer.apple.com/documentation/appkit/nstrackingarea/options /// https://developer.apple.com/documentation/appkit/nstrackingareaoptions -unsafe fn reinit_tracking_area(this: &Object, tracking_area: *mut Object) { +unsafe fn reinit_tracking_area(this: *const AnyObject, tracking_area: *mut AnyObject) { let options: usize = { let mouse_entered_and_exited = 0x01; let tracking_mouse_moved = 0x02; @@ -336,69 +422,85 @@ unsafe fn reinit_tracking_area(this: &Object, tracking_area: *mut Object) { | tracking_enabled_during_mouse_drag }; - let bounds: NSRect = msg_send![this, bounds]; + let bounds_raw: CgRect = msg_send![this, bounds]; - *tracking_area = msg_send![tracking_area, - initWithRect:bounds - options:options - owner:this - userInfo:nil + let _: *mut AnyObject = msg_send![tracking_area, + initWithRect: bounds_raw, + options: options, + owner: this, + userInfo: std::ptr::null_mut::(), ]; } -extern "C" fn view_will_move_to_window(this: &Object, _self: Sel, new_window: id) { +extern "C-unwind" fn view_will_move_to_window( + this: *const AnyObject, _self: Sel, new_window: *mut AnyObject, +) { unsafe { - let tracking_areas: *mut Object = msg_send![this, trackingAreas]; - let tracking_area_count = NSArray::count(tracking_areas); + let tracking_areas: *mut AnyObject = msg_send![this, trackingAreas]; + let tracking_area_count = NSArray::count(tracking_areas as id); - if new_window == nil { + if new_window.is_null() { if tracking_area_count != 0 { - let tracking_area = NSArray::objectAtIndex(tracking_areas, 0); + let tracking_area = NSArray::objectAtIndex(tracking_areas as id, 0); - let _: () = msg_send![this, removeTrackingArea: tracking_area]; - let _: () = msg_send![tracking_area, release]; + let _: () = msg_send![this, removeTrackingArea: tracking_area as *mut AnyObject]; + let _: () = msg_send![tracking_area as *mut AnyObject, release]; } } else { if tracking_area_count == 0 { - let class = Class::get("NSTrackingArea").unwrap(); + let class = + AnyClass::get(CStr::from_bytes_with_nul(b"NSTrackingArea\0").unwrap()).unwrap(); - let tracking_area: *mut Object = msg_send![class, alloc]; + let tracking_area: *mut AnyObject = msg_send![class, alloc]; reinit_tracking_area(this, tracking_area); let _: () = msg_send![this, addTrackingArea: tracking_area]; } - let _: () = msg_send![new_window, setAcceptsMouseMovedEvents: YES]; - let _: () = msg_send![new_window, makeFirstResponder: this]; + let _: () = msg_send![new_window, setAcceptsMouseMovedEvents: ObjcBool::YES]; + let _: ObjcBool = msg_send![new_window, makeFirstResponder: this]; } } unsafe { - let superclass = msg_send![this, superclass]; + let superclass: &AnyClass = msg_send![this, superclass]; - let () = msg_send![super(this, superclass), viewWillMoveToWindow: new_window]; + let () = msg_send![super(&*this, superclass), viewWillMoveToWindow: new_window]; } } -extern "C" fn update_tracking_areas(this: &Object, _self: Sel, _: id) { +extern "C-unwind" fn update_tracking_areas(this: *const AnyObject, _self: Sel, _: *mut AnyObject) { unsafe { - let tracking_areas: *mut Object = msg_send![this, trackingAreas]; - let tracking_area = NSArray::objectAtIndex(tracking_areas, 0); + let tracking_areas: *mut AnyObject = msg_send![this, trackingAreas]; + // Guard against `objectAtIndex:` raising NSRangeException — the + // companion `view_will_move_to_window` site already does this; mirror + // it here so an unwind out of this `extern "C-unwind"` callback can't + // happen if AppKit ever invokes `updateTrackingAreas:` before the + // first tracking area has been installed. + if NSArray::count(tracking_areas as id) == 0 { + return; + } + let tracking_area = NSArray::objectAtIndex(tracking_areas as id, 0); - reinit_tracking_area(this, tracking_area); + reinit_tracking_area(this, tracking_area as *mut AnyObject); } } -extern "C" fn mouse_moved(this: &Object, _sel: Sel, event: id) { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn mouse_moved(this: *const AnyObject, _sel: Sel, event: *mut AnyObject) { + let state = unsafe { WindowState::from_view(&*this) }; let point: NSPoint = unsafe { - let point = NSEvent::locationInWindow(event); - - msg_send![this, convertPoint:point fromView:nil] + let raw: CgPoint = CgPoint::from(NSEvent::locationInWindow(event as id)); + + let converted: CgPoint = msg_send![ + this, + convertPoint: raw, + fromView: std::ptr::null_mut::(), + ]; + converted.into() }; - let modifiers = unsafe { NSEvent::modifierFlags(event) }; + let modifiers = unsafe { NSEvent::modifierFlags(event as id) }; let position = Point { x: point.x, y: point.y }; @@ -408,21 +510,21 @@ extern "C" fn mouse_moved(this: &Object, _sel: Sel, event: id) { })); } -extern "C" fn scroll_wheel(this: &Object, _: Sel, event: id) { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn scroll_wheel(this: *const AnyObject, _: Sel, event: *mut AnyObject) { + let state = unsafe { WindowState::from_view(&*this) }; let delta = unsafe { - let x = NSEvent::scrollingDeltaX(event) as f32; - let y = NSEvent::scrollingDeltaY(event) as f32; + let x = NSEvent::scrollingDeltaX(event as id) as f32; + let y = NSEvent::scrollingDeltaY(event as id) as f32; - if NSEvent::hasPreciseScrollingDeltas(event) != NO { + if NSEvent::hasPreciseScrollingDeltas(event as id) != NO { ScrollDelta::Pixels { x, y } } else { ScrollDelta::Lines { x, y } } }; - let modifiers = unsafe { NSEvent::modifierFlags(event) }; + let modifiers = unsafe { NSEvent::modifierFlags(event as id) }; state.trigger_event(Event::Mouse(MouseEvent::WheelScrolled { delta, @@ -431,7 +533,7 @@ extern "C" fn scroll_wheel(this: &Object, _: Sel, event: id) { } fn get_drag_position(sender: id) -> Point { - let point: NSPoint = unsafe { msg_send![sender, draggingLocation] }; + let point: CgPoint = unsafe { msg_send![sender as *mut AnyObject, draggingLocation] }; Point::new(point.x, point.y) } @@ -441,16 +543,17 @@ fn get_drop_data(sender: id) -> DropData { } unsafe { - let pasteboard: id = msg_send![sender, draggingPasteboard]; - let file_list: id = msg_send![pasteboard, propertyListForType: NSFilenamesPboardType]; + let pasteboard: *mut AnyObject = msg_send![sender as *mut AnyObject, draggingPasteboard]; + let pboard_type = NSFilenamesPboardType as *mut AnyObject; + let file_list: *mut AnyObject = msg_send![pasteboard, propertyListForType: pboard_type]; - if file_list == nil { + if file_list.is_null() { return DropData::None; } let mut files = vec![]; - for i in 0..NSArray::count(file_list) { - let data = NSArray::objectAtIndex(file_list, i); + for i in 0..NSArray::count(file_list as id) { + let data = NSArray::objectAtIndex(file_list as id, i); files.push(from_nsstring(data).into()); } @@ -469,13 +572,15 @@ fn on_event(window_state: &WindowState, event: MouseEvent) -> NSUInteger { } } -extern "C" fn dragging_entered(this: &Object, _sel: Sel, sender: id) -> NSUInteger { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn dragging_entered( + this: *const AnyObject, _sel: Sel, sender: *mut AnyObject, +) -> NSUInteger { + let state = unsafe { WindowState::from_view(&*this) }; let modifiers = state.keyboard_state().last_mods(); - let drop_data = get_drop_data(sender); + let drop_data = get_drop_data(sender as id); let event = MouseEvent::DragEntered { - position: get_drag_position(sender), + position: get_drag_position(sender as id), modifiers: make_modifiers(modifiers), data: drop_data, }; @@ -483,13 +588,15 @@ extern "C" fn dragging_entered(this: &Object, _sel: Sel, sender: id) -> NSUInteg on_event(&state, event) } -extern "C" fn dragging_updated(this: &Object, _sel: Sel, sender: id) -> NSUInteger { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn dragging_updated( + this: *const AnyObject, _sel: Sel, sender: *mut AnyObject, +) -> NSUInteger { + let state = unsafe { WindowState::from_view(&*this) }; let modifiers = state.keyboard_state().last_mods(); - let drop_data = get_drop_data(sender); + let drop_data = get_drop_data(sender as id); let event = MouseEvent::DragMoved { - position: get_drag_position(sender), + position: get_drag_position(sender as id), modifiers: make_modifiers(modifiers), data: drop_data, }; @@ -497,56 +604,62 @@ extern "C" fn dragging_updated(this: &Object, _sel: Sel, sender: id) -> NSUInteg on_event(&state, event) } -extern "C" fn prepare_for_drag_operation(_this: &Object, _sel: Sel, _sender: id) -> BOOL { +extern "C-unwind" fn prepare_for_drag_operation( + _this: *const AnyObject, _sel: Sel, _sender: *mut AnyObject, +) -> ObjcBool { // Always accept drag operation if we get this far // This function won't be called unless dragging_entered/updated // has returned an acceptable operation - YES + ObjcBool::YES } -extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, sender: id) -> BOOL { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn perform_drag_operation( + this: *const AnyObject, _sel: Sel, sender: *mut AnyObject, +) -> ObjcBool { + let state = unsafe { WindowState::from_view(&*this) }; let modifiers = state.keyboard_state().last_mods(); - let drop_data = get_drop_data(sender); + let drop_data = get_drop_data(sender as id); let event = MouseEvent::DragDropped { - position: get_drag_position(sender), + position: get_drag_position(sender as id), modifiers: make_modifiers(modifiers), data: drop_data, }; let event_status = state.trigger_event(Event::Mouse(event)); match event_status { - EventStatus::AcceptDrop(_) => YES, - _ => NO, + EventStatus::AcceptDrop(_) => ObjcBool::YES, + _ => ObjcBool::NO, } } -extern "C" fn dragging_exited(this: &Object, _sel: Sel, _sender: id) { - let state = unsafe { WindowState::from_view(this) }; +extern "C-unwind" fn dragging_exited(this: *const AnyObject, _sel: Sel, _sender: *mut AnyObject) { + let state = unsafe { WindowState::from_view(&*this) }; on_event(&state, MouseEvent::DragLeft); } -extern "C" fn handle_notification(this: &Object, _cmd: Sel, notification: id) { +extern "C-unwind" fn handle_notification( + this: *const AnyObject, _cmd: Sel, notification: *mut AnyObject, +) { unsafe { - let state = WindowState::from_view(this); + let state = WindowState::from_view(&*this); // The subject of the notication, in this case an NSWindow object. - let notification_object: id = msg_send![notification, object]; + let notification_object: *mut AnyObject = msg_send![notification, object]; // The NSWindow object associated with our NSView. - let window: id = msg_send![this, window]; + let window: *mut AnyObject = msg_send![this, window]; - let first_responder: id = msg_send![window, firstResponder]; + let first_responder: *mut AnyObject = msg_send![window, firstResponder]; // Only trigger focus events if the NSWindow that's being notified about is our window, // and if the window's first responder is our NSView. // If the first responder isn't our NSView, the focus events will instead be triggered // by the becomeFirstResponder and resignFirstResponder methods on the NSView itself. - if notification_object == window && first_responder == this as *const Object as id { - let is_key_window: BOOL = msg_send![window, isKeyWindow]; - state.trigger_event(Event::Window(if is_key_window == YES { + if notification_object == window && first_responder == this as *mut AnyObject { + let is_key_window: ObjcBool = msg_send![window, isKeyWindow]; + state.trigger_event(Event::Window(if is_key_window.as_bool() { WindowEvent::Focused } else { WindowEvent::Unfocused diff --git a/src/macos/window.rs b/src/macos/window.rs index 57bca108..79d82464 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -8,14 +8,15 @@ use cocoa::appkit::{ NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered, NSPasteboard, NSView, NSWindow, NSWindowStyleMask, }; -use cocoa::base::{id, nil, BOOL, NO, YES}; +use cocoa::base::{id, nil, NO, YES}; use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString}; use core_foundation::runloop::{ - CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode, + __CFRunLoopTimer, kCFRunLoopDefaultMode, CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, }; use keyboard_types::KeyboardEvent; -use objc::class; -use objc::{msg_send, runtime::Object, sel, sel_impl}; +use objc2::class; +use objc2::msg_send; +use objc2::runtime::{AnyObject, Bool as ObjcBool}; use raw_window_handle::{ AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, @@ -74,7 +75,9 @@ impl WindowInner { self.open.set(false); unsafe { // Take back ownership of the NSView's Rc - let state_ptr: *const c_void = *(*self.ns_view).get_ivar(BASEVIEW_STATE_IVAR); + let ns_view_any = &*(self.ns_view as *mut AnyObject); + #[allow(deprecated)] + let state_ptr: *const c_void = *ns_view_any.get_ivar(BASEVIEW_STATE_IVAR); let window_state = Rc::from_raw(state_ptr as *mut WindowState); // Cancel the frame timer @@ -83,9 +86,10 @@ impl WindowInner { } // Deregister NSView from NotificationCenter. - let notification_center: id = + let notification_center: *mut AnyObject = msg_send![class!(NSNotificationCenter), defaultCenter]; - let () = msg_send![notification_center, removeObserver:self.ns_view]; + let () = + msg_send![notification_center, removeObserver: self.ns_view as *mut AnyObject]; drop(window_state); @@ -96,7 +100,7 @@ impl WindowInner { // Ensure that the NSView is detached from the parent window self.ns_view.removeFromSuperview(); - let () = msg_send![self.ns_view as id, release]; + let () = msg_send![self.ns_view as *mut AnyObject, release]; // If in non-parented mode, we want to also quit the app altogether let app = self.ns_app.take(); @@ -166,9 +170,12 @@ impl<'a> Window<'a> { let window_handle = Self::init(window_inner, window_info, build); unsafe { - let _: id = msg_send![handle.ns_view as *mut Object, addSubview: ns_view]; + let _: () = msg_send![ + handle.ns_view as *mut AnyObject, + addSubview: ns_view as *mut AnyObject + ]; - let () = msg_send![pool, drain]; + let () = msg_send![pool as *mut AnyObject, drain]; } window_handle @@ -244,7 +251,7 @@ impl<'a> Window<'a> { ns_window.setContentView_(ns_view); ns_window.setDelegate_(ns_view); - let () = msg_send![pool, drain]; + let () = msg_send![pool as *mut AnyObject, drain]; app.run(); } @@ -273,7 +280,12 @@ impl<'a> Window<'a> { let window_state_ptr = Rc::into_raw(Rc::clone(&window_state)); unsafe { - (*ns_view).set_ivar(BASEVIEW_STATE_IVAR, window_state_ptr as *const c_void); + let ns_view_any = &mut *(ns_view as *mut AnyObject); + #[allow(deprecated)] + { + *ns_view_any.get_mut_ivar::<*const c_void>(BASEVIEW_STATE_IVAR) = + window_state_ptr as *const c_void; + } WindowState::setup_timer(window_state_ptr); } @@ -287,24 +299,24 @@ impl<'a> Window<'a> { pub fn has_focus(&mut self) -> bool { unsafe { - let view = self.inner.ns_view.as_mut().unwrap(); - let window: id = msg_send![view, window]; - if window == nil { + let view = self.inner.ns_view as *mut AnyObject; + let window: *mut AnyObject = msg_send![view, window]; + if window.is_null() { return false; }; - let first_responder: id = msg_send![window, firstResponder]; - let is_key_window: BOOL = msg_send![window, isKeyWindow]; - let is_focused: BOOL = msg_send![view, isEqual: first_responder]; - is_key_window == YES && is_focused == YES + let first_responder: *mut AnyObject = msg_send![window, firstResponder]; + let is_key_window: ObjcBool = msg_send![window, isKeyWindow]; + let is_focused: ObjcBool = msg_send![view, isEqual: first_responder]; + is_key_window.as_bool() && is_focused.as_bool() } } pub fn focus(&mut self) { unsafe { - let view = self.inner.ns_view.as_mut().unwrap(); - let window: id = msg_send![view, window]; - if window != nil { - msg_send![window, makeFirstResponder:view] + let view = self.inner.ns_view as *mut AnyObject; + let window: *mut AnyObject = msg_send![view, window]; + if !window.is_null() { + let _: ObjcBool = msg_send![window, makeFirstResponder: view]; } } } @@ -317,7 +329,7 @@ impl<'a> Window<'a> { unsafe { NSView::setFrameSize(self.inner.ns_view, size) }; unsafe { - let _: () = msg_send![self.inner.ns_view, setNeedsDisplay: YES]; + let _: () = msg_send![self.inner.ns_view as *mut AnyObject, setNeedsDisplay: YES]; } // When using OpenGL the `NSOpenGLView` needs to be resized separately? Why? Because @@ -372,7 +384,8 @@ impl WindowState { /// This method returns a cloned `Rc` rather than just a `&WindowState`, since the /// original `Rc` owned by the `NSView` can be dropped at any time /// (including during an event handler). - pub(super) unsafe fn from_view(view: &Object) -> Rc { + pub(super) unsafe fn from_view(view: &AnyObject) -> Rc { + #[allow(deprecated)] let state_ptr: *const c_void = *view.get_ivar(BASEVIEW_STATE_IVAR); let state_rc = Rc::from_raw(state_ptr as *const WindowState); @@ -416,8 +429,8 @@ impl WindowState { &self.keyboard_state } - pub(super) fn process_native_key_event(&self, event: *mut Object) -> Option { - self.keyboard_state.process_native_event(event) + pub(super) fn process_native_key_event(&self, event: *mut AnyObject) -> Option { + self.keyboard_state.process_native_event(event as id) } unsafe fn setup_timer(window_state_ptr: *const WindowState) {