atspi_connection/
lib.rs

1//! A connection to AT-SPI.
2//! This connection may receive any [`atspi_common::events::Event`] structures.
3
4#![deny(clippy::all, clippy::pedantic, clippy::cargo, unsafe_code, rustdoc::all, missing_docs)]
5#![allow(clippy::multiple_crate_versions)]
6
7pub use atspi_common as common;
8use common::{
9	error::AtspiError,
10	events::{DBusInterface, DBusMatchRule, DBusMember, MessageConversion, RegistryEventString},
11	EventProperties, Result as AtspiResult,
12};
13
14#[cfg(feature = "wrappers")]
15use atspi_common::events::Event;
16
17#[cfg(feature = "p2p")]
18mod p2p;
19#[cfg(feature = "p2p")]
20pub use p2p::{Peer, P2P};
21
22use atspi_proxies::{
23	accessible::AccessibleProxy,
24	bus::{BusProxy, StatusProxy},
25	registry::RegistryProxy,
26};
27
28#[cfg(feature = "wrappers")]
29use futures_lite::stream::{Stream, StreamExt};
30use std::ops::Deref;
31use zbus::{
32	fdo::DBusProxy,
33	proxy::{CacheProperties, Defaults},
34	Address, MatchRule,
35};
36#[cfg(feature = "wrappers")]
37use zbus::{message::Type as MessageType, MessageStream};
38
39#[cfg(feature = "p2p")]
40use crate::p2p::Peers;
41
42/// A connection to the at-spi bus
43#[derive(Clone, Debug)]
44pub struct AccessibilityConnection {
45	registry: RegistryProxy<'static>,
46	dbus_proxy: DBusProxy<'static>,
47
48	#[cfg(feature = "p2p")]
49	peers: Peers,
50}
51
52impl AccessibilityConnection {
53	/// Open a new connection to the bus
54	/// # Errors
55	/// May error when a bus is not available,
56	/// or when the accessibility bus (AT-SPI) can not be found.
57	#[cfg_attr(feature = "tracing", tracing::instrument)]
58	pub async fn new() -> AtspiResult<Self> {
59		// Grab the a11y bus address from the session bus
60		let a11y_bus_addr = {
61			#[cfg(feature = "tracing")]
62			tracing::debug!("Connecting to session bus");
63			let session_bus = Box::pin(zbus::Connection::session()).await?;
64
65			#[cfg(feature = "tracing")]
66			tracing::debug!(
67				name = session_bus.unique_name().map(|n| n.as_str()),
68				"Connected to session bus"
69			);
70
71			let proxy = BusProxy::new(&session_bus).await?;
72			#[cfg(feature = "tracing")]
73			tracing::debug!("Getting a11y bus address from session bus");
74			proxy.get_address().await?
75		};
76
77		#[cfg(feature = "tracing")]
78		tracing::debug!(address = %a11y_bus_addr, "Got a11y bus address");
79		let addr: Address = a11y_bus_addr.parse()?;
80
81		let accessibility_conn = Self::from_address(addr).await?;
82
83		#[cfg(feature = "p2p")]
84		accessibility_conn
85			.peers
86			.spawn_peer_listener_task(accessibility_conn.connection());
87
88		Ok(accessibility_conn)
89	}
90
91	/// Returns an [`AccessibilityConnection`], a wrapper for the [`RegistryProxy`]; a handle for the registry provider
92	/// on the accessibility bus.
93	///
94	/// You may want to call this if you have the accessibility bus address and want a connection with
95	/// a convenient async event stream provisioning.
96	///
97	/// Without address, you will want to call  `open`, which tries to obtain the accessibility bus' address
98	/// on your behalf.
99	///
100	/// # Errors
101	///
102	/// `RegistryProxy` is configured with invalid path, interface or destination
103	pub async fn from_address(bus_addr: Address) -> AtspiResult<Self> {
104		#[cfg(feature = "tracing")]
105		tracing::info!("Connecting to a11y bus");
106		let bus = Box::pin(zbus::connection::Builder::address(bus_addr)?.build()).await?;
107
108		#[cfg(feature = "tracing")]
109		tracing::info!(name = bus.unique_name().map(|n| n.as_str()), "Connected to a11y bus");
110
111		// The Proxy holds a strong reference to a Connection, so we only need to store the proxy
112		let registry = RegistryProxy::new(&bus).await?;
113		let dbus_proxy = DBusProxy::new(&bus).await?;
114
115		#[cfg(not(feature = "p2p"))]
116		return Ok(Self { registry, dbus_proxy });
117
118		#[cfg(feature = "p2p")]
119		let peers = Peers::initialize_peers(&bus).await?;
120		#[cfg(feature = "p2p")]
121		return Ok(Self { registry, dbus_proxy, peers });
122	}
123
124	/// Stream yielding all `Event` types.
125	///
126	/// Monitor this stream to be notified and receive events on the a11y bus.
127	///
128	/// # Example
129	/// Basic use:
130	///
131	/// ```rust
132	/// use atspi_connection::AccessibilityConnection;
133	/// use enumflags2::BitFlag;
134	/// use atspi_connection::common::events::{ObjectEvents, object::StateChangedEvent};
135	/// use zbus::{fdo::DBusProxy, MatchRule, message::Type as MessageType};
136	/// use atspi_connection::common::events::Event;
137	/// # use futures_lite::StreamExt;
138	/// # use std::error::Error;
139	///
140	/// # fn main() {
141	/// #   assert!(tokio_test::block_on(example()).is_ok());
142	/// # }
143	///
144	/// # async fn example() -> Result<(), Box<dyn Error>> {
145	///     let atspi = AccessibilityConnection::new().await?;
146	///     atspi.register_event::<ObjectEvents>().await?;
147	///
148	///     let mut events = atspi.event_stream();
149	///     std::pin::pin!(&mut events);
150	/// #   let output = std::process::Command::new("busctl")
151	/// #       .arg("--user")
152	/// #       .arg("call")
153	/// #       .arg("org.a11y.Bus")
154	/// #       .arg("/org/a11y/bus")
155	/// #       .arg("org.a11y.Bus")
156	/// #       .arg("GetAddress")
157	/// #       .output()
158	/// #       .unwrap();
159	/// #    let addr_string = String::from_utf8(output.stdout).unwrap();
160	/// #    let addr_str = addr_string
161	/// #        .strip_prefix("s \"")
162	/// #        .unwrap()
163	/// #        .trim()
164	/// #        .strip_suffix('"')
165	/// #        .unwrap();
166	/// #   let mut base_cmd = std::process::Command::new("busctl");
167	/// #   let thing = base_cmd
168	/// #       .arg("--address")
169	/// #       .arg(addr_str)
170	/// #       .arg("emit")
171	/// #       .arg("/org/a11y/atspi/null")
172	/// #       .arg("org.a11y.atspi.Event.Object")
173	/// #       .arg("StateChanged")
174	/// #       .arg("siiva{sv}")
175	/// #       .arg("")
176	/// #       .arg("0")
177	/// #       .arg("0")
178	/// #       .arg("i")
179	/// #       .arg("0")
180	/// #       .arg("0")
181	/// #       .output()
182	/// #       .unwrap();
183	///
184	///     while let Some(Ok(ev)) = events.next().await {
185	///         // Handle Object events
186	///        if let Ok(event) = StateChangedEvent::try_from(ev) {
187	/// #        break;
188	///          // do something else here
189	///        } else { continue }
190	///     }
191	/// #    Ok(())
192	/// # }
193	/// ```
194	#[cfg(feature = "wrappers")]
195	pub fn event_stream(&self) -> impl Stream<Item = Result<Event, AtspiError>> {
196		MessageStream::from(self.registry.inner().connection()).filter_map(|res| {
197			let msg = match res {
198				Ok(m) => m,
199				Err(e) => return Some(Err(e.into())),
200			};
201
202			let msg_header = msg.header();
203			let Some(msg_interface) = msg_header.interface() else {
204				return Some(Err(AtspiError::MissingInterface));
205			};
206
207			// Most events we encounter are sent on an "org.a11y.atspi.Event.*" interface,
208			// but there are exceptions like "org.a11y.atspi.Cache" or "org.a11y.atspi.Registry".
209			// So only signals with a suitable interface name will be parsed.
210			if msg_interface.starts_with("org.a11y.atspi")
211				&& msg.message_type() == MessageType::Signal
212			{
213				Some(Event::try_from(&msg))
214			} else {
215				None
216			}
217		})
218	}
219
220	/// Registers an events as defined in [`atspi_common::events`].\
221	/// This function registers a single event, like so:
222	///
223	/// # Example
224	/// ```rust
225	/// use atspi_connection::common::events::object::StateChangedEvent;
226	/// # tokio_test::block_on(async {
227	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
228	/// connection.register_event::<StateChangedEvent>().await.unwrap();
229	/// # })
230	/// ```
231	///
232	/// # Errors
233	/// This function may return an error if a [`zbus::Error`] is caused by all the various calls to [`zbus::fdo::DBusProxy`] and [`zbus::MatchRule::try_from`].
234	pub async fn add_match_rule<T: DBusMatchRule>(&self) -> Result<(), AtspiError> {
235		let match_rule = MatchRule::try_from(<T as DBusMatchRule>::MATCH_RULE_STRING)?;
236		self.dbus_proxy.add_match_rule(match_rule).await?;
237		Ok(())
238	}
239
240	/// Deregisters an event as defined in [`atspi_common::events`].\
241	/// This function registers a single event, like so:
242	///
243	/// # Example
244	/// ```rust
245	/// use atspi_connection::common::events::object::StateChangedEvent;
246	/// # tokio_test::block_on(async {
247	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
248	/// connection.add_match_rule::<StateChangedEvent>().await.unwrap();
249	/// connection.remove_match_rule::<StateChangedEvent>().await.unwrap();
250	/// # })
251	/// ```
252	///
253	/// # Errors
254	/// This function may return an error if a [`zbus::Error`] is caused by all the various calls to [`zbus::fdo::DBusProxy`] and [`zbus::MatchRule::try_from`].
255	pub async fn remove_match_rule<T: DBusMatchRule>(&self) -> Result<(), AtspiError> {
256		let match_rule = MatchRule::try_from(<T as DBusMatchRule>::MATCH_RULE_STRING)?;
257		self.dbus_proxy.add_match_rule(match_rule).await?;
258		Ok(())
259	}
260
261	/// Add a registry event.
262	/// This tells accessible applications which events should be forwarded to the accessibility bus.
263	/// This is called by [`Self::register_event`].
264	///
265	/// # Example
266	/// ```rust
267	/// use atspi_connection::common::events::object::StateChangedEvent;
268	/// # tokio_test::block_on(async {
269	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
270	/// connection.add_registry_event::<StateChangedEvent>().await.unwrap();
271	/// connection.remove_registry_event::<StateChangedEvent>().await.unwrap();
272	/// # })
273	/// ```
274	///
275	/// # Errors
276	/// May cause an error if the `DBus` method [`atspi_proxies::registry::RegistryProxy::register_event`] fails.
277	pub async fn add_registry_event<T: RegistryEventString>(&self) -> Result<(), AtspiError> {
278		self.registry
279			.register_event(<T as RegistryEventString>::REGISTRY_EVENT_STRING)
280			.await?;
281		Ok(())
282	}
283
284	/// Remove a registry event.
285	/// This tells accessible applications which events should be forwarded to the accessibility bus.
286	/// This is called by [`Self::deregister_event`].
287	/// It may be called like so:
288	///
289	/// # Example
290	/// ```rust
291	/// use atspi_connection::common::events::object::StateChangedEvent;
292	/// # tokio_test::block_on(async {
293	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
294	/// connection.add_registry_event::<StateChangedEvent>().await.unwrap();
295	/// connection.remove_registry_event::<StateChangedEvent>().await.unwrap();
296	/// # })
297	/// ```
298	///
299	/// # Errors
300	/// May cause an error if the `DBus` method [`RegistryProxy::deregister_event`] fails.
301	pub async fn remove_registry_event<T: RegistryEventString>(&self) -> Result<(), AtspiError> {
302		self.registry
303			.deregister_event(<T as RegistryEventString>::REGISTRY_EVENT_STRING)
304			.await?;
305		Ok(())
306	}
307
308	/// This calls [`Self::add_registry_event`] and [`Self::add_match_rule`], two components necessary to receive accessibility events.
309	/// # Errors
310	/// This will only fail if [`Self::add_registry_event`[ or [`Self::add_match_rule`] fails.
311	pub async fn register_event<T: RegistryEventString + DBusMatchRule>(
312		&self,
313	) -> Result<(), AtspiError> {
314		self.add_registry_event::<T>().await?;
315		self.add_match_rule::<T>().await?;
316		Ok(())
317	}
318
319	/// This calls [`Self::remove_registry_event`] and [`Self::remove_match_rule`], two components necessary to receive accessibility events.
320	/// # Errors
321	/// This will only fail if [`Self::remove_registry_event`] or [`Self::remove_match_rule`] fails.
322	pub async fn deregister_event<T: RegistryEventString + DBusMatchRule>(
323		&self,
324	) -> Result<(), AtspiError> {
325		self.remove_registry_event::<T>().await?;
326		self.remove_match_rule::<T>().await?;
327		Ok(())
328	}
329
330	/// Shorthand for a reference to the underlying [`zbus::Connection`]
331	#[must_use = "The reference to the underlying zbus::Connection must be used"]
332	pub fn connection(&self) -> &zbus::Connection {
333		self.registry.inner().connection()
334	}
335
336	/// Send an event over the accessibility bus.
337	/// This converts the event into a [`zbus::Message`] using the [`DBusMember`] + [`DBusInterface`] trait.
338	///
339	/// # Errors
340	/// This will only fail if:
341	/// 1. [`zbus::Message`] fails at any point, or
342	/// 2. sending the event fails for some reason.
343	///
344	/// Both of these conditions should never happen as long as you have a valid event.
345	pub async fn send_event<'a, T>(&self, event: T) -> Result<(), AtspiError>
346	where
347		T: DBusMember + DBusInterface + EventProperties + MessageConversion<'a>,
348	{
349		let conn = self.connection();
350		let new_message = zbus::Message::signal(
351			event.path(),
352			<T as DBusInterface>::DBUS_INTERFACE,
353			<T as DBusMember>::DBUS_MEMBER,
354		)?
355		.sender(conn.unique_name().ok_or(AtspiError::MissingName)?)?
356		// this re-encodes the entire body; it's not great..., but you can't replace a sender once a message a created.
357		.build(&event.body())?;
358		Ok(conn.send(&new_message).await?)
359	}
360
361	/// Get the root accessible object from the atspi registry;
362	/// This semantically represents the root of the accessibility tree
363	/// and can be used for tree traversals.
364	///
365	/// It may be called like so:
366	///
367	/// # Example
368	/// ```rust
369	/// use atspi_connection::AccessibilityConnection;
370	/// use zbus::proxy::CacheProperties;
371	/// # tokio_test::block_on(async {
372	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
373	/// let root = connection.root_accessible_on_registry().await.unwrap();
374	/// let children = root.get_children().await;
375	/// assert!(children.is_ok());
376	/// # });
377	/// ```
378	/// # Errors
379	/// This will fail if a dbus connection cannot be established when trying to
380	/// connect to the registry
381	///
382	/// # Panics
383	/// This will panic if the `default_service` is not set on the `RegistryProxy`, which should never happen.
384	pub async fn root_accessible_on_registry(&self) -> Result<AccessibleProxy<'_>, AtspiError> {
385		let registry_well_known_name = RegistryProxy::DESTINATION
386			.as_ref()
387			.expect("Registry default_service is set");
388		let registry = AccessibleProxy::builder(self.connection())
389			.destination(registry_well_known_name)?
390			// registry has an incomplete implementation of the DBus interface
391			// and therefore cannot be cached; thus we always disable caching it
392			.cache_properties(CacheProperties::No)
393			.build()
394			.await?;
395
396		Ok(registry)
397	}
398}
399
400impl Deref for AccessibilityConnection {
401	type Target = RegistryProxy<'static>;
402
403	fn deref(&self) -> &Self::Target {
404		&self.registry
405	}
406}
407
408/// Set the `IsEnabled` property in the session bus.
409///
410/// Assistive Technology provider applications (ATs) should set the accessibility
411/// `IsEnabled` status on the users session bus on startup as applications may monitor this property
412/// to enable their accessibility support dynamically.
413///
414/// See: The [freedesktop - AT-SPI2 wiki](https://www.freedesktop.org/wiki/Accessibility/AT-SPI2/)
415///
416/// # Example
417/// ```rust
418///     let result =  tokio_test::block_on( atspi_connection::set_session_accessibility(true) );
419///     assert!(result.is_ok());
420/// ```
421/// # Errors
422///
423/// 1. when no connection with the session bus can be established,
424/// 2. if creation of a [`atspi_proxies::bus::StatusProxy`] fails
425/// 3. if the `IsEnabled` property cannot be read
426/// 4. the `IsEnabled` property cannot be set.
427pub async fn set_session_accessibility(status: bool) -> std::result::Result<(), AtspiError> {
428	// Get a connection to the session bus.
429	let session = Box::pin(zbus::Connection::session()).await?;
430
431	// Acquire a `StatusProxy` for the session bus.
432	let status_proxy = StatusProxy::new(&session).await?;
433
434	if status_proxy.is_enabled().await? != status {
435		status_proxy.set_is_enabled(status).await?;
436	}
437	Ok(())
438}
439
440/// Read the `IsEnabled` accessibility status property on the session bus.
441///
442/// # Examples
443/// ```rust
444///     # tokio_test::block_on( async {
445///     let status = atspi_connection::read_session_accessibility().await;
446///
447///     // The status is either true or false
448///        assert!(status.is_ok());
449///     # });
450/// ```
451///
452/// # Errors
453///
454/// - If no connection with the session bus could be established.
455/// - If creation of a [`atspi_proxies::bus::StatusProxy`] fails.
456/// - If the `IsEnabled` property cannot be read.
457pub async fn read_session_accessibility() -> AtspiResult<bool> {
458	// Get a connection to the session bus.
459	let session = Box::pin(zbus::Connection::session()).await?;
460
461	// Acquire a `StatusProxy` for the session bus.
462	let status_proxy = StatusProxy::new(&session).await?;
463
464	// Read the `IsEnabled` property.
465	status_proxy.is_enabled().await.map_err(Into::into)
466}