Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

297
vendor/accesskit_macos/src/adapter.rs vendored Normal file
View File

@@ -0,0 +1,297 @@
// 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 crate::{
context::{ActionHandlerNoMut, ActionHandlerWrapper, Context},
event::{focus_event, EventGenerator, QueuedEvents},
filters::filter,
node::can_be_focused,
util::*,
};
use accesskit::{
ActionHandler, ActionRequest, ActivationHandler, Node as NodeProvider, NodeId, Role,
Tree as TreeData, TreeUpdate,
};
use accesskit_consumer::{FilterResult, Tree};
use objc2::rc::{Id, WeakId};
use objc2_app_kit::NSView;
use objc2_foundation::{MainThreadMarker, NSArray, NSObject, NSPoint};
use std::fmt::{Debug, Formatter};
use std::{ffi::c_void, ptr::null_mut, rc::Rc};
const PLACEHOLDER_ROOT_ID: NodeId = NodeId(0);
enum State {
Inactive {
view: WeakId<NSView>,
is_view_focused: bool,
action_handler: Rc<dyn ActionHandlerNoMut>,
mtm: MainThreadMarker,
},
Placeholder {
placeholder_context: Rc<Context>,
is_view_focused: bool,
action_handler: Rc<dyn ActionHandlerNoMut>,
},
Active(Rc<Context>),
}
impl Debug for State {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
State::Inactive {
view,
is_view_focused,
action_handler: _,
mtm,
} => f
.debug_struct("Inactive")
.field("view", view)
.field("is_view_focused", is_view_focused)
.field("mtm", mtm)
.finish(),
State::Placeholder {
placeholder_context,
is_view_focused,
action_handler: _,
} => f
.debug_struct("Placeholder")
.field("placeholder_context", placeholder_context)
.field("is_view_focused", is_view_focused)
.finish(),
State::Active(context) => f.debug_struct("Active").field("context", context).finish(),
}
}
}
struct PlaceholderActionHandler;
impl ActionHandler for PlaceholderActionHandler {
fn do_action(&mut self, _request: ActionRequest) {}
}
#[derive(Debug)]
pub struct Adapter {
state: State,
}
impl Adapter {
/// Create a new macOS adapter. This function must be called on
/// the main thread.
///
/// The action handler will always be called on the main thread.
///
/// # Safety
///
/// `view` must be a valid, unreleased pointer to an `NSView`.
pub unsafe fn new(
view: *mut c_void,
is_view_focused: bool,
action_handler: impl 'static + ActionHandler,
) -> Self {
let view = unsafe { Id::retain(view as *mut NSView) }.unwrap();
let view = WeakId::from_id(&view);
let mtm = MainThreadMarker::new().unwrap();
let state = State::Inactive {
view,
is_view_focused,
action_handler: Rc::new(ActionHandlerWrapper::new(action_handler)),
mtm,
};
Self { state }
}
/// If and only if the tree has been initialized, call the provided function
/// and apply the resulting update. Note: If the caller's implementation of
/// [`ActivationHandler::request_initial_tree`] initially returned `None`,
/// the [`TreeUpdate`] returned by the provided function must contain
/// a full tree.
///
/// If a [`QueuedEvents`] instance is returned, the caller must call
/// [`QueuedEvents::raise`] on it.
pub fn update_if_active(
&mut self,
update_factory: impl FnOnce() -> TreeUpdate,
) -> Option<QueuedEvents> {
match &self.state {
State::Inactive { .. } => None,
State::Placeholder {
placeholder_context,
is_view_focused,
action_handler,
} => {
let tree = Tree::new(update_factory(), *is_view_focused);
let context = Context::new(
placeholder_context.view.clone(),
tree,
Rc::clone(action_handler),
placeholder_context.mtm,
);
let result = context
.tree
.borrow()
.state()
.focus_id()
.map(|id| QueuedEvents::new(Rc::clone(&context), vec![focus_event(id)]));
self.state = State::Active(context);
result
}
State::Active(context) => {
let mut event_generator = EventGenerator::new(context.clone());
let mut tree = context.tree.borrow_mut();
tree.update_and_process_changes(update_factory(), &mut event_generator);
Some(event_generator.into_result())
}
}
}
/// Update the tree state based on whether the window is focused.
///
/// If a [`QueuedEvents`] instance is returned, the caller must call
/// [`QueuedEvents::raise`] on it.
pub fn update_view_focus_state(&mut self, is_focused: bool) -> Option<QueuedEvents> {
match &mut self.state {
State::Inactive {
is_view_focused, ..
} => {
*is_view_focused = is_focused;
None
}
State::Placeholder {
is_view_focused, ..
} => {
*is_view_focused = is_focused;
None
}
State::Active(context) => {
let mut event_generator = EventGenerator::new(context.clone());
let mut tree = context.tree.borrow_mut();
tree.update_host_focus_state_and_process_changes(is_focused, &mut event_generator);
Some(event_generator.into_result())
}
}
}
fn get_or_init_context<H: ActivationHandler + ?Sized>(
&mut self,
activation_handler: &mut H,
) -> Rc<Context> {
match &self.state {
State::Inactive {
view,
is_view_focused,
action_handler,
mtm,
} => match activation_handler.request_initial_tree() {
Some(initial_state) => {
let tree = Tree::new(initial_state, *is_view_focused);
let context = Context::new(view.clone(), tree, Rc::clone(action_handler), *mtm);
let result = Rc::clone(&context);
self.state = State::Active(context);
result
}
None => {
let placeholder_update = TreeUpdate {
nodes: vec![(PLACEHOLDER_ROOT_ID, NodeProvider::new(Role::Window))],
tree: Some(TreeData::new(PLACEHOLDER_ROOT_ID)),
focus: PLACEHOLDER_ROOT_ID,
};
let placeholder_tree = Tree::new(placeholder_update, false);
let placeholder_context = Context::new(
view.clone(),
placeholder_tree,
Rc::new(ActionHandlerWrapper::new(PlaceholderActionHandler {})),
*mtm,
);
let result = Rc::clone(&placeholder_context);
self.state = State::Placeholder {
placeholder_context,
is_view_focused: *is_view_focused,
action_handler: Rc::clone(action_handler),
};
result
}
},
State::Placeholder {
placeholder_context,
..
} => Rc::clone(placeholder_context),
State::Active(context) => Rc::clone(context),
}
}
pub fn view_children<H: ActivationHandler + ?Sized>(
&mut self,
activation_handler: &mut H,
) -> *mut NSArray<NSObject> {
let context = self.get_or_init_context(activation_handler);
let tree = context.tree.borrow();
let state = tree.state();
let node = state.root();
let platform_nodes = if filter(&node) == FilterResult::Include {
vec![Id::into_super(Id::into_super(
context.get_or_create_platform_node(node.id()),
))]
} else {
node.filtered_children(filter)
.map(|node| {
Id::into_super(Id::into_super(
context.get_or_create_platform_node(node.id()),
))
})
.collect::<Vec<Id<NSObject>>>()
};
let array = NSArray::from_vec(platform_nodes);
Id::autorelease_return(array)
}
pub fn focus<H: ActivationHandler + ?Sized>(
&mut self,
activation_handler: &mut H,
) -> *mut NSObject {
let context = self.get_or_init_context(activation_handler);
let tree = context.tree.borrow();
let state = tree.state();
if let Some(node) = state.focus() {
if can_be_focused(&node) {
return Id::autorelease_return(context.get_or_create_platform_node(node.id()))
as *mut _;
}
}
null_mut()
}
fn weak_view(&self) -> &WeakId<NSView> {
match &self.state {
State::Inactive { view, .. } => view,
State::Placeholder {
placeholder_context,
..
} => &placeholder_context.view,
State::Active(context) => &context.view,
}
}
pub fn hit_test<H: ActivationHandler + ?Sized>(
&mut self,
point: NSPoint,
activation_handler: &mut H,
) -> *mut NSObject {
let view = match self.weak_view().load() {
Some(view) => view,
None => {
return null_mut();
}
};
let context = self.get_or_init_context(activation_handler);
let tree = context.tree.borrow();
let state = tree.state();
let root = state.root();
let point = from_ns_point(&view, &root, point);
let node = root.node_at_point(point, &filter).unwrap_or(root);
Id::autorelease_return(context.get_or_create_platform_node(node.id())) as *mut _
}
}

