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}