93 lines
3.1 KiB
Rust
93 lines
3.1 KiB
Rust
// 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()
|
|
}
|