103
vendor/accesskit_macos/src/context.rs vendored Normal file
View File

@@ -0,0 +1,103 @@
// 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 crate::node::PlatformNode;
use accesskit::{ActionHandler, ActionRequest, NodeId};
use accesskit_consumer::Tree;
use hashbrown::HashMap;
use objc2::rc::{Id, WeakId};
use objc2_app_kit::*;
use objc2_foundation::MainThreadMarker;
use std::fmt::Debug;
use std::{cell::RefCell, rc::Rc};
pub(crate) trait ActionHandlerNoMut {
fn do_action(&self, request: ActionRequest);
}
pub(crate) struct ActionHandlerWrapper<H: ActionHandler>(RefCell<H>);
impl<H: 'static + ActionHandler> ActionHandlerWrapper<H> {
pub(crate) fn new(inner: H) -> Self {
Self(RefCell::new(inner))
}
}
impl<H: ActionHandler> ActionHandlerNoMut for ActionHandlerWrapper<H> {
fn do_action(&self, request: ActionRequest) {
self.0.borrow_mut().do_action(request)
}
}
pub(crate) struct Context {
pub(crate) view: WeakId<NSView>,
pub(crate) tree: RefCell<Tree>,
pub(crate) action_handler: Rc<dyn ActionHandlerNoMut>,
platform_nodes: RefCell<HashMap<NodeId, Id<PlatformNode>>>,
pub(crate) mtm: MainThreadMarker,
}
impl Debug for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Context")
.field("view", &self.view)
.field("tree", &self.tree)
.field("action_handler", &"ActionHandler")
.field("platform_nodes", &self.platform_nodes)
.field("mtm", &self.mtm)
.finish()
}
}
impl Context {
pub(crate) fn new(
view: WeakId<NSView>,
tree: Tree,
action_handler: Rc<dyn ActionHandlerNoMut>,
mtm: MainThreadMarker,
) -> Rc<Self> {
Rc::new(Self {
view,
tree: RefCell::new(tree),
action_handler,
platform_nodes: RefCell::new(HashMap::new()),
mtm,
})
}
pub(crate) fn get_or_create_platform_node(self: &Rc<Self>, id: NodeId) -> Id<PlatformNode> {
let mut platform_nodes = self.platform_nodes.borrow_mut();
if let Some(result) = platform_nodes.get(&id) {
return result.clone();
}
let result = PlatformNode::new(Rc::downgrade(self), id);
platform_nodes.insert(id, result.clone());
result
}
pub(crate) fn remove_platform_node(&self, id: NodeId) -> Option<Id<PlatformNode>> {
let mut platform_nodes = self.platform_nodes.borrow_mut();
platform_nodes.remove(&id)
}
pub(crate) fn do_action(&self, request: ActionRequest) {
self.action_handler.do_action(request);
}
}
impl Drop for Context {
fn drop(&mut self) {
let platform_nodes = self.platform_nodes.borrow();
for platform_node in platform_nodes.values() {
unsafe {
NSAccessibilityPostNotification(
platform_node,
NSAccessibilityUIElementDestroyedNotification,
)
};
}
}
}

