1// Copyright 2022 The AccessKit Authors. All rights reserved.
2// Licensed under the Apache License, Version 2.0 (found in
3// the LICENSE-APACHE file) or the MIT license (found in
4// the LICENSE-MIT file), at your option.
5
6// Derived from Chromium's accessibility abstraction.
7// Copyright 2017 The Chromium Authors. All rights reserved.
8// Use of this source code is governed by a BSD-style license that can be
9// found in the LICENSE.chromium file.
10
11use accesskit::{
12 Action, ActionData, ActionRequest, Affine, Checked, DefaultActionVerb, Live, NodeId, Point,
13 Rect, Role,
14};
15use accesskit_consumer::{DetachedNode, FilterResult, Node, NodeState, TreeState};
16use atspi_common::{
17 CoordType, Interface, InterfaceSet, Layer, Live as AtspiLive, Role as AtspiRole, State,
18 StateSet,
19};
20use std::{
21 hash::{Hash, Hasher},
22 iter::FusedIterator,
23 sync::{Arc, RwLock, RwLockReadGuard, Weak},
24};
25
26use crate::{
27 adapter::Adapter,
28 context::{AppContext, Context},
29 filters::{filter, filter_detached},
30 util::WindowBounds,
31 Action as AtspiAction, Error, ObjectEvent, Property, Rect as AtspiRect, Result,
32};
33
34pub(crate) enum NodeWrapper<'a> {
35 Node(&'a Node<'a>),
36 DetachedNode(&'a DetachedNode),
37}
38
39impl<'a> NodeWrapper<'a> {
40 fn node_state(&self) -> &NodeState {
41 match self {
42 Self::Node(node) => node.state(),
43 Self::DetachedNode(node) => node.state(),
44 }
45 }
46
47 pub fn name(&self) -> Option<String> {
48 match self {
49 Self::Node(node) => node.name(),
50 Self::DetachedNode(node) => node.name(),
51 }
52 }
53
54 pub fn description(&self) -> String {
55 String::new()
56 }
57
58 pub fn parent_id(&self) -> Option<NodeId> {
59 self.node_state().parent_id()
60 }
61
62 pub fn id(&self) -> NodeId {
63 self.node_state().id()
64 }
65
66 fn child_ids(
67 &self,
68 ) -> impl DoubleEndedIterator<Item = NodeId>
69 + ExactSizeIterator<Item = NodeId>
70 + FusedIterator<Item = NodeId>
71 + '_ {
72 self.node_state().child_ids()
73 }
74
75 fn filtered_child_ids(
76 &self,
77 ) -> impl DoubleEndedIterator<Item = NodeId> + FusedIterator<Item = NodeId> + '_ {
78 match self {
79 Self::Node(node) => node.filtered_children(&filter).map(|child| child.id()),
80 _ => unreachable!(),
81 }
82 }
83
84 pub fn role(&self) -> AtspiRole {
85 if self.node_state().has_role_description() {
86 return AtspiRole::Extended;
87 }
88
89 match self.node_state().role() {
90 Role::Alert => AtspiRole::Notification,
91 Role::AlertDialog => AtspiRole::Alert,
92 Role::Comment | Role::Suggestion => AtspiRole::Section,
93 // TODO: See how to represent ARIA role="application"
94 Role::Application => AtspiRole::Embedded,
95 Role::Article => AtspiRole::Article,
96 Role::Audio => AtspiRole::Audio,
97 Role::Banner | Role::Header => AtspiRole::Landmark,
98 Role::Blockquote => AtspiRole::BlockQuote,
99 Role::Caret => AtspiRole::Unknown,
100 Role::Button | Role::DefaultButton => AtspiRole::PushButton,
101 Role::Canvas => AtspiRole::Canvas,
102 Role::Caption => AtspiRole::Caption,
103 Role::Cell => AtspiRole::TableCell,
104 Role::CheckBox => AtspiRole::CheckBox,
105 Role::Switch => AtspiRole::ToggleButton,
106 Role::ColorWell => AtspiRole::PushButton,
107 Role::Column => AtspiRole::Unknown,
108 Role::ColumnHeader => AtspiRole::ColumnHeader,
109 Role::ComboBox | Role::EditableComboBox => AtspiRole::ComboBox,
110 Role::Complementary => AtspiRole::Landmark,
111 Role::ContentDeletion => AtspiRole::ContentDeletion,
112 Role::ContentInsertion => AtspiRole::ContentInsertion,
113 Role::ContentInfo | Role::Footer => AtspiRole::Landmark,
114 Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue,
115 Role::DescriptionList => AtspiRole::DescriptionList,
116 Role::DescriptionListTerm => AtspiRole::DescriptionTerm,
117 Role::Details => AtspiRole::Panel,
118 Role::Dialog => AtspiRole::Dialog,
119 Role::Directory => AtspiRole::List,
120 Role::DisclosureTriangle => AtspiRole::ToggleButton,
121 Role::DocCover => AtspiRole::Image,
122 Role::DocBackLink | Role::DocBiblioRef | Role::DocGlossRef | Role::DocNoteRef => {
123 AtspiRole::Link
124 }
125 Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem,
126 Role::DocNotice | Role::DocTip => AtspiRole::Comment,
127 Role::DocFootnote => AtspiRole::Footnote,
128 Role::DocPageBreak => AtspiRole::Separator,
129 Role::DocPageFooter => AtspiRole::Footer,
130 Role::DocPageHeader => AtspiRole::Header,
131 Role::DocAcknowledgements
132 | Role::DocAfterword
133 | Role::DocAppendix
134 | Role::DocBibliography
135 | Role::DocChapter
136 | Role::DocConclusion
137 | Role::DocCredits
138 | Role::DocEndnotes
139 | Role::DocEpilogue
140 | Role::DocErrata
141 | Role::DocForeword
142 | Role::DocGlossary
143 | Role::DocIndex
144 | Role::DocIntroduction
145 | Role::DocPageList
146 | Role::DocPart
147 | Role::DocPreface
148 | Role::DocPrologue
149 | Role::DocToc => AtspiRole::Landmark,
150 Role::DocAbstract
151 | Role::DocColophon
152 | Role::DocCredit
153 | Role::DocDedication
154 | Role::DocEpigraph
155 | Role::DocExample
156 | Role::DocPullquote
157 | Role::DocQna => AtspiRole::Section,
158 Role::DocSubtitle => AtspiRole::Heading,
159 Role::Document => AtspiRole::DocumentFrame,
160 Role::EmbeddedObject => AtspiRole::Embedded,
161 // TODO: Forms which lack an accessible name are no longer
162 // exposed as forms. Forms which have accessible
163 // names should be exposed as `AtspiRole::Landmark` according to Core AAM.
164 Role::Form => AtspiRole::Form,
165 Role::Figure | Role::Feed => AtspiRole::Panel,
166 Role::GenericContainer
167 | Role::FooterAsNonLandmark
168 | Role::HeaderAsNonLandmark
169 | Role::Ruby => AtspiRole::Section,
170 Role::GraphicsDocument => AtspiRole::DocumentFrame,
171 Role::GraphicsObject => AtspiRole::Panel,
172 Role::GraphicsSymbol => AtspiRole::Image,
173 Role::Grid => AtspiRole::Table,
174 Role::Group => AtspiRole::Panel,
175 Role::Heading => AtspiRole::Heading,
176 Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame,
177 // TODO: If there are unignored children, then it should be AtspiRole::ImageMap.
178 Role::Image => AtspiRole::Image,
179 Role::InlineTextBox => AtspiRole::Static,
180 Role::Legend => AtspiRole::Label,
181 // Layout table objects are treated the same as `Role::GenericContainer`.
182 Role::LayoutTable => AtspiRole::Section,
183 Role::LayoutTableCell => AtspiRole::Section,
184 Role::LayoutTableRow => AtspiRole::Section,
185 // TODO: Having a separate accessible object for line breaks
186 // is inconsistent with other implementations.
187 Role::LineBreak => AtspiRole::Static,
188 Role::Link => AtspiRole::Link,
189 Role::List => AtspiRole::List,
190 Role::ListBox => AtspiRole::ListBox,
191 // TODO: Use `AtspiRole::MenuItem' inside a combo box.
192 Role::ListBoxOption => AtspiRole::ListItem,
193 Role::ListGrid => AtspiRole::Table,
194 Role::ListItem => AtspiRole::ListItem,
195 // Regular list markers only expose their alternative text, but do not
196 // expose their descendants, and the descendants should be ignored. This
197 // is because the alternative text depends on the counter style and can
198 // be different from the actual (visual) marker text, and hence,
199 // inconsistent with the descendants. We treat a list marker as non-text
200 // only if it still has non-ignored descendants, which happens only when =>
201 // - The list marker itself is ignored but the descendants are not
202 // - Or the list marker contains images
203 // TODO: How to check for unignored children when the node is detached?
204 Role::ListMarker => AtspiRole::Static,
205 Role::Log => AtspiRole::Log,
206 Role::Main => AtspiRole::Landmark,
207 Role::Mark => AtspiRole::Static,
208 Role::Math => AtspiRole::Math,
209 Role::Marquee => AtspiRole::Marquee,
210 Role::Menu | Role::MenuListPopup => AtspiRole::Menu,
211 Role::MenuBar => AtspiRole::MenuBar,
212 Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem,
213 Role::MenuItemCheckBox => AtspiRole::CheckMenuItem,
214 Role::MenuItemRadio => AtspiRole::RadioMenuItem,
215 Role::Meter => AtspiRole::LevelBar,
216 Role::Navigation => AtspiRole::Landmark,
217 Role::Note => AtspiRole::Comment,
218 Role::Pane | Role::ScrollView => AtspiRole::Panel,
219 Role::Paragraph => AtspiRole::Paragraph,
220 Role::PdfActionableHighlight => AtspiRole::PushButton,
221 Role::PdfRoot => AtspiRole::DocumentFrame,
222 Role::PluginObject => AtspiRole::Embedded,
223 Role::Portal => AtspiRole::PushButton,
224 Role::Pre => AtspiRole::Section,
225 Role::ProgressIndicator => AtspiRole::ProgressBar,
226 Role::RadioButton => AtspiRole::RadioButton,
227 Role::RadioGroup => AtspiRole::Panel,
228 Role::Region => AtspiRole::Landmark,
229 Role::RootWebArea => AtspiRole::DocumentWeb,
230 Role::Row => AtspiRole::TableRow,
231 Role::RowGroup => AtspiRole::Panel,
232 Role::RowHeader => AtspiRole::RowHeader,
233 // TODO: Generally exposed as description on <ruby> (`Role::Ruby`) element, not
234 // as its own object in the tree.
235 // However, it's possible to make a `Role::RubyAnnotation` element show up in the
236 // tree, for example by adding tabindex="0" to the source <rp> or <rt>
237 // element or making the source element the target of an aria-owns.
238 // Therefore, we need to gracefully handle it if it actually
239 // shows up in the tree.
240 Role::RubyAnnotation => AtspiRole::Static,
241 Role::Section => AtspiRole::Section,
242 Role::ScrollBar => AtspiRole::ScrollBar,
243 Role::Search => AtspiRole::Landmark,
244 Role::Slider => AtspiRole::Slider,
245 Role::SpinButton => AtspiRole::SpinButton,
246 Role::Splitter => AtspiRole::Separator,
247 Role::StaticText => AtspiRole::Static,
248 Role::Status => AtspiRole::StatusBar,
249 Role::SvgRoot => AtspiRole::DocumentFrame,
250 Role::Tab => AtspiRole::PageTab,
251 Role::Table => AtspiRole::Table,
252 // TODO: This mapping is correct, but it doesn't seem to be
253 // used. We don't necessarily want to always expose these containers, but
254 // we must do so if they are focusable.
255 Role::TableHeaderContainer => AtspiRole::Panel,
256 Role::TabList => AtspiRole::PageTabList,
257 Role::TabPanel => AtspiRole::ScrollPane,
258 // TODO: This mapping should also be applied to the dfn
259 // element.
260 Role::Term => AtspiRole::DescriptionTerm,
261 Role::TitleBar => AtspiRole::TitleBar,
262 Role::TextInput
263 | Role::MultilineTextInput
264 | Role::SearchInput
265 | Role::EmailInput
266 | Role::NumberInput
267 | Role::PhoneNumberInput
268 | Role::UrlInput => AtspiRole::Entry,
269 Role::DateInput
270 | Role::DateTimeInput
271 | Role::WeekInput
272 | Role::MonthInput
273 | Role::TimeInput => AtspiRole::DateEditor,
274 Role::PasswordInput => AtspiRole::PasswordText,
275 Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => {
276 AtspiRole::Static
277 }
278 Role::Timer => AtspiRole::Timer,
279 Role::ToggleButton => AtspiRole::ToggleButton,
280 Role::Toolbar => AtspiRole::ToolBar,
281 Role::Tooltip => AtspiRole::ToolTip,
282 Role::Tree => AtspiRole::Tree,
283 Role::TreeItem => AtspiRole::TreeItem,
284 Role::TreeGrid => AtspiRole::TreeTable,
285 Role::Video => AtspiRole::Video,
286 // In AT-SPI, elements with `AtspiRole::Frame` are windows with titles and
287 // buttons, while those with `AtspiRole::Window` are windows without those
288 // elements.
289 Role::Window => AtspiRole::Frame,
290 Role::WebView => AtspiRole::Panel,
291 Role::FigureCaption => AtspiRole::Caption,
292 // TODO: Are there special cases to consider?
293 Role::Unknown => AtspiRole::Unknown,
294 Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject,
295 Role::Terminal => AtspiRole::Terminal,
296 }
297 }
298
299 fn is_focused(&self) -> bool {
300 match self {
301 Self::Node(node) => node.is_focused(),
302 Self::DetachedNode(node) => node.is_focused(),
303 }
304 }
305
306 pub fn state(&self, is_window_focused: bool) -> StateSet {
307 let state = self.node_state();
308 let atspi_role = self.role();
309 let mut atspi_state = StateSet::empty();
310 if state.parent_id().is_none() && state.role() == Role::Window && is_window_focused {
311 atspi_state.insert(State::Active);
312 }
313 // TODO: Focus and selection.
314 if state.is_focusable() {
315 atspi_state.insert(State::Focusable);
316 }
317 let filter_result = match self {
318 Self::Node(node) => filter(node),
319 Self::DetachedNode(node) => filter_detached(node),
320 };
321 if filter_result == FilterResult::Include {
322 atspi_state.insert(State::Visible | State::Showing);
323 }
324 if atspi_role != AtspiRole::ToggleButton && state.checked().is_some() {
325 atspi_state.insert(State::Checkable);
326 }
327 if let Some(selected) = state.is_selected() {
328 if !state.is_disabled() {
329 atspi_state.insert(State::Selectable);
330 }
331 if selected {
332 atspi_state.insert(State::Selected);
333 }
334 }
335 if state.is_text_input() {
336 atspi_state.insert(State::SelectableText);
337 atspi_state.insert(match state.is_multiline() {
338 true => State::MultiLine,
339 false => State::SingleLine,
340 });
341 }
342
343 // Special case for indeterminate progressbar.
344 if state.role() == Role::ProgressIndicator && state.numeric_value().is_none() {
345 atspi_state.insert(State::Indeterminate);
346 }
347
348 // Checked state
349 match state.checked() {
350 Some(Checked::Mixed) => atspi_state.insert(State::Indeterminate),
351 Some(Checked::True) if atspi_role == AtspiRole::ToggleButton => {
352 atspi_state.insert(State::Pressed)
353 }
354 Some(Checked::True) => atspi_state.insert(State::Checked),
355 _ => {}
356 }
357
358 if state.is_read_only_supported() && state.is_read_only_or_disabled() {
359 atspi_state.insert(State::ReadOnly);
360 } else {
361 atspi_state.insert(State::Enabled | State::Sensitive);
362 }
363
364 if self.is_focused() {
365 atspi_state.insert(State::Focused);
366 }
367
368 atspi_state
369 }
370
371 fn is_root(&self) -> bool {
372 match self {
373 Self::Node(node) => node.is_root(),
374 Self::DetachedNode(node) => node.is_root(),
375 }
376 }
377
378 fn supports_action(&self) -> bool {
379 self.node_state().default_action_verb().is_some()
380 }
381
382 fn supports_component(&self) -> bool {
383 self.node_state().raw_bounds().is_some() || self.is_root()
384 }
385
386 fn supports_value(&self) -> bool {
387 self.current_value().is_some()
388 }
389
390 pub fn interfaces(&self) -> InterfaceSet {
391 let mut interfaces = InterfaceSet::new(Interface::Accessible);
392 if self.supports_action() {
393 interfaces.insert(Interface::Action);
394 }
395 if self.supports_component() {
396 interfaces.insert(Interface::Component);
397 }
398 if self.supports_value() {
399 interfaces.insert(Interface::Value);
400 }
401 interfaces
402 }
403
404 pub(crate) fn live(&self) -> AtspiLive {
405 let live = match self {
406 Self::Node(node) => node.live(),
407 Self::DetachedNode(node) => node.live(),
408 };
409 match live {
410 Live::Off => AtspiLive::None,
411 Live::Polite => AtspiLive::Polite,
412 Live::Assertive => AtspiLive::Assertive,
413 }
414 }
415
416 fn n_actions(&self) -> i32 {
417 match self.node_state().default_action_verb() {
418 Some(_) => 1,
419 None => 0,
420 }
421 }
422
423 fn get_action_name(&self, index: i32) -> String {
424 if index != 0 {
425 return String::new();
426 }
427 String::from(match self.node_state().default_action_verb() {
428 Some(DefaultActionVerb::Click) => "click",
429 Some(DefaultActionVerb::Focus) => "focus",
430 Some(DefaultActionVerb::Check) => "check",
431 Some(DefaultActionVerb::Uncheck) => "uncheck",
432 Some(DefaultActionVerb::ClickAncestor) => "clickAncestor",
433 Some(DefaultActionVerb::Jump) => "jump",
434 Some(DefaultActionVerb::Open) => "open",
435 Some(DefaultActionVerb::Press) => "press",
436 Some(DefaultActionVerb::Select) => "select",
437 Some(DefaultActionVerb::Unselect) => "unselect",
438 None => "",
439 })
440 }
441
442 fn raw_bounds_and_transform(&self) -> (Option<Rect>, Affine) {
443 let state = self.node_state();
444 (state.raw_bounds(), state.direct_transform())
445 }
446
447 fn extents(&self, window_bounds: &WindowBounds) -> AtspiRect {
448 if self.is_root() {
449 return window_bounds.outer.into();
450 }
451 match self {
452 Self::Node(node) => node.bounding_box().map_or_else(
453 || AtspiRect::INVALID,
454 |bounds| {
455 let window_top_left = window_bounds.inner.origin();
456 let node_origin = bounds.origin();
457 let new_origin = Point::new(
458 window_top_left.x + node_origin.x,
459 window_top_left.y + node_origin.y,
460 );
461 bounds.with_origin(new_origin).into()
462 },
463 ),
464 _ => unreachable!(),
465 }
466 }
467
468 fn current_value(&self) -> Option<f64> {
469 self.node_state().numeric_value()
470 }
471
472 pub(crate) fn notify_changes(
473 &self,
474 window_bounds: &WindowBounds,
475 adapter: &Adapter,
476 old: &NodeWrapper<'_>,
477 ) {
478 self.notify_state_changes(adapter, old);
479 self.notify_property_changes(adapter, old);
480 self.notify_bounds_changes(window_bounds, adapter, old);
481 self.notify_children_changes(adapter, old);
482 }
483
484 fn notify_state_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
485 let old_state = old.state(true);
486 let new_state = self.state(true);
487 let changed_states = old_state ^ new_state;
488 for state in changed_states.iter() {
489 adapter.emit_object_event(
490 self.id(),
491 ObjectEvent::StateChanged(state, new_state.contains(state)),
492 );
493 }
494 }
495
496 fn notify_property_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
497 let name = self.name();
498 if name != old.name() {
499 let name = name.unwrap_or_default();
500 adapter.emit_object_event(
501 self.id(),
502 ObjectEvent::PropertyChanged(Property::Name(name.clone())),
503 );
504
505 let live = self.live();
506 if live != AtspiLive::None {
507 adapter.emit_object_event(self.id(), ObjectEvent::Announcement(name, live));
508 }
509 }
510 let description = self.description();
511 if description != old.description() {
512 adapter.emit_object_event(
513 self.id(),
514 ObjectEvent::PropertyChanged(Property::Description(description)),
515 );
516 }
517 let parent_id = self.parent_id();
518 if parent_id != old.parent_id() {
519 let node = match self {
520 NodeWrapper::Node(node) => node,
521 _ => unreachable!(),
522 };
523 let parent = node
524 .filtered_parent(&filter)
525 .map_or(NodeIdOrRoot::Root, |node| NodeIdOrRoot::Node(node.id()));
526 adapter.emit_object_event(
527 self.id(),
528 ObjectEvent::PropertyChanged(Property::Parent(parent)),
529 );
530 }
531 let role = self.role();
532 if role != old.role() {
533 adapter.emit_object_event(
534 self.id(),
535 ObjectEvent::PropertyChanged(Property::Role(role)),
536 );
537 }
538 if let Some(value) = self.current_value() {
539 if Some(value) != old.current_value() {
540 adapter.emit_object_event(
541 self.id(),
542 ObjectEvent::PropertyChanged(Property::Value(value)),
543 );
544 }
545 }
546 }
547
548 fn notify_bounds_changes(
549 &self,
550 window_bounds: &WindowBounds,
551 adapter: &Adapter,
552 old: &NodeWrapper<'_>,
553 ) {
554 if self.raw_bounds_and_transform() != old.raw_bounds_and_transform() {
555 adapter.emit_object_event(
556 self.id(),
557 ObjectEvent::BoundsChanged(self.extents(window_bounds)),
558 );
559 }
560 }
561
562 fn notify_children_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
563 let old_children = old.child_ids().collect::<Vec<NodeId>>();
564 let filtered_children = self.filtered_child_ids().collect::<Vec<NodeId>>();
565 for (index, child) in filtered_children.iter().enumerate() {
566 if !old_children.contains(child) {
567 adapter.emit_object_event(self.id(), ObjectEvent::ChildAdded(index, *child));
568 }
569 }
570 for child in old_children.into_iter() {
571 if !filtered_children.contains(&child) {
572 adapter.emit_object_event(self.id(), ObjectEvent::ChildRemoved(child));
573 }
574 }
575 }
576}
577
578#[derive(Clone)]
579pub struct PlatformNode {
580 context: Weak<Context>,
581 adapter_id: usize,
582 id: NodeId,
583}
584
585impl PlatformNode {
586 pub(crate) fn new(context: &Arc<Context>, adapter_id: usize, id: NodeId) -> Self {
587 Self {
588 context: Arc::downgrade(context),
589 adapter_id,
590 id,
591 }
592 }
593
594 fn from_adapter_root(adapter_id_and_context: &(usize, Arc<Context>)) -> Self {
595 let (adapter_id, context) = adapter_id_and_context;
596 Self::new(context, *adapter_id, context.read_tree().state().root_id())
597 }
598
599 fn upgrade_context(&self) -> Result<Arc<Context>> {
600 if let Some(context) = self.context.upgrade() {
601 Ok(context)
602 } else {
603 Err(Error::Defunct)
604 }
605 }
606
607 fn with_tree_state<F, T>(&self, f: F) -> Result<T>
608 where
609 F: FnOnce(&TreeState) -> Result<T>,
610 {
611 let context = self.upgrade_context()?;
612 let tree = context.read_tree();
613 f(tree.state())
614 }
615
616 fn with_tree_state_and_context<F, T>(&self, f: F) -> Result<T>
617 where
618 F: FnOnce(&TreeState, &Context) -> Result<T>,
619 {
620 let context = self.upgrade_context()?;
621 let tree = context.read_tree();
622 f(tree.state(), &context)
623 }
624
625 fn resolve_with_context<F, T>(&self, f: F) -> Result<T>
626 where
627 for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
628 {
629 self.with_tree_state_and_context(|state, context| {
630 if let Some(node) = state.node_by_id(self.id) {
631 f(node, context)
632 } else {
633 Err(Error::Defunct)
634 }
635 })
636 }
637
638 fn resolve<F, T>(&self, f: F) -> Result<T>
639 where
640 for<'a> F: FnOnce(Node<'a>) -> Result<T>,
641 {
642 self.resolve_with_context(|node, _| f(node))
643 }
644
645 fn do_action_internal<F>(&self, f: F) -> Result<()>
646 where
647 F: FnOnce(&TreeState, &Context) -> ActionRequest,
648 {
649 let context = self.upgrade_context()?;
650 let tree = context.read_tree();
651 if tree.state().has_node(self.id) {
652 let request = f(tree.state(), &context);
653 drop(tree);
654 context.do_action(request);
655 Ok(())
656 } else {
657 Err(Error::Defunct)
658 }
659 }
660
661 pub fn name(&self) -> Result<String> {
662 self.resolve(|node| {
663 let wrapper = NodeWrapper::Node(&node);
664 Ok(wrapper.name().unwrap_or_default())
665 })
666 }
667
668 pub fn description(&self) -> Result<String> {
669 self.resolve(|node| {
670 let wrapper = NodeWrapper::Node(&node);
671 Ok(wrapper.description())
672 })
673 }
674
675 pub fn relative(&self, id: NodeId) -> Self {
676 Self {
677 context: self.context.clone(),
678 adapter_id: self.adapter_id,
679 id,
680 }
681 }
682
683 pub fn root(&self) -> Result<PlatformRoot> {
684 let context = self.upgrade_context()?;
685 Ok(PlatformRoot::new(&context.app_context))
686 }
687
688 pub fn toolkit_name(&self) -> Result<String> {
689 self.with_tree_state(|state| Ok(state.toolkit_name().unwrap_or_default()))
690 }
691
692 pub fn toolkit_version(&self) -> Result<String> {
693 self.with_tree_state(|state| Ok(state.toolkit_version().unwrap_or_default()))
694 }
695
696 pub fn parent(&self) -> Result<NodeIdOrRoot> {
697 self.resolve(|node| {
698 let parent = node
699 .filtered_parent(&filter)
700 .map_or(NodeIdOrRoot::Root, |node| NodeIdOrRoot::Node(node.id()));
701 Ok(parent)
702 })
703 }
704
705 pub fn child_count(&self) -> Result<i32> {
706 self.resolve(|node| {
707 i32::try_from(node.filtered_children(&filter).count())
708 .map_err(|_| Error::TooManyChildren)
709 })
710 }
711
712 pub fn adapter_id(&self) -> usize {
713 self.adapter_id
714 }
715
716 pub fn id(&self) -> NodeId {
717 self.id
718 }
719
720 pub fn child_at_index(&self, index: usize) -> Result<Option<NodeId>> {
721 self.resolve(|node| {
722 let child = node
723 .filtered_children(&filter)
724 .nth(index)
725 .map(|child| child.id());
726 Ok(child)
727 })
728 }
729
730 pub fn map_children<T, I>(&self, f: impl Fn(NodeId) -> I) -> Result<T>
731 where
732 T: FromIterator<I>,
733 {
734 self.resolve(|node| {
735 let children = node
736 .filtered_children(&filter)
737 .map(|child| child.id())
738 .map(f)
739 .collect();
740 Ok(children)
741 })
742 }
743
744 pub fn index_in_parent(&self) -> Result<i32> {
745 self.resolve(|node| {
746 i32::try_from(node.preceding_filtered_siblings(&filter).count())
747 .map_err(|_| Error::IndexOutOfRange)
748 })
749 }
750
751 pub fn role(&self) -> Result<AtspiRole> {
752 self.resolve(|node| {
753 let wrapper = NodeWrapper::Node(&node);
754 Ok(wrapper.role())
755 })
756 }
757
758 pub fn localized_role_name(&self) -> Result<String> {
759 self.resolve(|node| Ok(node.state().role_description().unwrap_or_default()))
760 }
761
762 pub fn state(&self) -> StateSet {
763 self.resolve_with_context(|node, context| {
764 let wrapper = NodeWrapper::Node(&node);
765 Ok(wrapper.state(context.read_tree().state().focus_id().is_some()))
766 })
767 .unwrap_or(State::Defunct.into())
768 }
769
770 pub fn supports_action(&self) -> Result<bool> {
771 self.resolve(|node| {
772 let wrapper = NodeWrapper::Node(&node);
773 Ok(wrapper.supports_action())
774 })
775 }
776
777 pub fn supports_component(&self) -> Result<bool> {
778 self.resolve(|node| {
779 let wrapper = NodeWrapper::Node(&node);
780 Ok(wrapper.supports_component())
781 })
782 }
783
784 pub fn supports_value(&self) -> Result<bool> {
785 self.resolve(|node| {
786 let wrapper = NodeWrapper::Node(&node);
787 Ok(wrapper.supports_value())
788 })
789 }
790
791 pub fn interfaces(&self) -> Result<InterfaceSet> {
792 self.resolve(|node| {
793 let wrapper = NodeWrapper::Node(&node);
794 Ok(wrapper.interfaces())
795 })
796 }
797
798 pub fn n_actions(&self) -> Result<i32> {
799 self.resolve(|node| {
800 let wrapper = NodeWrapper::Node(&node);
801 Ok(wrapper.n_actions())
802 })
803 }
804
805 pub fn action_name(&self, index: i32) -> Result<String> {
806 self.resolve(|node| {
807 let wrapper = NodeWrapper::Node(&node);
808 Ok(wrapper.get_action_name(index))
809 })
810 }
811
812 pub fn actions(&self) -> Result<Vec<AtspiAction>> {
813 self.resolve(|node| {
814 let wrapper = NodeWrapper::Node(&node);
815 let n_actions = wrapper.n_actions() as usize;
816 let mut actions = Vec::with_capacity(n_actions);
817 for i in 0..n_actions {
818 actions.push(AtspiAction {
819 localized_name: wrapper.get_action_name(i as i32),
820 description: "".into(),
821 key_binding: "".into(),
822 });
823 }
824 Ok(actions)
825 })
826 }
827
828 pub fn do_action(&self, index: i32) -> Result<bool> {
829 if index != 0 {
830 return Ok(false);
831 }
832 self.do_action_internal(|_, _| ActionRequest {
833 action: Action::Default,
834 target: self.id,
835 data: None,
836 })?;
837 Ok(true)
838 }
839
840 pub fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> Result<bool> {
841 self.resolve_with_context(|node, context| {
842 let window_bounds = context.read_root_window_bounds();
843 let bounds = match node.bounding_box() {
844 Some(node_bounds) => {
845 let top_left = window_bounds.top_left(coord_type, node.is_root());
846 let new_origin =
847 Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0);
848 node_bounds.with_origin(new_origin)
849 }
850 None if node.is_root() => {
851 let bounds = window_bounds.outer;
852 match coord_type {
853 CoordType::Screen => bounds,
854 CoordType::Window => bounds.with_origin(Point::ZERO),
855 _ => unimplemented!(),
856 }
857 }
858 _ => return Err(Error::UnsupportedInterface),
859 };
860 Ok(bounds.contains(Point::new(x.into(), y.into())))
861 })
862 }
863
864 pub fn accessible_at_point(
865 &self,
866 x: i32,
867 y: i32,
868 coord_type: CoordType,
869 ) -> Result<Option<NodeId>> {
870 self.resolve_with_context(|node, context| {
871 let window_bounds = context.read_root_window_bounds();
872 let top_left = window_bounds.top_left(coord_type, node.is_root());
873 let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y);
874 let point = node.transform().inverse() * point;
875 Ok(node.node_at_point(point, &filter).map(|node| node.id()))
876 })
877 }
878
879 pub fn extents(&self, coord_type: CoordType) -> Result<AtspiRect> {
880 self.resolve_with_context(|node, context| {
881 let window_bounds = context.read_root_window_bounds();
882 match node.bounding_box() {
883 Some(node_bounds) => {
884 let top_left = window_bounds.top_left(coord_type, node.is_root());
885 let new_origin =
886 Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0);
887 Ok(node_bounds.with_origin(new_origin).into())
888 }
889 None if node.is_root() => {
890 let bounds = window_bounds.outer;
891 Ok(match coord_type {
892 CoordType::Screen => bounds.into(),
893 CoordType::Window => bounds.with_origin(Point::ZERO).into(),
894 _ => unimplemented!(),
895 })
896 }
897 _ => Err(Error::UnsupportedInterface),
898 }
899 })
900 }
901
902 pub fn layer(&self) -> Result<Layer> {
903 self.resolve(|node| {
904 let wrapper = NodeWrapper::Node(&node);
905 if wrapper.role() == AtspiRole::Window {
906 Ok(Layer::Window)
907 } else {
908 Ok(Layer::Widget)
909 }
910 })
911 }
912
913 pub fn grab_focus(&self) -> Result<bool> {
914 self.do_action_internal(|_, _| ActionRequest {
915 action: Action::Focus,
916 target: self.id,
917 data: None,
918 })?;
919 Ok(true)
920 }
921
922 pub fn scroll_to_point(&self, coord_type: CoordType, x: i32, y: i32) -> Result<bool> {
923 self.do_action_internal(|tree_state, context| {
924 let window_bounds = context.read_root_window_bounds();
925 let is_root = self.id == tree_state.root_id();
926 let top_left = window_bounds.top_left(coord_type, is_root);
927 let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y);
928 ActionRequest {
929 action: Action::ScrollToPoint,
930 target: self.id,
931 data: Some(ActionData::ScrollToPoint(point)),
932 }
933 })?;
934 Ok(true)
935 }
936
937 pub fn minimum_value(&self) -> Result<f64> {
938 self.resolve(|node| Ok(node.state().min_numeric_value().unwrap_or(std::f64::MIN)))
939 }
940
941 pub fn maximum_value(&self) -> Result<f64> {
942 self.resolve(|node| Ok(node.state().max_numeric_value().unwrap_or(std::f64::MAX)))
943 }
944
945 pub fn minimum_increment(&self) -> Result<f64> {
946 self.resolve(|node| Ok(node.state().numeric_value_step().unwrap_or(0.0)))
947 }
948
949 pub fn current_value(&self) -> Result<f64> {
950 self.resolve(|node| {
951 let wrapper = NodeWrapper::Node(&node);
952 Ok(wrapper.current_value().unwrap_or(0.0))
953 })
954 }
955
956 pub fn set_current_value(&self, value: f64) -> Result<()> {
957 self.do_action_internal(|_, _| ActionRequest {
958 action: Action::SetValue,
959 target: self.id,
960 data: Some(ActionData::NumericValue(value)),
961 })
962 }
963}
964
965impl PartialEq for PlatformNode {
966 fn eq(&self, other: &Self) -> bool {
967 self.adapter_id == other.adapter_id && self.id == other.id
968 }
969}
970
971impl Eq for PlatformNode {}
972
973impl Hash for PlatformNode {
974 fn hash<H: Hasher>(&self, state: &mut H) {
975 self.adapter_id.hash(state);
976 self.id.hash(state);
977 }
978}
979
980#[derive(Clone)]
981pub struct PlatformRoot {
982 app_context: Weak<RwLock<AppContext>>,
983}
984
985impl PlatformRoot {
986 pub fn new(app_context: &Arc<RwLock<AppContext>>) -> Self {
987 Self {
988 app_context: Arc::downgrade(app_context),
989 }
990 }
991
992 fn resolve_app_context<F, T>(&self, f: F) -> Result<T>
993 where
994 for<'a> F: FnOnce(RwLockReadGuard<'a, AppContext>) -> Result<T>,
995 {
996 let app_context = match self.app_context.upgrade() {
997 Some(context) => context,
998 None => return Err(Error::Defunct),
999 };
1000 let app_context = app_context.read().unwrap();
1001 f(app_context)
1002 }
1003
1004 pub fn name(&self) -> Result<String> {
1005 self.resolve_app_context(|context| Ok(context.name.clone().unwrap_or_default()))
1006 }
1007
1008 pub fn child_count(&self) -> Result<i32> {
1009 self.resolve_app_context(|context| {
1010 i32::try_from(context.adapters.len()).map_err(|_| Error::TooManyChildren)
1011 })
1012 }
1013
1014 pub fn child_at_index(&self, index: usize) -> Result<Option<PlatformNode>> {
1015 self.resolve_app_context(|context| {
1016 let child = context
1017 .adapters
1018 .get(index)
1019 .map(PlatformNode::from_adapter_root);
1020 Ok(child)
1021 })
1022 }
1023
1024 pub fn child_id_at_index(&self, index: usize) -> Result<Option<(usize, NodeId)>> {
1025 self.resolve_app_context(|context| {
1026 let child = context
1027 .adapters
1028 .get(index)
1029 .map(|(adapter_id, context)| (*adapter_id, context.read_tree().state().root_id()));
1030 Ok(child)
1031 })
1032 }
1033
1034 pub fn map_children<T, I>(&self, f: impl Fn(PlatformNode) -> I) -> Result<T>
1035 where
1036 T: FromIterator<I>,
1037 {
1038 self.resolve_app_context(|context| {
1039 let children = context
1040 .adapters
1041 .iter()
1042 .map(PlatformNode::from_adapter_root)
1043 .map(f)
1044 .collect();
1045 Ok(children)
1046 })
1047 }
1048
1049 pub fn map_child_ids<T, I>(&self, f: impl Fn((usize, NodeId)) -> I) -> Result<T>
1050 where
1051 T: FromIterator<I>,
1052 {
1053 self.resolve_app_context(|context| {
1054 let children = context
1055 .adapters
1056 .iter()
1057 .map(|(adapter_id, context)| (*adapter_id, context.read_tree().state().root_id()))
1058 .map(f)
1059 .collect();
1060 Ok(children)
1061 })
1062 }
1063
1064 pub fn toolkit_name(&self) -> Result<String> {
1065 self.resolve_app_context(|context| Ok(context.toolkit_name.clone().unwrap_or_default()))
1066 }
1067
1068 pub fn toolkit_version(&self) -> Result<String> {
1069 self.resolve_app_context(|context| Ok(context.toolkit_version.clone().unwrap_or_default()))
1070 }
1071
1072 pub fn id(&self) -> Result<i32> {
1073 self.resolve_app_context(|context| Ok(context.id.unwrap_or(-1)))
1074 }
1075
1076 pub fn set_id(&mut self, id: i32) -> Result<()> {
1077 let app_context = match self.app_context.upgrade() {
1078 Some(context) => context,
1079 None => return Err(Error::Defunct),
1080 };
1081 let mut app_context = app_context.write().unwrap();
1082 app_context.id = Some(id);
1083 Ok(())
1084 }
1085}
1086
1087impl PartialEq for PlatformRoot {
1088 fn eq(&self, other: &Self) -> bool {
1089 self.app_context.ptr_eq(&other.app_context)
1090 }
1091}
1092
1093impl Hash for PlatformRoot {
1094 fn hash<H: Hasher>(&self, state: &mut H) {
1095 self.app_context.as_ptr().hash(state);
1096 }
1097}
1098
1099#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1100pub enum NodeIdOrRoot {
1101 Node(NodeId),
1102 Root,
1103}
1104