// Copyright 2023 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. use objc2::{ encode::{Encode, EncodeArguments, EncodeReturn, Encoding}, ffi::class_addMethod, msg_send, runtime::{AnyClass, AnyObject, Bool, MethodImplementation, Sel}, sel, Message, }; use objc2_app_kit::NSWindow; use std::{ffi::CString, ptr::null_mut}; extern "C" fn focus_forwarder(this: &NSWindow, _cmd: Sel) -> *mut AnyObject { unsafe { this.contentView().map_or_else(null_mut, |view| { msg_send![&*view, accessibilityFocusedUIElement] }) } } /// Modifies the specified class, which must be a subclass of `NSWindow`, /// to include an `accessibilityFocusedUIElement` method that calls /// the corresponding method on the window's content view. This is needed /// for windowing libraries such as SDL that place the keyboard focus /// directly on the window rather than the content view. /// /// # Safety /// /// This function is declared unsafe because the caller must ensure that the /// code for this crate is never unloaded from the application process, /// since it's not possible to reverse this operation. It's safest /// if this crate is statically linked into the application's main executable. /// Also, this function assumes that the specified class is a subclass /// of `NSWindow`. pub unsafe fn add_focus_forwarder_to_window_class(class_name: &str) { let class = AnyClass::get(class_name).unwrap(); unsafe { add_method( class as *const AnyClass as *mut AnyClass, sel!(accessibilityFocusedUIElement), focus_forwarder as unsafe extern "C" fn(_, _) -> _, ) }; } // The rest of this file is copied from objc2 with only minor adaptations, // to allow a method to be added to an existing class. unsafe fn add_method(class: *mut AnyClass, sel: Sel, func: F) where T: Message + ?Sized, F: MethodImplementation, { let encs = F::Arguments::ENCODINGS; let sel_args = count_args(sel); assert_eq!( sel_args, encs.len(), "Selector {:?} accepts {} arguments, but function accepts {}", sel, sel_args, encs.len(), ); let types = method_type_encoding(&F::Return::ENCODING_RETURN, encs); let success = Bool::from_raw(unsafe { class_addMethod( class as *mut _, sel.as_ptr(), Some(func.__imp()), types.as_ptr(), ) }); assert!(success.as_bool(), "Failed to add method {:?}", sel); } fn count_args(sel: Sel) -> usize { sel.name().chars().filter(|&c| c == ':').count() } fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString { // First two arguments are always self and the selector let mut types = format!("{}{}{}", ret, <*mut AnyObject>::ENCODING, Sel::ENCODING); for enc in args { use core::fmt::Write; write!(&mut types, "{}", enc).unwrap(); } CString::new(types).unwrap() }