298
vendor/accesskit_macos/src/event.rs vendored Normal file
View File

@@ -0,0 +1,298 @@
// 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 accesskit::{Live, NodeId, Role};
use accesskit_consumer::{FilterResult, Node, TreeChangeHandler};
use hashbrown::HashSet;
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2_app_kit::*;
use objc2_foundation::{NSMutableDictionary, NSNumber, NSString};
use std::rc::Rc;
use crate::{context::Context, filters::filter, node::NodeWrapper};
// This type is designed to be safe to create on a non-main thread
// and send to the main thread. This ability isn't yet used though.
pub(crate) enum QueuedEvent {
Generic {
node_id: NodeId,
notification: &'static NSAccessibilityNotificationName,
},
NodeDestroyed(NodeId),
Announcement {
text: String,
priority: NSAccessibilityPriorityLevel,
},
}
impl QueuedEvent {
fn live_region_announcement(node: &Node) -> Self {
Self::Announcement {
text: node.value().unwrap(),
priority: if node.live() == Live::Assertive {
NSAccessibilityPriorityLevel::NSAccessibilityPriorityHigh
} else {
NSAccessibilityPriorityLevel::NSAccessibilityPriorityMedium
},
}
}
fn raise(self, context: &Rc<Context>) {
match self {
Self::Generic {
node_id,
notification,
} => {
let platform_node = context.get_or_create_platform_node(node_id);
unsafe { NSAccessibilityPostNotification(&platform_node, notification) };
}
Self::NodeDestroyed(node_id) => {
if let Some(platform_node) = context.remove_platform_node(node_id) {
unsafe {
NSAccessibilityPostNotification(
&platform_node,
NSAccessibilityUIElementDestroyedNotification,
)
};
}
}
Self::Announcement { text, priority } => {
let view = match context.view.load() {
Some(view) => view,
None => {
return;
}
};
let window = match view.window() {
Some(window) => window,
None => {
return;
}
};
let mut user_info = NSMutableDictionary::<_, AnyObject>::new();
let text = NSString::from_str(&text);
unsafe {
user_info.setObject_forKey(
&*text,
ProtocolObject::from_ref(NSAccessibilityAnnouncementKey),
)
};
let priority = NSNumber::new_isize(priority.0);
unsafe {
user_info.setObject_forKey(
&*priority,
ProtocolObject::from_ref(NSAccessibilityPriorityKey),
)
};
unsafe {
NSAccessibilityPostNotificationWithUserInfo(
&window,
NSAccessibilityAnnouncementRequestedNotification,
Some(&**user_info),
)
};
}
}
}
}
/// Events generated by a tree update.
#[must_use = "events must be explicitly raised"]
pub struct QueuedEvents {
context: Rc<Context>,
events: Vec<QueuedEvent>,
}
impl QueuedEvents {
pub(crate) fn new(context: Rc<Context>, events: Vec<QueuedEvent>) -> Self {
Self { context, events }
}
/// Raise all queued events synchronously.
///
/// It is unknown whether accessibility methods on the view may be
/// called while events are being raised. This means that any locks
/// or runtime borrows required to access the adapter must not
/// be held while this method is called.
pub fn raise(self) {
for event in self.events {
event.raise(&self.context);
}
}
}
pub(crate) fn focus_event(node_id: NodeId) -> QueuedEvent {
QueuedEvent::Generic {
node_id,
notification: unsafe { NSAccessibilityFocusedUIElementChangedNotification },
}
}
pub(crate) struct EventGenerator {
context: Rc<Context>,
events: Vec<QueuedEvent>,
text_changed: HashSet<NodeId>,
selected_rows_changed: HashSet<NodeId>,
}
impl EventGenerator {
pub(crate) fn new(context: Rc<Context>) -> Self {
Self {
context,
events: Vec::new(),
text_changed: HashSet::new(),
selected_rows_changed: HashSet::new(),
}
}
pub(crate) fn into_result(self) -> QueuedEvents {
QueuedEvents::new(self.context, self.events)
}
fn insert_text_change_if_needed_parent(&mut self, node: Node) {
if !node.supports_text_ranges() {
return;
}
let id = node.id();
if self.text_changed.contains(&id) {
return;
}
// Text change events must come before selection change
// events. It doesn't matter if text change events come
// before other events.
self.events.insert(
0,
QueuedEvent::Generic {
node_id: id,
notification: unsafe { NSAccessibilityValueChangedNotification },
},
);
self.text_changed.insert(id);
}
fn insert_text_change_if_needed(&mut self, node: &Node) {
if node.role() != Role::TextRun {
return;
}
if let Some(node) = node.filtered_parent(&filter) {
self.insert_text_change_if_needed_parent(node);
}
}
fn enqueue_selected_rows_change_if_needed_parent(&mut self, node: Node) {
if !node.is_container_with_selectable_children() {
return;
}
let id = node.id();
if self.selected_rows_changed.contains(&id) {
return;
}
self.events.push(QueuedEvent::Generic {
node_id: id,
notification: unsafe { NSAccessibilitySelectedRowsChangedNotification },
});
self.selected_rows_changed.insert(id);
}
fn enqueue_selected_rows_change_if_needed(&mut self, node: &Node) {
if !node.is_item_like() {
return;
}
if let Some(node) = node.selection_container(&filter) {
self.enqueue_selected_rows_change_if_needed_parent(node);
}
}
}
impl TreeChangeHandler for EventGenerator {
fn node_added(&mut self, node: &Node) {
self.insert_text_change_if_needed(node);
if filter(node) != FilterResult::Include {
return;
}
if let Some(true) = node.is_selected() {
self.enqueue_selected_rows_change_if_needed(node);
}
if node.value().is_some() && node.live() != Live::Off {
self.events
.push(QueuedEvent::live_region_announcement(node));
}
}
fn node_updated(&mut self, old_node: &Node, new_node: &Node) {
if old_node.raw_value() != new_node.raw_value() {
self.insert_text_change_if_needed(new_node);
}
let old_node_was_filtered_out = filter(old_node) != FilterResult::Include;
if filter(new_node) != FilterResult::Include {
if !old_node_was_filtered_out
&& old_node.is_item_like()
&& old_node.is_selected() == Some(true)
{
self.enqueue_selected_rows_change_if_needed(old_node);
}
return;
}
let node_id = new_node.id();
let old_wrapper = NodeWrapper(old_node);
let new_wrapper = NodeWrapper(new_node);
if old_wrapper.title() != new_wrapper.title() {
self.events.push(QueuedEvent::Generic {
node_id,
notification: unsafe { NSAccessibilityTitleChangedNotification },
});
}
if old_wrapper.value() != new_wrapper.value() {
self.events.push(QueuedEvent::Generic {
node_id,
notification: unsafe { NSAccessibilityValueChangedNotification },
});
}
if old_wrapper.supports_text_ranges()
&& new_wrapper.supports_text_ranges()
&& old_wrapper.raw_text_selection() != new_wrapper.raw_text_selection()
{
self.events.push(QueuedEvent::Generic {
node_id,
notification: unsafe { NSAccessibilitySelectedTextChangedNotification },
});
}
if new_node.value().is_some()
&& new_node.live() != Live::Off
&& (new_node.value() != old_node.value()
|| new_node.live() != old_node.live()
|| old_node_was_filtered_out)
{
self.events
.push(QueuedEvent::live_region_announcement(new_node));
}
if new_node.is_item_like()
&& (new_node.is_selected() != old_node.is_selected()
|| (old_node_was_filtered_out && new_node.is_selected() == Some(true)))
{
self.enqueue_selected_rows_change_if_needed(new_node);
}
}
fn focus_moved(&mut self, _old_node: Option<&Node>, new_node: Option<&Node>) {
if let Some(new_node) = new_node {
if filter(new_node) != FilterResult::Include {
return;
}
self.events.push(focus_event(new_node.id()));
}
}
fn node_removed(&mut self, node: &Node) {
self.insert_text_change_if_needed(node);
if let Some(true) = node.is_selected() {
self.enqueue_selected_rows_change_if_needed(node);
}
self.events.push(QueuedEvent::NodeDestroyed(node.id()));
}
}

