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}