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 | |
11 | use accesskit::{ |
12 | Action, ActionData, ActionRequest, Affine, Checked, DefaultActionVerb, Live, NodeId, Point, |
13 | Rect, Role, |
14 | }; |
15 | use accesskit_consumer::{DetachedNode, FilterResult, Node, NodeState, TreeState}; |
16 | use atspi_common::{ |
17 | CoordType, Interface, InterfaceSet, Layer, Live as AtspiLive, Role as AtspiRole, State, |
18 | StateSet, |
19 | }; |
20 | use std::{ |
21 | hash::{Hash, Hasher}, |
22 | iter::FusedIterator, |
23 | sync::{Arc, RwLock, RwLockReadGuard, Weak}, |
24 | }; |
25 | |
26 | use 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 | |
34 | pub(crate) enum NodeWrapper<'a> { |
35 | Node(&'a Node<'a>), |
36 | DetachedNode(&'a DetachedNode), |
37 | } |
38 | |
39 | impl<'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)] |
579 | pub struct PlatformNode { |
580 | context: Weak<Context>, |
581 | adapter_id: usize, |
582 | id: NodeId, |
583 | } |
584 | |
585 | impl 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 | |
965 | impl PartialEq for PlatformNode { |
966 | fn eq(&self, other: &Self) -> bool { |
967 | self.adapter_id == other.adapter_id && self.id == other.id |
968 | } |
969 | } |
970 | |
971 | impl Eq for PlatformNode {} |
972 | |
973 | impl 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)] |
981 | pub struct PlatformRoot { |
982 | app_context: Weak<RwLock<AppContext>>, |
983 | } |
984 | |
985 | impl 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 | |
1087 | impl PartialEq for PlatformRoot { |
1088 | fn eq(&self, other: &Self) -> bool { |
1089 | self.app_context.ptr_eq(&other.app_context) |
1090 | } |
1091 | } |
1092 | |
1093 | impl 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)] |
1100 | pub enum NodeIdOrRoot { |
1101 | Node(NodeId), |
1102 | Root, |
1103 | } |
1104 | |