6
vendor/accesskit_macos/src/filters.rs vendored Normal file
View File

@@ -0,0 +1,6 @@
// 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.
pub(crate) use accesskit_consumer::common_filter as filter;

25
vendor/accesskit_macos/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,25 @@
// 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.
#![deny(unsafe_op_in_unsafe_fn)]
mod context;
mod filters;
mod node;
mod util;
mod adapter;
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;
pub use objc2_foundation::{NSArray, NSObject, NSPoint};

1015
vendor/accesskit_macos/src/node.rs vendored Normal file

File diff suppressed because it is too large Load Diff

92
vendor/accesskit_macos/src/patch.rs vendored Normal file
View File

@@ -0,0 +1,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()
}

265
vendor/accesskit_macos/src/subclass.rs vendored Normal file
View File

@@ -0,0 +1,265 @@
// 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 accesskit::{ActionHandler, ActivationHandler, TreeUpdate};
use objc2::{
declare::ClassBuilder,
declare_class,
ffi::{
objc_getAssociatedObject, objc_setAssociatedObject, object_setClass,
OBJC_ASSOCIATION_RETAIN_NONATOMIC,
},
msg_send_id,
mutability::InteriorMutable,
rc::Id,
runtime::{AnyClass, Sel},
sel, ClassType, DeclaredClass,
};
use objc2_app_kit::{NSView, NSWindow};
use objc2_foundation::{NSArray, NSObject, NSPoint};
use std::{cell::RefCell, ffi::c_void, sync::Mutex};
use crate::{event::QueuedEvents, Adapter};
static SUBCLASSES: Mutex<Vec<(&'static AnyClass, &'static AnyClass)>> = Mutex::new(Vec::new());
static ASSOCIATED_OBJECT_KEY: u8 = 0;
fn associated_object_key() -> *const c_void {
(&ASSOCIATED_OBJECT_KEY as *const u8).cast()
}
struct AssociatedObjectState {
adapter: Adapter,
activation_handler: Box<dyn ActivationHandler>,
}
struct AssociatedObjectIvars {
state: RefCell<AssociatedObjectState>,
prev_class: &'static AnyClass,
}
declare_class!(
struct AssociatedObject;
unsafe impl ClassType for AssociatedObject {
type Super = NSObject;
type Mutability = InteriorMutable;
const NAME: &'static str = "AccessKitSubclassAssociatedObject";
}
impl DeclaredClass for AssociatedObject {
type Ivars = AssociatedObjectIvars;
}
);
impl AssociatedObject {
fn new(
adapter: Adapter,
activation_handler: impl 'static + ActivationHandler,
prev_class: &'static AnyClass,
) -> Id<Self> {
let state = RefCell::new(AssociatedObjectState {
adapter,
activation_handler: Box::new(activation_handler),
});
let this = Self::alloc().set_ivars(AssociatedObjectIvars { state, prev_class });
unsafe { msg_send_id![super(this), init] }
}
}
fn associated_object(view: &NSView) -> &AssociatedObject {
unsafe {
(objc_getAssociatedObject(view as *const NSView as *const _, associated_object_key())
as *const AssociatedObject)
.as_ref()
}
.unwrap()
}
// Some view classes, like the one in winit 0.27, assume that they are the
// lowest subclass, and call [self superclass] to get their superclass.
// Give them the answer they need.
unsafe extern "C" fn superclass(this: &NSView, _cmd: Sel) -> Option<&AnyClass> {
let associated = associated_object(this);
associated.ivars().prev_class.superclass()
}
unsafe extern "C" fn children(this: &NSView, _cmd: Sel) -> *mut NSArray<NSObject> {
let associated = associated_object(this);
let mut state = associated.ivars().state.borrow_mut();
let state_mut = &mut *state;
state_mut
.adapter
.view_children(&mut *state_mut.activation_handler)
}
unsafe extern "C" fn focus(this: &NSView, _cmd: Sel) -> *mut NSObject {
let associated = associated_object(this);
let mut state = associated.ivars().state.borrow_mut();
let state_mut = &mut *state;
state_mut.adapter.focus(&mut *state_mut.activation_handler)
}
unsafe extern "C" fn hit_test(this: &NSView, _cmd: Sel, point: NSPoint) -> *mut NSObject {
let associated = associated_object(this);
let mut state = associated.ivars().state.borrow_mut();
let state_mut = &mut *state;
state_mut
.adapter
.hit_test(point, &mut *state_mut.activation_handler)
}
/// Uses dynamic Objective-C subclassing to implement the `NSView`
/// accessibility methods when normal subclassing isn't an option.
pub struct SubclassingAdapter {
view: Id<NSView>,
associated: Id<AssociatedObject>,
}
impl SubclassingAdapter {
/// Create an adapter that dynamically subclasses the specified view.
/// This must be done before the view is shown or focused for
/// the first time.
///
/// The action handler will always be called on the main thread.
///
/// # Safety
///
/// `view` must be a valid, unreleased pointer to an `NSView`.
pub unsafe fn new(
view: *mut c_void,
activation_handler: impl 'static + ActivationHandler,
action_handler: impl 'static + ActionHandler,
) -> Self {
let view = view as *mut NSView;
let retained_view = unsafe { Id::retain(view) }.unwrap();
Self::new_internal(retained_view, activation_handler, action_handler)
}
fn new_internal(
retained_view: Id<NSView>,
activation_handler: impl 'static + ActivationHandler,
action_handler: impl 'static + ActionHandler,
) -> Self {
let view = Id::as_ptr(&retained_view) as *mut NSView;
let adapter = unsafe { Adapter::new(view as *mut c_void, false, action_handler) };
// Cast to a pointer and back to force the lifetime to 'static
// SAFETY: We know the class will live as long as the instance,
// and we only use this reference while the instance is alive.
let prev_class = unsafe { &*((*view).class() as *const AnyClass) };
let associated = AssociatedObject::new(adapter, activation_handler, prev_class);
unsafe {
objc_setAssociatedObject(
view as *mut _,
associated_object_key(),
Id::as_ptr(&associated) as *mut _,
OBJC_ASSOCIATION_RETAIN_NONATOMIC,
)
};
let mut subclasses = SUBCLASSES.lock().unwrap();
let subclass = match subclasses.iter().find(|entry| entry.0 == prev_class) {
Some(entry) => entry.1,
None => {
let name = format!("AccessKitSubclassOf{}", prev_class.name());
let mut builder = ClassBuilder::new(&name, prev_class).unwrap();
unsafe {
builder.add_method(
sel!(superclass),
superclass as unsafe extern "C" fn(_, _) -> _,
);
builder.add_method(
sel!(accessibilityChildren),
children as unsafe extern "C" fn(_, _) -> _,
);
builder.add_method(
sel!(accessibilityFocusedUIElement),
focus as unsafe extern "C" fn(_, _) -> _,
);
builder.add_method(
sel!(accessibilityHitTest:),
hit_test as unsafe extern "C" fn(_, _, _) -> _,
);
}
let class = builder.register();
subclasses.push((prev_class, class));
class
}
};
// SAFETY: Changing the view's class is only safe because
// the subclass doesn't add any instance variables;
// it uses an associated object instead.
unsafe { object_setClass(view as *mut _, (subclass as *const AnyClass).cast()) };
Self {
view: retained_view,
associated,
}
}
/// Create an adapter that dynamically subclasses the content view
/// of the specified window.
///
/// The action handler will always be called on the main thread.
///
/// # Safety
///
/// `window` must be a valid, unreleased pointer to an `NSWindow`.
///
/// # Panics
///
/// This function panics if the specified window doesn't currently have
/// a content view.
pub unsafe fn for_window(
window: *mut c_void,
activation_handler: impl 'static + ActivationHandler,
action_handler: impl 'static + ActionHandler,
) -> Self {
let window = unsafe { &*(window as *const NSWindow) };
let retained_view = window.contentView().unwrap();
Self::new_internal(retained_view, activation_handler, action_handler)
}
/// If and only if the tree has been initialized, call the provided function
/// and apply the resulting update. Note: If the caller's implementation of
/// [`ActivationHandler::request_initial_tree`] initially returned `None`,
/// the [`TreeUpdate`] returned by the provided function must contain
/// a full tree.
///
/// If a [`QueuedEvents`] instance is returned, the caller must call
/// [`QueuedEvents::raise`] on it.
pub fn update_if_active(
&mut self,
update_factory: impl FnOnce() -> TreeUpdate,
) -> Option<QueuedEvents> {
let mut state = self.associated.ivars().state.borrow_mut();
state.adapter.update_if_active(update_factory)
}
/// Update the tree state based on whether the window is focused.
///
/// If a [`QueuedEvents`] instance is returned, the caller must call
/// [`QueuedEvents::raise`] on it.
pub fn update_view_focus_state(&mut self, is_focused: bool) -> Option<QueuedEvents> {
let mut state = self.associated.ivars().state.borrow_mut();
state.adapter.update_view_focus_state(is_focused)
}
}
impl Drop for SubclassingAdapter {
fn drop(&mut self) {
let prev_class = self.associated.ivars().prev_class;
let view = Id::as_ptr(&self.view) as *mut NSView;
unsafe { object_setClass(view as *mut _, (prev_class as *const AnyClass).cast()) };
unsafe {
objc_setAssociatedObject(
view as *mut _,
associated_object_key(),
std::ptr::null_mut(),
OBJC_ASSOCIATION_RETAIN_NONATOMIC,
)
};
}
}

