1use accesskit::{
12 Action, ActionData, ActionRequest, Affine, Live, NodeId, Orientation, Point, Rect, Role,
13 Toggled,
14};
15use accesskit_consumer::{FilterResult, Node, TreeState};
16use atspi_common::{
17 CoordType, Granularity, Interface, InterfaceSet, Layer, Live as AtspiLive, Role as AtspiRole,
18 ScrollType, State, StateSet,
19};
20use std::{
21 collections::HashMap,
22 hash::{Hash, Hasher},
23 iter::FusedIterator,
24 sync::{Arc, RwLock, RwLockReadGuard, Weak},
25};
26
27use crate::{
28 adapter::Adapter,
29 context::{AppContext, Context},
30 filters::filter,
31 util::*,
32 Action as AtspiAction, Error, ObjectEvent, Property, Rect as AtspiRect, Result,
33};
34
35pub(crate) struct NodeWrapper<'a>(pub(crate) &'a Node<'a>);
36
37impl<'a> NodeWrapper<'a> {
38 pub(crate) fn name(&self) -> Option<String> {
39 if self.0.label_comes_from_value() {
40 self.0.value()
41 } else {
42 self.0.label()
43 }
44 }
45
46 pub(crate) fn description(&self) -> Option<String> {
47 self.0.description()
48 }
49
50 pub(crate) fn parent_id(&self) -> Option<NodeId> {
51 self.0.parent_id()
52 }
53
54 pub(crate) fn id(&self) -> NodeId {
55 self.0.id()
56 }
57
58 fn filtered_child_ids(
59 &self,
60 ) -> impl DoubleEndedIterator<Item = NodeId> + FusedIterator<Item = NodeId> + '_ {
61 self.0.filtered_children(&filter).map(|child| child.id())
62 }
63
64 pub(crate) fn role(&self) -> AtspiRole {
65 if self.0.has_role_description() {
66 return AtspiRole::Extended;
67 }
68
69 match self.0.role() {
70 Role::Alert => AtspiRole::Notification,
71 Role::AlertDialog => AtspiRole::Alert,
72 Role::Comment | Role::Suggestion => AtspiRole::Section,
73 Role::Application => AtspiRole::Embedded,
75 Role::Article => AtspiRole::Article,
76 Role::Audio => AtspiRole::Audio,
77 Role::Banner | Role::Header => AtspiRole::Landmark,
78 Role::Blockquote => AtspiRole::BlockQuote,
79 Role::Caret => AtspiRole::Unknown,
80 Role::Button => {
81 if self.0.toggled().is_some() {
82 AtspiRole::ToggleButton
83 } else {
84 AtspiRole::PushButton
85 }
86 }
87 Role::DefaultButton => AtspiRole::PushButton,
88 Role::Canvas => AtspiRole::Canvas,
89 Role::Caption => AtspiRole::Caption,
90 Role::Cell => AtspiRole::TableCell,
91 Role::CheckBox => AtspiRole::CheckBox,
92 Role::Switch => AtspiRole::ToggleButton,
93 Role::ColorWell => AtspiRole::PushButton,
94 Role::ColumnHeader => AtspiRole::ColumnHeader,
95 Role::ComboBox | Role::EditableComboBox => AtspiRole::ComboBox,
96 Role::Complementary => AtspiRole::Landmark,
97 Role::ContentDeletion => AtspiRole::ContentDeletion,
98 Role::ContentInsertion => AtspiRole::ContentInsertion,
99 Role::ContentInfo | Role::Footer => AtspiRole::Landmark,
100 Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue,
101 Role::DescriptionList => AtspiRole::DescriptionList,
102 Role::DescriptionListTerm => AtspiRole::DescriptionTerm,
103 Role::Details => AtspiRole::Panel,
104 Role::Dialog => AtspiRole::Dialog,
105 Role::Directory => AtspiRole::List,
106 Role::DisclosureTriangle => AtspiRole::ToggleButton,
107 Role::DocCover => AtspiRole::Image,
108 Role::DocBackLink | Role::DocBiblioRef | Role::DocGlossRef | Role::DocNoteRef => {
109 AtspiRole::Link
110 }
111 Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem,
112 Role::DocNotice | Role::DocTip => AtspiRole::Comment,
113 Role::DocFootnote => AtspiRole::Footnote,
114 Role::DocPageBreak => AtspiRole::Separator,
115 Role::DocPageFooter => AtspiRole::Footer,
116 Role::DocPageHeader => AtspiRole::Header,
117 Role::DocAcknowledgements
118 | Role::DocAfterword
119 | Role::DocAppendix
120 | Role::DocBibliography
121 | Role::DocChapter
122 | Role::DocConclusion
123 | Role::DocCredits
124 | Role::DocEndnotes
125 | Role::DocEpilogue
126 | Role::DocErrata
127 | Role::DocForeword
128 | Role::DocGlossary
129 | Role::DocIndex
130 | Role::DocIntroduction
131 | Role::DocPageList
132 | Role::DocPart
133 | Role::DocPreface
134 | Role::DocPrologue
135 | Role::DocToc => AtspiRole::Landmark,
136 Role::DocAbstract
137 | Role::DocColophon
138 | Role::DocCredit
139 | Role::DocDedication
140 | Role::DocEpigraph
141 | Role::DocExample
142 | Role::DocPullquote
143 | Role::DocQna => AtspiRole::Section,
144 Role::DocSubtitle => AtspiRole::Heading,
145 Role::Document => AtspiRole::DocumentFrame,
146 Role::EmbeddedObject => AtspiRole::Embedded,
147 Role::Form => AtspiRole::Form,
151 Role::Figure | Role::Feed => AtspiRole::Panel,
152 Role::GenericContainer
153 | Role::FooterAsNonLandmark
154 | Role::HeaderAsNonLandmark
155 | Role::Ruby => AtspiRole::Section,
156 Role::GraphicsDocument => AtspiRole::DocumentFrame,
157 Role::GraphicsObject => AtspiRole::Panel,
158 Role::GraphicsSymbol => AtspiRole::Image,
159 Role::Grid => AtspiRole::Table,
160 Role::Group => AtspiRole::Panel,
161 Role::Heading => AtspiRole::Heading,
162 Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame,
163 Role::Image => AtspiRole::Image,
165 Role::TextRun => AtspiRole::Static,
166 Role::Legend => AtspiRole::Label,
167 Role::LayoutTable => AtspiRole::Section,
169 Role::LayoutTableCell => AtspiRole::Section,
170 Role::LayoutTableRow => AtspiRole::Section,
171 Role::LineBreak => AtspiRole::Static,
174 Role::Link => AtspiRole::Link,
175 Role::List => AtspiRole::List,
176 Role::ListBox => AtspiRole::ListBox,
177 Role::ListBoxOption => AtspiRole::ListItem,
179 Role::ListGrid => AtspiRole::Table,
180 Role::ListItem => AtspiRole::ListItem,
181 Role::ListMarker => AtspiRole::Static,
190 Role::Log => AtspiRole::Log,
191 Role::Main => AtspiRole::Landmark,
192 Role::Mark => AtspiRole::Static,
193 Role::Math => AtspiRole::Math,
194 Role::Marquee => AtspiRole::Marquee,
195 Role::Menu | Role::MenuListPopup => AtspiRole::Menu,
196 Role::MenuBar => AtspiRole::MenuBar,
197 Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem,
198 Role::MenuItemCheckBox => AtspiRole::CheckMenuItem,
199 Role::MenuItemRadio => AtspiRole::RadioMenuItem,
200 Role::Meter => AtspiRole::LevelBar,
201 Role::Navigation => AtspiRole::Landmark,
202 Role::Note => AtspiRole::Comment,
203 Role::Pane | Role::ScrollView => AtspiRole::Panel,
204 Role::Paragraph => AtspiRole::Paragraph,
205 Role::PdfActionableHighlight => AtspiRole::PushButton,
206 Role::PdfRoot => AtspiRole::DocumentFrame,
207 Role::PluginObject => AtspiRole::Embedded,
208 Role::Portal => AtspiRole::PushButton,
209 Role::Pre => AtspiRole::Section,
210 Role::ProgressIndicator => AtspiRole::ProgressBar,
211 Role::RadioButton => AtspiRole::RadioButton,
212 Role::RadioGroup => AtspiRole::Panel,
213 Role::Region => AtspiRole::Landmark,
214 Role::RootWebArea => AtspiRole::DocumentWeb,
215 Role::Row => AtspiRole::TableRow,
216 Role::RowGroup => AtspiRole::Panel,
217 Role::RowHeader => AtspiRole::RowHeader,
218 Role::RubyAnnotation => AtspiRole::Static,
226 Role::Section => AtspiRole::Section,
227 Role::ScrollBar => AtspiRole::ScrollBar,
228 Role::Search => AtspiRole::Landmark,
229 Role::Slider => AtspiRole::Slider,
230 Role::SpinButton => AtspiRole::SpinButton,
231 Role::Splitter => AtspiRole::Separator,
232 Role::Label => AtspiRole::Label,
233 Role::Status => AtspiRole::StatusBar,
234 Role::SvgRoot => AtspiRole::DocumentFrame,
235 Role::Tab => AtspiRole::PageTab,
236 Role::Table => AtspiRole::Table,
237 Role::TabList => AtspiRole::PageTabList,
238 Role::TabPanel => AtspiRole::ScrollPane,
239 Role::Term => AtspiRole::DescriptionTerm,
242 Role::TitleBar => AtspiRole::TitleBar,
243 Role::TextInput
244 | Role::MultilineTextInput
245 | Role::SearchInput
246 | Role::EmailInput
247 | Role::NumberInput
248 | Role::PhoneNumberInput
249 | Role::UrlInput => AtspiRole::Entry,
250 Role::DateInput
251 | Role::DateTimeInput
252 | Role::WeekInput
253 | Role::MonthInput
254 | Role::TimeInput => AtspiRole::DateEditor,
255 Role::PasswordInput => AtspiRole::PasswordText,
256 Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => {
257 AtspiRole::Static
258 }
259 Role::Timer => AtspiRole::Timer,
260 Role::Toolbar => AtspiRole::ToolBar,
261 Role::Tooltip => AtspiRole::ToolTip,
262 Role::Tree => AtspiRole::Tree,
263 Role::TreeItem => AtspiRole::TreeItem,
264 Role::TreeGrid => AtspiRole::TreeTable,
265 Role::Video => AtspiRole::Video,
266 Role::Window => AtspiRole::Frame,
270 Role::WebView => AtspiRole::Panel,
271 Role::FigureCaption => AtspiRole::Caption,
272 Role::Unknown => AtspiRole::Unknown,
274 Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject,
275 Role::Terminal => AtspiRole::Terminal,
276 }
277 }
278
279 fn is_focused(&self) -> bool {
280 self.0.is_focused()
281 }
282
283 pub(crate) fn state(&self, is_window_focused: bool) -> StateSet {
284 let state = self.0;
285 let atspi_role = self.role();
286 let mut atspi_state = StateSet::empty();
287 if state.parent_id().is_none() && state.role() == Role::Window && is_window_focused {
288 atspi_state.insert(State::Active);
289 }
290 if state.is_text_input() && !state.is_read_only() {
291 atspi_state.insert(State::Editable);
292 }
293 if state.is_focusable() {
295 atspi_state.insert(State::Focusable);
296 }
297 if let Some(orientation) = state.orientation() {
298 atspi_state.insert(if orientation == Orientation::Horizontal {
299 State::Horizontal
300 } else {
301 State::Vertical
302 });
303 }
304 let filter_result = filter(self.0);
305 if filter_result == FilterResult::Include {
306 atspi_state.insert(State::Visible | State::Showing);
307 }
308 if atspi_role != AtspiRole::ToggleButton && state.toggled().is_some() {
309 atspi_state.insert(State::Checkable);
310 }
311 if let Some(selected) = state.is_selected() {
312 if !state.is_disabled() {
313 atspi_state.insert(State::Selectable);
314 }
315 if selected {
316 atspi_state.insert(State::Selected);
317 }
318 }
319 if state.is_text_input() {
320 atspi_state.insert(State::SelectableText);
321 atspi_state.insert(match state.is_multiline() {
322 true => State::MultiLine,
323 false => State::SingleLine,
324 });
325 }
326
327 if state.role() == Role::ProgressIndicator && state.numeric_value().is_none() {
329 atspi_state.insert(State::Indeterminate);
330 }
331
332 match state.toggled() {
334 Some(Toggled::Mixed) => atspi_state.insert(State::Indeterminate),
335 Some(Toggled::True) if atspi_role == AtspiRole::ToggleButton => {
336 atspi_state.insert(State::Pressed)
337 }
338 Some(Toggled::True) => atspi_state.insert(State::Checked),
339 _ => {}
340 }
341
342 if state.is_read_only_supported() && state.is_read_only_or_disabled() {
343 atspi_state.insert(State::ReadOnly);
344 } else {
345 atspi_state.insert(State::Enabled | State::Sensitive);
346 }
347
348 if self.is_focused() {
349 atspi_state.insert(State::Focused);
350 }
351
352 atspi_state
353 }
354
355 fn attributes(&self) -> HashMap<&'static str, String> {
356 let mut attributes = HashMap::new();
357 if let Some(placeholder) = self.0.placeholder() {
358 attributes.insert("placeholder-text", placeholder);
359 }
360 attributes
361 }
362
363 fn is_root(&self) -> bool {
364 self.0.is_root()
365 }
366
367 fn supports_action(&self) -> bool {
368 self.0.is_clickable()
369 }
370
371 fn supports_component(&self) -> bool {
372 self.0.raw_bounds().is_some() || self.is_root()
373 }
374
375 fn supports_text(&self) -> bool {
376 self.0.supports_text_ranges()
377 }
378
379 fn supports_value(&self) -> bool {
380 self.current_value().is_some()
381 }
382
383 pub(crate) fn interfaces(&self) -> InterfaceSet {
384 let mut interfaces = InterfaceSet::new(Interface::Accessible);
385 if self.supports_action() {
386 interfaces.insert(Interface::Action);
387 }
388 if self.supports_component() {
389 interfaces.insert(Interface::Component);
390 }
391 if self.supports_text() {
392 interfaces.insert(Interface::Text);
393 }
394 if self.supports_value() {
395 interfaces.insert(Interface::Value);
396 }
397 interfaces
398 }
399
400 pub(crate) fn live(&self) -> AtspiLive {
401 let live = self.0.live();
402 match live {
403 Live::Off => AtspiLive::None,
404 Live::Polite => AtspiLive::Polite,
405 Live::Assertive => AtspiLive::Assertive,
406 }
407 }
408
409 fn n_actions(&self) -> i32 {
410 if self.0.is_clickable() {
411 1
412 } else {
413 0
414 }
415 }
416
417 fn get_action_name(&self, index: i32) -> String {
418 if index != 0 {
419 return String::new();
420 }
421 String::from(if self.0.is_clickable() { "click" } else { "" })
422 }
423
424 fn raw_bounds_and_transform(&self) -> (Option<Rect>, Affine) {
425 let state = self.0;
426 (state.raw_bounds(), state.direct_transform())
427 }
428
429 fn extents(&self, window_bounds: &WindowBounds, coord_type: CoordType) -> Option<Rect> {
430 let mut bounds = self.0.bounding_box();
431 if self.is_root() {
432 let window_bounds = window_bounds.inner.with_origin(Point::ZERO);
433 if !window_bounds.is_empty() {
434 if let Some(bounds) = &mut bounds {
435 bounds.x0 = bounds.x0.min(window_bounds.x1);
436 bounds.y0 = bounds.y0.min(window_bounds.y1);
437 bounds.x1 = bounds.x1.min(window_bounds.x1);
438 bounds.y1 = bounds.y1.min(window_bounds.y1);
439 } else {
440 bounds = Some(window_bounds);
441 }
442 }
443 }
444 bounds.map(|bounds| {
445 let new_origin = window_bounds.accesskit_point_to_atspi_point(
446 bounds.origin(),
447 self.0.filtered_parent(&filter),
448 coord_type,
449 );
450 bounds.with_origin(new_origin)
451 })
452 }
453
454 fn current_value(&self) -> Option<f64> {
455 self.0.numeric_value()
456 }
457
458 pub(crate) fn notify_changes(
459 &self,
460 window_bounds: &WindowBounds,
461 adapter: &Adapter,
462 old: &NodeWrapper<'_>,
463 ) {
464 self.notify_state_changes(adapter, old);
465 self.notify_property_changes(adapter, old);
466 self.notify_bounds_changes(window_bounds, adapter, old);
467 self.notify_children_changes(adapter, old);
468 }
469
470 fn notify_state_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
471 let old_state = old.state(true);
472 let new_state = self.state(true);
473 let changed_states = old_state ^ new_state;
474 for state in changed_states.iter() {
475 if state == State::Focused {
476 continue;
478 }
479 adapter.emit_object_event(
480 self.id(),
481 ObjectEvent::StateChanged(state, new_state.contains(state)),
482 );
483 }
484 }
485
486 fn notify_property_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
487 let name = self.name();
488 if name != old.name() {
489 let name = name.unwrap_or_default();
490 adapter.emit_object_event(
491 self.id(),
492 ObjectEvent::PropertyChanged(Property::Name(name.clone())),
493 );
494
495 let live = self.live();
496 if live != AtspiLive::None {
497 adapter.emit_object_event(self.id(), ObjectEvent::Announcement(name, live));
498 }
499 }
500 let description = self.description();
501 if description != old.description() {
502 adapter.emit_object_event(
503 self.id(),
504 ObjectEvent::PropertyChanged(Property::Description(
505 description.unwrap_or_default(),
506 )),
507 );
508 }
509 let parent_id = self.parent_id();
510 if parent_id != old.parent_id() {
511 let parent = self
512 .0
513 .filtered_parent(&filter)
514 .map_or(NodeIdOrRoot::Root, |node| NodeIdOrRoot::Node(node.id()));
515 adapter.emit_object_event(
516 self.id(),
517 ObjectEvent::PropertyChanged(Property::Parent(parent)),
518 );
519 }
520 let role = self.role();
521 if role != old.role() {
522 adapter.emit_object_event(
523 self.id(),
524 ObjectEvent::PropertyChanged(Property::Role(role)),
525 );
526 }
527 if let Some(value) = self.current_value() {
528 if Some(value) != old.current_value() {
529 adapter.emit_object_event(
530 self.id(),
531 ObjectEvent::PropertyChanged(Property::Value(value)),
532 );
533 }
534 }
535 }
536
537 fn notify_bounds_changes(
538 &self,
539 window_bounds: &WindowBounds,
540 adapter: &Adapter,
541 old: &NodeWrapper<'_>,
542 ) {
543 if self.raw_bounds_and_transform() != old.raw_bounds_and_transform() {
544 if let Some(extents) = self.extents(window_bounds, CoordType::Window) {
545 adapter.emit_object_event(self.id(), ObjectEvent::BoundsChanged(extents.into()));
546 }
547 }
548 }
549
550 fn notify_children_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
551 let old_filtered_children = old.filtered_child_ids().collect::<Vec<NodeId>>();
552 let new_filtered_children = self.filtered_child_ids().collect::<Vec<NodeId>>();
553 for (index, child) in new_filtered_children.iter().enumerate() {
554 if !old_filtered_children.contains(child) {
555 adapter.emit_object_event(self.id(), ObjectEvent::ChildAdded(index, *child));
556 }
557 }
558 for child in old_filtered_children.into_iter() {
559 if !new_filtered_children.contains(&child) {
560 adapter.emit_object_event(self.id(), ObjectEvent::ChildRemoved(child));
561 }
562 }
563 }
564}
565
566#[derive(Clone)]
567pub struct PlatformNode {
568 context: Weak<Context>,
569 adapter_id: usize,
570 id: NodeId,
571}
572
573impl PlatformNode {
574 pub(crate) fn new(context: &Arc<Context>, adapter_id: usize, id: NodeId) -> Self {
575 Self {
576 context: Arc::downgrade(context),
577 adapter_id,
578 id,
579 }
580 }
581
582 fn from_adapter_root(adapter_id_and_context: &(usize, Arc<Context>)) -> Self {
583 let (adapter_id, context) = adapter_id_and_context;
584 Self::new(context, *adapter_id, context.read_tree().state().root_id())
585 }
586
587 fn upgrade_context(&self) -> Result<Arc<Context>> {
588 if let Some(context) = self.context.upgrade() {
589 Ok(context)
590 } else {
591 Err(Error::Defunct)
592 }
593 }
594
595 fn with_tree_state<F, T>(&self, f: F) -> Result<T>
596 where
597 F: FnOnce(&TreeState) -> Result<T>,
598 {
599 let context = self.upgrade_context()?;
600 let tree = context.read_tree();
601 f(tree.state())
602 }
603
604 fn with_tree_state_and_context<F, T>(&self, f: F) -> Result<T>
605 where
606 F: FnOnce(&TreeState, &Context) -> Result<T>,
607 {
608 let context = self.upgrade_context()?;
609 let tree = context.read_tree();
610 f(tree.state(), &context)
611 }
612
613 fn resolve_with_context<F, T>(&self, f: F) -> Result<T>
614 where
615 for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
616 {
617 self.with_tree_state_and_context(|state, context| {
618 if let Some(node) = state.node_by_id(self.id) {
619 f(node, context)
620 } else {
621 Err(Error::Defunct)
622 }
623 })
624 }
625
626 fn resolve_for_text_with_context<F, T>(&self, f: F) -> Result<T>
627 where
628 for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
629 {
630 self.resolve_with_context(|node, context| {
631 let wrapper = NodeWrapper(&node);
632 if wrapper.supports_text() {
633 f(node, context)
634 } else {
635 Err(Error::UnsupportedInterface)
636 }
637 })
638 }
639
640 fn resolve<F, T>(&self, f: F) -> Result<T>
641 where
642 for<'a> F: FnOnce(Node<'a>) -> Result<T>,
643 {
644 self.resolve_with_context(|node, _| f(node))
645 }
646
647 fn resolve_for_text<F, T>(&self, f: F) -> Result<T>
648 where
649 for<'a> F: FnOnce(Node<'a>) -> Result<T>,
650 {
651 self.resolve_for_text_with_context(|node, _| f(node))
652 }
653
654 fn do_action_internal<F>(&self, f: F) -> Result<()>
655 where
656 F: FnOnce(&TreeState, &Context) -> ActionRequest,
657 {
658 let context = self.upgrade_context()?;
659 let tree = context.read_tree();
660 if tree.state().has_node(self.id) {
661 let request = f(tree.state(), &context);
662 drop(tree);
663 context.do_action(request);
664 Ok(())
665 } else {
666 Err(Error::Defunct)
667 }
668 }
669
670 pub fn name(&self) -> Result<String> {
671 self.resolve(|node| {
672 let wrapper = NodeWrapper(&node);
673 Ok(wrapper.name().unwrap_or_default())
674 })
675 }
676
677 pub fn description(&self) -> Result<String> {
678 self.resolve(|node| {
679 let wrapper = NodeWrapper(&node);
680 Ok(wrapper.description().unwrap_or_default())
681 })
682 }
683
684 pub fn relative(&self, id: NodeId) -> Self {
685 Self {
686 context: self.context.clone(),
687 adapter_id: self.adapter_id,
688 id,
689 }
690 }
691
692 pub fn root(&self) -> Result<PlatformRoot> {
693 let context = self.upgrade_context()?;
694 Ok(PlatformRoot::new(&context.app_context))
695 }
696
697 pub fn toolkit_name(&self) -> Result<String> {
698 self.with_tree_state(|state| Ok(state.toolkit_name().unwrap_or_default()))
699 }
700
701 pub fn toolkit_version(&self) -> Result<String> {
702 self.with_tree_state(|state| Ok(state.toolkit_version().unwrap_or_default()))
703 }
704
705 pub fn parent(&self) -> Result<NodeIdOrRoot> {
706 self.resolve(|node| {
707 let parent = node
708 .filtered_parent(&filter)
709 .map_or(NodeIdOrRoot::Root, |node| NodeIdOrRoot::Node(node.id()));
710 Ok(parent)
711 })
712 }
713
714 pub fn child_count(&self) -> Result<i32> {
715 self.resolve(|node| {
716 i32::try_from(node.filtered_children(&filter).count())
717 .map_err(|_| Error::TooManyChildren)
718 })
719 }
720
721 pub fn adapter_id(&self) -> usize {
722 self.adapter_id
723 }
724
725 pub fn id(&self) -> NodeId {
726 self.id
727 }
728
729 pub fn accessible_id(&self) -> Result<String> {
730 self.resolve(|node| {
731 if let Some(author_id) = node.author_id() {
732 Ok(author_id.to_string())
733 } else {
734 Ok(String::new())
735 }
736 })
737 }
738
739 pub fn child_at_index(&self, index: usize) -> Result<Option<NodeId>> {
740 self.resolve(|node| {
741 let child = node
742 .filtered_children(&filter)
743 .nth(index)
744 .map(|child| child.id());
745 Ok(child)
746 })
747 }
748
749 pub fn map_children<T, I>(&self, f: impl Fn(NodeId) -> I) -> Result<T>
750 where
751 T: FromIterator<I>,
752 {
753 self.resolve(|node| {
754 let children = node
755 .filtered_children(&filter)
756 .map(|child| child.id())
757 .map(f)
758 .collect();
759 Ok(children)
760 })
761 }
762
763 pub fn index_in_parent(&self) -> Result<i32> {
764 self.resolve(|node| {
765 i32::try_from(node.preceding_filtered_siblings(&filter).count())
766 .map_err(|_| Error::IndexOutOfRange)
767 })
768 }
769
770 pub fn role(&self) -> Result<AtspiRole> {
771 self.resolve(|node| {
772 let wrapper = NodeWrapper(&node);
773 Ok(wrapper.role())
774 })
775 }
776
777 pub fn localized_role_name(&self) -> Result<String> {
778 self.resolve(|node| Ok(node.role_description().unwrap_or_default()))
779 }
780
781 pub fn state(&self) -> StateSet {
782 self.resolve_with_context(|node, context| {
783 let wrapper = NodeWrapper(&node);
784 Ok(wrapper.state(context.read_tree().state().focus_id().is_some()))
785 })
786 .unwrap_or(State::Defunct.into())
787 }
788
789 pub fn attributes(&self) -> Result<HashMap<&'static str, String>> {
790 self.resolve(|node| {
791 let wrapper = NodeWrapper(&node);
792 Ok(wrapper.attributes())
793 })
794 }
795
796 pub fn supports_action(&self) -> Result<bool> {
797 self.resolve(|node| {
798 let wrapper = NodeWrapper(&node);
799 Ok(wrapper.supports_action())
800 })
801 }
802
803 pub fn supports_component(&self) -> Result<bool> {
804 self.resolve(|node| {
805 let wrapper = NodeWrapper(&node);
806 Ok(wrapper.supports_component())
807 })
808 }
809
810 pub fn supports_text(&self) -> Result<bool> {
811 self.resolve(|node| {
812 let wrapper = NodeWrapper(&node);
813 Ok(wrapper.supports_text())
814 })
815 }
816
817 pub fn supports_value(&self) -> Result<bool> {
818 self.resolve(|node| {
819 let wrapper = NodeWrapper(&node);
820 Ok(wrapper.supports_value())
821 })
822 }
823
824 pub fn interfaces(&self) -> Result<InterfaceSet> {
825 self.resolve(|node| {
826 let wrapper = NodeWrapper(&node);
827 Ok(wrapper.interfaces())
828 })
829 }
830
831 pub fn n_actions(&self) -> Result<i32> {
832 self.resolve(|node| {
833 let wrapper = NodeWrapper(&node);
834 Ok(wrapper.n_actions())
835 })
836 }
837
838 pub fn action_name(&self, index: i32) -> Result<String> {
839 self.resolve(|node| {
840 let wrapper = NodeWrapper(&node);
841 Ok(wrapper.get_action_name(index))
842 })
843 }
844
845 pub fn actions(&self) -> Result<Vec<AtspiAction>> {
846 self.resolve(|node| {
847 let wrapper = NodeWrapper(&node);
848 let n_actions = wrapper.n_actions() as usize;
849 let mut actions = Vec::with_capacity(n_actions);
850 for i in 0..n_actions {
851 actions.push(AtspiAction {
852 localized_name: wrapper.get_action_name(i as i32),
853 description: "".into(),
854 key_binding: "".into(),
855 });
856 }
857 Ok(actions)
858 })
859 }
860
861 pub fn do_action(&self, index: i32) -> Result<bool> {
862 if index != 0 {
863 return Ok(false);
864 }
865 self.do_action_internal(|_, _| ActionRequest {
866 action: Action::Click,
867 target: self.id,
868 data: None,
869 })?;
870 Ok(true)
871 }
872
873 pub fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> Result<bool> {
874 self.resolve_with_context(|node, context| {
875 let window_bounds = context.read_root_window_bounds();
876 let wrapper = NodeWrapper(&node);
877 if let Some(extents) = wrapper.extents(&window_bounds, coord_type) {
878 Ok(extents.contains(Point::new(x.into(), y.into())))
879 } else {
880 Ok(false)
881 }
882 })
883 }
884
885 pub fn accessible_at_point(
886 &self,
887 x: i32,
888 y: i32,
889 coord_type: CoordType,
890 ) -> Result<Option<NodeId>> {
891 self.resolve_with_context(|node, context| {
892 let window_bounds = context.read_root_window_bounds();
893 let point = window_bounds.atspi_point_to_accesskit_point(
894 Point::new(x.into(), y.into()),
895 Some(node),
896 coord_type,
897 );
898 let point = node.transform().inverse() * point;
899 Ok(node.node_at_point(point, &filter).map(|node| node.id()))
900 })
901 }
902
903 pub fn extents(&self, coord_type: CoordType) -> Result<AtspiRect> {
904 self.resolve_with_context(|node, context| {
905 let window_bounds = context.read_root_window_bounds();
906 let wrapper = NodeWrapper(&node);
907 Ok(wrapper
908 .extents(&window_bounds, coord_type)
909 .map_or(AtspiRect::INVALID, AtspiRect::from))
910 })
911 }
912
913 pub fn layer(&self) -> Result<Layer> {
914 self.resolve(|node| {
915 let wrapper = NodeWrapper(&node);
916 if wrapper.role() == AtspiRole::Window {
917 Ok(Layer::Window)
918 } else {
919 Ok(Layer::Widget)
920 }
921 })
922 }
923
924 pub fn grab_focus(&self) -> Result<bool> {
925 self.do_action_internal(|_, _| ActionRequest {
926 action: Action::Focus,
927 target: self.id,
928 data: None,
929 })?;
930 Ok(true)
931 }
932
933 pub fn scroll_to_point(&self, coord_type: CoordType, x: i32, y: i32) -> Result<bool> {
934 self.resolve_with_context(|node, context| {
935 let window_bounds = context.read_root_window_bounds();
936 let point = window_bounds.atspi_point_to_accesskit_point(
937 Point::new(x.into(), y.into()),
938 node.filtered_parent(&filter),
939 coord_type,
940 );
941 context.do_action(ActionRequest {
942 action: Action::ScrollToPoint,
943 target: self.id,
944 data: Some(ActionData::ScrollToPoint(point)),
945 });
946 Ok(())
947 })?;
948 Ok(true)
949 }
950
951 pub fn character_count(&self) -> Result<i32> {
952 self.resolve_for_text(|node| {
953 node.document_range()
954 .end()
955 .to_global_usv_index()
956 .try_into()
957 .map_err(|_| Error::TooManyCharacters)
958 })
959 }
960
961 pub fn caret_offset(&self) -> Result<i32> {
962 self.resolve_for_text(|node| {
963 node.text_selection_focus().map_or(Ok(-1), |focus| {
964 focus
965 .to_global_usv_index()
966 .try_into()
967 .map_err(|_| Error::TooManyCharacters)
968 })
969 })
970 }
971
972 pub fn string_at_offset(
973 &self,
974 offset: i32,
975 granularity: Granularity,
976 ) -> Result<(String, i32, i32)> {
977 self.resolve_for_text(|node| {
978 let range = text_range_from_offset(&node, offset, granularity)?;
979 let text = range.text();
980 let start = range
981 .start()
982 .to_global_usv_index()
983 .try_into()
984 .map_err(|_| Error::TooManyCharacters)?;
985 let end = range
986 .end()
987 .to_global_usv_index()
988 .try_into()
989 .map_err(|_| Error::TooManyCharacters)?;
990
991 Ok((text, start, end))
992 })
993 }
994
995 pub fn text(&self, start_offset: i32, end_offset: i32) -> Result<String> {
996 self.resolve_for_text(|node| {
997 let range = text_range_from_offsets(&node, start_offset, end_offset)
998 .ok_or(Error::IndexOutOfRange)?;
999 Ok(range.text())
1000 })
1001 }
1002
1003 pub fn set_caret_offset(&self, offset: i32) -> Result<bool> {
1004 self.resolve_for_text_with_context(|node, context| {
1005 let offset = text_position_from_offset(&node, offset).ok_or(Error::IndexOutOfRange)?;
1006 context.do_action(ActionRequest {
1007 action: Action::SetTextSelection,
1008 target: node.id(),
1009 data: Some(ActionData::SetTextSelection(
1010 offset.to_degenerate_range().to_text_selection(),
1011 )),
1012 });
1013 Ok(true)
1014 })
1015 }
1016
1017 pub fn text_attribute_value(&self, _offset: i32, _attribute_name: &str) -> Result<String> {
1018 Err(Error::UnsupportedInterface)
1020 }
1021
1022 pub fn text_attributes(&self, _offset: i32) -> Result<(HashMap<String, String>, i32, i32)> {
1023 Err(Error::UnsupportedInterface)
1025 }
1026
1027 pub fn default_text_attributes(&self) -> Result<HashMap<String, String>> {
1028 Err(Error::UnsupportedInterface)
1030 }
1031
1032 pub fn character_extents(&self, offset: i32, coord_type: CoordType) -> Result<AtspiRect> {
1033 self.resolve_for_text_with_context(|node, context| {
1034 let range = text_range_from_offset(&node, offset, Granularity::Char)?;
1035 if let Some(bounds) = range.bounding_boxes().first() {
1036 let window_bounds = context.read_root_window_bounds();
1037 let new_origin = window_bounds.accesskit_point_to_atspi_point(
1038 bounds.origin(),
1039 Some(node),
1040 coord_type,
1041 );
1042 Ok(bounds.with_origin(new_origin).into())
1043 } else {
1044 Ok(AtspiRect::INVALID)
1045 }
1046 })
1047 }
1048
1049 pub fn offset_at_point(&self, x: i32, y: i32, coord_type: CoordType) -> Result<i32> {
1050 self.resolve_for_text_with_context(|node, context| {
1051 let window_bounds = context.read_root_window_bounds();
1052 let point = window_bounds.atspi_point_to_accesskit_point(
1053 Point::new(x.into(), y.into()),
1054 Some(node),
1055 coord_type,
1056 );
1057 let point = node.transform().inverse() * point;
1058 node.text_position_at_point(point)
1059 .to_global_usv_index()
1060 .try_into()
1061 .map_err(|_| Error::TooManyCharacters)
1062 })
1063 }
1064
1065 pub fn n_selections(&self) -> Result<i32> {
1066 self.resolve_for_text(|node| {
1067 match node.text_selection().filter(|range| !range.is_degenerate()) {
1068 Some(_) => Ok(1),
1069 None => Ok(0),
1070 }
1071 })
1072 }
1073
1074 pub fn selection(&self, selection_num: i32) -> Result<(i32, i32)> {
1075 if selection_num != 0 {
1076 return Ok((-1, -1));
1077 }
1078
1079 self.resolve_for_text(|node| {
1080 node.text_selection()
1081 .filter(|range| !range.is_degenerate())
1082 .map_or(Ok((-1, -1)), |range| {
1083 let start = range
1084 .start()
1085 .to_global_usv_index()
1086 .try_into()
1087 .map_err(|_| Error::TooManyCharacters)?;
1088 let end = range
1089 .end()
1090 .to_global_usv_index()
1091 .try_into()
1092 .map_err(|_| Error::TooManyCharacters)?;
1093
1094 Ok((start, end))
1095 })
1096 })
1097 }
1098
1099 pub fn add_selection(&self, start_offset: i32, end_offset: i32) -> Result<bool> {
1100 self.set_selection(0, start_offset, end_offset)
1102 }
1103
1104 pub fn remove_selection(&self, selection_num: i32) -> Result<bool> {
1105 if selection_num != 0 {
1106 return Ok(false);
1107 }
1108
1109 self.resolve_for_text_with_context(|node, context| {
1110 let selection_end = node
1113 .text_selection_focus()
1114 .unwrap_or_else(|| node.document_range().start());
1115 context.do_action(ActionRequest {
1116 action: Action::SetTextSelection,
1117 target: node.id(),
1118 data: Some(ActionData::SetTextSelection(
1119 selection_end.to_degenerate_range().to_text_selection(),
1120 )),
1121 });
1122 Ok(true)
1123 })
1124 }
1125
1126 pub fn set_selection(
1127 &self,
1128 selection_num: i32,
1129 start_offset: i32,
1130 end_offset: i32,
1131 ) -> Result<bool> {
1132 if selection_num != 0 {
1133 return Ok(false);
1134 }
1135
1136 self.resolve_for_text_with_context(|node, context| {
1137 let range = text_range_from_offsets(&node, start_offset, end_offset)
1138 .ok_or(Error::IndexOutOfRange)?;
1139 context.do_action(ActionRequest {
1140 action: Action::SetTextSelection,
1141 target: node.id(),
1142 data: Some(ActionData::SetTextSelection(range.to_text_selection())),
1143 });
1144 Ok(true)
1145 })
1146 }
1147
1148 pub fn range_extents(
1149 &self,
1150 start_offset: i32,
1151 end_offset: i32,
1152 coord_type: CoordType,
1153 ) -> Result<AtspiRect> {
1154 self.resolve_for_text_with_context(|node, context| {
1155 if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) {
1156 let window_bounds = context.read_root_window_bounds();
1157 let new_origin = window_bounds.accesskit_point_to_atspi_point(
1158 rect.origin(),
1159 Some(node),
1160 coord_type,
1161 );
1162 Ok(rect.with_origin(new_origin).into())
1163 } else {
1164 Ok(AtspiRect::INVALID)
1165 }
1166 })
1167 }
1168
1169 pub fn text_attribute_run(
1170 &self,
1171 _offset: i32,
1172 _include_defaults: bool,
1173 ) -> Result<(HashMap<String, String>, i32, i32)> {
1174 let character_count = self.character_count()?;
1178 Ok((HashMap::new(), 0, character_count))
1179 }
1180
1181 pub fn scroll_substring_to(
1182 &self,
1183 start_offset: i32,
1184 end_offset: i32,
1185 _: ScrollType,
1186 ) -> Result<bool> {
1187 self.resolve_for_text_with_context(|node, context| {
1188 if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) {
1189 context.do_action(ActionRequest {
1190 action: Action::ScrollIntoView,
1191 target: node.id(),
1192 data: Some(ActionData::ScrollTargetRect(rect)),
1193 });
1194 Ok(true)
1195 } else {
1196 Ok(false)
1197 }
1198 })
1199 }
1200
1201 pub fn scroll_substring_to_point(
1202 &self,
1203 start_offset: i32,
1204 end_offset: i32,
1205 coord_type: CoordType,
1206 x: i32,
1207 y: i32,
1208 ) -> Result<bool> {
1209 self.resolve_for_text_with_context(|node, context| {
1210 let window_bounds = context.read_root_window_bounds();
1211 let target_point = window_bounds.atspi_point_to_accesskit_point(
1212 Point::new(x.into(), y.into()),
1213 Some(node),
1214 coord_type,
1215 );
1216
1217 if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) {
1218 let point = Point::new(target_point.x - rect.x0, target_point.y - rect.y0);
1219 context.do_action(ActionRequest {
1220 action: Action::ScrollToPoint,
1221 target: node.id(),
1222 data: Some(ActionData::ScrollToPoint(point)),
1223 });
1224 return Ok(true);
1225 }
1226 Ok(false)
1227 })
1228 }
1229
1230 pub fn minimum_value(&self) -> Result<f64> {
1231 self.resolve(|node| Ok(node.min_numeric_value().unwrap_or(f64::MIN)))
1232 }
1233
1234 pub fn maximum_value(&self) -> Result<f64> {
1235 self.resolve(|node| Ok(node.max_numeric_value().unwrap_or(f64::MAX)))
1236 }
1237
1238 pub fn minimum_increment(&self) -> Result<f64> {
1239 self.resolve(|node| Ok(node.numeric_value_step().unwrap_or(0.0)))
1240 }
1241
1242 pub fn current_value(&self) -> Result<f64> {
1243 self.resolve(|node| {
1244 let wrapper = NodeWrapper(&node);
1245 Ok(wrapper.current_value().unwrap_or(0.0))
1246 })
1247 }
1248
1249 pub fn set_current_value(&self, value: f64) -> Result<()> {
1250 self.do_action_internal(|_, _| ActionRequest {
1251 action: Action::SetValue,
1252 target: self.id,
1253 data: Some(ActionData::NumericValue(value)),
1254 })
1255 }
1256}
1257
1258impl PartialEq for PlatformNode {
1259 fn eq(&self, other: &Self) -> bool {
1260 self.adapter_id == other.adapter_id && self.id == other.id
1261 }
1262}
1263
1264impl Eq for PlatformNode {}
1265
1266impl Hash for PlatformNode {
1267 fn hash<H: Hasher>(&self, state: &mut H) {
1268 self.adapter_id.hash(state);
1269 self.id.hash(state);
1270 }
1271}
1272
1273#[derive(Clone)]
1274pub struct PlatformRoot {
1275 app_context: Weak<RwLock<AppContext>>,
1276}
1277
1278impl PlatformRoot {
1279 pub fn new(app_context: &Arc<RwLock<AppContext>>) -> Self {
1280 Self {
1281 app_context: Arc::downgrade(app_context),
1282 }
1283 }
1284
1285 fn resolve_app_context<F, T>(&self, f: F) -> Result<T>
1286 where
1287 for<'a> F: FnOnce(RwLockReadGuard<'a, AppContext>) -> Result<T>,
1288 {
1289 let app_context = match self.app_context.upgrade() {
1290 Some(context) => context,
1291 None => return Err(Error::Defunct),
1292 };
1293 let app_context = app_context.read().unwrap();
1294 f(app_context)
1295 }
1296
1297 pub fn name(&self) -> Result<String> {
1298 self.resolve_app_context(|context| Ok(context.name.clone().unwrap_or_default()))
1299 }
1300
1301 pub fn child_count(&self) -> Result<i32> {
1302 self.resolve_app_context(|context| {
1303 i32::try_from(context.adapters.len()).map_err(|_| Error::TooManyChildren)
1304 })
1305 }
1306
1307 pub fn child_at_index(&self, index: usize) -> Result<Option<PlatformNode>> {
1308 self.resolve_app_context(|context| {
1309 let child = context
1310 .adapters
1311 .get(index)
1312 .map(PlatformNode::from_adapter_root);
1313 Ok(child)
1314 })
1315 }
1316
1317 pub fn child_id_at_index(&self, index: usize) -> Result<Option<(usize, NodeId)>> {
1318 self.resolve_app_context(|context| {
1319 let child = context
1320 .adapters
1321 .get(index)
1322 .map(|(adapter_id, context)| (*adapter_id, context.read_tree().state().root_id()));
1323 Ok(child)
1324 })
1325 }
1326
1327 pub fn map_children<T, I>(&self, f: impl Fn(PlatformNode) -> I) -> Result<T>
1328 where
1329 T: FromIterator<I>,
1330 {
1331 self.resolve_app_context(|context| {
1332 let children = context
1333 .adapters
1334 .iter()
1335 .map(PlatformNode::from_adapter_root)
1336 .map(f)
1337 .collect();
1338 Ok(children)
1339 })
1340 }
1341
1342 pub fn map_child_ids<T, I>(&self, f: impl Fn((usize, NodeId)) -> I) -> Result<T>
1343 where
1344 T: FromIterator<I>,
1345 {
1346 self.resolve_app_context(|context| {
1347 let children = context
1348 .adapters
1349 .iter()
1350 .map(|(adapter_id, context)| (*adapter_id, context.read_tree().state().root_id()))
1351 .map(f)
1352 .collect();
1353 Ok(children)
1354 })
1355 }
1356
1357 pub fn toolkit_name(&self) -> Result<String> {
1358 self.resolve_app_context(|context| Ok(context.toolkit_name.clone().unwrap_or_default()))
1359 }
1360
1361 pub fn toolkit_version(&self) -> Result<String> {
1362 self.resolve_app_context(|context| Ok(context.toolkit_version.clone().unwrap_or_default()))
1363 }
1364
1365 pub fn id(&self) -> Result<i32> {
1366 self.resolve_app_context(|context| Ok(context.id.unwrap_or(-1)))
1367 }
1368
1369 pub fn set_id(&mut self, id: i32) -> Result<()> {
1370 let app_context = match self.app_context.upgrade() {
1371 Some(context) => context,
1372 None => return Err(Error::Defunct),
1373 };
1374 let mut app_context = app_context.write().unwrap();
1375 app_context.id = Some(id);
1376 Ok(())
1377 }
1378}
1379
1380impl PartialEq for PlatformRoot {
1381 fn eq(&self, other: &Self) -> bool {
1382 self.app_context.ptr_eq(&other.app_context)
1383 }
1384}
1385
1386impl Hash for PlatformRoot {
1387 fn hash<H: Hasher>(&self, state: &mut H) {
1388 self.app_context.as_ptr().hash(state);
1389 }
1390}
1391
1392#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1393pub enum NodeIdOrRoot {
1394 Node(NodeId),
1395 Root,
1396}