atspi_common/events/
mod.rs

1pub mod document;
2pub mod focus;
3pub mod keyboard;
4pub mod mouse;
5pub mod object;
6pub mod terminal;
7pub 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
20pub const ATSPI_EVENT_SIGNATURE: Signature<'_> =
21	Signature::from_static_str_unchecked("(siiva{sv})");
22pub const QSPI_EVENT_SIGNATURE: Signature<'_> = Signature::from_static_str_unchecked("(siiv(so))");
23pub const EVENT_LISTENER_SIGNATURE: Signature<'_> = Signature::from_static_str_unchecked("(ss)");
24pub const CACHE_ADD_SIGNATURE: Signature<'_> =
25	Signature::from_static_str_unchecked("((so)(so)(so)iiassusau)");
26
27use std::collections::HashMap;
28
29use serde::{Deserialize, Serialize};
30#[cfg(feature = "zbus")]
31use zbus::{MessageField, MessageFieldCode};
32use zbus_names::{OwnedUniqueName, UniqueName};
33use zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Signature, Type, Value};
34
35use 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]
47pub 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)]
75pub 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
94impl<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)]
103pub 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
120impl 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)]
136pub 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
153impl From<EventBodyQT> for EventBodyOwned {
154	fn from(body: EventBodyQT) -> Self {
155		let accessible = Accessible { name: body.properties.name, path: body.properties.path };
156		let mut props = HashMap::new();
157		props.insert(accessible.name, 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
168impl 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]
185pub 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
208impl HasMatchRule for CacheEvents {
209	const MATCH_RULE_STRING: &'static str = "type='signal',interface='org.a11y.atspi.Event.Cache'";
210}
211
212impl HasRegistryEventString for CacheEvents {
213	const REGISTRY_EVENT_STRING: &'static str = "Cache";
214}
215
216impl HasMatchRule for EventListenerEvents {
217	const MATCH_RULE_STRING: &'static str =
218		"type='signal',interface='org.a11y.atspi.Event.Registry'";
219}
220
221impl 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)]
230pub 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)]
242pub 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
249impl_from_user_facing_event_for_interface_event_enum!(
250	LegacyAddAccessibleEvent,
251	CacheEvents,
252	CacheEvents::LegacyAdd
253);
254impl_from_user_facing_type_for_event_enum!(LegacyAddAccessibleEvent, Event::Cache);
255impl_try_from_event_for_user_facing_type!(
256	LegacyAddAccessibleEvent,
257	CacheEvents::LegacyAdd,
258	Event::Cache
259);
260event_test_cases!(LegacyAddAccessibleEvent);
261impl_from_dbus_message!(LegacyAddAccessibleEvent);
262impl_to_dbus_message!(LegacyAddAccessibleEvent);
263
264impl 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)]
291pub 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
298impl_from_user_facing_event_for_interface_event_enum!(
299	AddAccessibleEvent,
300	CacheEvents,
301	CacheEvents::Add
302);
303impl_from_user_facing_type_for_event_enum!(AddAccessibleEvent, Event::Cache);
304impl_try_from_event_for_user_facing_type!(AddAccessibleEvent, CacheEvents::Add, Event::Cache);
305event_test_cases!(AddAccessibleEvent);
306
307impl 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}
330impl<'a, T: GenericEvent<'a>> HasMatchRule for T {
331	const MATCH_RULE_STRING: &'static str = <T as GenericEvent>::MATCH_RULE_STRING;
332}
333impl<'a, T: GenericEvent<'a>> HasRegistryEventString for T {
334	const REGISTRY_EVENT_STRING: &'static str = <T as GenericEvent>::REGISTRY_EVENT_STRING;
335}
336impl_from_dbus_message!(AddAccessibleEvent);
337impl_to_dbus_message!(AddAccessibleEvent);
338
339/// `Cache::RemoveAccessible` signal event type.
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, Eq, Hash)]
341pub 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
349impl_from_user_facing_event_for_interface_event_enum!(
350	RemoveAccessibleEvent,
351	CacheEvents,
352	CacheEvents::Remove
353);
354impl_from_user_facing_type_for_event_enum!(RemoveAccessibleEvent, Event::Cache);
355impl_try_from_event_for_user_facing_type!(RemoveAccessibleEvent, CacheEvents::Remove, Event::Cache);
356event_test_cases!(RemoveAccessibleEvent);
357impl 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
380impl_from_dbus_message!(RemoveAccessibleEvent);
381impl_to_dbus_message!(RemoveAccessibleEvent);
382
383#[cfg(test)]
384pub 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)]
411pub 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")]
422impl TryFrom<&zbus::Message> for Accessible {
423	type Error = AtspiError;
424	fn try_from(message: &zbus::Message) -> Result<Self, Self::Error> {
425		let path = message.path().expect("returned path is either Some or panics");
426		let owned_path = OwnedObjectPath::try_from(path)?;
427		let fields = message.fields()?;
428		let sender = fields.get_field(MessageFieldCode::Sender);
429		let sender = sender
430			.expect("We get the sender field from a valid MessageFieldCode, so it should be there");
431
432		let MessageField::Sender(unique_name) = sender else {
433			return Err(AtspiError::Conversion("Unable to convert zbus::Message to Accessible"));
434		};
435		let name_string = unique_name.as_str().to_owned();
436
437		Ok(Accessible { name: name_string, path: owned_path })
438	}
439}
440
441#[cfg(feature = "zbus")]
442impl TryFrom<&zbus::Message> for EventBodyOwned {
443	type Error = AtspiError;
444
445	fn try_from(message: &zbus::Message) -> Result<Self, Self::Error> {
446		let 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)]
462pub struct EventListeners {
463	pub bus_name: OwnedUniqueName,
464	pub path: String,
465}
466impl 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]
475fn test_event_listener_default_no_panic() {
476	let el = 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]
482fn 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)]
489pub 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)]
499pub 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
507impl_from_user_facing_event_for_interface_event_enum!(
508	EventListenerDeregisteredEvent,
509	EventListenerEvents,
510	EventListenerEvents::Deregistered
511);
512impl_from_user_facing_type_for_event_enum!(EventListenerDeregisteredEvent, Event::Listener);
513impl_try_from_event_for_user_facing_type!(
514	EventListenerDeregisteredEvent,
515	EventListenerEvents::Deregistered,
516	Event::Listener
517);
518event_test_cases!(EventListenerDeregisteredEvent);
519impl 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}
541impl_from_dbus_message!(EventListenerDeregisteredEvent);
542impl_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)]
546pub 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
554impl_from_user_facing_event_for_interface_event_enum!(
555	EventListenerRegisteredEvent,
556	EventListenerEvents,
557	EventListenerEvents::Registered
558);
559impl_from_user_facing_type_for_event_enum!(EventListenerRegisteredEvent, Event::Listener);
560impl_try_from_event_for_user_facing_type!(
561	EventListenerRegisteredEvent,
562	EventListenerEvents::Registered,
563	Event::Listener
564);
565event_test_cases!(EventListenerRegisteredEvent);
566impl 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}
588impl_from_dbus_message!(EventListenerRegisteredEvent);
589impl_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)]
593pub struct AvailableEvent {
594	/// The [`Accessible`] the event applies to.
595	pub item: Accessible,
596	pub socket: Accessible,
597}
598impl From<AvailableEvent> for Event {
599	fn from(ev: AvailableEvent) -> Event {
600		Event::Available(ev)
601	}
602}
603impl 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) = generic_event {
607			Ok(specific_event)
608		} else {
609			Err(AtspiError::Conversion("Invalid type"))
610		}
611	}
612}
613event_test_cases!(AvailableEvent);
614impl 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}
636impl_from_dbus_message!(AvailableEvent);
637impl_to_dbus_message!(AvailableEvent);
638
639#[cfg(feature = "zbus")]
640impl 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.
706pub 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.
749pub 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.
757pub 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)]
764mod 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}