1 | #[cfg (all(not(feature = "async-std" ), not(feature = "tokio" )))] |
2 | compile_error!("You must specify at least one of the `async-std` or `tokio` features." ); |
3 | |
4 | pub use atspi_common as common; |
5 | |
6 | use atspi_proxies::{ |
7 | bus::{BusProxy, StatusProxy}, |
8 | registry::RegistryProxy, |
9 | }; |
10 | use common::error::AtspiError; |
11 | use common::events::{Event, GenericEvent, HasMatchRule, HasRegistryEventString}; |
12 | use futures_lite::stream::{Stream, StreamExt}; |
13 | use std::ops::Deref; |
14 | use zbus::{fdo::DBusProxy, Address, MatchRule, MessageStream, MessageType}; |
15 | |
16 | pub type AtspiResult<T> = std::result::Result<T, AtspiError>; |
17 | |
18 | /// A connection to the at-spi bus |
19 | pub struct AccessibilityConnection { |
20 | registry: RegistryProxy<'static>, |
21 | dbus_proxy: DBusProxy<'static>, |
22 | } |
23 | |
24 | impl 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 | |
297 | impl 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. |
324 | pub 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. |
354 | pub 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 | |