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