Skip to content

Commit c2db1b0

Browse files
authored
feat: Workaround for libraries that put the macOS keyboard focus on the window rather than the content view (#266)
1 parent b9b3cd1 commit c2db1b0

File tree

3 files changed

+125
-2
lines changed

3 files changed

+125
-2
lines changed

bindings/c/src/macos.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ use crate::{
77
action_handler, box_from_ptr, ref_from_ptr, tree_update, tree_update_factory, BoxCastPtr,
88
CastPtr,
99
};
10-
use accesskit_macos::{Adapter, NSPoint, QueuedEvents, SubclassingAdapter};
11-
use std::{os::raw::c_void, ptr};
10+
use accesskit_macos::{
11+
add_focus_forwarder_to_window_class, Adapter, NSPoint, QueuedEvents, SubclassingAdapter,
12+
};
13+
use std::{
14+
ffi::CStr,
15+
os::raw::{c_char, c_void},
16+
ptr,
17+
};
1218

1319
pub struct macos_queued_events {
1420
_private: [u8; 0],
@@ -200,3 +206,25 @@ impl macos_subclassing_adapter {
200206
}
201207
}
202208
}
209+
210+
/// Modifies the specified class, which must be a subclass of `NSWindow`,
211+
/// to include an `accessibilityFocusedUIElement` method that calls
212+
/// the corresponding method on the window's content view. This is needed
213+
/// for windowing libraries such as SDL that place the keyboard focus
214+
/// directly on the window rather than the content view.
215+
///
216+
/// # Safety
217+
///
218+
/// This function is declared unsafe because the caller must ensure that the
219+
/// code for this library is never unloaded from the application process,
220+
/// since it's not possible to reverse this operation. It's safest
221+
/// if this library is statically linked into the application's main executable.
222+
/// Also, this function assumes that the specified class is a subclass
223+
/// of `NSWindow`.
224+
#[no_mangle]
225+
pub unsafe extern "C" fn accesskit_macos_add_focus_forwarder_to_window_class(
226+
class_name: *const c_char,
227+
) {
228+
let class_name = unsafe { CStr::from_ptr(class_name).to_string_lossy() };
229+
add_focus_forwarder_to_window_class(&class_name);
230+
}

platforms/macos/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ pub use adapter::Adapter;
1616
mod event;
1717
pub use event::QueuedEvents;
1818

19+
mod patch;
20+
pub use patch::add_focus_forwarder_to_window_class;
21+
1922
mod subclass;
2023
pub use subclass::SubclassingAdapter;
2124

platforms/macos/src/patch.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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

Comments
 (0)