1use crate::arg;
2use crate::{Message, MessageType};
3use crate::message::MatchRule;
4use crate::strings::{BusName, Path, Interface, Member};
5
6/// Helper methods for structs representing a Signal
7///
8/// # Example
9///
10/// Listen to InterfacesRemoved signal from org.bluez.obex.
11///
12/// ```rust,no_run
13/// use dbus::blocking::Connection;
14/// use dbus::message::SignalArgs;
15/// use dbus::blocking::stdintf::org_freedesktop_dbus::ObjectManagerInterfacesRemoved as IR;
16/// use std::time::Duration;
17///
18/// let c = Connection::new_session().unwrap();
19/// // Add a match for this signal
20/// let mr = IR::match_rule(Some(&"org.bluez.obex".into()), None).static_clone();
21/// c.add_match(mr, |ir: IR, _, _| {
22/// println!("Interfaces {:?} have been removed from bluez on path {}.", ir.interfaces, ir.object);
23/// true
24/// });
25///
26/// // Wait for the signal to arrive.
27/// loop { c.process(Duration::from_millis(1000)).unwrap(); }
28/// ```
29
30pub trait SignalArgs {
31 /// D-Bus name of signal
32 const NAME: &'static str;
33
34 /// D-Bus name of interface this signal belongs to
35 const INTERFACE: &'static str;
36
37 /// Returns a message that emits the signal.
38 fn to_emit_message(&self, path: &Path) -> Message where Self: arg::AppendAll {
39 let mut m = Message::signal(path, &Interface::from(Self::INTERFACE), &Member::from(Self::NAME));
40 arg::AppendAll::append(self, &mut arg::IterAppend::new(&mut m));
41 m
42 }
43
44 /// If the message is a signal of the correct type, return its arguments, otherwise return None.
45 ///
46 /// This does not check sender and path of the message, which is likely relevant to you as well.
47 #[allow(clippy::if_same_then_else)]
48 fn from_message(m: &Message) -> Option<Self> where Self: Sized + arg::ReadAll {
49 if m.msg_type() != MessageType::Signal { None }
50 else if m.interface().as_ref().map(|x| &**x) != Some(Self::INTERFACE) { None }
51 else if m.member().as_ref().map(|x| &**x) != Some(Self::NAME) { None }
52 else {
53 arg::ReadAll::read(&mut m.iter_init()).ok()
54 }
55 }
56
57 /// Returns a match rule matching this signal.
58 ///
59 /// If sender and/or path is None, matches all senders and/or paths.
60 fn match_rule<'a>(sender: Option<&'a BusName>, path: Option<&'a Path>) -> MatchRule<'a> {
61 let mut m: MatchRule = Default::default();
62 m.sender = sender.cloned();
63 m.path = path.cloned();
64 m.msg_type = Some(MessageType::Signal);
65 m.interface = Some(Self::INTERFACE.into());
66 m.member = Some(Self::NAME.into());
67 m
68 }
69
70
71 /// Returns a string that can be sent to `Connection::add_match`.
72 ///
73 /// If sender and/or path is None, matches all senders and/or paths.
74 fn match_str(sender: Option<&BusName>, path: Option<&Path>) -> String {
75 Self::match_rule(sender, path).match_str()
76 }
77}
78
79#[test]
80fn intf_removed() {
81 use crate::blocking::LocalConnection;
82 use crate::blocking::stdintf::org_freedesktop_dbus::ObjectManagerInterfacesRemoved as IR;
83 use std::{time::Duration, cell::Cell, rc::Rc};
84 let c = LocalConnection::new_session().unwrap();
85
86 let mr = IR::match_rule(Some(&c.unique_name().into()), Some(&"/hello".into())).static_clone();
87 println!("Match: {:?}", mr);
88
89 let ir = IR { object: "/hello".into(), interfaces: vec!("ABC.DEF".into(), "GHI.JKL".into()) };
90 let ir_msg = ir.to_emit_message(&"/hello".into());
91 let done = Rc::new(Cell::new(false));
92 let done2 = done.clone();
93
94 c.add_match(mr, move |ir2: IR, _, _| {
95 assert_eq!(ir2.object, ir.object);
96 assert_eq!(ir2.interfaces, ir.interfaces);
97 done2.set(true);
98 false
99 }).unwrap();
100 use crate::channel::Sender;
101 c.send(ir_msg).expect("Failed to send message");
102 while !done.get() { c.process(Duration::from_millis(1000)).unwrap(); }
103}
104