atspi_client/
accessible_ext.rs

1use crate::convertable::{Convertable, ConvertableBlocking};
2use async_trait::async_trait;
3use atspi_common::{AtspiError as Error, MatcherArgs, ObjectPair, RelationType, Role};
4use atspi_proxies::{
5	accessible::{Accessible, AccessibleBlocking, AccessibleProxy, AccessibleProxyBlocking},
6	hyperlink::Hyperlink,
7	text::{Text, TextBlocking},
8};
9use std::collections::HashMap;
10
11#[async_trait]
12pub trait AccessibleExt {
13	type Error: std::error::Error;
14	async fn get_application_ext<'a>(&self) -> Result<Self, Self::Error>
15	where
16		Self: Sized;
17	async fn get_parent_ext<'a>(&self) -> Result<Self, Self::Error>
18	where
19		Self: Sized;
20	async fn get_children_ext<'a>(&self) -> Result<Vec<Self>, Self::Error>
21	where
22		Self: Sized;
23	async fn get_siblings<'a>(&self) -> Result<Vec<Self>, Self::Error>
24	where
25		Self: Sized;
26	async fn get_children_indexes<'a>(&self) -> Result<Vec<i32>, Self::Error>;
27	async fn get_siblings_before<'a>(&self) -> Result<Vec<Self>, Self::Error>
28	where
29		Self: Sized;
30	async fn get_siblings_after<'a>(&self) -> Result<Vec<Self>, Self::Error>
31	where
32		Self: Sized;
33	async fn get_children_caret<'a>(&self, after: bool) -> Result<Vec<Self>, Self::Error>
34	where
35		Self: Sized;
36	/* TODO: not sure where these should go since it requires both Text as a self interface and
37	 * Hyperlink as children interfaces. */
38	async fn get_next<'a>(
39		&self,
40		matcher_args: &MatcherArgs,
41		backward: bool,
42		already_visited: &'a mut Vec<ObjectPair>,
43	) -> Result<Option<Self>, Self::Error>
44	where
45		Self: Sized;
46	/// Get all edges for a given accessible object.
47	/// This means: all children, siblings, and parent, in that order.
48	/// If a direction is specified, then it will only get the appicable matching siblings/children.
49	/// This also checks if the element supports the text interface, and then checks if the caret position is contained within the string, if it is, then children are also handled by direction.
50	async fn edges<'a>(&self, backward: Option<bool>) -> Result<Vec<Self>, Self::Error>
51	where
52		Self: Sized;
53	async fn get_relation_set_ext<'a>(
54		&self,
55	) -> Result<HashMap<RelationType, Vec<Self>>, Self::Error>
56	where
57		Self: Sized;
58	async fn match_(
59		&self,
60		matcher_args: &MatcherArgs,
61	) -> Result<bool, <Self as AccessibleExt>::Error>;
62}
63// TODO: implement AccessibleExt
64pub trait AccessibleBlockingExt {}
65
66#[allow(clippy::module_name_repetitions)]
67pub trait AccessibleExtError: Accessible + Convertable {
68	type Error: std::error::Error
69		+ From<<Self as Accessible>::Error>
70		+ From<<Self as Convertable>::Error>
71		// TODO: add all convertable error types
72		+ From<<<Self as Convertable>::Text as Text>::Error>
73		+ From<std::num::TryFromIntError>
74		+ Send;
75}
76impl AccessibleExtError for AccessibleProxy<'_> {
77	type Error = Error;
78}
79
80#[allow(clippy::module_name_repetitions)]
81pub trait AccessibleBlockingExtError: AccessibleBlocking + ConvertableBlocking {
82	type Error: std::error::Error
83		+ From<<Self as AccessibleBlocking>::Error>
84		+ From<<Self as ConvertableBlocking>::Error>
85		// TODO: add all convertable error types
86		+ From<<<Self as ConvertableBlocking>::Text as TextBlocking>::Error>
87		+ From<std::num::TryFromIntError>;
88}
89impl AccessibleBlockingExtError for AccessibleProxyBlocking<'_> {
90	type Error = Error;
91}
92
93#[async_trait]
94impl<T: Accessible + Convertable + AccessibleExtError + Send + Sync + Clone> AccessibleExt for T
95where
96	ObjectPair: for<'c> TryFrom<&'c T>,
97{
98	type Error = <T as AccessibleExtError>::Error;
99	async fn get_application_ext<'a>(&self) -> Result<Self, Self::Error>
100	where
101		Self: Sized,
102	{
103		Ok(self.get_application().await?)
104	}
105	async fn get_parent_ext<'a>(&self) -> Result<Self, Self::Error>
106	where
107		Self: Sized,
108	{
109		Ok(self.parent().await?)
110	}
111	async fn get_children_indexes<'a>(&self) -> Result<Vec<i32>, Self::Error> {
112		let mut indexes = Vec::new();
113		for child in self.get_children_ext().await? {
114			indexes.push(child.get_index_in_parent().await?);
115		}
116		Ok(indexes)
117	}
118	async fn get_children_ext<'a>(&self) -> Result<Vec<Self>, Self::Error>
119	where
120		Self: Sized,
121	{
122		Ok(self.get_children().await?)
123		/*
124		let children_parts = self.get_children().await?;
125		let mut children = Vec::new();
126		for child_parts in children_parts {
127			let acc = AccessibleProxy::builder(self.connection())
128				.destination(child_parts.0)?
129				.cache_properties(CacheProperties::No)
130				.path(child_parts.1)?
131				.build()
132				.await?;
133			children.push(acc);
134		}
135		Ok(children)
136				*/
137	}
138	async fn get_siblings<'a>(&self) -> Result<Vec<Self>, Self::Error>
139	where
140		Self: Sized,
141	{
142		let parent = self.parent().await?;
143		let pin = self.get_index_in_parent().await?;
144		let index = pin.try_into()?;
145		// Clippy false positive: Standard pattern for excluding index item from list.
146		#[allow(clippy::if_not_else)]
147		let children: Vec<Self> = parent
148			.get_children()
149			.await?
150			.into_iter()
151			.enumerate()
152			.filter_map(|(i, a)| if i != index { Some(a) } else { None })
153			.collect();
154		Ok(children)
155	}
156	async fn get_siblings_before<'a>(&self) -> Result<Vec<Self>, Self::Error>
157	where
158		Self: Sized,
159	{
160		let parent = self.parent().await?;
161		let index = self.get_index_in_parent().await?.try_into()?;
162		let children: Vec<Self> = parent
163			.get_children_ext()
164			.await?
165			.into_iter()
166			.enumerate()
167			.filter_map(|(i, a)| if i < index { Some(a) } else { None })
168			.collect();
169		Ok(children)
170	}
171	async fn get_siblings_after<'a>(&self) -> Result<Vec<Self>, Self::Error>
172	where
173		Self: Sized,
174	{
175		let parent = self.parent().await?;
176		let index = self.get_index_in_parent().await?.try_into()?;
177		let children: Vec<Self> = parent
178			.get_children_ext()
179			.await?
180			.into_iter()
181			.enumerate()
182			.filter_map(|(i, a)| if i > index { Some(a) } else { None })
183			.collect();
184		Ok(children)
185	}
186	async fn get_children_caret<'a>(&self, backward: bool) -> Result<Vec<Self>, Self::Error>
187	where
188		Self: Sized,
189	{
190		let mut children_after_before = Vec::new();
191		let text_iface = self.to_text().await?;
192		let caret_pos = text_iface.caret_offset().await?;
193		let children_hyperlink = self.get_children_ext().await?;
194		for child in children_hyperlink {
195			let hyperlink = child.to_hyperlink().await?;
196			if let Ok(start_index) = hyperlink.start_index().await {
197				if (start_index <= caret_pos && backward) || (start_index >= caret_pos && !backward)
198				{
199					children_after_before.push(child);
200				}
201			// include all children which do not identify their positions, for some reason
202			} else {
203				children_after_before.push(child);
204			}
205		}
206		Ok(children_after_before)
207	}
208	async fn edges<'a>(&self, backward: Option<bool>) -> Result<Vec<Self>, Self::Error>
209	where
210		Self: Sized,
211	{
212		let mut edge_elements = Vec::new();
213		let children = match backward {
214			Some(backward) => {
215				if let Ok(caret_children) = self.get_children_caret(backward).await {
216					caret_children
217				} else {
218					self.get_children().await?
219				}
220			}
221			None => self.get_children().await?,
222		};
223		children.into_iter().for_each(|child| edge_elements.push(child));
224		let siblings = match backward {
225			Some(false) => self.get_siblings_before().await?,
226			Some(true) => self.get_siblings_after().await?,
227			None => self.get_siblings().await?,
228		};
229		siblings.into_iter().for_each(|sibling| edge_elements.push(sibling));
230		let parent = self.get_parent_ext().await?;
231		edge_elements.push(parent);
232		Ok(edge_elements)
233	}
234	async fn get_next<'a>(
235		&self,
236		matcher_args: &MatcherArgs,
237		backward: bool,
238		visited: &'a mut Vec<ObjectPair>,
239	) -> Result<Option<Self>, Self::Error>
240	where
241		Self: Sized,
242	{
243		let mut stack: Vec<T> = Vec::new();
244		let edges = self.edges(Some(backward)).await?;
245		edges.into_iter().for_each(|edge| stack.push(edge));
246		while let Some(item) = stack.pop() {
247			// TODO: properly bubble up error
248			let Ok(identifier) = ObjectPair::try_from(&item) else {
249				return Ok(None);
250			};
251			// the top of the hirearchy for strctural navigation.
252			if visited.contains(&identifier) {
253				continue;
254			}
255			visited.push(identifier);
256			if item.get_role().await? == Role::InternalFrame {
257				return Ok(None);
258			}
259			// if it matches, then return it
260			if item.match_(matcher_args).await? {
261				return Ok(Some(item));
262			}
263			// if it doesnt match, add all edges
264			self.edges(Some(backward))
265				.await?
266				.into_iter()
267				.for_each(|edge| stack.push(edge));
268		}
269		return Ok(None);
270	}
271	async fn get_relation_set_ext<'a>(
272		&self,
273	) -> Result<HashMap<RelationType, Vec<Self>>, Self::Error>
274	where
275		Self: Sized,
276	{
277		let raw_relations = self.get_relation_set().await?;
278		let mut relations = HashMap::new();
279		for relation in raw_relations {
280			let mut related_vec = Vec::new();
281			for related in relation.1 {
282				related_vec.push(related);
283			}
284			relations.insert(relation.0, related_vec);
285		}
286		Ok(relations)
287	}
288	// TODO: make match more broad, allow use of other parameters; also, support multiple roles, since right now, multiple will just exit immediately with false
289	async fn match_(
290		&self,
291		matcher_args: &MatcherArgs,
292	) -> Result<bool, <Self as AccessibleExt>::Error> {
293		let roles = &matcher_args.0;
294		if roles.len() != 1 {
295			return Ok(false);
296		}
297		// our unwrap is protected from panicing with the above check
298		Ok(self.get_role().await? == *roles.get(0).unwrap())
299	}
300}
301
302impl<T: AccessibleBlocking + ConvertableBlocking + AccessibleBlockingExtError> AccessibleBlockingExt
303	for T
304{
305}
306
307assert_impl_all!(AccessibleProxy: Accessible, AccessibleExt);
308assert_impl_all!(AccessibleProxyBlocking: AccessibleBlocking, AccessibleBlockingExt);