|
| 1 | +// Copyright 2023 The AccessKit Authors. All rights reserved. |
| 2 | +// Licensed under the Apache License, Version 2.0 (found in |
| 3 | +// the LICENSE-APACHE file) or the MIT license (found in |
| 4 | +// the LICENSE-MIT file), at your option. |
| 5 | + |
| 6 | +use objc2::{ |
| 7 | + declare::MethodImplementation, |
| 8 | + encode::{Encode, EncodeArguments, Encoding}, |
| 9 | + ffi::class_addMethod, |
| 10 | + msg_send, |
| 11 | + runtime::{Bool, Class, Object, Sel}, |
| 12 | + sel, Message, |
| 13 | +}; |
| 14 | +use std::{ffi::CString, ptr::null_mut}; |
| 15 | + |
| 16 | +use crate::appkit::NSWindow; |
| 17 | + |
| 18 | +extern "C" fn focus_forwarder(this: &NSWindow, _cmd: Sel) -> *mut Object { |
| 19 | + this.content_view().map_or_else(null_mut, |view| unsafe { |
| 20 | + msg_send![&*view, accessibilityFocusedUIElement] |
| 21 | + }) |
| 22 | +} |
| 23 | + |
| 24 | +/// Modifies the specified class, which must be a subclass of `NSWindow`, |
| 25 | +/// to include an `accessibilityFocusedUIElement` method that calls |
| 26 | +/// the corresponding method on the window's content view. This is needed |
| 27 | +/// for windowing libraries such as SDL that place the keyboard focus |
| 28 | +/// directly on the window rather than the content view. |
| 29 | +/// |
| 30 | +/// # Safety |
| 31 | +/// |
| 32 | +/// This function is declared unsafe because the caller must ensure that the |
| 33 | +/// code for this crate is never unloaded from the application process, |
| 34 | +/// since it's not possible to reverse this operation. It's safest |
| 35 | +/// if this crate is statically linked into the application's main executable. |
| 36 | +/// Also, this function assumes that the specified class is a subclass |
| 37 | +/// of `NSWindow`. |
| 38 | +pub unsafe fn add_focus_forwarder_to_window_class(class_name: &str) { |
| 39 | + let class = Class::get(class_name).unwrap(); |
| 40 | + unsafe { |
| 41 | + add_method( |
| 42 | + class as *const Class as *mut Class, |
| 43 | + sel!(accessibilityFocusedUIElement), |
| 44 | + focus_forwarder as unsafe extern "C" fn(_, _) -> _, |
| 45 | + ) |
| 46 | + }; |
| 47 | +} |
| 48 | + |
| 49 | +// The rest of this file is copied from objc2 with only minor adaptations, |
| 50 | +// to allow a method to be added to an existing class. |
| 51 | + |
| 52 | +unsafe fn add_method<T, F>(class: *mut Class, sel: Sel, func: F) |
| 53 | +where |
| 54 | + T: Message + ?Sized, |
| 55 | + F: MethodImplementation<Callee = T>, |
| 56 | +{ |
| 57 | + let encs = F::Args::ENCODINGS; |
| 58 | + let sel_args = count_args(sel); |
| 59 | + assert_eq!( |
| 60 | + sel_args, |
| 61 | + encs.len(), |
| 62 | + "Selector {:?} accepts {} arguments, but function accepts {}", |
| 63 | + sel, |
| 64 | + sel_args, |
| 65 | + encs.len(), |
| 66 | + ); |
| 67 | + |
| 68 | + let types = method_type_encoding(&F::Ret::ENCODING, encs); |
| 69 | + let success = Bool::from_raw(unsafe { |
| 70 | + class_addMethod( |
| 71 | + class as *mut _, |
| 72 | + sel.as_ptr(), |
| 73 | + Some(func.__imp()), |
| 74 | + types.as_ptr(), |
| 75 | + ) |
| 76 | + }); |
| 77 | + assert!(success.as_bool(), "Failed to add method {:?}", sel); |
| 78 | +} |
| 79 | + |
| 80 | +fn count_args(sel: Sel) -> usize { |
| 81 | + sel.name().chars().filter(|&c| c == ':').count() |
| 82 | +} |
| 83 | + |
| 84 | +fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString { |
| 85 | + // First two arguments are always self and the selector |
| 86 | + let mut types = format!("{}{}{}", ret, <*mut Object>::ENCODING, Sel::ENCODING); |
| 87 | + for enc in args { |
| 88 | + use core::fmt::Write; |
| 89 | + write!(&mut types, "{}", enc).unwrap(); |
| 90 | + } |
| 91 | + CString::new(types).unwrap() |
| 92 | +} |
0 commit comments