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: Connection = 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: Connection = 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}
364