diff --git a/Cargo.lock b/Cargo.lock index 34c2478d1..eb0d6e41f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,7 +85,7 @@ name = "accesskit_unix" version = "0.7.1" dependencies = [ "accesskit", - "accesskit_consumer", + "accesskit_atspi_common", "async-channel 2.1.1", "async-executor", "async-task", diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index f65f43ea3..0add022e1 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -18,7 +18,7 @@ tokio = ["dep:tokio", "dep:tokio-stream", "atspi/tokio", "zbus/tokio"] [dependencies] accesskit = { version = "0.12.2", path = "../../common" } -accesskit_consumer = { version = "0.17.0", path = "../../consumer" } +accesskit_atspi_common = { version = "0.1.0", path = "../atspi-common" } atspi = { version = "0.19", default-features = false } futures-lite = "1.13" once_cell = "1.17.1" diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index f5073a974..45a0ad216 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -3,365 +3,60 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::{ - atspi::{ - interfaces::{Event, ObjectEvent, WindowEvent}, - ObjectId, - }, - context::{AppContext, Context}, - filters::{filter, filter_detached}, - node::NodeWrapper, - util::WindowBounds, +use accesskit::{ActionHandler, NodeId, Rect, TreeUpdate}; +use accesskit_atspi_common::{ + Adapter as AdapterImpl, AdapterCallback, AdapterIdToken, Event, PlatformNode, WindowBounds, }; -use accesskit::{ActionHandler, NodeId, Rect, Role, TreeUpdate}; -use accesskit_consumer::{DetachedNode, FilterResult, Node, Tree, TreeChangeHandler, TreeState}; #[cfg(not(feature = "tokio"))] use async_channel::Sender; -use atspi::{InterfaceSet, Live, State}; +use atspi::InterfaceSet; use once_cell::sync::Lazy; use std::sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, - Arc, Mutex, Weak, + atomic::{AtomicBool, Ordering}, + Arc, Mutex, }; #[cfg(feature = "tokio")] use tokio::sync::mpsc::UnboundedSender as Sender; -struct AdapterChangeHandler<'a> { - adapter: &'a AdapterImpl, -} - -impl AdapterChangeHandler<'_> { - fn add_node(&mut self, node: &Node) { - let role = node.role(); - let is_root = node.is_root(); - let node = NodeWrapper::Node { - adapter: self.adapter.id, - node, - }; - let interfaces = node.interfaces(); - self.adapter.register_interfaces(node.id(), interfaces); - if is_root && role == Role::Window { - let adapter_index = AppContext::read().adapter_index(self.adapter.id).unwrap(); - self.adapter.window_created(adapter_index, node.id()); - } - - let live = node.live(); - if live != Live::None { - if let Some(name) = node.name() { - self.adapter.emit_object_event( - ObjectId::Node { - adapter: self.adapter.id, - node: node.id(), - }, - ObjectEvent::Announcement(name, live), - ); - } - } - } - - fn remove_node(&mut self, node: &DetachedNode) { - let role = node.role(); - let is_root = node.is_root(); - let node = NodeWrapper::DetachedNode { - adapter: self.adapter.id, - node, - }; - if is_root && role == Role::Window { - self.adapter.window_destroyed(node.id()); - } - self.adapter.emit_object_event( - ObjectId::Node { - adapter: self.adapter.id, - node: node.id(), - }, - ObjectEvent::StateChanged(State::Defunct, true), - ); - self.adapter - .unregister_interfaces(node.id(), node.interfaces()); - } -} - -impl TreeChangeHandler for AdapterChangeHandler<'_> { - fn node_added(&mut self, node: &Node) { - if filter(node) == FilterResult::Include { - self.add_node(node); - } - } - - fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) { - let filter_old = filter_detached(old_node); - let filter_new = filter(new_node); - if filter_new != filter_old { - if filter_new == FilterResult::Include { - self.add_node(new_node); - } else if filter_old == FilterResult::Include { - self.remove_node(old_node); - } - } else if filter_new == FilterResult::Include { - let old_wrapper = NodeWrapper::DetachedNode { - adapter: self.adapter.id, - node: old_node, - }; - let new_wrapper = NodeWrapper::Node { - adapter: self.adapter.id, - node: new_node, - }; - let old_interfaces = old_wrapper.interfaces(); - let new_interfaces = new_wrapper.interfaces(); - let kept_interfaces = old_interfaces & new_interfaces; - self.adapter - .unregister_interfaces(new_wrapper.id(), old_interfaces ^ kept_interfaces); - self.adapter - .register_interfaces(new_node.id(), new_interfaces ^ kept_interfaces); - let bounds = *self.adapter.context.read_root_window_bounds(); - new_wrapper.notify_changes(&bounds, self.adapter, &old_wrapper); - } - } +use crate::context::{get_or_init_app_context, get_or_init_messages}; - fn focus_moved( - &mut self, - old_node: Option<&DetachedNode>, - new_node: Option<&Node>, - current_state: &TreeState, - ) { - if let Some(root_window) = root_window(current_state) { - if old_node.is_none() && new_node.is_some() { - self.adapter.window_activated(&NodeWrapper::Node { - adapter: self.adapter.id, - node: &root_window, - }); - } else if old_node.is_some() && new_node.is_none() { - self.adapter.window_deactivated(&NodeWrapper::Node { - adapter: self.adapter.id, - node: &root_window, - }); - } - } - } - - fn node_removed(&mut self, node: &DetachedNode, _: &TreeState) { - if filter_detached(node) == FilterResult::Include { - self.remove_node(node); - } - } -} - -pub(crate) struct AdapterImpl { - id: usize, +struct Callback { messages: Sender, - context: Arc, } -impl AdapterImpl { - fn new( - id: usize, - messages: Sender, - initial_state: TreeUpdate, - is_window_focused: bool, - root_window_bounds: WindowBounds, - action_handler: Box, - ) -> Self { - let tree = Tree::new(initial_state, is_window_focused); - let context = { - let mut app_context = AppContext::write(); - let context = Context::new(tree, action_handler, root_window_bounds); - app_context.push_adapter(id, &context); - context - }; - AdapterImpl { - id, - messages, - context, - } - } - - pub(crate) fn register_tree(&self) { - fn add_children( - node: Node<'_>, - to_add: &mut Vec<(NodeId, InterfaceSet)>, - adapter_id: usize, - ) { - for child in node.filtered_children(&filter) { - let child_id = child.id(); - let wrapper = NodeWrapper::Node { - adapter: adapter_id, - node: &child, - }; - let interfaces = wrapper.interfaces(); - to_add.push((child_id, interfaces)); - add_children(child, to_add, adapter_id); - } - } - - let mut objects_to_add = Vec::new(); - - let (adapter_index, root_id) = { - let tree = self.context.read_tree(); - let tree_state = tree.state(); - let mut app_context = AppContext::write(); - app_context.name = tree_state.app_name(); - app_context.toolkit_name = tree_state.toolkit_name(); - app_context.toolkit_version = tree_state.toolkit_version(); - let adapter_index = app_context.adapter_index(self.id).unwrap(); - let root = tree_state.root(); - let root_id = root.id(); - let wrapper = NodeWrapper::Node { - adapter: self.id, - node: &root, - }; - objects_to_add.push((root_id, wrapper.interfaces())); - add_children(root, &mut objects_to_add, self.id); - (adapter_index, root_id) - }; - - for (id, interfaces) in objects_to_add { - self.register_interfaces(id, interfaces); - if id == root_id { - self.window_created(adapter_index, id); - } - } - } - - pub(crate) fn send_message(&self, message: Message) { +impl Callback { + fn send_message(&self, message: Message) { #[cfg(not(feature = "tokio"))] let _ = self.messages.try_send(message); #[cfg(feature = "tokio")] let _ = self.messages.send(message); } +} - fn register_interfaces(&self, id: NodeId, new_interfaces: InterfaceSet) { - self.send_message(Message::RegisterInterfaces { - adapter_id: self.id, - context: Arc::downgrade(&self.context), - node_id: id, - interfaces: new_interfaces, - }); +impl AdapterCallback for Callback { + fn register_interfaces(&self, adapter: &AdapterImpl, id: NodeId, interfaces: InterfaceSet) { + let node = adapter.platform_node(id); + self.send_message(Message::RegisterInterfaces { node, interfaces }); } - fn unregister_interfaces(&self, id: NodeId, old_interfaces: InterfaceSet) { + fn unregister_interfaces(&self, adapter: &AdapterImpl, id: NodeId, interfaces: InterfaceSet) { self.send_message(Message::UnregisterInterfaces { - adapter_id: self.id, + adapter_id: adapter.id(), node_id: id, - interfaces: old_interfaces, - }); - } - - pub(crate) fn emit_object_event(&self, target: ObjectId, event: ObjectEvent) { - self.send_message(Message::EmitEvent(Event::Object { target, event })); - } - - fn set_root_window_bounds(&self, bounds: WindowBounds) { - let mut old_bounds = self.context.root_window_bounds.write().unwrap(); - *old_bounds = bounds; - } - - fn update(&self, update: TreeUpdate) { - let mut handler = AdapterChangeHandler { adapter: self }; - let mut tree = self.context.tree.write().unwrap(); - tree.update_and_process_changes(update, &mut handler); - } - - fn update_window_focus_state(&self, is_focused: bool) { - let mut handler = AdapterChangeHandler { adapter: self }; - let mut tree = self.context.tree.write().unwrap(); - tree.update_host_focus_state_and_process_changes(is_focused, &mut handler); - } - - fn window_created(&self, adapter_index: usize, window: NodeId) { - self.emit_object_event( - ObjectId::Root, - ObjectEvent::ChildAdded( - adapter_index, - ObjectId::Node { - adapter: self.id, - node: window, - }, - ), - ); - } - - fn window_activated(&self, window: &NodeWrapper<'_>) { - self.send_message(Message::EmitEvent(Event::Window { - target: ObjectId::Node { - adapter: self.id, - node: window.id(), - }, - name: window.name().unwrap_or_default(), - event: WindowEvent::Activated, - })); - self.emit_object_event( - ObjectId::Node { - adapter: self.id, - node: window.id(), - }, - ObjectEvent::StateChanged(State::Active, true), - ); - self.emit_object_event( - ObjectId::Root, - ObjectEvent::ActiveDescendantChanged(ObjectId::Node { - adapter: self.id, - node: window.id(), - }), - ); - } - - fn window_deactivated(&self, window: &NodeWrapper<'_>) { - self.send_message(Message::EmitEvent(Event::Window { - target: ObjectId::Node { - adapter: self.id, - node: window.id(), - }, - name: window.name().unwrap_or_default(), - event: WindowEvent::Deactivated, - })); - self.emit_object_event( - ObjectId::Node { - adapter: self.id, - node: window.id(), - }, - ObjectEvent::StateChanged(State::Active, false), - ); - } - - fn window_destroyed(&self, window: NodeId) { - self.emit_object_event( - ObjectId::Root, - ObjectEvent::ChildRemoved(ObjectId::Node { - adapter: self.id, - node: window, - }), - ); - } -} - -fn root_window(current_state: &TreeState) -> Option { - const WINDOW_ROLES: &[Role] = &[Role::AlertDialog, Role::Dialog, Role::Window]; - let root = current_state.root(); - if WINDOW_ROLES.contains(&root.role()) { - Some(root) - } else { - None + interfaces, + }) } -} -impl Drop for AdapterImpl { - fn drop(&mut self) { - AppContext::write().remove_adapter(self.id); - let root_id = self.context.read_tree().state().root_id(); - self.emit_object_event( - ObjectId::Root, - ObjectEvent::ChildRemoved(ObjectId::Node { - adapter: self.id, - node: root_id, - }), - ); + fn emit_event(&self, adapter: &AdapterImpl, event: Event) { + self.send_message(Message::EmitEvent { + adapter_id: adapter.id(), + event, + }); } } pub(crate) type LazyAdapter = Arc AdapterImpl + Send>>>; -static NEXT_ADAPTER_ID: AtomicUsize = AtomicUsize::new(0); - pub struct Adapter { messages: Sender, id: usize, @@ -376,8 +71,9 @@ impl Adapter { source: impl 'static + FnOnce() -> TreeUpdate + Send, action_handler: Box, ) -> Self { - let id = NEXT_ADAPTER_ID.fetch_add(1, Ordering::SeqCst); - let messages = AppContext::read().messages.clone(); + let id_token = AdapterIdToken::next(); + let id = id_token.id(); + let messages = get_or_init_messages(); let is_window_focused = Arc::new(AtomicBool::new(false)); let root_window_bounds = Arc::new(Mutex::new(Default::default())); let r#impl: LazyAdapter = Arc::new(Lazy::new(Box::new({ @@ -385,9 +81,10 @@ impl Adapter { let is_window_focused = Arc::clone(&is_window_focused); let root_window_bounds = Arc::clone(&root_window_bounds); move || { - AdapterImpl::new( - id, - messages, + AdapterImpl::with_id( + id_token, + get_or_init_app_context(), + Box::new(Callback { messages }), source(), is_window_focused.load(Ordering::Relaxed), *root_window_bounds.lock().unwrap(), @@ -459,9 +156,7 @@ pub(crate) enum Message { id: usize, }, RegisterInterfaces { - adapter_id: usize, - context: Weak, - node_id: NodeId, + node: PlatformNode, interfaces: InterfaceSet, }, UnregisterInterfaces { @@ -469,5 +164,8 @@ pub(crate) enum Message { node_id: NodeId, interfaces: InterfaceSet, }, - EmitEvent(Event), + EmitEvent { + adapter_id: usize, + event: Event, + }, } diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index d076a39e1..d5b2565cd 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -5,18 +5,20 @@ use crate::{ atspi::{interfaces::*, ObjectId}, - context::{AppContext, Context}, + context::get_or_init_app_context, executor::{Executor, Task}, - PlatformNode, PlatformRootNode, }; use accesskit::NodeId; +use accesskit_atspi_common::{ + NodeIdOrRoot, ObjectEvent, PlatformNode, PlatformRoot, Property, WindowEvent, +}; use atspi::{ events::EventBody, proxy::{bus::BusProxy, socket::SocketProxy}, Interface, InterfaceSet, }; use serde::Serialize; -use std::{collections::HashMap, env::var, io, sync::Weak}; +use std::{collections::HashMap, env::var, io}; use zbus::{ names::{BusName, InterfaceName, MemberName, OwnedUniqueName}, zvariant::{Str, Value}, @@ -67,30 +69,31 @@ impl Bus { } async fn register_root_node(&mut self) -> Result<()> { - let node = PlatformRootNode::new(); + let node = PlatformRoot::new(get_or_init_app_context()); let path = ObjectId::Root.path(); - let app_node_added = self + if self .conn .object_server() .at(path.clone(), ApplicationInterface(node.clone())) .await? - && self - .conn + { + let desktop = self + .socket_proxy + .embed(&(self.unique_name().as_str(), ObjectId::Root.path().into())) + .await?; + + self.conn .object_server() .at( path, - AccessibleInterface::new(self.unique_name().to_owned(), node), + RootAccessibleInterface::new( + self.unique_name().to_owned(), + desktop.into(), + node, + ), ) .await?; - - if app_node_added { - let desktop = self - .socket_proxy - .embed(&(self.unique_name().as_str(), ObjectId::Root.path().into())) - .await?; - let mut app_context = AppContext::write(); - app_context.desktop_address = Some(desktop.into()); } Ok(()) @@ -98,46 +101,32 @@ impl Bus { pub(crate) async fn register_interfaces( &self, - adapter_id: usize, - context: Weak, - node_id: NodeId, + node: PlatformNode, new_interfaces: InterfaceSet, ) -> zbus::Result<()> { - let path = ObjectId::Node { - adapter: adapter_id, - node: node_id, - } - .path(); + let path = ObjectId::from(&node).path(); + let bus_name = self.unique_name().to_owned(); if new_interfaces.contains(Interface::Accessible) { self.register_interface( &path, - AccessibleInterface::new( - self.unique_name().to_owned(), - PlatformNode::new(context.clone(), adapter_id, node_id), - ), + NodeAccessibleInterface::new(bus_name.clone(), node.clone()), ) .await?; } if new_interfaces.contains(Interface::Action) { - self.register_interface( - &path, - ActionInterface::new(PlatformNode::new(context.clone(), adapter_id, node_id)), - ) - .await?; + self.register_interface(&path, ActionInterface::new(node.clone())) + .await?; } if new_interfaces.contains(Interface::Component) { self.register_interface( &path, - ComponentInterface::new(PlatformNode::new(context.clone(), adapter_id, node_id)), + ComponentInterface::new(bus_name.clone(), node.clone()), ) .await?; } if new_interfaces.contains(Interface::Value) { - self.register_interface( - &path, - ValueInterface::new(PlatformNode::new(context, adapter_id, node_id)), - ) - .await?; + self.register_interface(&path, ValueInterface::new(node.clone())) + .await?; } Ok(()) @@ -166,7 +155,7 @@ impl Bus { } .path(); if old_interfaces.contains(Interface::Accessible) { - self.unregister_interface::>(&path) + self.unregister_interface::(&path) .await?; } if old_interfaces.contains(Interface::Action) { @@ -196,9 +185,17 @@ impl Bus { pub(crate) async fn emit_object_event( &self, - target: ObjectId, + adapter_id: usize, + target: NodeIdOrRoot, event: ObjectEvent, ) -> Result<()> { + let target = match target { + NodeIdOrRoot::Node(node) => ObjectId::Node { + adapter: adapter_id, + node, + }, + NodeIdOrRoot::Root => ObjectId::Root, + }; let interface = "org.a11y.atspi.Event.Object"; let signal = match event { ObjectEvent::ActiveDescendantChanged(_) => "ActiveDescendantChanged", @@ -211,6 +208,10 @@ impl Bus { let properties = HashMap::new(); match event { ObjectEvent::ActiveDescendantChanged(child) => { + let child = ObjectId::Node { + adapter: adapter_id, + node: child, + }; self.emit_event( target, interface, @@ -256,6 +257,10 @@ impl Bus { .await } ObjectEvent::ChildAdded(index, child) => { + let child = ObjectId::Node { + adapter: adapter_id, + node: child, + }; self.emit_event( target, interface, @@ -271,6 +276,10 @@ impl Bus { .await } ObjectEvent::ChildRemoved(child) => { + let child = ObjectId::Node { + adapter: adapter_id, + node: child, + }; self.emit_event( target, interface, @@ -304,6 +313,13 @@ impl Bus { Property::Name(value) => Str::from(value).into(), Property::Description(value) => Str::from(value).into(), Property::Parent(parent) => { + let parent = match parent { + NodeIdOrRoot::Node(node) => ObjectId::Node { + adapter: adapter_id, + node, + }, + NodeIdOrRoot::Root => ObjectId::Root, + }; parent.to_address(self.unique_name().clone()).into() } Property::Role(value) => Value::U32(value as u32), @@ -334,10 +350,15 @@ impl Bus { pub(crate) async fn emit_window_event( &self, - target: ObjectId, + adapter_id: usize, + target: NodeId, window_name: String, event: WindowEvent, ) -> Result<()> { + let target = ObjectId::Node { + adapter: adapter_id, + node: target, + }; let signal = match event { WindowEvent::Activated => "Activate", WindowEvent::Deactivated => "Deactivate", diff --git a/platforms/unix/src/atspi/interfaces/accessible.rs b/platforms/unix/src/atspi/interfaces/accessible.rs index d1113e60a..ad76ca614 100644 --- a/platforms/unix/src/atspi/interfaces/accessible.rs +++ b/platforms/unix/src/atspi/interfaces/accessible.rs @@ -3,46 +3,57 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::{ - atspi::{ObjectId, OwnedObjectAddress}, - PlatformNode, PlatformRootNode, -}; +use accesskit_atspi_common::{NodeIdOrRoot, PlatformNode, PlatformRoot}; use atspi::{Interface, InterfaceSet, Role, StateSet}; -use zbus::{fdo, names::OwnedUniqueName, MessageHeader}; +use zbus::{fdo, names::OwnedUniqueName}; -pub(crate) struct AccessibleInterface { +use super::map_root_error; +use crate::atspi::{ObjectId, OwnedObjectAddress}; + +pub(crate) struct NodeAccessibleInterface { bus_name: OwnedUniqueName, - node: T, + node: PlatformNode, } -impl AccessibleInterface { - pub fn new(bus_name: OwnedUniqueName, node: T) -> Self { +impl NodeAccessibleInterface { + pub fn new(bus_name: OwnedUniqueName, node: PlatformNode) -> Self { Self { bus_name, node } } + + fn map_error(&self) -> impl '_ + FnOnce(accesskit_atspi_common::Error) -> fdo::Error { + |error| crate::util::map_error_from_node(&self.node, error) + } } #[dbus_interface(name = "org.a11y.atspi.Accessible")] -impl AccessibleInterface { +impl NodeAccessibleInterface { #[dbus_interface(property)] fn name(&self) -> fdo::Result { - self.node.name() + self.node.name().map_err(self.map_error()) } #[dbus_interface(property)] fn description(&self) -> fdo::Result { - self.node.description() + self.node.description().map_err(self.map_error()) } #[dbus_interface(property)] fn parent(&self) -> fdo::Result { - self.node - .parent() - .map(|parent| parent.to_address(self.bus_name.clone())) + self.node.parent().map_err(self.map_error()).map(|parent| { + match parent { + NodeIdOrRoot::Node(node) => ObjectId::Node { + adapter: self.node.adapter_id(), + node, + }, + NodeIdOrRoot::Root => ObjectId::Root, + } + .to_address(self.bus_name.clone()) + }) } #[dbus_interface(property)] fn child_count(&self) -> fdo::Result { - self.node.child_count() + self.node.child_count().map_err(self.map_error()) } #[dbus_interface(property)] @@ -52,62 +63,86 @@ impl AccessibleInterface { #[dbus_interface(property)] fn accessible_id(&self) -> ObjectId { - self.node.accessible_id() + ObjectId::from(&self.node) } - fn get_child_at_index( - &self, - #[zbus(header)] hdr: MessageHeader<'_>, - index: i32, - ) -> fdo::Result<(OwnedObjectAddress,)> { + fn get_child_at_index(&self, index: i32) -> fdo::Result<(OwnedObjectAddress,)> { let index = index .try_into() .map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; - super::object_address(hdr.destination()?, self.node.child_at_index(index)?) + let child = self + .node + .child_at_index(index) + .map_err(self.map_error())? + .map(|child| ObjectId::Node { + adapter: self.node.adapter_id(), + node: child, + }); + Ok(super::optional_object_address(&self.bus_name, child)) } fn get_children(&self) -> fdo::Result> { - Ok(self - .node - .children()? - .into_iter() - .map(|child| child.to_address(self.bus_name.clone())) - .collect()) + self.node + .map_children(|child| { + ObjectId::Node { + adapter: self.node.adapter_id(), + node: child, + } + .to_address(self.bus_name.clone()) + }) + .map_err(self.map_error()) } fn get_index_in_parent(&self) -> fdo::Result { - self.node.index_in_parent() + self.node.index_in_parent().map_err(self.map_error()) } fn get_role(&self) -> fdo::Result { - self.node.role() + self.node.role().map_err(self.map_error()) } fn get_localized_role_name(&self) -> fdo::Result { - self.node.localized_role_name() + self.node.localized_role_name().map_err(self.map_error()) } - fn get_state(&self) -> fdo::Result { + fn get_state(&self) -> StateSet { self.node.state() } - fn get_application( - &self, - #[zbus(header)] hdr: MessageHeader<'_>, - ) -> fdo::Result<(OwnedObjectAddress,)> { - super::object_address(hdr.destination()?, Some(ObjectId::Root)) + fn get_application(&self) -> (OwnedObjectAddress,) { + (ObjectId::Root.to_address(self.bus_name.clone()),) } fn get_interfaces(&self) -> fdo::Result { - self.node.interfaces() + self.node.interfaces().map_err(self.map_error()) + } +} + +pub(crate) struct RootAccessibleInterface { + bus_name: OwnedUniqueName, + desktop_address: OwnedObjectAddress, + root: PlatformRoot, +} + +impl RootAccessibleInterface { + pub fn new( + bus_name: OwnedUniqueName, + desktop_address: OwnedObjectAddress, + root: PlatformRoot, + ) -> Self { + Self { + bus_name, + desktop_address, + root, + } } } #[dbus_interface(name = "org.a11y.atspi.Accessible")] -impl AccessibleInterface { +impl RootAccessibleInterface { #[dbus_interface(property)] fn name(&self) -> fdo::Result { - self.node.name() + self.root.name().map_err(map_root_error) } #[dbus_interface(property)] @@ -116,16 +151,13 @@ impl AccessibleInterface { } #[dbus_interface(property)] - fn parent(&self) -> fdo::Result { - Ok(self - .node - .parent()? - .unwrap_or_else(|| OwnedObjectAddress::null(self.bus_name.clone()))) + fn parent(&self) -> OwnedObjectAddress { + self.desktop_address.clone() } #[dbus_interface(property)] fn child_count(&self) -> fdo::Result { - self.node.child_count() + self.root.child_count().map_err(map_root_error) } #[dbus_interface(property)] @@ -135,29 +167,27 @@ impl AccessibleInterface { #[dbus_interface(property)] fn accessible_id(&self) -> ObjectId { - self.node.accessible_id() + ObjectId::Root } - fn get_child_at_index( - &self, - #[zbus(header)] hdr: MessageHeader<'_>, - index: i32, - ) -> fdo::Result<(OwnedObjectAddress,)> { + fn get_child_at_index(&self, index: i32) -> fdo::Result<(OwnedObjectAddress,)> { let index = index .try_into() .map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; - let child = self.node.child_at_index(index)?; - super::object_address(hdr.destination()?, child) + let child = self + .root + .child_id_at_index(index) + .map_err(map_root_error)? + .map(|(adapter, node)| ObjectId::Node { adapter, node }); + Ok(super::optional_object_address(&self.bus_name, child)) } fn get_children(&self) -> fdo::Result> { - let children = self - .node - .children()? - .drain(..) - .map(|child| child.to_address(self.bus_name.clone())) - .collect(); - Ok(children) + self.root + .map_child_ids(|(adapter, node)| { + ObjectId::Node { adapter, node }.to_address(self.bus_name.clone()) + }) + .map_err(map_root_error) } fn get_index_in_parent(&self) -> i32 { @@ -172,11 +202,8 @@ impl AccessibleInterface { StateSet::empty() } - fn get_application( - &self, - #[zbus(header)] hdr: MessageHeader<'_>, - ) -> fdo::Result<(OwnedObjectAddress,)> { - super::object_address(hdr.destination()?, Some(ObjectId::Root)) + fn get_application(&self) -> (OwnedObjectAddress,) { + (ObjectId::Root.to_address(self.bus_name.clone()),) } fn get_interfaces(&self) -> InterfaceSet { diff --git a/platforms/unix/src/atspi/interfaces/action.rs b/platforms/unix/src/atspi/interfaces/action.rs index 0855c7366..6dee6f950 100644 --- a/platforms/unix/src/atspi/interfaces/action.rs +++ b/platforms/unix/src/atspi/interfaces/action.rs @@ -3,16 +3,8 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::PlatformNode; -use serde::{Deserialize, Serialize}; -use zbus::{dbus_interface, fdo, zvariant::Type}; - -#[derive(Deserialize, Serialize, Type)] -pub(crate) struct Action { - pub localized_name: String, - pub description: String, - pub key_binding: String, -} +use accesskit_atspi_common::{Action, PlatformNode}; +use zbus::{dbus_interface, fdo}; pub(crate) struct ActionInterface(PlatformNode); @@ -20,13 +12,17 @@ impl ActionInterface { pub fn new(node: PlatformNode) -> Self { Self(node) } + + fn map_error(&self) -> impl '_ + FnOnce(accesskit_atspi_common::Error) -> fdo::Error { + |error| crate::util::map_error_from_node(&self.0, error) + } } #[dbus_interface(name = "org.a11y.atspi.Action")] impl ActionInterface { #[dbus_interface(property)] fn n_actions(&self) -> fdo::Result { - self.0.n_actions() + self.0.n_actions().map_err(self.map_error()) } fn get_description(&self, _index: i32) -> &str { @@ -34,11 +30,11 @@ impl ActionInterface { } fn get_name(&self, index: i32) -> fdo::Result { - self.0.get_action_name(index) + self.0.action_name(index).map_err(self.map_error()) } fn get_localized_name(&self, index: i32) -> fdo::Result { - self.0.get_action_name(index) + self.0.action_name(index).map_err(self.map_error()) } fn get_key_binding(&self, _index: i32) -> &str { @@ -46,10 +42,10 @@ impl ActionInterface { } fn get_actions(&self) -> fdo::Result> { - self.0.get_actions() + self.0.actions().map_err(self.map_error()) } fn do_action(&self, index: i32) -> fdo::Result { - self.0.do_action(index) + self.0.do_action(index).map_err(self.map_error()) } } diff --git a/platforms/unix/src/atspi/interfaces/application.rs b/platforms/unix/src/atspi/interfaces/application.rs index df510ba76..3f6688488 100644 --- a/platforms/unix/src/atspi/interfaces/application.rs +++ b/platforms/unix/src/atspi/interfaces/application.rs @@ -3,21 +3,23 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::PlatformRootNode; +use accesskit_atspi_common::PlatformRoot; use zbus::fdo; -pub(crate) struct ApplicationInterface(pub PlatformRootNode); +use super::map_root_error; + +pub(crate) struct ApplicationInterface(pub PlatformRoot); #[dbus_interface(name = "org.a11y.atspi.Application")] impl ApplicationInterface { #[dbus_interface(property)] fn toolkit_name(&self) -> fdo::Result { - self.0.toolkit_name() + self.0.toolkit_name().map_err(map_root_error) } #[dbus_interface(property)] fn version(&self) -> fdo::Result { - self.0.toolkit_version() + self.0.toolkit_version().map_err(map_root_error) } #[dbus_interface(property)] @@ -27,11 +29,11 @@ impl ApplicationInterface { #[dbus_interface(property)] fn id(&self) -> fdo::Result { - self.0.id() + self.0.id().map_err(map_root_error) } #[dbus_interface(property)] fn set_id(&mut self, id: i32) -> fdo::Result<()> { - self.0.set_id(id) + self.0.set_id(id).map_err(map_root_error) } } diff --git a/platforms/unix/src/atspi/interfaces/component.rs b/platforms/unix/src/atspi/interfaces/component.rs index a90d00427..bc0b4dafb 100644 --- a/platforms/unix/src/atspi/interfaces/component.rs +++ b/platforms/unix/src/atspi/interfaces/component.rs @@ -3,53 +3,70 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::{ - atspi::{OwnedObjectAddress, Rect}, - PlatformNode, -}; +use accesskit_atspi_common::{PlatformNode, Rect}; use atspi::{CoordType, Layer}; -use zbus::{fdo, MessageHeader}; +use zbus::{fdo, names::OwnedUniqueName}; + +use crate::atspi::{ObjectId, OwnedObjectAddress}; pub(crate) struct ComponentInterface { + bus_name: OwnedUniqueName, node: PlatformNode, } impl ComponentInterface { - pub(crate) fn new(node: PlatformNode) -> Self { - Self { node } + pub fn new(bus_name: OwnedUniqueName, node: PlatformNode) -> Self { + Self { bus_name, node } + } + + fn map_error(&self) -> impl '_ + FnOnce(accesskit_atspi_common::Error) -> fdo::Error { + |error| crate::util::map_error_from_node(&self.node, error) } } #[dbus_interface(name = "org.a11y.atspi.Component")] impl ComponentInterface { fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { - self.node.contains(x, y, coord_type) + self.node + .contains(x, y, coord_type) + .map_err(self.map_error()) } fn get_accessible_at_point( &self, - #[zbus(header)] hdr: MessageHeader<'_>, x: i32, y: i32, coord_type: CoordType, ) -> fdo::Result<(OwnedObjectAddress,)> { - let accessible = self.node.get_accessible_at_point(x, y, coord_type)?; - super::object_address(hdr.destination()?, accessible) + let accessible = self + .node + .accessible_at_point(x, y, coord_type) + .map_err(self.map_error())? + .map(|node| ObjectId::Node { + adapter: self.node.adapter_id(), + node, + }); + Ok(super::optional_object_address(&self.bus_name, accessible)) } fn get_extents(&self, coord_type: CoordType) -> fdo::Result<(Rect,)> { - self.node.get_extents(coord_type) + self.node + .extents(coord_type) + .map(|rect| (rect,)) + .map_err(self.map_error()) } fn get_layer(&self) -> fdo::Result { - self.node.get_layer() + self.node.layer().map_err(self.map_error()) } fn grab_focus(&self) -> fdo::Result { - self.node.grab_focus() + self.node.grab_focus().map_err(self.map_error()) } fn scroll_to_point(&self, coord_type: CoordType, x: i32, y: i32) -> fdo::Result { - self.node.scroll_to_point(coord_type, x, y) + self.node + .scroll_to_point(coord_type, x, y) + .map_err(self.map_error()) } } diff --git a/platforms/unix/src/atspi/interfaces/events.rs b/platforms/unix/src/atspi/interfaces/events.rs deleted file mode 100644 index d5cb7f10a..000000000 --- a/platforms/unix/src/atspi/interfaces/events.rs +++ /dev/null @@ -1,43 +0,0 @@ -// 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::atspi::{ObjectId, Rect}; -use atspi::{Live, Role, State}; - -pub(crate) enum Event { - Object { - target: ObjectId, - event: ObjectEvent, - }, - Window { - target: ObjectId, - name: String, - event: WindowEvent, - }, -} - -pub(crate) enum Property { - Name(String), - Description(String), - Parent(ObjectId), - Role(Role), - Value(f64), -} - -#[allow(clippy::enum_variant_names)] -pub(crate) enum ObjectEvent { - ActiveDescendantChanged(ObjectId), - Announcement(String, Live), - BoundsChanged(Rect), - ChildAdded(usize, ObjectId), - ChildRemoved(ObjectId), - PropertyChanged(Property), - StateChanged(State, bool), -} - -pub(crate) enum WindowEvent { - Activated, - Deactivated, -} diff --git a/platforms/unix/src/atspi/interfaces/mod.rs b/platforms/unix/src/atspi/interfaces/mod.rs index 35a63be38..8c94965f0 100644 --- a/platforms/unix/src/atspi/interfaces/mod.rs +++ b/platforms/unix/src/atspi/interfaces/mod.rs @@ -7,33 +7,28 @@ mod accessible; mod action; mod application; mod component; -mod events; mod value; use crate::atspi::{ObjectId, OwnedObjectAddress}; -use zbus::{ - fdo, - names::{BusName, OwnedUniqueName, UniqueName}, -}; +use zbus::{fdo, names::OwnedUniqueName}; -fn object_address( - destination: Option<&BusName>, +fn map_root_error(error: accesskit_atspi_common::Error) -> fdo::Error { + crate::util::map_error(ObjectId::Root, error) +} + +fn optional_object_address( + bus_name: &OwnedUniqueName, object_id: Option, -) -> fdo::Result<(OwnedObjectAddress,)> { +) -> (OwnedObjectAddress,) { + let bus_name = bus_name.clone(); match object_id { - Some(id) => Ok((id.to_address(app_name(destination)?),)), - None => Ok((OwnedObjectAddress::null(app_name(destination)?),)), + Some(id) => (id.to_address(bus_name),), + None => (OwnedObjectAddress::null(bus_name),), } } -fn app_name(destination: Option<&BusName>) -> fdo::Result { - let destination = destination.ok_or(fdo::Error::ZBus(zbus::Error::MissingField))?; - Ok(UniqueName::from_str_unchecked(destination.as_str()).into()) -} - pub(crate) use accessible::*; pub(crate) use action::*; pub(crate) use application::*; pub(crate) use component::*; -pub(crate) use events::*; pub(crate) use value::*; diff --git a/platforms/unix/src/atspi/interfaces/value.rs b/platforms/unix/src/atspi/interfaces/value.rs index c7956b9d7..f9c125071 100644 --- a/platforms/unix/src/atspi/interfaces/value.rs +++ b/platforms/unix/src/atspi/interfaces/value.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::PlatformNode; +use accesskit_atspi_common::PlatformNode; use zbus::fdo; pub(crate) struct ValueInterface { @@ -14,32 +14,36 @@ impl ValueInterface { pub fn new(node: PlatformNode) -> Self { Self { node } } + + fn map_error(&self) -> impl '_ + FnOnce(accesskit_atspi_common::Error) -> fdo::Error { + |error| crate::util::map_error_from_node(&self.node, error) + } } #[dbus_interface(name = "org.a11y.atspi.Value")] impl ValueInterface { #[dbus_interface(property)] fn minimum_value(&self) -> fdo::Result { - self.node.minimum_value() + self.node.minimum_value().map_err(self.map_error()) } #[dbus_interface(property)] fn maximum_value(&self) -> fdo::Result { - self.node.maximum_value() + self.node.maximum_value().map_err(self.map_error()) } #[dbus_interface(property)] fn minimum_increment(&self) -> fdo::Result { - self.node.minimum_increment() + self.node.minimum_increment().map_err(self.map_error()) } #[dbus_interface(property)] fn current_value(&self) -> fdo::Result { - self.node.current_value() + self.node.current_value().map_err(self.map_error()) } #[dbus_interface(property)] fn set_current_value(&mut self, value: f64) -> fdo::Result<()> { - self.node.set_current_value(value) + self.node.set_current_value(value).map_err(self.map_error()) } } diff --git a/platforms/unix/src/atspi/mod.rs b/platforms/unix/src/atspi/mod.rs index 510557bc4..b4f7a165e 100644 --- a/platforms/unix/src/atspi/mod.rs +++ b/platforms/unix/src/atspi/mod.rs @@ -3,42 +3,11 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use serde::{Deserialize, Serialize}; -use zbus::zvariant::{OwnedValue, Type, Value}; - mod bus; pub(crate) mod interfaces; mod object_address; mod object_id; -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, OwnedValue, Type, Value)] -pub(crate) struct Rect { - x: i32, - y: i32, - width: i32, - height: i32, -} - -impl Rect { - pub const INVALID: Rect = Rect { - x: -1, - y: -1, - width: -1, - height: -1, - }; -} - -impl From for Rect { - fn from(value: accesskit::Rect) -> Rect { - Rect { - x: value.x0 as i32, - y: value.y0 as i32, - width: value.width() as i32, - height: value.height() as i32, - } - } -} - pub(crate) use bus::*; pub(crate) use object_address::OwnedObjectAddress; pub(crate) use object_id::ObjectId; diff --git a/platforms/unix/src/atspi/object_id.rs b/platforms/unix/src/atspi/object_id.rs index 6cc181084..41028597e 100644 --- a/platforms/unix/src/atspi/object_id.rs +++ b/platforms/unix/src/atspi/object_id.rs @@ -5,6 +5,7 @@ use crate::atspi::OwnedObjectAddress; use accesskit::NodeId; +use accesskit_atspi_common::PlatformNode; use serde::{Serialize, Serializer}; use zbus::{ names::OwnedUniqueName, @@ -65,3 +66,12 @@ impl From for Structure<'_> { .build() } } + +impl From<&PlatformNode> for ObjectId { + fn from(node: &PlatformNode) -> Self { + Self::Node { + adapter: node.adapter_id(), + node: node.id(), + } + } +} diff --git a/platforms/unix/src/context.rs b/platforms/unix/src/context.rs index 19a3c934f..8efa9fdb0 100644 --- a/platforms/unix/src/context.rs +++ b/platforms/unix/src/context.rs @@ -3,16 +3,15 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{ActionHandler, ActionRequest}; -use accesskit_consumer::Tree; +use accesskit_atspi_common::{AppContext, Event}; #[cfg(not(feature = "tokio"))] use async_channel::{Receiver, Sender}; use atspi::proxy::bus::StatusProxy; #[cfg(not(feature = "tokio"))] use futures_util::{pin_mut as pin, select, StreamExt}; -use once_cell::sync::OnceCell; +use once_cell::sync::{Lazy, OnceCell}; use std::{ - sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak}, + sync::{Arc, RwLock}, thread, }; #[cfg(feature = "tokio")] @@ -26,66 +25,21 @@ use zbus::{Connection, ConnectionBuilder}; use crate::{ adapter::{LazyAdapter, Message}, - atspi::{interfaces::Event, map_or_ignoring_broken_pipe, Bus, OwnedObjectAddress}, + atspi::{map_or_ignoring_broken_pipe, Bus}, executor::Executor, - util::{block_on, WindowBounds}, + util::block_on, }; -pub(crate) struct Context { - pub(crate) tree: RwLock, - pub(crate) action_handler: Mutex>, - pub(crate) root_window_bounds: RwLock, -} - -impl Context { - pub(crate) fn new( - tree: Tree, - action_handler: Box, - root_window_bounds: WindowBounds, - ) -> Arc { - Arc::new(Self { - tree: RwLock::new(tree), - action_handler: Mutex::new(action_handler), - root_window_bounds: RwLock::new(root_window_bounds), - }) - } - - pub(crate) fn read_tree(&self) -> RwLockReadGuard<'_, Tree> { - self.tree.read().unwrap() - } - - pub(crate) fn read_root_window_bounds(&self) -> RwLockReadGuard<'_, WindowBounds> { - self.root_window_bounds.read().unwrap() - } - - pub fn do_action(&self, request: ActionRequest) { - self.action_handler.lock().unwrap().do_action(request); - } -} - -pub(crate) struct AdapterAndContext(usize, Weak); - -impl AdapterAndContext { - pub(crate) fn upgrade(&self) -> Option<(usize, Arc)> { - self.1.upgrade().map(|context| (self.0, context)) - } -} - static APP_CONTEXT: OnceCell>> = OnceCell::new(); +static MESSAGES: OnceCell> = OnceCell::new(); -pub(crate) struct AppContext { - pub(crate) messages: Sender, - pub(crate) name: Option, - pub(crate) toolkit_name: Option, - pub(crate) toolkit_version: Option, - pub(crate) id: Option, - pub(crate) desktop_address: Option, - pub(crate) adapters: Vec, +pub(crate) fn get_or_init_app_context<'a>() -> &'a Arc> { + APP_CONTEXT.get_or_init(AppContext::new) } -impl AppContext { - fn get_or_init<'a>() -> &'a Arc> { - APP_CONTEXT.get_or_init(|| { +pub(crate) fn get_or_init_messages() -> Sender { + MESSAGES + .get_or_init(|| { #[cfg(not(feature = "tokio"))] let (tx, rx) = async_channel::unbounded(); #[cfg(feature = "tokio")] @@ -103,40 +57,9 @@ impl AppContext { })) }); - Arc::new(RwLock::new(Self { - messages: tx, - name: None, - toolkit_name: None, - toolkit_version: None, - id: None, - desktop_address: None, - adapters: Vec::new(), - })) + tx }) - } - - pub(crate) fn read<'a>() -> RwLockReadGuard<'a, AppContext> { - AppContext::get_or_init().read().unwrap() - } - - pub(crate) fn write<'a>() -> RwLockWriteGuard<'a, AppContext> { - AppContext::get_or_init().write().unwrap() - } - - pub(crate) fn adapter_index(&self, id: usize) -> Result { - self.adapters.binary_search_by(|adapter| adapter.0.cmp(&id)) - } - - pub(crate) fn push_adapter(&mut self, id: usize, context: &Arc) { - self.adapters - .push(AdapterAndContext(id, Arc::downgrade(context))); - } - - pub(crate) fn remove_adapter(&mut self, id: usize) { - if let Ok(index) = self.adapter_index(id) { - self.adapters.remove(index); - } - } + .clone() } async fn run_event_loop( @@ -178,7 +101,7 @@ async fn run_event_loop( } if atspi_bus.is_some() { for (_, adapter) in &adapters { - adapter.register_tree(); + Lazy::force(adapter); } } } @@ -201,7 +124,7 @@ async fn process_adapter_message( adapters.push((id, adapter)); if atspi_bus.is_some() { let adapter = &adapters.last_mut().unwrap().1; - adapter.register_tree(); + Lazy::force(adapter); } } Message::RemoveAdapter { id } => { @@ -209,15 +132,9 @@ async fn process_adapter_message( adapters.remove(index); } } - Message::RegisterInterfaces { - adapter_id, - context, - node_id, - interfaces, - } => { + Message::RegisterInterfaces { node, interfaces } => { if let Some(bus) = atspi_bus { - bus.register_interfaces(adapter_id, context, node_id, interfaces) - .await? + bus.register_interfaces(node, interfaces).await? } } Message::UnregisterInterfaces { @@ -230,18 +147,26 @@ async fn process_adapter_message( .await? } } - Message::EmitEvent(Event::Object { target, event }) => { + Message::EmitEvent { + adapter_id, + event: Event::Object { target, event }, + } => { if let Some(bus) = atspi_bus { - bus.emit_object_event(target, event).await? + bus.emit_object_event(adapter_id, target, event).await? } } - Message::EmitEvent(Event::Window { - target, - name, - event, - }) => { + Message::EmitEvent { + adapter_id, + event: + Event::Window { + target, + name, + event, + }, + } => { if let Some(bus) = atspi_bus { - bus.emit_window_event(target, name, event).await?; + bus.emit_window_event(adapter_id, target, name, event) + .await?; } } } diff --git a/platforms/unix/src/filters.rs b/platforms/unix/src/filters.rs deleted file mode 100644 index f062ee72c..000000000 --- a/platforms/unix/src/filters.rs +++ /dev/null @@ -1,8 +0,0 @@ -// 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, common_filter_detached as filter_detached, -}; diff --git a/platforms/unix/src/lib.rs b/platforms/unix/src/lib.rs index 884f057c2..ee521afa2 100644 --- a/platforms/unix/src/lib.rs +++ b/platforms/unix/src/lib.rs @@ -25,9 +25,6 @@ mod adapter; mod atspi; mod context; mod executor; -mod filters; -mod node; mod util; pub use adapter::Adapter; -pub(crate) use node::{PlatformNode, PlatformRootNode}; diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs deleted file mode 100644 index 3470ed513..000000000 --- a/platforms/unix/src/node.rs +++ /dev/null @@ -1,1070 +0,0 @@ -// 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. - -// Derived from Chromium's accessibility abstraction. -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE.chromium file. - -use crate::{ - adapter::AdapterImpl, - atspi::{ - interfaces::{Action as AtspiAction, ObjectEvent, Property}, - ObjectId, OwnedObjectAddress, Rect as AtspiRect, - }, - context::{AdapterAndContext, AppContext, Context}, - filters::{filter, filter_detached}, - util::WindowBounds, -}; -use accesskit::{ - Action, ActionData, ActionRequest, Affine, Checked, DefaultActionVerb, Live, NodeId, Point, - Rect, Role, -}; -use accesskit_consumer::{DetachedNode, FilterResult, Node, NodeState, TreeState}; -use atspi::{ - CoordType, Interface, InterfaceSet, Layer, Live as AtspiLive, Role as AtspiRole, State, - StateSet, -}; -use std::{ - iter::FusedIterator, - sync::{Arc, RwLockReadGuard, Weak}, -}; -use zbus::fdo; - -pub(crate) enum NodeWrapper<'a> { - Node { - adapter: usize, - node: &'a Node<'a>, - }, - DetachedNode { - adapter: usize, - node: &'a DetachedNode, - }, -} - -impl<'a> NodeWrapper<'a> { - fn node_state(&self) -> &NodeState { - match self { - Self::Node { node, .. } => node.state(), - Self::DetachedNode { node, .. } => node.state(), - } - } - - fn adapter(&self) -> usize { - match self { - Self::Node { adapter, .. } => *adapter, - Self::DetachedNode { adapter, .. } => *adapter, - } - } - - pub fn name(&self) -> Option { - match self { - Self::Node { node, .. } => node.name(), - Self::DetachedNode { node, .. } => node.name(), - } - } - - pub fn description(&self) -> String { - String::new() - } - - pub fn parent_id(&self) -> Option { - self.node_state().parent_id() - } - - pub fn filtered_parent(&self) -> ObjectId { - match self { - Self::Node { adapter, node } => node - .filtered_parent(&filter) - .map(|parent| ObjectId::Node { - adapter: *adapter, - node: parent.id(), - }) - .unwrap_or(ObjectId::Root), - _ => unreachable!(), - } - } - - pub fn id(&self) -> NodeId { - self.node_state().id() - } - - fn child_ids( - &self, - ) -> impl DoubleEndedIterator - + ExactSizeIterator - + FusedIterator - + '_ { - self.node_state().child_ids() - } - - fn filtered_child_ids( - &self, - ) -> impl DoubleEndedIterator + FusedIterator + '_ { - match self { - Self::Node { node, .. } => node.filtered_children(&filter).map(|child| child.id()), - _ => unreachable!(), - } - } - - pub fn role(&self) -> AtspiRole { - if self.node_state().has_role_description() { - return AtspiRole::Extended; - } - - match self.node_state().role() { - Role::Alert => AtspiRole::Notification, - Role::AlertDialog => AtspiRole::Alert, - Role::Comment | Role::Suggestion => AtspiRole::Section, - // TODO: See how to represent ARIA role="application" - Role::Application => AtspiRole::Embedded, - Role::Article => AtspiRole::Article, - Role::Audio => AtspiRole::Audio, - Role::Banner | Role::Header => AtspiRole::Landmark, - Role::Blockquote => AtspiRole::BlockQuote, - Role::Caret => AtspiRole::Unknown, - Role::Button | Role::DefaultButton => AtspiRole::PushButton, - Role::Canvas => AtspiRole::Canvas, - Role::Caption => AtspiRole::Caption, - Role::Cell => AtspiRole::TableCell, - Role::CheckBox => AtspiRole::CheckBox, - Role::Switch => AtspiRole::ToggleButton, - Role::ColorWell => AtspiRole::PushButton, - Role::Column => AtspiRole::Unknown, - Role::ColumnHeader => AtspiRole::ColumnHeader, - Role::ComboBox | Role::EditableComboBox => AtspiRole::ComboBox, - Role::Complementary => AtspiRole::Landmark, - Role::ContentDeletion => AtspiRole::ContentDeletion, - Role::ContentInsertion => AtspiRole::ContentInsertion, - Role::ContentInfo | Role::Footer => AtspiRole::Landmark, - Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue, - Role::DescriptionList => AtspiRole::DescriptionList, - Role::DescriptionListTerm => AtspiRole::DescriptionTerm, - Role::Details => AtspiRole::Panel, - Role::Dialog => AtspiRole::Dialog, - Role::Directory => AtspiRole::List, - Role::DisclosureTriangle => AtspiRole::ToggleButton, - Role::DocCover => AtspiRole::Image, - Role::DocBackLink | Role::DocBiblioRef | Role::DocGlossRef | Role::DocNoteRef => { - AtspiRole::Link - } - Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem, - Role::DocNotice | Role::DocTip => AtspiRole::Comment, - Role::DocFootnote => AtspiRole::Footnote, - Role::DocPageBreak => AtspiRole::Separator, - Role::DocPageFooter => AtspiRole::Footer, - Role::DocPageHeader => AtspiRole::Header, - Role::DocAcknowledgements - | Role::DocAfterword - | Role::DocAppendix - | Role::DocBibliography - | Role::DocChapter - | Role::DocConclusion - | Role::DocCredits - | Role::DocEndnotes - | Role::DocEpilogue - | Role::DocErrata - | Role::DocForeword - | Role::DocGlossary - | Role::DocIndex - | Role::DocIntroduction - | Role::DocPageList - | Role::DocPart - | Role::DocPreface - | Role::DocPrologue - | Role::DocToc => AtspiRole::Landmark, - Role::DocAbstract - | Role::DocColophon - | Role::DocCredit - | Role::DocDedication - | Role::DocEpigraph - | Role::DocExample - | Role::DocPullquote - | Role::DocQna => AtspiRole::Section, - Role::DocSubtitle => AtspiRole::Heading, - Role::Document => AtspiRole::DocumentFrame, - Role::EmbeddedObject => AtspiRole::Embedded, - // TODO: Forms which lack an accessible name are no longer - // exposed as forms. Forms which have accessible - // names should be exposed as `AtspiRole::Landmark` according to Core AAM. - Role::Form => AtspiRole::Form, - Role::Figure | Role::Feed => AtspiRole::Panel, - Role::GenericContainer - | Role::FooterAsNonLandmark - | Role::HeaderAsNonLandmark - | Role::Ruby => AtspiRole::Section, - Role::GraphicsDocument => AtspiRole::DocumentFrame, - Role::GraphicsObject => AtspiRole::Panel, - Role::GraphicsSymbol => AtspiRole::Image, - Role::Grid => AtspiRole::Table, - Role::Group => AtspiRole::Panel, - Role::Heading => AtspiRole::Heading, - Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, - // TODO: If there are unignored children, then it should be AtspiRole::ImageMap. - Role::Image => AtspiRole::Image, - Role::InlineTextBox => AtspiRole::Static, - Role::Legend => AtspiRole::Label, - // Layout table objects are treated the same as `Role::GenericContainer`. - Role::LayoutTable => AtspiRole::Section, - Role::LayoutTableCell => AtspiRole::Section, - Role::LayoutTableRow => AtspiRole::Section, - // TODO: Having a separate accessible object for line breaks - // is inconsistent with other implementations. - Role::LineBreak => AtspiRole::Static, - Role::Link => AtspiRole::Link, - Role::List => AtspiRole::List, - Role::ListBox => AtspiRole::ListBox, - // TODO: Use `AtspiRole::MenuItem' inside a combo box. - Role::ListBoxOption => AtspiRole::ListItem, - Role::ListGrid => AtspiRole::Table, - Role::ListItem => AtspiRole::ListItem, - // Regular list markers only expose their alternative text, but do not - // expose their descendants, and the descendants should be ignored. This - // is because the alternative text depends on the counter style and can - // be different from the actual (visual) marker text, and hence, - // inconsistent with the descendants. We treat a list marker as non-text - // only if it still has non-ignored descendants, which happens only when => - // - The list marker itself is ignored but the descendants are not - // - Or the list marker contains images - // TODO: How to check for unignored children when the node is detached? - Role::ListMarker => AtspiRole::Static, - Role::Log => AtspiRole::Log, - Role::Main => AtspiRole::Landmark, - Role::Mark => AtspiRole::Static, - Role::Math => AtspiRole::Math, - Role::Marquee => AtspiRole::Marquee, - Role::Menu | Role::MenuListPopup => AtspiRole::Menu, - Role::MenuBar => AtspiRole::MenuBar, - Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem, - Role::MenuItemCheckBox => AtspiRole::CheckMenuItem, - Role::MenuItemRadio => AtspiRole::RadioMenuItem, - Role::Meter => AtspiRole::LevelBar, - Role::Navigation => AtspiRole::Landmark, - Role::Note => AtspiRole::Comment, - Role::Pane | Role::ScrollView => AtspiRole::Panel, - Role::Paragraph => AtspiRole::Paragraph, - Role::PdfActionableHighlight => AtspiRole::PushButton, - Role::PdfRoot => AtspiRole::DocumentFrame, - Role::PluginObject => AtspiRole::Embedded, - Role::Portal => AtspiRole::PushButton, - Role::Pre => AtspiRole::Section, - Role::ProgressIndicator => AtspiRole::ProgressBar, - Role::RadioButton => AtspiRole::RadioButton, - Role::RadioGroup => AtspiRole::Panel, - Role::Region => AtspiRole::Landmark, - Role::RootWebArea => AtspiRole::DocumentWeb, - Role::Row => AtspiRole::TableRow, - Role::RowGroup => AtspiRole::Panel, - Role::RowHeader => AtspiRole::RowHeader, - // TODO: Generally exposed as description on (`Role::Ruby`) element, not - // as its own object in the tree. - // However, it's possible to make a `Role::RubyAnnotation` element show up in the - // tree, for example by adding tabindex="0" to the source or - // element or making the source element the target of an aria-owns. - // Therefore, we need to gracefully handle it if it actually - // shows up in the tree. - Role::RubyAnnotation => AtspiRole::Static, - Role::Section => AtspiRole::Section, - Role::ScrollBar => AtspiRole::ScrollBar, - Role::Search => AtspiRole::Landmark, - Role::Slider => AtspiRole::Slider, - Role::SpinButton => AtspiRole::SpinButton, - Role::Splitter => AtspiRole::Separator, - Role::StaticText => AtspiRole::Static, - Role::Status => AtspiRole::StatusBar, - Role::SvgRoot => AtspiRole::DocumentFrame, - Role::Tab => AtspiRole::PageTab, - Role::Table => AtspiRole::Table, - // TODO: This mapping is correct, but it doesn't seem to be - // used. We don't necessarily want to always expose these containers, but - // we must do so if they are focusable. - Role::TableHeaderContainer => AtspiRole::Panel, - Role::TabList => AtspiRole::PageTabList, - Role::TabPanel => AtspiRole::ScrollPane, - // TODO: This mapping should also be applied to the dfn - // element. - Role::Term => AtspiRole::DescriptionTerm, - Role::TitleBar => AtspiRole::TitleBar, - Role::TextInput - | Role::MultilineTextInput - | Role::SearchInput - | Role::EmailInput - | Role::NumberInput - | Role::PhoneNumberInput - | Role::UrlInput => AtspiRole::Entry, - Role::DateInput - | Role::DateTimeInput - | Role::WeekInput - | Role::MonthInput - | Role::TimeInput => AtspiRole::DateEditor, - Role::PasswordInput => AtspiRole::PasswordText, - Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => { - AtspiRole::Static - } - Role::Timer => AtspiRole::Timer, - Role::ToggleButton => AtspiRole::ToggleButton, - Role::Toolbar => AtspiRole::ToolBar, - Role::Tooltip => AtspiRole::ToolTip, - Role::Tree => AtspiRole::Tree, - Role::TreeItem => AtspiRole::TreeItem, - Role::TreeGrid => AtspiRole::TreeTable, - Role::Video => AtspiRole::Video, - // In AT-SPI, elements with `AtspiRole::Frame` are windows with titles and - // buttons, while those with `AtspiRole::Window` are windows without those - // elements. - Role::Window => AtspiRole::Frame, - Role::WebView => AtspiRole::Panel, - Role::FigureCaption => AtspiRole::Caption, - // TODO: Are there special cases to consider? - Role::Unknown => AtspiRole::Unknown, - Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject, - Role::Terminal => AtspiRole::Terminal, - } - } - - fn is_focused(&self) -> bool { - match self { - Self::Node { node, .. } => node.is_focused(), - Self::DetachedNode { node, .. } => node.is_focused(), - } - } - - pub fn state(&self, is_window_focused: bool) -> StateSet { - let state = self.node_state(); - let atspi_role = self.role(); - let mut atspi_state = StateSet::empty(); - if state.parent_id().is_none() && state.role() == Role::Window && is_window_focused { - atspi_state.insert(State::Active); - } - // TODO: Focus and selection. - if state.is_focusable() { - atspi_state.insert(State::Focusable); - } - let filter_result = match self { - Self::Node { node, .. } => filter(node), - Self::DetachedNode { node, .. } => filter_detached(node), - }; - if filter_result == FilterResult::Include { - atspi_state.insert(State::Visible | State::Showing); - } - if atspi_role != AtspiRole::ToggleButton && state.checked().is_some() { - atspi_state.insert(State::Checkable); - } - if let Some(selected) = state.is_selected() { - if !state.is_disabled() { - atspi_state.insert(State::Selectable); - } - if selected { - atspi_state.insert(State::Selected); - } - } - if state.is_text_input() { - atspi_state.insert(State::SelectableText); - atspi_state.insert(match state.is_multiline() { - true => State::MultiLine, - false => State::SingleLine, - }); - } - - // Special case for indeterminate progressbar. - if state.role() == Role::ProgressIndicator && state.numeric_value().is_none() { - atspi_state.insert(State::Indeterminate); - } - - // Checked state - match state.checked() { - Some(Checked::Mixed) => atspi_state.insert(State::Indeterminate), - Some(Checked::True) if atspi_role == AtspiRole::ToggleButton => { - atspi_state.insert(State::Pressed) - } - Some(Checked::True) => atspi_state.insert(State::Checked), - _ => {} - } - - if state.is_read_only_supported() && state.is_read_only_or_disabled() { - atspi_state.insert(State::ReadOnly); - } else { - atspi_state.insert(State::Enabled | State::Sensitive); - } - - if self.is_focused() { - atspi_state.insert(State::Focused); - } - - atspi_state - } - - fn is_root(&self) -> bool { - match self { - Self::Node { node, .. } => node.is_root(), - Self::DetachedNode { node, .. } => node.is_root(), - } - } - - pub fn interfaces(&self) -> InterfaceSet { - let state = self.node_state(); - let mut interfaces = InterfaceSet::new(Interface::Accessible); - if state.default_action_verb().is_some() { - interfaces.insert(Interface::Action); - } - if state.raw_bounds().is_some() || self.is_root() { - interfaces.insert(Interface::Component); - } - if self.current_value().is_some() { - interfaces.insert(Interface::Value); - } - interfaces - } - - pub(crate) fn live(&self) -> AtspiLive { - let live = match self { - Self::Node { node, .. } => node.live(), - Self::DetachedNode { node, .. } => node.live(), - }; - match live { - Live::Off => AtspiLive::None, - Live::Polite => AtspiLive::Polite, - Live::Assertive => AtspiLive::Assertive, - } - } - - fn n_actions(&self) -> i32 { - match self.node_state().default_action_verb() { - Some(_) => 1, - None => 0, - } - } - - fn get_action_name(&self, index: i32) -> String { - if index != 0 { - return String::new(); - } - String::from(match self.node_state().default_action_verb() { - Some(DefaultActionVerb::Click) => "click", - Some(DefaultActionVerb::Focus) => "focus", - Some(DefaultActionVerb::Check) => "check", - Some(DefaultActionVerb::Uncheck) => "uncheck", - Some(DefaultActionVerb::ClickAncestor) => "clickAncestor", - Some(DefaultActionVerb::Jump) => "jump", - Some(DefaultActionVerb::Open) => "open", - Some(DefaultActionVerb::Press) => "press", - Some(DefaultActionVerb::Select) => "select", - Some(DefaultActionVerb::Unselect) => "unselect", - None => "", - }) - } - - fn raw_bounds_and_transform(&self) -> (Option, Affine) { - let state = self.node_state(); - (state.raw_bounds(), state.direct_transform()) - } - - fn extents(&self, window_bounds: &WindowBounds) -> AtspiRect { - if self.is_root() { - return window_bounds.outer.into(); - } - match self { - Self::Node { node, .. } => node.bounding_box().map_or_else( - || AtspiRect::INVALID, - |bounds| { - let window_top_left = window_bounds.inner.origin(); - let node_origin = bounds.origin(); - let new_origin = Point::new( - window_top_left.x + node_origin.x, - window_top_left.y + node_origin.y, - ); - bounds.with_origin(new_origin).into() - }, - ), - _ => unreachable!(), - } - } - - fn current_value(&self) -> Option { - self.node_state().numeric_value() - } - - pub(crate) fn notify_changes( - &self, - window_bounds: &WindowBounds, - adapter: &AdapterImpl, - old: &NodeWrapper<'_>, - ) { - self.notify_state_changes(adapter, old); - self.notify_property_changes(adapter, old); - self.notify_bounds_changes(window_bounds, adapter, old); - self.notify_children_changes(adapter, old); - } - - fn notify_state_changes(&self, adapter: &AdapterImpl, old: &NodeWrapper<'_>) { - let adapter_id = self.adapter(); - let old_state = old.state(true); - let new_state = self.state(true); - let changed_states = old_state ^ new_state; - for state in changed_states.iter() { - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::StateChanged(state, new_state.contains(state)), - ); - } - } - - fn notify_property_changes(&self, adapter: &AdapterImpl, old: &NodeWrapper<'_>) { - let adapter_id = self.adapter(); - let name = self.name(); - if name != old.name() { - let name = name.unwrap_or_default(); - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::PropertyChanged(Property::Name(name.clone())), - ); - - let live = self.live(); - if live != AtspiLive::None { - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::Announcement(name, live), - ); - } - } - let description = self.description(); - if description != old.description() { - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::PropertyChanged(Property::Description(description)), - ); - } - let parent_id = self.parent_id(); - if parent_id != old.parent_id() { - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::PropertyChanged(Property::Parent(self.filtered_parent())), - ); - } - let role = self.role(); - if role != old.role() { - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::PropertyChanged(Property::Role(role)), - ); - } - if let Some(value) = self.current_value() { - if Some(value) != old.current_value() { - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::PropertyChanged(Property::Value(value)), - ); - } - } - } - - fn notify_bounds_changes( - &self, - window_bounds: &WindowBounds, - adapter: &AdapterImpl, - old: &NodeWrapper<'_>, - ) { - if self.raw_bounds_and_transform() != old.raw_bounds_and_transform() { - adapter.emit_object_event( - ObjectId::Node { - adapter: self.adapter(), - node: self.id(), - }, - ObjectEvent::BoundsChanged(self.extents(window_bounds)), - ); - } - } - - fn notify_children_changes(&self, adapter: &AdapterImpl, old: &NodeWrapper<'_>) { - let adapter_id = self.adapter(); - let old_children = old.child_ids().collect::>(); - let filtered_children = self.filtered_child_ids().collect::>(); - for (index, child) in filtered_children.iter().enumerate() { - if !old_children.contains(child) { - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::ChildAdded( - index, - ObjectId::Node { - adapter: adapter_id, - node: *child, - }, - ), - ); - } - } - for child in old_children.into_iter() { - if !filtered_children.contains(&child) { - adapter.emit_object_event( - ObjectId::Node { - adapter: adapter_id, - node: self.id(), - }, - ObjectEvent::ChildRemoved(ObjectId::Node { - adapter: adapter_id, - node: child, - }), - ); - } - } - } -} - -pub(crate) fn unknown_object(id: &ObjectId) -> fdo::Error { - fdo::Error::UnknownObject(id.path().to_string()) -} - -#[derive(Clone)] -pub(crate) struct PlatformNode { - context: Weak, - adapter_id: usize, - node_id: NodeId, -} - -impl PlatformNode { - pub(crate) fn new(context: Weak, adapter_id: usize, node_id: NodeId) -> Self { - Self { - context, - adapter_id, - node_id, - } - } - - fn node_wrapper<'a>(&self, node: &'a Node) -> NodeWrapper<'a> { - NodeWrapper::Node { - adapter: self.adapter_id, - node, - } - } - - fn upgrade_context(&self) -> fdo::Result> { - if let Some(context) = self.context.upgrade() { - Ok(context) - } else { - Err(unknown_object(&self.accessible_id())) - } - } - - fn with_tree_state_and_context(&self, f: F) -> fdo::Result - where - F: FnOnce(&TreeState, &Context) -> fdo::Result, - { - let context = self.upgrade_context()?; - let tree = context.read_tree(); - f(tree.state(), &context) - } - - fn resolve_with_context(&self, f: F) -> fdo::Result - where - for<'a> F: FnOnce(Node<'a>, &Context) -> fdo::Result, - { - self.with_tree_state_and_context(|state, context| { - if let Some(node) = state.node_by_id(self.node_id) { - f(node, context) - } else { - Err(unknown_object(&self.accessible_id())) - } - }) - } - - fn resolve(&self, f: F) -> fdo::Result - where - for<'a> F: FnOnce(Node<'a>) -> fdo::Result, - { - self.resolve_with_context(|node, _| f(node)) - } - - fn do_action_internal(&self, f: F) -> fdo::Result<()> - where - F: FnOnce(&TreeState, &Context) -> ActionRequest, - { - let context = self.upgrade_context()?; - let tree = context.read_tree(); - if tree.state().has_node(self.node_id) { - let request = f(tree.state(), &context); - drop(tree); - context.do_action(request); - Ok(()) - } else { - Err(unknown_object(&self.accessible_id())) - } - } - - pub fn name(&self) -> fdo::Result { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - Ok(wrapper.name().unwrap_or_default()) - }) - } - - pub fn description(&self) -> fdo::Result { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - Ok(wrapper.description()) - }) - } - - pub fn parent(&self) -> fdo::Result { - self.resolve(|node| { - Ok(node - .filtered_parent(&filter) - .map(|parent| ObjectId::Node { - adapter: self.adapter_id, - node: parent.id(), - }) - .unwrap_or(ObjectId::Root)) - }) - } - - pub fn child_count(&self) -> fdo::Result { - self.resolve(|node| { - i32::try_from(node.filtered_children(&filter).count()) - .map_err(|_| fdo::Error::Failed("Too many children.".into())) - }) - } - - pub fn accessible_id(&self) -> ObjectId { - ObjectId::Node { - adapter: self.adapter_id, - node: self.node_id, - } - } - - pub fn child_at_index(&self, index: usize) -> fdo::Result> { - self.resolve(|node| { - let child = node - .filtered_children(&filter) - .nth(index) - .map(|child| ObjectId::Node { - adapter: self.adapter_id, - node: child.id(), - }); - Ok(child) - }) - } - - pub fn children(&self) -> fdo::Result> { - self.resolve(|node| { - let children = node - .filtered_children(&filter) - .map(|child| ObjectId::Node { - adapter: self.adapter_id, - node: child.id(), - }) - .collect(); - Ok(children) - }) - } - - pub fn index_in_parent(&self) -> fdo::Result { - self.resolve(|node| { - i32::try_from(node.preceding_filtered_siblings(&filter).count()) - .map_err(|_| fdo::Error::Failed("Index is too big.".into())) - }) - } - - pub fn role(&self) -> fdo::Result { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - Ok(wrapper.role()) - }) - } - - pub(crate) fn localized_role_name(&self) -> fdo::Result { - self.resolve(|node| Ok(node.state().role_description().unwrap_or_default())) - } - - pub fn state(&self) -> fdo::Result { - self.resolve_with_context(|node, context| { - let wrapper = self.node_wrapper(&node); - Ok(wrapper.state(context.read_tree().state().focus_id().is_some())) - }) - } - - pub fn interfaces(&self) -> fdo::Result { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - Ok(wrapper.interfaces()) - }) - } - - pub fn n_actions(&self) -> fdo::Result { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - Ok(wrapper.n_actions()) - }) - } - - pub fn get_action_name(&self, index: i32) -> fdo::Result { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - Ok(wrapper.get_action_name(index)) - }) - } - - pub fn get_actions(&self) -> fdo::Result> { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - let n_actions = wrapper.n_actions() as usize; - let mut actions = Vec::with_capacity(n_actions); - for i in 0..n_actions { - actions.push(AtspiAction { - localized_name: wrapper.get_action_name(i as i32), - description: "".into(), - key_binding: "".into(), - }); - } - Ok(actions) - }) - } - - pub fn do_action(&self, index: i32) -> fdo::Result { - if index != 0 { - return Ok(false); - } - self.do_action_internal(|_, _| ActionRequest { - action: Action::Default, - target: self.node_id, - data: None, - })?; - Ok(true) - } - - pub fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { - self.resolve_with_context(|node, context| { - let window_bounds = context.read_root_window_bounds(); - let bounds = match node.bounding_box() { - Some(node_bounds) => { - let top_left = window_bounds.top_left(coord_type, node.is_root()); - let new_origin = - Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); - node_bounds.with_origin(new_origin) - } - None if node.is_root() => { - let bounds = window_bounds.outer; - match coord_type { - CoordType::Screen => bounds, - CoordType::Window => bounds.with_origin(Point::ZERO), - _ => unimplemented!(), - } - } - _ => return Err(unknown_object(&self.accessible_id())), - }; - Ok(bounds.contains(Point::new(x.into(), y.into()))) - }) - } - - pub fn get_accessible_at_point( - &self, - x: i32, - y: i32, - coord_type: CoordType, - ) -> fdo::Result> { - self.resolve_with_context(|node, context| { - let window_bounds = context.read_root_window_bounds(); - let top_left = window_bounds.top_left(coord_type, node.is_root()); - let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); - let point = node.transform().inverse() * point; - Ok(node - .node_at_point(point, &filter) - .map(|node| ObjectId::Node { - adapter: self.adapter_id, - node: node.id(), - })) - }) - } - - pub fn get_extents(&self, coord_type: CoordType) -> fdo::Result<(AtspiRect,)> { - self.resolve_with_context(|node, context| { - let window_bounds = context.read_root_window_bounds(); - match node.bounding_box() { - Some(node_bounds) => { - let top_left = window_bounds.top_left(coord_type, node.is_root()); - let new_origin = - Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); - Ok((node_bounds.with_origin(new_origin).into(),)) - } - None if node.is_root() => { - let bounds = window_bounds.outer; - Ok((match coord_type { - CoordType::Screen => bounds.into(), - CoordType::Window => bounds.with_origin(Point::ZERO).into(), - _ => unimplemented!(), - },)) - } - _ => Err(unknown_object(&self.accessible_id())), - } - }) - } - - pub fn get_layer(&self) -> fdo::Result { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - if wrapper.role() == AtspiRole::Window { - Ok(Layer::Window) - } else { - Ok(Layer::Widget) - } - }) - } - - pub fn grab_focus(&self) -> fdo::Result { - self.do_action_internal(|_, _| ActionRequest { - action: Action::Focus, - target: self.node_id, - data: None, - })?; - Ok(true) - } - - pub fn scroll_to_point(&self, coord_type: CoordType, x: i32, y: i32) -> fdo::Result { - self.do_action_internal(|tree_state, context| { - let window_bounds = context.read_root_window_bounds(); - let is_root = self.node_id == tree_state.root_id(); - let top_left = window_bounds.top_left(coord_type, is_root); - let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); - ActionRequest { - action: Action::ScrollToPoint, - target: self.node_id, - data: Some(ActionData::ScrollToPoint(point)), - } - })?; - Ok(true) - } - - pub fn minimum_value(&self) -> fdo::Result { - self.resolve(|node| Ok(node.state().min_numeric_value().unwrap_or(std::f64::MIN))) - } - - pub fn maximum_value(&self) -> fdo::Result { - self.resolve(|node| Ok(node.state().max_numeric_value().unwrap_or(std::f64::MAX))) - } - - pub fn minimum_increment(&self) -> fdo::Result { - self.resolve(|node| Ok(node.state().numeric_value_step().unwrap_or(0.0))) - } - - pub fn current_value(&self) -> fdo::Result { - self.resolve(|node| { - let wrapper = self.node_wrapper(&node); - Ok(wrapper.current_value().unwrap_or(0.0)) - }) - } - - pub fn set_current_value(&self, value: f64) -> fdo::Result<()> { - self.do_action_internal(|_, _| ActionRequest { - action: Action::SetValue, - target: self.node_id, - data: Some(ActionData::NumericValue(value)), - }) - } -} - -#[derive(Clone)] -pub(crate) struct PlatformRootNode; - -impl PlatformRootNode { - pub(crate) fn new() -> Self { - Self {} - } - - fn resolve_app_context(&self, f: F) -> fdo::Result - where - for<'a> F: FnOnce(RwLockReadGuard<'a, AppContext>) -> fdo::Result, - { - let app_context = AppContext::read(); - f(app_context) - } - - pub(crate) fn name(&self) -> fdo::Result { - self.resolve_app_context(|context| Ok(context.name.clone().unwrap_or_default())) - } - - pub(crate) fn parent(&self) -> fdo::Result> { - self.resolve_app_context(|context| Ok(context.desktop_address.clone())) - } - - pub(crate) fn child_count(&self) -> fdo::Result { - self.resolve_app_context(|context| { - i32::try_from(context.adapters.len()) - .map_err(|_| fdo::Error::Failed("Too many children.".into())) - }) - } - - pub(crate) fn accessible_id(&self) -> ObjectId { - ObjectId::Root - } - - pub(crate) fn child_at_index(&self, index: usize) -> fdo::Result> { - self.resolve_app_context(|context| { - let child = context - .adapters - .get(index) - .and_then(AdapterAndContext::upgrade) - .map(|(adapter, context)| ObjectId::Node { - adapter, - node: context.read_tree().state().root_id(), - }); - Ok(child) - }) - } - - pub(crate) fn children(&self) -> fdo::Result> { - self.resolve_app_context(|context| { - let children = context - .adapters - .iter() - .filter_map(AdapterAndContext::upgrade) - .map(|(adapter, context)| ObjectId::Node { - adapter, - node: context.read_tree().state().root_id(), - }) - .collect(); - Ok(children) - }) - } - - pub(crate) fn toolkit_name(&self) -> fdo::Result { - self.resolve_app_context(|context| Ok(context.toolkit_name.clone().unwrap_or_default())) - } - - pub(crate) fn toolkit_version(&self) -> fdo::Result { - self.resolve_app_context(|context| Ok(context.toolkit_version.clone().unwrap_or_default())) - } - - pub(crate) fn id(&self) -> fdo::Result { - self.resolve_app_context(|context| Ok(context.id.unwrap_or(-1))) - } - - pub(crate) fn set_id(&mut self, id: i32) -> fdo::Result<()> { - let mut app_context = AppContext::write(); - app_context.id = Some(id); - Ok(()) - } -} diff --git a/platforms/unix/src/util.rs b/platforms/unix/src/util.rs index 239fd600f..fe7c818c3 100644 --- a/platforms/unix/src/util.rs +++ b/platforms/unix/src/util.rs @@ -3,8 +3,10 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{Point, Rect}; -use atspi::CoordType; +use accesskit_atspi_common::{Error as InternalError, PlatformNode}; +use zbus::fdo::Error as FdoError; + +use crate::atspi::ObjectId; #[cfg(not(feature = "tokio"))] pub(crate) fn block_on(future: F) -> F::Output { @@ -21,31 +23,16 @@ pub(crate) fn block_on(future: F) -> F::Output { runtime.block_on(future) } -#[derive(Clone, Copy, Default)] -pub(crate) struct WindowBounds { - pub(crate) outer: Rect, - pub(crate) inner: Rect, -} - -impl WindowBounds { - pub(crate) fn new(outer: Rect, inner: Rect) -> Self { - Self { outer, inner } - } - - pub(crate) fn top_left(&self, coord_type: CoordType, is_root: bool) -> Point { - match coord_type { - CoordType::Screen if is_root => self.outer.origin(), - CoordType::Screen => self.inner.origin(), - CoordType::Window if is_root => Point::ZERO, - CoordType::Window => { - let outer_position = self.outer.origin(); - let inner_position = self.inner.origin(); - Point::new( - inner_position.x - outer_position.x, - inner_position.y - outer_position.y, - ) - } - _ => unimplemented!(), +pub(crate) fn map_error(source: ObjectId, error: InternalError) -> FdoError { + match error { + InternalError::Defunct | InternalError::UnsupportedInterface => { + FdoError::UnknownObject(source.path().to_string()) } + InternalError::TooManyChildren => FdoError::Failed("Too many children.".into()), + InternalError::IndexOutOfRange => FdoError::Failed("Index is too big.".into()), } } + +pub(crate) fn map_error_from_node(source: &PlatformNode, error: InternalError) -> FdoError { + map_error(ObjectId::from(source), error) +}