79
vendor/accesskit_macos/src/util.rs vendored Normal file
View File

@@ -0,0 +1,79 @@
// 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 accesskit::{Point, Rect};
use accesskit_consumer::{Node, TextPosition, TextRange};
use objc2_app_kit::*;
use objc2_foundation::{NSPoint, NSRange, NSRect, NSSize};
pub(crate) fn from_ns_range<'a>(node: &'a Node<'a>, ns_range: NSRange) -> Option<TextRange<'a>> {
let pos = node.text_position_from_global_utf16_index(ns_range.location)?;
let mut range = pos.to_degenerate_range();
if ns_range.length > 0 {
let end =
node.text_position_from_global_utf16_index(ns_range.location + ns_range.length)?;
range.set_end(end);
}
Some(range)
}
pub(crate) fn to_ns_range(range: &TextRange) -> NSRange {
let start = range.start().to_global_utf16_index();
let end = range.end().to_global_utf16_index();
NSRange::from(start..end)
}
pub(crate) fn to_ns_range_for_character(pos: &TextPosition) -> NSRange {
let mut range = pos.to_degenerate_range();
if !pos.is_document_end() {
range.set_end(pos.forward_to_character_end());
}
to_ns_range(&range)
}
pub(crate) fn from_ns_point(view: &NSView, node: &Node, point: NSPoint) -> Point {
let window = view.window().unwrap();
let point = window.convertPointFromScreen(point);
let point = view.convertPoint_fromView(point, None);
// AccessKit coordinates are in physical (DPI-dependent) pixels, but
// macOS provides logical (DPI-independent) coordinates here.
let factor = window.backingScaleFactor();
let point = Point::new(
point.x * factor,
if view.isFlipped() {
point.y * factor
} else {
let view_bounds = view.bounds();
(view_bounds.size.height - point.y) * factor
},
);
node.transform().inverse() * point
}
pub(crate) fn to_ns_rect(view: &NSView, rect: Rect) -> NSRect {
let window = view.window().unwrap();
// AccessKit coordinates are in physical (DPI-dependent)
// pixels, but macOS expects logical (DPI-independent)
// coordinates here.
let factor = window.backingScaleFactor();
let rect = NSRect {
origin: NSPoint {
x: rect.x0 / factor,
y: if view.isFlipped() {
rect.y0 / factor
} else {
let view_bounds = view.bounds();
view_bounds.size.height - rect.y1 / factor
},
},
size: NSSize {
width: rect.width() / factor,
height: rect.height() / factor,
},
};
let rect = view.convertRect_toView(rect, None);
let window = view.window().unwrap();
window.convertRectToScreen(rect)
}