atspi_connection/
lib.rs

1//! A connection to AT-SPI.
2//!  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
7#[cfg(all(not(feature = "async-std"), not(feature = "tokio")))]
8compile_error!("You must specify at least one of the `async-std` or `tokio` features.");
9
10pub use atspi_common as common;
11
12use atspi_proxies::{
13	bus::{BusProxy, StatusProxy},
14	registry::RegistryProxy,
15};
16use common::error::AtspiError;
17use common::events::{BusProperties, Event, EventProperties, HasMatchRule, HasRegistryEventString};
18use futures_lite::stream::{Stream, StreamExt};
19use std::ops::Deref;
20use zbus::{fdo::DBusProxy, Address, MatchRule, MessageStream, MessageType};
21
22/// A wrapper for results whose error type is [`AtspiError`].
23pub type AtspiResult<T> = std::result::Result<T, AtspiError>;
24
25/// A connection to the at-spi bus
26pub struct AccessibilityConnection {
27	registry: RegistryProxy<'static>,
28	dbus_proxy: DBusProxy<'static>,
29}
30
31impl AccessibilityConnection {
32	/// Open a new connection to the bus
33	/// # Errors
34	/// May error when a bus is not available,
35	/// or when the accessibility bus (AT-SPI) can not be found.
36	#[cfg_attr(feature = "tracing", tracing::instrument)]
37	pub async fn new() -> zbus::Result<Self> {
38		// Grab the a11y bus address from the session bus
39		let a11y_bus_addr = {
40			#[cfg(feature = "tracing")]
41			tracing::debug!("Connecting to session bus");
42			let session_bus = Box::pin(zbus::Connection::session()).await?;
43
44			#[cfg(feature = "tracing")]
45			tracing::debug!(
46				name = session_bus.unique_name().map(|n| n.as_str()),
47				"Connected to session bus"
48			);
49
50			let proxy = BusProxy::new(&session_bus).await?;
51			#[cfg(feature = "tracing")]
52			tracing::debug!("Getting a11y bus address from session bus");
53			proxy.get_address().await?
54		};
55
56		#[cfg(feature = "tracing")]
57		tracing::debug!(address = %a11y_bus_addr, "Got a11y bus address");
58		let addr: Address = a11y_bus_addr.parse()?;
59
60		Self::from_address(addr).await
61	}
62
63	/// Returns an [`AccessibilityConnection`], a wrapper for the [`RegistryProxy`]; a handle for the registry provider
64	/// on the accessibility bus.
65	///
66	/// You may want to call this if you have the accessibility bus address and want a connection with
67	/// a convenient async event stream provisioning.
68	///
69	/// Without address, you will want to call  `open`, which tries to obtain the accessibility bus' address
70	/// on your behalf.
71	///
72	/// # Errors
73	///
74	/// `RegistryProxy` is configured with invalid path, interface or destination
75	pub async fn from_address(bus_addr: Address) -> zbus::Result<Self> {
76		#[cfg(feature = "tracing")]
77		tracing::debug!("Connecting to a11y bus");
78		let bus = Box::pin(zbus::ConnectionBuilder::address(bus_addr)?.build()).await?;
79
80		#[cfg(feature = "tracing")]
81		tracing::debug!(name = bus.unique_name().map(|n| n.as_str()), "Connected to a11y bus");
82
83		// The Proxy holds a strong reference to a Connection, so we only need to store the proxy
84		let registry = RegistryProxy::new(&bus).await?;
85		let dbus_proxy = DBusProxy::new(registry.inner().connection()).await?;
86
87		Ok(Self { registry, dbus_proxy })
88	}
89
90	/// Stream yielding all `Event` types.
91	///
92	/// Monitor this stream to be notified and receive events on the a11y bus.
93	///
94	/// # Example
95	/// Basic use:
96	///
97	/// ```rust
98	/// use atspi_connection::AccessibilityConnection;
99	/// use enumflags2::BitFlag;
100	/// use atspi_connection::common::events::object::{ObjectEvents, StateChangedEvent};
101	/// use zbus::{fdo::DBusProxy, MatchRule, MessageType};
102	/// use atspi_connection::common::events::Event;
103	/// # use futures_lite::StreamExt;
104	/// # use std::error::Error;
105	///
106	/// # fn main() {
107	/// #   assert!(tokio_test::block_on(example()).is_ok());
108	/// # }
109	///
110	/// # async fn example() -> Result<(), Box<dyn Error>> {
111	///     let atspi = AccessibilityConnection::new().await?;
112	///     atspi.register_event::<ObjectEvents>().await?;
113	///
114	///     let mut events = atspi.event_stream();
115	///     std::pin::pin!(&mut events);
116	/// #   let output = std::process::Command::new("busctl")
117	/// #       .arg("--user")
118	/// #       .arg("call")
119	/// #       .arg("org.a11y.Bus")
120	/// #       .arg("/org/a11y/bus")
121	/// #       .arg("org.a11y.Bus")
122	/// #       .arg("GetAddress")
123	/// #       .output()
124	/// #       .unwrap();
125	/// #    let addr_string = String::from_utf8(output.stdout).unwrap();
126	/// #    let addr_str = addr_string
127	/// #        .strip_prefix("s \"")
128	/// #        .unwrap()
129	/// #        .trim()
130	/// #        .strip_suffix('"')
131	/// #        .unwrap();
132	/// #   let mut base_cmd = std::process::Command::new("busctl");
133	/// #   let thing = base_cmd
134	/// #       .arg("--address")
135	/// #       .arg(addr_str)
136	/// #       .arg("emit")
137	/// #       .arg("/org/a11y/atspi/accessible/null")
138	/// #       .arg("org.a11y.atspi.Event.Object")
139	/// #       .arg("StateChanged")
140	/// #       .arg("siiva{sv}")
141	/// #       .arg("")
142	/// #       .arg("0")
143	/// #       .arg("0")
144	/// #       .arg("i")
145	/// #       .arg("0")
146	/// #       .arg("0")
147	/// #       .output()
148	/// #       .unwrap();
149	///
150	///     while let Some(Ok(ev)) = events.next().await {
151	///         // Handle Object events
152	///        if let Ok(event) = StateChangedEvent::try_from(ev) {
153	/// #        break;
154	///          // do something else here
155	///        } else { continue }
156	///     }
157	/// #    Ok(())
158	/// # }
159	/// ```
160	pub fn event_stream(&self) -> impl Stream<Item = Result<Event, AtspiError>> {
161		MessageStream::from(self.registry.inner().connection()).filter_map(|res| {
162			let msg = match res {
163				Ok(m) => m,
164				Err(e) => return Some(Err(e.into())),
165			};
166			match msg.message_type() {
167				MessageType::Signal => Some(Event::try_from(&msg)),
168				_ => None,
169			}
170		})
171	}
172
173	/// Registers an events as defined in [`atspi-types::events`]. This function registers a single event, like so:
174	/// ```rust
175	/// use atspi_connection::common::events::object::StateChangedEvent;
176	/// # tokio_test::block_on(async {
177	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
178	/// connection.register_event::<StateChangedEvent>().await.unwrap();
179	/// # })
180	/// ```
181	///
182	/// # Errors
183	///
184	/// 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`].
185	pub async fn add_match_rule<T: HasMatchRule>(&self) -> Result<(), AtspiError> {
186		let match_rule = MatchRule::try_from(<T as HasMatchRule>::MATCH_RULE_STRING)?;
187		self.dbus_proxy.add_match_rule(match_rule).await?;
188		Ok(())
189	}
190
191	/// Deregisters an events as defined in [`atspi-types::events`]. This function registers a single event, like so:
192	/// ```rust
193	/// use atspi_connection::common::events::object::StateChangedEvent;
194	/// # tokio_test::block_on(async {
195	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
196	/// connection.add_match_rule::<StateChangedEvent>().await.unwrap();
197	/// connection.remove_match_rule::<StateChangedEvent>().await.unwrap();
198	/// # })
199	/// ```
200	///
201	/// # Errors
202	///
203	/// 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`].
204	pub async fn remove_match_rule<T: HasMatchRule>(&self) -> Result<(), AtspiError> {
205		let match_rule = MatchRule::try_from(<T as HasMatchRule>::MATCH_RULE_STRING)?;
206		self.dbus_proxy.add_match_rule(match_rule).await?;
207		Ok(())
208	}
209
210	/// Add a registry event.
211	/// This tells accessible applications which events should be forwarded to the accessibility bus.
212	/// This is called by [`Self::register_event`].
213	///
214	/// ```rust
215	/// use atspi_connection::common::events::object::StateChangedEvent;
216	/// # tokio_test::block_on(async {
217	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
218	/// connection.add_registry_event::<StateChangedEvent>().await.unwrap();
219	/// connection.remove_registry_event::<StateChangedEvent>().await.unwrap();
220	/// # })
221	/// ```
222	///
223	/// # Errors
224	///
225	/// May cause an error if the `DBus` method [`atspi_proxies::registry::RegistryProxy::register_event`] fails.
226	pub async fn add_registry_event<T: HasRegistryEventString>(&self) -> Result<(), AtspiError> {
227		self.registry
228			.register_event(<T as HasRegistryEventString>::REGISTRY_EVENT_STRING)
229			.await?;
230		Ok(())
231	}
232
233	/// Remove a registry event.
234	/// This tells accessible applications which events should be forwarded to the accessibility bus.
235	/// This is called by [`Self::deregister_event`].
236	/// It may be called like so:
237	///
238	/// ```rust
239	/// use atspi_connection::common::events::object::StateChangedEvent;
240	/// # tokio_test::block_on(async {
241	/// let connection = atspi_connection::AccessibilityConnection::new().await.unwrap();
242	/// connection.add_registry_event::<StateChangedEvent>().await.unwrap();
243	/// connection.remove_registry_event::<StateChangedEvent>().await.unwrap();
244	/// # })
245	/// ```
246	///
247	/// # Errors
248	///
249	/// May cause an error if the `DBus` method [`RegistryProxy::deregister_event`] fails.
250	pub async fn remove_registry_event<T: HasRegistryEventString>(&self) -> Result<(), AtspiError> {
251		self.registry
252			.deregister_event(<T as HasRegistryEventString>::REGISTRY_EVENT_STRING)
253			.await?;
254		Ok(())
255	}
256
257	/// This calls [`Self::add_registry_event`] and [`Self::add_match_rule`], two components necessary to receive accessibility events.
258	/// # Errors
259	/// This will only fail if [`Self::add_registry_event`[ or [`Self::add_match_rule`] fails.
260	pub async fn register_event<T: HasRegistryEventString + HasMatchRule>(
261		&self,
262	) -> Result<(), AtspiError> {
263		self.add_registry_event::<T>().await?;
264		self.add_match_rule::<T>().await?;
265		Ok(())
266	}
267
268	/// This calls [`Self::remove_registry_event`] and [`Self::remove_match_rule`], two components necessary to receive accessibility events.
269	/// # Errors
270	/// This will only fail if [`Self::remove_registry_event`] or [`Self::remove_match_rule`] fails.
271	pub async fn deregister_event<T: HasRegistryEventString + HasMatchRule>(
272		&self,
273	) -> Result<(), AtspiError> {
274		self.remove_registry_event::<T>().await?;
275		self.remove_match_rule::<T>().await?;
276		Ok(())
277	}
278
279	/// Shorthand for a reference to the underlying [`zbus::Connection`]
280	#[must_use = "The reference to the underlying zbus::Connection must be used"]
281	pub fn connection(&self) -> &zbus::Connection {
282		self.registry.inner().connection()
283	}
284
285	/// Send an event over the accessibility bus.
286	/// This converts the event into a [`zbus::Message`] using the [`BusProperties`] trait.
287	///
288	/// # Errors
289	///
290	/// This will only fail if:
291	/// 1. [`zbus::Message`] fails at any point, or
292	/// 2. sending the event fails for some reason.
293	///
294	/// Both of these conditions should never happen as long as you have a valid event.
295	pub async fn send_event<T>(&self, event: T) -> Result<(), AtspiError>
296	where
297		T: BusProperties + EventProperties,
298	{
299		let conn = self.connection();
300		let new_message = zbus::Message::signal(
301			event.path(),
302			<T as BusProperties>::DBUS_INTERFACE,
303			<T as BusProperties>::DBUS_MEMBER,
304		)?
305		.sender(conn.unique_name().ok_or(AtspiError::MissingName)?)?
306		// this re-encodes the entire body; it's not great..., but you can't replace a sender once a message a created.
307		.build(&event.body())?;
308		Ok(conn.send(&new_message).await?)
309	}
310}
311
312impl Deref for AccessibilityConnection {
313	type Target = RegistryProxy<'static>;
314
315	fn deref(&self) -> &Self::Target {
316		&self.registry
317	}
318}
319
320/// Set the `IsEnabled` property in the session bus.
321///
322/// Assistive Technology provider applications (ATs) should set the accessibility
323/// `IsEnabled` status on the users session bus on startup as applications may monitor this property
324/// to  enable their accessibility support dynamically.
325///
326/// See: The [freedesktop - AT-SPI2 wiki](https://www.freedesktop.org/wiki/Accessibility/AT-SPI2/)
327///
328///  ## Example
329/// ```rust
330///     let result =  tokio_test::block_on( atspi_connection::set_session_accessibility(true) );
331///     assert!(result.is_ok());
332/// ```
333/// # Errors
334///
335/// 1. when no connection with the session bus can be established,
336/// 2. if creation of a [`atspi_proxies::bus::StatusProxy`] fails
337/// 3. if the `IsEnabled` property cannot be read
338/// 4. the `IsEnabled` property cannot be set.
339pub async fn set_session_accessibility(status: bool) -> std::result::Result<(), AtspiError> {
340	// Get a connection to the session bus.
341	let session = Box::pin(zbus::Connection::session()).await?;
342
343	// Acquire a `StatusProxy` for the session bus.
344	let status_proxy = StatusProxy::new(&session).await?;
345
346	if status_proxy.is_enabled().await? != status {
347		status_proxy.set_is_enabled(status).await?;
348	}
349	Ok(())
350}
351
352/// Read the `IsEnabled` accessibility status property on the session bus.
353///
354/// # Examples
355/// ```rust
356///     # tokio_test::block_on( async {
357///     let status = atspi_connection::read_session_accessibility().await;
358///
359///     // The status is either true or false
360///        assert!(status.is_ok());
361///     # });
362/// ```
363///
364/// # Errors
365///
366/// - If no connection with the session bus could be established.
367/// - If creation of a [`atspi_proxies::bus::StatusProxy`] fails.
368/// - If the `IsEnabled` property cannot be read.
369pub async fn read_session_accessibility() -> AtspiResult<bool> {
370	// Get a connection to the session bus.
371	let session = Box::pin(zbus::Connection::session()).await?;
372
373	// Acquire a `StatusProxy` for the session bus.
374	let status_proxy = StatusProxy::new(&session).await?;
375
376	// Read the `IsEnabled` property.
377	status_proxy.is_enabled().await.map_err(Into::into)
378}