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