accesskit_macos/patch.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
// 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<T, F>(class: *mut AnyClass, sel: Sel, func: F)
where
T: Message + ?Sized,
F: MethodImplementation<Callee = T>,
{
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()
}