atspi_connection/
lib.rs

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