From 6a58a414320824f9962012e56996d6dced682f8a Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Mon, 7 Aug 2023 15:23:46 -0500 Subject: [PATCH 1/4] feat: Workaround for libraries that put the macOS keyboard focus on the window rather than the content view --- bindings/c/src/macos.rs | 30 +++++++++++- platforms/macos/src/lib.rs | 3 ++ platforms/macos/src/patch.rs | 90 ++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 platforms/macos/src/patch.rs diff --git a/bindings/c/src/macos.rs b/bindings/c/src/macos.rs index 0e778562e..1e84ffabe 100644 --- a/bindings/c/src/macos.rs +++ b/bindings/c/src/macos.rs @@ -7,8 +7,14 @@ use crate::{ action_handler, box_from_ptr, ref_from_ptr, tree_update, tree_update_factory, BoxCastPtr, CastPtr, }; -use accesskit_macos::{Adapter, NSPoint, QueuedEvents, SubclassingAdapter}; -use std::{os::raw::c_void, ptr}; +use accesskit_macos::{ + add_focus_forwarder_to_window_class, Adapter, NSPoint, QueuedEvents, SubclassingAdapter, +}; +use std::{ + ffi::CStr, + os::raw::{c_char, c_void}, + ptr, +}; pub struct macos_queued_events { _private: [u8; 0], @@ -200,3 +206,23 @@ impl macos_subclassing_adapter { } } } + +/// 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 library is never unloaded from the application process, +/// since it's not possible to reverse this operation. It's safest +/// if this library is statically linked into the application's main executable. +#[no_mangle] +pub unsafe extern "C" fn accesskit_macos_add_focus_forwarder_to_window_class( + class_name: *const c_char, +) { + let class_name = unsafe { CStr::from_ptr(class_name).to_string_lossy() }; + add_focus_forwarder_to_window_class(&class_name); +} diff --git a/platforms/macos/src/lib.rs b/platforms/macos/src/lib.rs index 2a13e03d2..0debbc7ed 100644 --- a/platforms/macos/src/lib.rs +++ b/platforms/macos/src/lib.rs @@ -16,6 +16,9 @@ pub use adapter::Adapter; mod event; pub use event::QueuedEvents; +mod patch; +pub use patch::add_focus_forwarder_to_window_class; + mod subclass; pub use subclass::SubclassingAdapter; diff --git a/platforms/macos/src/patch.rs b/platforms/macos/src/patch.rs new file mode 100644 index 000000000..b9c7cc11c --- /dev/null +++ b/platforms/macos/src/patch.rs @@ -0,0 +1,90 @@ +// Copyright 2022 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::{ + declare::MethodImplementation, + encode::{Encode, EncodeArguments, Encoding}, + ffi::class_addMethod, + msg_send, + runtime::{Bool, Class, Object, Sel}, + sel, Message, +}; +use std::{ffi::CString, ptr::null_mut}; + +use crate::appkit::NSWindow; + +unsafe extern "C" fn focus_forwarder(this: &NSWindow, _cmd: Sel) -> *mut Object { + this.content_view().map_or_else(null_mut, |view| unsafe { + 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. +pub unsafe fn add_focus_forwarder_to_window_class(class_name: &str) { + let class = Class::get(class_name).unwrap(); + unsafe { + add_method( + class as *const Class as *mut Class, + 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 Class, sel: Sel, func: F) +where + T: Message + ?Sized, + F: MethodImplementation, +{ + let encs = F::Args::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::Ret::ENCODING, 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 Object>::ENCODING, Sel::ENCODING); + for enc in args { + use core::fmt::Write; + write!(&mut types, "{}", enc).unwrap(); + } + CString::new(types).unwrap() +} From d0b8e6ea62ac0ce1bd7141c6ec6e396a3e4f9273 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Mon, 7 Aug 2023 16:10:01 -0500 Subject: [PATCH 2/4] Remove unnecessary 'unsafe' --- platforms/macos/src/patch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/macos/src/patch.rs b/platforms/macos/src/patch.rs index b9c7cc11c..b9000000b 100644 --- a/platforms/macos/src/patch.rs +++ b/platforms/macos/src/patch.rs @@ -15,7 +15,7 @@ use std::{ffi::CString, ptr::null_mut}; use crate::appkit::NSWindow; -unsafe extern "C" fn focus_forwarder(this: &NSWindow, _cmd: Sel) -> *mut Object { +extern "C" fn focus_forwarder(this: &NSWindow, _cmd: Sel) -> *mut Object { this.content_view().map_or_else(null_mut, |view| unsafe { msg_send![&*view, accessibilityFocusedUIElement] }) From 222616ab5e8182011df07d0326893baf08865102 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Mon, 7 Aug 2023 16:11:07 -0500 Subject: [PATCH 3/4] Fix copyright year --- platforms/macos/src/patch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/macos/src/patch.rs b/platforms/macos/src/patch.rs index b9000000b..711c8c239 100644 --- a/platforms/macos/src/patch.rs +++ b/platforms/macos/src/patch.rs @@ -1,4 +1,4 @@ -// Copyright 2022 The AccessKit Authors. All rights reserved. +// 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. From 9160a9e15d39a04c48ba91ae9e45b77d020a5720 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Mon, 7 Aug 2023 16:12:09 -0500 Subject: [PATCH 4/4] Mention another safety concern --- bindings/c/src/macos.rs | 2 ++ platforms/macos/src/patch.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/bindings/c/src/macos.rs b/bindings/c/src/macos.rs index 1e84ffabe..729ecce28 100644 --- a/bindings/c/src/macos.rs +++ b/bindings/c/src/macos.rs @@ -219,6 +219,8 @@ impl macos_subclassing_adapter { /// code for this library is never unloaded from the application process, /// since it's not possible to reverse this operation. It's safest /// if this library is statically linked into the application's main executable. +/// Also, this function assumes that the specified class is a subclass +/// of `NSWindow`. #[no_mangle] pub unsafe extern "C" fn accesskit_macos_add_focus_forwarder_to_window_class( class_name: *const c_char, diff --git a/platforms/macos/src/patch.rs b/platforms/macos/src/patch.rs index 711c8c239..0a131ebd7 100644 --- a/platforms/macos/src/patch.rs +++ b/platforms/macos/src/patch.rs @@ -33,6 +33,8 @@ extern "C" fn focus_forwarder(this: &NSWindow, _cmd: Sel) -> *mut Object { /// 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 = Class::get(class_name).unwrap(); unsafe {