1 | pub mod document; |
2 | pub mod focus; |
3 | pub mod keyboard; |
4 | pub mod mouse; |
5 | pub mod object; |
6 | pub mod terminal; |
7 | pub mod window; |
8 | |
9 | // Unmarshalled event body signatures: These outline the event specific deserialized event types. |
10 | // Safety: These are evaluated at compile time. |
11 | // ---- |
12 | // The signal signature "(so)" (an Accessible) is ambiguous, because it is used in: |
13 | // - Cache : RemoveAccessible |
14 | // - Socket: Available *( signals the availability of the `Registry` daemon.) |
15 | // |
16 | // ATSPI- and QSPI both describe the generic events. These can be converted into |
17 | // specific signal types with TryFrom implementations. See crate::[`identify`] |
18 | // EVENT_LISTENER_SIGNATURE is a type signature used to notify when events are registered or deregistered. |
19 | // CACHE_ADD_SIGNATURE and *_REMOVE have very different types |
20 | pub const ATSPI_EVENT_SIGNATURE: Signature<'_> = |
21 | Signature::from_static_str_unchecked(signature:"(siiva{sv})" ); |
22 | pub const QSPI_EVENT_SIGNATURE: Signature<'_> = Signature::from_static_str_unchecked(signature:"(siiv(so))" ); |
23 | pub const EVENT_LISTENER_SIGNATURE: Signature<'_> = Signature::from_static_str_unchecked(signature:"(ss)" ); |
24 | pub const CACHE_ADD_SIGNATURE: Signature<'_> = |
25 | Signature::from_static_str_unchecked(signature:"((so)(so)(so)iiassusau)" ); |
26 | |
27 | use std::collections::HashMap; |
28 | |
29 | use serde::{Deserialize, Serialize}; |
30 | #[cfg (feature = "zbus" )] |
31 | use zbus::{MessageField, MessageFieldCode}; |
32 | use zbus_names::{OwnedUniqueName, UniqueName}; |
33 | use zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Signature, Type, Value}; |
34 | |
35 | use crate::{ |
36 | accessible::Accessible, |
37 | cache::{CacheItem, LegacyCacheItem}, |
38 | events::{ |
39 | document::DocumentEvents, focus::FocusEvents, keyboard::KeyboardEvents, mouse::MouseEvents, |
40 | object::ObjectEvents, terminal::TerminalEvents, window::WindowEvents, |
41 | }, |
42 | AtspiError, |
43 | }; |
44 | //use atspi_macros::try_from_zbus_message; |
45 | |
46 | #[must_use ] |
47 | pub fn signatures_are_eq(lhs: &Signature, rhs: &Signature) -> bool { |
48 | fn has_outer_parentheses(bytes: &[u8]) -> bool { |
49 | if let [b'(' , inner @ .., b')' ] = bytes { |
50 | inner.iter().fold(0, |count, byte| match byte { |
51 | b'(' => count + 1, |
52 | b')' if count != 0 => count - 1, |
53 | _ => count, |
54 | }) == 0 |
55 | } else { |
56 | false |
57 | } |
58 | } |
59 | |
60 | let bytes = lhs.as_bytes(); |
61 | let lhs_sig_has_outer_parens = has_outer_parentheses(bytes); |
62 | |
63 | let bytes = rhs.as_bytes(); |
64 | let rhs_sig_has_outer_parens = has_outer_parentheses(bytes); |
65 | |
66 | match (lhs_sig_has_outer_parens, rhs_sig_has_outer_parens) { |
67 | (true, false) => lhs.slice(1..lhs.len() - 1).as_bytes() == rhs.as_bytes(), |
68 | (false, true) => lhs.as_bytes() == rhs.slice(1..rhs.len() - 1).as_bytes(), |
69 | _ => lhs.as_bytes() == rhs.as_bytes(), |
70 | } |
71 | } |
72 | |
73 | /// A borrowed body for events. |
74 | #[derive (Debug, Serialize, Deserialize)] |
75 | pub struct EventBody<'a, T> { |
76 | /// A generic "kind" type, defined by AT-SPI: |
77 | /// usually a `&'a str`, but can be another type like [`crate::state::State`]. |
78 | #[serde(rename = "type" )] |
79 | pub kind: T, |
80 | /// Generic first detail defined by AT-SPI. |
81 | pub detail1: i32, |
82 | /// Generic second detail defined by AT-SPI. |
83 | pub detail2: i32, |
84 | /// Generic "any_data" field defined in AT-SPI. |
85 | /// Can contain any type. |
86 | #[serde(borrow)] |
87 | pub any_data: Value<'a>, |
88 | /// Map of string to an any type. |
89 | /// This is not used for anything, but it is defined by AT-SPI. |
90 | #[serde(borrow)] |
91 | pub properties: HashMap<&'a str, Value<'a>>, |
92 | } |
93 | |
94 | impl<T> Type for EventBody<'_, T> { |
95 | fn signature() -> Signature<'static> { |
96 | <(&str, i32, i32, Value, HashMap<&str, Value>)>::signature() |
97 | } |
98 | } |
99 | |
100 | /// Qt event body, which is not the same as other GUI frameworks. |
101 | /// Signature: "siiv(so)" |
102 | #[derive (Debug, Serialize, Deserialize, Type)] |
103 | pub struct EventBodyQT { |
104 | /// kind variant, used for specifying an event triple "object:state-changed:focused", |
105 | /// the "focus" part of this event is what is contained within the kind. |
106 | // #[serde(rename = "type")] |
107 | pub kind: String, |
108 | /// Generic detail1 value described by AT-SPI. |
109 | pub detail1: i32, |
110 | /// Generic detail2 value described by AT-SPI. |
111 | pub detail2: i32, |
112 | /// Generic any_data value described by AT-SPI. |
113 | /// This can be any type. |
114 | pub any_data: OwnedValue, |
115 | /// A tuple of properties. |
116 | /// Not in use. |
117 | pub properties: Accessible, |
118 | } |
119 | |
120 | impl Default for EventBodyQT { |
121 | fn default() -> Self { |
122 | Self { |
123 | kind: String::new(), |
124 | detail1: 0, |
125 | detail2: 0, |
126 | any_data: Value::U8(0u8).into(), |
127 | properties: Accessible::default(), |
128 | } |
129 | } |
130 | } |
131 | |
132 | /// Standard event body (GTK, `egui`, etc.) |
133 | /// NOTE: Qt has its own signature: [`EventBodyQT`]. |
134 | /// Signature `(siiva{sv})`, |
135 | #[derive (Clone, Debug, Serialize, Deserialize, Type, PartialEq)] |
136 | pub struct EventBodyOwned { |
137 | /// kind variant, used for specifying an event triple "object:state-changed:focused", |
138 | /// the "focus" part of this event is what is contained within the kind. |
139 | #[serde(rename = "type" )] |
140 | pub kind: String, |
141 | /// Generic detail1 value described by AT-SPI. |
142 | pub detail1: i32, |
143 | /// Generic detail2 value described by AT-SPI. |
144 | pub detail2: i32, |
145 | /// Generic any_data value described by AT-SPI. |
146 | /// This can be any type. |
147 | pub any_data: OwnedValue, |
148 | /// A map of properties. |
149 | /// Not in use. |
150 | pub properties: HashMap<String, OwnedValue>, |
151 | } |
152 | |
153 | impl From<EventBodyQT> for EventBodyOwned { |
154 | fn from(body: EventBodyQT) -> Self { |
155 | let accessible: Accessible = Accessible { name: body.properties.name, path: body.properties.path }; |
156 | let mut props: HashMap = HashMap::new(); |
157 | props.insert(k:accessible.name, v:Value::ObjectPath(accessible.path.into()).to_owned()); |
158 | Self { |
159 | kind: body.kind, |
160 | detail1: body.detail1, |
161 | detail2: body.detail2, |
162 | any_data: body.any_data, |
163 | properties: props, |
164 | } |
165 | } |
166 | } |
167 | |
168 | impl Default for EventBodyOwned { |
169 | fn default() -> Self { |
170 | Self { |
171 | kind: String::new(), |
172 | detail1: 0, |
173 | detail2: 0, |
174 | any_data: Value::U8(0u8).into(), |
175 | properties: HashMap::new(), |
176 | } |
177 | } |
178 | } |
179 | |
180 | /// Encapsulates the various different accessibility bus signal types. |
181 | /// |
182 | /// Assumes being non exhaustive to allow for future- or custom signals. |
183 | #[derive (Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
184 | #[non_exhaustive ] |
185 | pub enum Event { |
186 | /// See: [`DocumentEvents`]. |
187 | Document(DocumentEvents), |
188 | /// See: [`FocusEvents`]. |
189 | Focus(FocusEvents), |
190 | /// See: [`KeyboardEvents`]. |
191 | Keyboard(KeyboardEvents), |
192 | /// See: [`MouseEvents`]. |
193 | Mouse(MouseEvents), |
194 | /// See: [`ObjectEvents`]. |
195 | Object(ObjectEvents), |
196 | /// See: [`TerminalEvents`]. |
197 | Terminal(TerminalEvents), |
198 | /// See: [`WindowEvents`]. |
199 | Window(WindowEvents), |
200 | /// See: [`AvailableEvent`]. |
201 | Available(AvailableEvent), |
202 | /// See: [`CacheEvents`]. |
203 | Cache(CacheEvents), |
204 | /// See: [`EventListenerEvents`]. |
205 | Listener(EventListenerEvents), |
206 | } |
207 | |
208 | impl HasMatchRule for CacheEvents { |
209 | const MATCH_RULE_STRING: &'static str = "type='signal',interface='org.a11y.atspi.Event.Cache'" ; |
210 | } |
211 | |
212 | impl HasRegistryEventString for CacheEvents { |
213 | const REGISTRY_EVENT_STRING: &'static str = "Cache" ; |
214 | } |
215 | |
216 | impl HasMatchRule for EventListenerEvents { |
217 | const MATCH_RULE_STRING: &'static str = |
218 | "type='signal',interface='org.a11y.atspi.Event.Registry'" ; |
219 | } |
220 | |
221 | impl HasRegistryEventString for EventListenerEvents { |
222 | const REGISTRY_EVENT_STRING: &'static str = "Event" ; |
223 | } |
224 | |
225 | /// All events related to the `org.a11y.atspi.Cache` interface. |
226 | /// Note that these are not telling the client that an item *has been added* to a cache. |
227 | /// It is telling the client "here is a bunch of information to store it in your cache". |
228 | #[derive (Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)] |
229 | #[allow (clippy::module_name_repetitions)] |
230 | pub enum CacheEvents { |
231 | /// See: [`AddAccessibleEvent`]. |
232 | Add(AddAccessibleEvent), |
233 | /// See: [`LegacyAddAccessibleEvent`]. |
234 | LegacyAdd(LegacyAddAccessibleEvent), |
235 | /// See: [`RemoveAccessibleEvent`]. |
236 | Remove(RemoveAccessibleEvent), |
237 | } |
238 | |
239 | /// Type that contains the `zbus::Message` for meta information and |
240 | /// the [`crate::cache::LegacyCacheItem`] |
241 | #[derive (Debug, Clone, PartialEq, Serialize, Deserialize, Default, Eq, Hash)] |
242 | pub struct LegacyAddAccessibleEvent { |
243 | /// The [`Accessible`] the event applies to. |
244 | pub item: Accessible, |
245 | /// A cache item to add to the internal cache. |
246 | pub node_added: LegacyCacheItem, |
247 | } |
248 | |
249 | impl_from_user_facing_event_for_interface_event_enum!( |
250 | LegacyAddAccessibleEvent, |
251 | CacheEvents, |
252 | CacheEvents::LegacyAdd |
253 | ); |
254 | impl_from_user_facing_type_for_event_enum!(LegacyAddAccessibleEvent, Event::Cache); |
255 | impl_try_from_event_for_user_facing_type!( |
256 | LegacyAddAccessibleEvent, |
257 | CacheEvents::LegacyAdd, |
258 | Event::Cache |
259 | ); |
260 | event_test_cases!(LegacyAddAccessibleEvent); |
261 | impl_from_dbus_message!(LegacyAddAccessibleEvent); |
262 | impl_to_dbus_message!(LegacyAddAccessibleEvent); |
263 | |
264 | impl GenericEvent<'_> for LegacyAddAccessibleEvent { |
265 | const REGISTRY_EVENT_STRING: &'static str = "Cache:Add" ; |
266 | const MATCH_RULE_STRING: &'static str = |
267 | "type='signal',interface='org.a11y.atspi.Cache',member='AddAccessible'" ; |
268 | const DBUS_MEMBER: &'static str = "AddAccessible" ; |
269 | const DBUS_INTERFACE: &'static str = "org.a11y.atspi.Cache" ; |
270 | |
271 | type Body = LegacyCacheItem; |
272 | |
273 | fn build(item: Accessible, body: Self::Body) -> Result<Self, AtspiError> { |
274 | Ok(Self { item, node_added: body }) |
275 | } |
276 | |
277 | fn sender(&self) -> String { |
278 | self.item.name.clone() |
279 | } |
280 | fn path(&self) -> ObjectPath<'_> { |
281 | self.item.path.clone().into() |
282 | } |
283 | fn body(&self) -> Self::Body { |
284 | self.node_added.clone() |
285 | } |
286 | } |
287 | |
288 | /// Type that contains the `zbus::Message` for meta information and |
289 | /// the [`crate::cache::CacheItem`] |
290 | #[derive (Debug, Clone, PartialEq, Serialize, Deserialize, Default, Eq, Hash)] |
291 | pub struct AddAccessibleEvent { |
292 | /// The [`Accessible`] the event applies to. |
293 | pub item: Accessible, |
294 | /// A cache item to add to the internal cache. |
295 | pub node_added: CacheItem, |
296 | } |
297 | |
298 | impl_from_user_facing_event_for_interface_event_enum!( |
299 | AddAccessibleEvent, |
300 | CacheEvents, |
301 | CacheEvents::Add |
302 | ); |
303 | impl_from_user_facing_type_for_event_enum!(AddAccessibleEvent, Event::Cache); |
304 | impl_try_from_event_for_user_facing_type!(AddAccessibleEvent, CacheEvents::Add, Event::Cache); |
305 | event_test_cases!(AddAccessibleEvent); |
306 | |
307 | impl GenericEvent<'_> for AddAccessibleEvent { |
308 | const REGISTRY_EVENT_STRING: &'static str = "Cache:Add" ; |
309 | const MATCH_RULE_STRING: &'static str = |
310 | "type='signal',interface='org.a11y.atspi.Cache',member='AddAccessible'" ; |
311 | const DBUS_MEMBER: &'static str = "AddAccessible" ; |
312 | const DBUS_INTERFACE: &'static str = "org.a11y.atspi.Cache" ; |
313 | |
314 | type Body = CacheItem; |
315 | |
316 | fn build(item: Accessible, body: Self::Body) -> Result<Self, AtspiError> { |
317 | Ok(Self { item, node_added: body }) |
318 | } |
319 | |
320 | fn sender(&self) -> String { |
321 | self.item.name.clone() |
322 | } |
323 | fn path(&self) -> ObjectPath<'_> { |
324 | self.item.path.clone().into() |
325 | } |
326 | fn body(&self) -> Self::Body { |
327 | self.node_added.clone() |
328 | } |
329 | } |
330 | impl<'a, T: GenericEvent<'a>> HasMatchRule for T { |
331 | const MATCH_RULE_STRING: &'static str = <T as GenericEvent>::MATCH_RULE_STRING; |
332 | } |
333 | impl<'a, T: GenericEvent<'a>> HasRegistryEventString for T { |
334 | const REGISTRY_EVENT_STRING: &'static str = <T as GenericEvent>::REGISTRY_EVENT_STRING; |
335 | } |
336 | impl_from_dbus_message!(AddAccessibleEvent); |
337 | impl_to_dbus_message!(AddAccessibleEvent); |
338 | |
339 | /// `Cache::RemoveAccessible` signal event type. |
340 | #[derive (Debug, Clone, PartialEq, Serialize, Deserialize, Default, Eq, Hash)] |
341 | pub struct RemoveAccessibleEvent { |
342 | /// The application that emitted the signal TODO Check Me |
343 | /// The [`Accessible`] the event applies to. |
344 | pub item: Accessible, |
345 | /// The node that was removed from the application tree TODO Check Me |
346 | pub node_removed: Accessible, |
347 | } |
348 | |
349 | impl_from_user_facing_event_for_interface_event_enum!( |
350 | RemoveAccessibleEvent, |
351 | CacheEvents, |
352 | CacheEvents::Remove |
353 | ); |
354 | impl_from_user_facing_type_for_event_enum!(RemoveAccessibleEvent, Event::Cache); |
355 | impl_try_from_event_for_user_facing_type!(RemoveAccessibleEvent, CacheEvents::Remove, Event::Cache); |
356 | event_test_cases!(RemoveAccessibleEvent); |
357 | impl GenericEvent<'_> for RemoveAccessibleEvent { |
358 | const REGISTRY_EVENT_STRING: &'static str = "Cache:Remove" ; |
359 | const MATCH_RULE_STRING: &'static str = |
360 | "type='signal',interface='org.a11y.atspi.Cache',member='RemoveAccessible'" ; |
361 | const DBUS_MEMBER: &'static str = "RemoveAccessible" ; |
362 | const DBUS_INTERFACE: &'static str = "org.a11y.atspi.Cache" ; |
363 | |
364 | type Body = Accessible; |
365 | |
366 | fn build(item: Accessible, body: Self::Body) -> Result<Self, AtspiError> { |
367 | Ok(Self { item, node_removed: body }) |
368 | } |
369 | fn sender(&self) -> String { |
370 | self.item.name.clone() |
371 | } |
372 | fn path(&self) -> ObjectPath<'_> { |
373 | self.item.path.clone().into() |
374 | } |
375 | fn body(&self) -> Self::Body { |
376 | self.node_removed.clone() |
377 | } |
378 | } |
379 | |
380 | impl_from_dbus_message!(RemoveAccessibleEvent); |
381 | impl_to_dbus_message!(RemoveAccessibleEvent); |
382 | |
383 | #[cfg (test)] |
384 | pub mod accessible_deserialization_tests { |
385 | use crate::events::Accessible; |
386 | use zvariant::Value; |
387 | |
388 | #[test ] |
389 | fn try_into_value() { |
390 | let acc = Accessible::default(); |
391 | let value_struct = Value::try_from(acc).expect("Unable to convert into a zvariant::Value" ); |
392 | let Value::Structure(structure) = value_struct else { |
393 | panic!("Unable to destructure a structure out of the Value." ); |
394 | }; |
395 | let vals = structure.into_fields(); |
396 | assert_eq!(vals.len(), 2); |
397 | let Value::Str(bus_name) = vals.get(0).unwrap() else { |
398 | panic!("Unable to destructure field value: {:?}" , vals.get(0).unwrap()); |
399 | }; |
400 | assert_eq!(bus_name, ":0.0" ); |
401 | let Value::ObjectPath(path) = vals.get(1).unwrap() else { |
402 | panic!("Unable to destructure field value: {:?}" , vals.get(1).unwrap()); |
403 | }; |
404 | assert_eq!(path.as_str(), "/org/a11y/atspi/accessible/null" ); |
405 | } |
406 | #[test ] |
407 | fn try_from_value() {} |
408 | } |
409 | |
410 | #[cfg (test)] |
411 | pub mod accessible_tests { |
412 | use super::Accessible; |
413 | |
414 | #[test ] |
415 | fn test_accessible_default_doesnt_panic() { |
416 | let acc = Accessible::default(); |
417 | assert_eq!(acc.name.as_str(), ":0.0" ); |
418 | assert_eq!(acc.path.as_str(), "/org/a11y/atspi/accessible/null" ); |
419 | } |
420 | } |
421 | #[cfg (feature = "zbus" )] |
422 | impl TryFrom<&zbus::Message> for Accessible { |
423 | type Error = AtspiError; |
424 | fn try_from(message: &zbus::Message) -> Result<Self, Self::Error> { |
425 | let path: ObjectPath<'_> = message.path().expect(msg:"returned path is either Some or panics" ); |
426 | let owned_path: OwnedObjectPath = OwnedObjectPath::try_from(path)?; |
427 | let fields: MessageFields<'_> = message.fields()?; |
428 | let sender: Option<&MessageField<'_>> = fields.get_field(code:MessageFieldCode::Sender); |
429 | let sender: &MessageField<'_> = sender |
430 | .expect(msg:"We get the sender field from a valid MessageFieldCode, so it should be there" ); |
431 | |
432 | let MessageField::Sender(unique_name: &UniqueName<'_>) = sender else { |
433 | return Err(AtspiError::Conversion("Unable to convert zbus::Message to Accessible" )); |
434 | }; |
435 | let name_string: String = unique_name.as_str().to_owned(); |
436 | |
437 | Ok(Accessible { name: name_string, path: owned_path }) |
438 | } |
439 | } |
440 | |
441 | #[cfg (feature = "zbus" )] |
442 | impl TryFrom<&zbus::Message> for EventBodyOwned { |
443 | type Error = AtspiError; |
444 | |
445 | fn try_from(message: &zbus::Message) -> Result<Self, Self::Error> { |
446 | let signature: Signature<'_> = message.body_signature()?; |
447 | if signatures_are_eq(&signature, &QSPI_EVENT_SIGNATURE) { |
448 | Ok(EventBodyOwned::from(message.body::<EventBodyQT>()?)) |
449 | } else if signatures_are_eq(&signature, &ATSPI_EVENT_SIGNATURE) { |
450 | Ok(message.body::<EventBodyOwned>()?) |
451 | } else { |
452 | Err(AtspiError::Conversion( |
453 | "Unable to convert from zbus::Message to EventBodyQT or EventBodyOwned" , |
454 | )) |
455 | } |
456 | } |
457 | } |
458 | |
459 | /// Signal type emitted by `EventListenerRegistered` and `EventListenerDeregistered` signals, |
460 | /// which belong to the `Registry` interface, implemented by the registry-daemon. |
461 | #[derive (Debug, Clone, Serialize, Deserialize, Type, PartialEq, Eq, Hash)] |
462 | pub struct EventListeners { |
463 | pub bus_name: OwnedUniqueName, |
464 | pub path: String, |
465 | } |
466 | impl Default for EventListeners { |
467 | fn default() -> Self { |
468 | Self { |
469 | bus_name: UniqueName::try_from(":0.0" ).unwrap().into(), |
470 | path: "/org/a11y/atspi/accessible/null" .to_string(), |
471 | } |
472 | } |
473 | } |
474 | #[test ] |
475 | fn test_event_listener_default_no_panic() { |
476 | let el: EventListeners = EventListeners::default(); |
477 | assert_eq!(el.bus_name.as_str(), ":0.0" ); |
478 | assert_eq!(el.path.as_str(), "/org/a11y/atspi/accessible/null" ); |
479 | } |
480 | |
481 | #[test ] |
482 | fn test_event_listener_signature() { |
483 | assert_eq_signatures!(&EventListeners::signature(), &EVENT_LISTENER_SIGNATURE); |
484 | } |
485 | |
486 | /// Covers both `EventListener` events. |
487 | #[derive (Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] |
488 | #[allow (clippy::module_name_repetitions)] |
489 | pub enum EventListenerEvents { |
490 | /// See: [`EventListenerRegisteredEvent`]. |
491 | Registered(EventListenerRegisteredEvent), |
492 | /// See: [`EventListenerDeregisteredEvent`]. |
493 | Deregistered(EventListenerDeregisteredEvent), |
494 | } |
495 | |
496 | /// An event that is emitted by the registry daemon, to inform that an event has been deregistered |
497 | /// to no longer listen for. |
498 | #[derive (Debug, Clone, PartialEq, Serialize, Deserialize, Default, Eq, Hash)] |
499 | pub struct EventListenerDeregisteredEvent { |
500 | /// The [`Accessible`] the event applies to. |
501 | pub item: Accessible, |
502 | /// A list of events that have been deregistered via the registry interface. |
503 | /// See `atspi-connection`. |
504 | pub deregistered_event: EventListeners, |
505 | } |
506 | |
507 | impl_from_user_facing_event_for_interface_event_enum!( |
508 | EventListenerDeregisteredEvent, |
509 | EventListenerEvents, |
510 | EventListenerEvents::Deregistered |
511 | ); |
512 | impl_from_user_facing_type_for_event_enum!(EventListenerDeregisteredEvent, Event::Listener); |
513 | impl_try_from_event_for_user_facing_type!( |
514 | EventListenerDeregisteredEvent, |
515 | EventListenerEvents::Deregistered, |
516 | Event::Listener |
517 | ); |
518 | event_test_cases!(EventListenerDeregisteredEvent); |
519 | impl GenericEvent<'_> for EventListenerDeregisteredEvent { |
520 | const REGISTRY_EVENT_STRING: &'static str = "Registry:EventListenerDeregistered" ; |
521 | const MATCH_RULE_STRING: &'static str = |
522 | "type='signal',interface='org.a11y.atspi.Registry',member='EventListenerDeregistered'" ; |
523 | const DBUS_MEMBER: &'static str = "EventListenerDeregistered" ; |
524 | const DBUS_INTERFACE: &'static str = "org.a11y.atspi.Registry" ; |
525 | |
526 | type Body = EventListeners; |
527 | |
528 | fn build(item: Accessible, body: Self::Body) -> Result<Self, AtspiError> { |
529 | Ok(Self { item, deregistered_event: body }) |
530 | } |
531 | fn sender(&self) -> String { |
532 | self.item.name.clone() |
533 | } |
534 | fn path(&self) -> ObjectPath<'_> { |
535 | self.item.path.clone().into() |
536 | } |
537 | fn body(&self) -> Self::Body { |
538 | self.deregistered_event.clone() |
539 | } |
540 | } |
541 | impl_from_dbus_message!(EventListenerDeregisteredEvent); |
542 | impl_to_dbus_message!(EventListenerDeregisteredEvent); |
543 | |
544 | /// An event that is emitted by the regostry daemon to signal that an event has been registered to listen for. |
545 | #[derive (Debug, Clone, PartialEq, Serialize, Deserialize, Default, Eq, Hash)] |
546 | pub struct EventListenerRegisteredEvent { |
547 | /// The [`Accessible`] the event applies to. |
548 | pub item: Accessible, |
549 | /// A list of events that have been registered via the registry interface. |
550 | /// See `atspi-connection`. |
551 | pub registered_event: EventListeners, |
552 | } |
553 | |
554 | impl_from_user_facing_event_for_interface_event_enum!( |
555 | EventListenerRegisteredEvent, |
556 | EventListenerEvents, |
557 | EventListenerEvents::Registered |
558 | ); |
559 | impl_from_user_facing_type_for_event_enum!(EventListenerRegisteredEvent, Event::Listener); |
560 | impl_try_from_event_for_user_facing_type!( |
561 | EventListenerRegisteredEvent, |
562 | EventListenerEvents::Registered, |
563 | Event::Listener |
564 | ); |
565 | event_test_cases!(EventListenerRegisteredEvent); |
566 | impl GenericEvent<'_> for EventListenerRegisteredEvent { |
567 | const REGISTRY_EVENT_STRING: &'static str = "Registry:EventListenerRegistered" ; |
568 | const MATCH_RULE_STRING: &'static str = |
569 | "type='signal',interface='org.a11y.atspi.Registry',member='EventListenerRegistered'" ; |
570 | const DBUS_MEMBER: &'static str = "EventListenerRegistered" ; |
571 | const DBUS_INTERFACE: &'static str = "org.a11y.atspi.Registry" ; |
572 | |
573 | type Body = EventListeners; |
574 | |
575 | fn build(item: Accessible, body: Self::Body) -> Result<Self, AtspiError> { |
576 | Ok(Self { item, registered_event: body }) |
577 | } |
578 | fn sender(&self) -> String { |
579 | self.item.name.clone() |
580 | } |
581 | fn path(&self) -> ObjectPath<'_> { |
582 | self.item.path.clone().into() |
583 | } |
584 | fn body(&self) -> Self::Body { |
585 | self.registered_event.clone() |
586 | } |
587 | } |
588 | impl_from_dbus_message!(EventListenerRegisteredEvent); |
589 | impl_to_dbus_message!(EventListenerRegisteredEvent); |
590 | |
591 | /// An event that is emitted when the registry daemon has started. |
592 | #[derive (Debug, Clone, Serialize, Deserialize, PartialEq, Default, Eq, Hash)] |
593 | pub struct AvailableEvent { |
594 | /// The [`Accessible`] the event applies to. |
595 | pub item: Accessible, |
596 | pub socket: Accessible, |
597 | } |
598 | impl From<AvailableEvent> for Event { |
599 | fn from(ev: AvailableEvent) -> Event { |
600 | Event::Available(ev) |
601 | } |
602 | } |
603 | impl TryFrom<Event> for AvailableEvent { |
604 | type Error = AtspiError; |
605 | fn try_from(generic_event: Event) -> Result<AvailableEvent, Self::Error> { |
606 | if let Event::Available(specific_event: AvailableEvent) = generic_event { |
607 | Ok(specific_event) |
608 | } else { |
609 | Err(AtspiError::Conversion("Invalid type" )) |
610 | } |
611 | } |
612 | } |
613 | event_test_cases!(AvailableEvent); |
614 | impl GenericEvent<'_> for AvailableEvent { |
615 | const REGISTRY_EVENT_STRING: &'static str = "Socket:Available" ; |
616 | const MATCH_RULE_STRING: &'static str = |
617 | "type='signal',interface='org.a11y.atspi.Socket',member='Available'" ; |
618 | const DBUS_MEMBER: &'static str = "Available" ; |
619 | const DBUS_INTERFACE: &'static str = "org.a11y.atspi.Socket" ; |
620 | |
621 | type Body = Accessible; |
622 | |
623 | fn build(item: Accessible, body: Self::Body) -> Result<Self, AtspiError> { |
624 | Ok(Self { item, socket: body }) |
625 | } |
626 | fn sender(&self) -> String { |
627 | self.item.name.clone() |
628 | } |
629 | fn path(&self) -> ObjectPath<'_> { |
630 | self.item.path.clone().into() |
631 | } |
632 | fn body(&self) -> Self::Body { |
633 | self.socket.clone() |
634 | } |
635 | } |
636 | impl_from_dbus_message!(AvailableEvent); |
637 | impl_to_dbus_message!(AvailableEvent); |
638 | |
639 | #[cfg (feature = "zbus" )] |
640 | impl TryFrom<&zbus::Message> for Event { |
641 | type Error = AtspiError; |
642 | |
643 | fn try_from(msg: &zbus::Message) -> Result<Event, AtspiError> { |
644 | let body_signature = msg.body_signature()?; |
645 | let body_signature = body_signature.as_str(); |
646 | let signal_member = msg.member().ok_or(AtspiError::MissingMember)?; |
647 | let member_str = signal_member.as_str(); |
648 | let Some(interface) = msg.interface() else { |
649 | return Err(AtspiError::MissingInterface); |
650 | }; |
651 | |
652 | // As we are matching against `body_signature()`, which yields the marshalled D-Bus signatures, |
653 | // we do not expect outer parentheses. |
654 | // However, `Cache` signals are often emitted with an outer parentheses, so we also try to |
655 | // match against the same signature, but with outer parentheses. |
656 | match (interface.as_str(), member_str, body_signature) { |
657 | ("org.a11y.atspi.Socket" , "Available" , "so" ) => { |
658 | Ok(AvailableEvent::try_from(msg)?.into()) |
659 | } |
660 | ("org.a11y.atspi.Event.Object" , _, "siiva{sv}" | "siiv(so)" ) => { |
661 | Ok(Event::Object(ObjectEvents::try_from(msg)?)) |
662 | } |
663 | ("org.a11y.atspi.Event.Document" , _, "siiva{sv}" | "siiv(so)" ) => { |
664 | Ok(Event::Document(DocumentEvents::try_from(msg)?)) |
665 | } |
666 | ("org.a11y.atspi.Event.Window" , _, "siiva{sv}" | "siiv(so)" ) => { |
667 | Ok(Event::Window(WindowEvents::try_from(msg)?)) |
668 | } |
669 | ("org.a11y.atspi.Event.Terminal" , _, "siiva{sv}" | "siiv(so)" ) => { |
670 | Ok(Event::Terminal(TerminalEvents::try_from(msg)?)) |
671 | } |
672 | ("org.a11y.atspi.Event.Mouse" , _, "siiva{sv}" | "siiv(so)" ) => { |
673 | Ok(Event::Mouse(MouseEvents::try_from(msg)?)) |
674 | } |
675 | ("org.a11y.atspi.Event.Focus" , _, "siiva{sv}" | "siiv(so)" ) => { |
676 | Ok(Event::Focus(FocusEvents::try_from(msg)?)) |
677 | } |
678 | ("org.a11y.atspi.Event.Keyboard" , _, "siiva{sv}" | "siiv(so)" ) => { |
679 | Ok(Event::Keyboard(KeyboardEvents::try_from(msg)?)) |
680 | } |
681 | ("org.a11y.atspi.Registry" , "EventListenerRegistered" , "ss" ) => { |
682 | Ok(EventListenerRegisteredEvent::try_from(msg)?.into()) |
683 | } |
684 | ("org.a11y.atspi.Registry" , "EventListenerDeregistered" , "ss" ) => { |
685 | Ok(EventListenerDeregisteredEvent::try_from(msg)?.into()) |
686 | } |
687 | ( |
688 | "org.a11y.atspi.Cache" , |
689 | "AddAccessible" , |
690 | "(so)(so)(so)iiassusau" | "((so)(so)(so)iiassusau)" , |
691 | ) => Ok(AddAccessibleEvent::try_from(msg)?.into()), |
692 | ( |
693 | "org.a11y.atspi.Cache" , |
694 | "AddAccessible" , |
695 | "(so)(so)(so)a(so)assusau" | "((so)(so)(so)a(so)assusau)" , |
696 | ) => Ok(LegacyAddAccessibleEvent::try_from(msg)?.into()), |
697 | ("org.a11y.atspi.Cache" , "RemoveAccessible" , "so" | "(so)" ) => { |
698 | Ok(RemoveAccessibleEvent::try_from(msg)?.into()) |
699 | } |
700 | (_iface, _method, sig) => Err(AtspiError::UnknownBusSignature(sig.to_string())), |
701 | } |
702 | } |
703 | } |
704 | |
705 | /// Shared behavior of bus `Signal` events. |
706 | pub trait GenericEvent<'a> { |
707 | /// The `DBus` member for the event. |
708 | /// For example, for an [`object::TextChangedEvent`] this should be `"TextChanged"` |
709 | const DBUS_MEMBER: &'static str; |
710 | /// The `DBus` interface name for this event. |
711 | /// For example, for any event within [`object`], this should be "org.a11y.atspi.Event.Object". |
712 | const DBUS_INTERFACE: &'static str; |
713 | /// A static match rule string for `DBus`. |
714 | /// This should usually be a string that looks like this: `"type='signal',interface='org.a11y.atspi.Event.Object',member='PropertyChange'"`; |
715 | /// This should be deprecated in favour of composing the string from [`Self::DBUS_MEMBER`] and [`Self::DBUS_INTERFACE`]. |
716 | const MATCH_RULE_STRING: &'static str; |
717 | /// A registry event string for registering for event receiving via the `RegistryProxy`. |
718 | /// This should be deprecated in favour of composing the string from [`Self::DBUS_MEMBER`] and [`Self::DBUS_INTERFACE`]. |
719 | const REGISTRY_EVENT_STRING: &'static str; |
720 | |
721 | /// What is the body type of this event. |
722 | type Body: Type + Serialize + Deserialize<'a>; |
723 | |
724 | /// Build the event from the object pair (Accessible and the Body). |
725 | /// |
726 | /// # Errors |
727 | /// |
728 | /// When the body type, which is what the raw message looks like over `DBus`, does not match the type that is expected for the given event. |
729 | /// It is not possible for this to error on most events, but on events whose raw message [`Self::Body`] type contains a [`enum@zvariant::Value`], you may get errors when constructing the structure. |
730 | fn build(item: Accessible, body: Self::Body) -> Result<Self, AtspiError> |
731 | where |
732 | Self: Sized; |
733 | |
734 | /// Path of the signalling object. |
735 | fn path(&self) -> ObjectPath<'_>; |
736 | |
737 | /// Sender of the signal. |
738 | /// |
739 | /// ### Errors |
740 | /// - when deserializing the header failed, or |
741 | /// - When `zbus::get_field!` finds that 'sender' is an invalid field. |
742 | fn sender(&self) -> String; |
743 | |
744 | /// The body of the object. |
745 | fn body(&self) -> Self::Body; |
746 | } |
747 | |
748 | /// A specific trait *only* to define match rules. |
749 | pub trait HasMatchRule { |
750 | /// A static match rule string for `DBus`. |
751 | /// This should usually be a string that looks like this: `"type='signal',interface='org.a11y.atspi.Event.Object',member='PropertyChange'"`; |
752 | /// This should be deprecated in favour of composing the string from [`GenericEvent::DBUS_MEMBER`] and [`GenericEvent::DBUS_INTERFACE`]. |
753 | const MATCH_RULE_STRING: &'static str; |
754 | } |
755 | |
756 | /// A specific trait *only* to define registry event matches. |
757 | pub trait HasRegistryEventString { |
758 | /// A registry event string for registering for event receiving via the `RegistryProxy`. |
759 | /// This should be deprecated in favour of composing the string from [`GenericEvent::DBUS_MEMBER`] and [`GenericEvent::DBUS_INTERFACE`]. |
760 | const REGISTRY_EVENT_STRING: &'static str; |
761 | } |
762 | |
763 | #[cfg (test)] |
764 | mod tests { |
765 | use super::{ |
766 | signatures_are_eq, EventBodyOwned, EventBodyQT, ATSPI_EVENT_SIGNATURE, QSPI_EVENT_SIGNATURE, |
767 | }; |
768 | use std::collections::HashMap; |
769 | use zvariant::{ObjectPath, Signature, Type}; |
770 | |
771 | #[test ] |
772 | fn check_event_body_qt_signature() { |
773 | assert_eq_signatures!(&<EventBodyQT as Type>::signature(), &QSPI_EVENT_SIGNATURE); |
774 | } |
775 | |
776 | #[test ] |
777 | fn check_event_body_signature() { |
778 | assert_eq_signatures!(&<EventBodyOwned as Type>::signature(), &ATSPI_EVENT_SIGNATURE); |
779 | } |
780 | |
781 | #[test ] |
782 | fn test_event_body_qt_to_event_body_owned_conversion() { |
783 | let event_body: EventBodyOwned = EventBodyQT::default().into(); |
784 | |
785 | let accessible = crate::Accessible::default(); |
786 | let name = accessible.name; |
787 | let path = accessible.path; |
788 | let props = HashMap::from([(name, ObjectPath::try_from(path).unwrap().into())]); |
789 | assert_eq!(event_body.properties, props); |
790 | } |
791 | |
792 | // `assert_eq_signatures!` and `signatures_are_eq` are helpers to deal with the difference |
793 | // in `Signatures` as consequence of marshalling. While `zvariant` is very lenient with respect |
794 | // to outer parentheses, these helpers only take one marshalling step into account. |
795 | #[test ] |
796 | fn test_signatures_are_equal_macro_and_fn() { |
797 | let with_parentheses = &Signature::from_static_str_unchecked("(ii)" ); |
798 | let without_parentheses = &Signature::from_static_str_unchecked("ii" ); |
799 | assert_eq_signatures!(with_parentheses, without_parentheses); |
800 | assert!(signatures_are_eq(with_parentheses, without_parentheses)); |
801 | // test against themselves |
802 | assert!(signatures_are_eq(with_parentheses, with_parentheses)); |
803 | assert!(signatures_are_eq(without_parentheses, without_parentheses)); |
804 | assert!(signatures_are_eq(with_parentheses, with_parentheses)); |
805 | assert!(signatures_are_eq(without_parentheses, without_parentheses)); |
806 | let with_parentheses = &Signature::from_static_str_unchecked("(ii)(ii)" ); |
807 | let without_parentheses = &Signature::from_static_str_unchecked("((ii)(ii))" ); |
808 | assert_eq_signatures!(with_parentheses, without_parentheses); |
809 | assert!(signatures_are_eq(with_parentheses, without_parentheses)); |
810 | // test against themselves |
811 | assert!(signatures_are_eq(with_parentheses, with_parentheses)); |
812 | assert!(signatures_are_eq(without_parentheses, without_parentheses)); |
813 | assert_eq_signatures!(with_parentheses, with_parentheses); |
814 | assert_eq_signatures!(without_parentheses, without_parentheses); |
815 | // test false cases with unbalanced parentheses |
816 | let with_parentheses = &Signature::from_static_str_unchecked("(ii)(ii)" ); |
817 | let without_parentheses = &Signature::from_static_str_unchecked("((ii)(ii)" ); |
818 | assert!(!signatures_are_eq(with_parentheses, without_parentheses)); |
819 | // test case with more than one extra outer parentheses |
820 | let with_parentheses = &Signature::from_static_str_unchecked("((ii)(ii))" ); |
821 | let without_parentheses = &Signature::from_static_str_unchecked("((((ii)(ii))))" ); |
822 | assert!(!signatures_are_eq(with_parentheses, without_parentheses)); |
823 | } |
824 | } |
825 | |