dbus_crossroads/
stdimpl.rs

1use crate::utils::Dbg;
2use std::sync::Mutex;
3use std::sync::Arc;
4use dbus::channel::Sender;
5use std::collections::HashMap;
6use crate::{IfaceToken, Crossroads, Context, MethodErr};
7use dbus::arg::{Variant, RefArg, Arg, Append, PropMap};
8use std::marker::PhantomData;
9use crate::ifacedesc::EMITS_CHANGED;
10
11fn introspect(cr: &Crossroads, path: &dbus::Path<'static>) -> String {
12    let mut children = cr.get_children(path, true);
13    let mut childstr = String::new();
14    children.sort_unstable();
15    for c in children {
16        childstr += &format!("  <node name=\"{}\"/>\n", c);
17    }
18    let (reg, ifaces) = cr.registry_and_ifaces(path);
19    let ifacestr = reg.introspect(ifaces);
20
21    let nodestr = format!(
22r##"<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
23 "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
24<node name="{}">
25{}{}</node>"##, path, ifacestr, childstr);
26    nodestr
27}
28
29pub fn introspectable(cr: &mut Crossroads) -> IfaceToken<()> {
30    cr.register("org.freedesktop.DBus.Introspectable", |b| {
31        b.method_with_cr("Introspect", (), ("xml_data",), |ctx, cr, _: ()| {
32            Ok((introspect(cr, ctx.path()),))
33        });
34    })
35}
36
37
38pub (crate) fn make_emits_message<V: dbus::arg::Arg + dbus::arg::Append>(prop_name: &str, emits_changed: &str, ctx: &Context, v: &V) -> Option<dbus::Message> {
39    let arr = [prop_name];
40    let (d, i) = match emits_changed {
41        "false" => return None,
42        "invalidates" => (None, &arr[..]),
43        "true" => (Some((arr[0], Variant(v))), &[][..]),
44        _ => panic!("Invalid value of EmitsChangedSignal: {:?}", emits_changed)
45    };
46
47    use dbus::message::SignalArgs;
48    use dbus::blocking::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged as PPC;
49    let s: &str = ctx.message().read1().unwrap();
50    Some(dbus::Message::signal(ctx.path(), &PPC::INTERFACE.into(), &PPC::NAME.into())
51        .append3(s, dbus::arg::Dict::new(d), i))
52}
53
54#[derive(Debug)]
55/// PropContext is a struct that provides helpful information inside a get/set property handler.
56///
57/// Like Context, but for get/set property handlers.
58pub struct PropContext {
59    path: dbus::Path<'static>,
60    interface: dbus::strings::Interface<'static>,
61    name: String,
62    context: Option<Context>,
63
64    iface_token: usize,
65    emits_changed: Option<&'static str>,
66    get_all: Option<Arc<Mutex<PropAllCtx>>>,
67}
68
69impl PropContext {
70    /// The current object path.
71    pub fn path(&self) -> &dbus::Path<'static> { &self.path }
72
73    /// The current interface name.
74    pub fn interface(&self) -> &dbus::strings::Interface<'static> { &self.interface }
75
76    /// The current property name.
77    pub fn name(&self) -> &str { &self.name }
78
79    /// The message, if any, that caused this method to be called.
80    pub fn message(&self) -> Option<&dbus::Message> { self.context.as_ref().map(|ctx| ctx.message()) }
81
82    /// Set a reply to message (use in async context only)
83    ///
84    /// Returns PhantomData just to aid the type system
85    pub fn reply<A: Arg + RefArg + Send + Append + 'static>(&mut self, reply: Result<A, MethodErr>) -> PhantomData<A> {
86        if let Some(ec) = &self.emits_changed {
87            let mut emit_msg = None;
88            if let Ok(v) = &reply {
89                if let Some(ctx) = &self.context {
90                    emit_msg = make_emits_message(&self.name, ec, &ctx, v);
91                }
92            }
93            self.reply_noemit(reply.map(|_| ()));
94            emit_msg.map(|emit_msg| self.context.as_mut().map(|ctx| { ctx.push_msg(emit_msg) }));
95        } else {
96            if let Some(ga) = &self.get_all {
97                ga.lock().unwrap().add_reply(self.name.clone(), reply.ok().map(|a| Box::new(a) as Box<(dyn RefArg + Send)>));
98            } else {
99                self.context.as_mut().map(|ctx| ctx.reply(reply.map(|a| (Variant(a),))));
100            }
101        }
102        PhantomData
103    }
104
105    /// Set a reply to a "set property" message (use in async context only)
106    ///
107    /// This can be used when the property does not send a "EmitsChanged" signal.
108    pub fn reply_noemit(&mut self, reply: Result<(), MethodErr>) {
109        debug_assert!(self.emits_changed.is_some());
110        self.context.as_mut().map(|ctx| ctx.reply(reply));
111    }
112
113    pub (crate) fn set_send_on_drop(&mut self, value: Arc<dyn Sender + Send + Sync>) {
114        if let Some(get_all) = &self.get_all {
115            let mut pactx = get_all.lock().unwrap();
116            if let Some(ref mut propctx) = pactx.propctx {
117                propctx.context.as_mut().map(|ctx| ctx.set_send_on_drop(value));
118                return;
119            }
120        }
121        self.context.as_mut().map(|ctx| ctx.set_send_on_drop(value));
122    }
123
124    fn new(cr: &Crossroads, path: dbus::Path<'static>, interface: String, name: String) -> Result<Self, MethodErr> {
125        let interface = dbus::strings::Interface::new(interface).map_err(|s| MethodErr::no_interface(&s))?;
126        let iface_token = cr.find_iface_token(&path, Some(&interface))?;
127        Ok(PropContext {
128            path,
129            iface_token,
130            interface,
131            name,
132            get_all: None,
133            context: None,
134            emits_changed: None
135        })
136    }
137
138    fn call_prop(mut self, cr: &mut Crossroads, is_set: bool) -> Option<Self> {
139        let token = self.iface_token;
140        let name = self.name.clone();
141        let mut cb = match self.check(|_| {
142            cr.registry().take_prop(token, &name, is_set)
143        }) {
144            Ok(cb) => cb,
145            Err(_) => return Some(self)
146        };
147        let octx = cb(self, cr);
148        cr.registry().give_prop(token, &name, cb, is_set);
149        // dbg!(&name, octx.is_some());
150        octx
151    }
152
153    fn call_all_props<F: FnOnce(&mut PropAllCtx) + Send + 'static>(self, cr: &mut Crossroads, f: F) -> Option<Self> {
154        let token = self.iface_token;
155        let pactx = Arc::new(Mutex::new(PropAllCtx {
156            remaining: 0,
157            answers: HashMap::new(),
158            donefn: Some(Dbg(Box::new(f))),
159            propctx: Some(self),
160        }));
161        let mut pb = pactx.lock().unwrap();
162        let pctxs: Vec<_> = cr.registry().prop_names_readable(token).map(|prop_name| {
163            pb.remaining += 1;
164            let parent = pb.propctx.as_ref().unwrap();
165            PropContext {
166                path: parent.path.clone(),
167                iface_token: parent.iface_token,
168                interface: parent.interface().clone(),
169                name: prop_name.into(),
170                get_all: Some(pactx.clone()),
171                context: None,
172                emits_changed: None
173            }
174        }).collect();
175        drop(pb);
176        for pctx in pctxs {
177            pctx.call_prop(cr, false);
178        }
179        let mut temp = pactx.lock().unwrap();
180        // dbg!(&temp);
181        if temp.check_finished() { Some(temp.propctx.take().unwrap()) } else { None }
182    }
183
184    /// Convenience method that sets an error reply if the closure returns an error.
185    pub fn check<R, F: FnOnce(Option<&mut Context>) -> Result<R, MethodErr>>(&mut self, f: F) -> Result<R, ()> {
186        match &mut self.context {
187            Some(ctx) => ctx.check(|ctx| f(Some(ctx))),
188            None => match f(None) {
189                Ok(r) => Ok(r),
190                Err(_) => { todo!() },
191            },
192        }
193    }
194
195    /// The underlying Context, if any.
196    ///
197    /// This gives you access to additional methods (e.g., push_msg and make_signal).
198    pub fn context_mut(&mut self) -> Option<&mut Context> {
199        self.context.as_mut()
200    }
201}
202
203#[derive(Debug)]
204struct PropAllCtx {
205    remaining: usize,
206    answers: PropMap,
207    donefn: Option<Dbg<Box<dyn FnOnce(&mut Self) + Send + 'static>>>,
208    propctx: Option<PropContext>,
209}
210
211impl PropAllCtx {
212    fn check_finished(&mut self) -> bool {
213        if self.remaining > 0 { return false; }
214        if let Some(donefn) = self.donefn.take() {
215            (donefn.0)(self);
216        }
217        true
218    }
219
220    fn add_reply(&mut self, prop_name: String, prop_value: Option<Box<dyn RefArg + Send>>) {
221        if let Some(v) = prop_value {
222            self.answers.insert(prop_name, Variant(v));
223        }
224        self.remaining -= 1;
225        self.check_finished();
226    }
227}
228
229fn get(mut ctx: Context, cr: &mut Crossroads, (interface_name, property_name): (String, String)) -> Option<Context> {
230    let mut propctx = match ctx.check(|ctx| { PropContext::new(cr, ctx.path().clone(), interface_name, property_name)}) {
231        Ok(p) => p,
232        Err(_) => return Some(ctx),
233    };
234    propctx.context = Some(ctx);
235    propctx.call_prop(cr, false).map(|propctx| { propctx.context.unwrap() })
236}
237
238fn getall_all(ctx: Context, cr: &mut Crossroads) -> Option<Context> {
239    get_all_for_path(&ctx.path().clone(), cr, Some(ctx), move |ictx, octx| {
240        let props: HashMap<_, _> = ictx.ifaces.values().flatten().collect();
241        octx.as_mut().unwrap().do_reply(|msg| {
242            msg.append_all((props,));
243        });
244    })
245}
246
247fn getall(mut ctx: Context, cr: &mut Crossroads, (interface_name,): (String,)) -> Option<Context> {
248    if interface_name == "" {
249        return getall_all(ctx, cr);
250    }
251    let mut propctx = match ctx.check(|ctx| { PropContext::new(cr, ctx.path().clone(), interface_name, "".into())}) {
252        Ok(p) => p,
253        Err(_) => return Some(ctx),
254    };
255    propctx.context = Some(ctx);
256    propctx.call_all_props(cr, move |pactx| {
257        let pctx = pactx.propctx.as_mut().unwrap();
258        let answers = &pactx.answers;
259        pctx.context.as_mut().unwrap().do_reply(|msg| {
260            msg.append_all((answers,));
261        });
262    }).map(|propctx| { propctx.context.unwrap() })
263}
264
265fn set(mut ctx: Context, cr: &mut Crossroads, (interface_name, property_name, _value): (String, String, Variant<Box<dyn RefArg>>)) -> Option<Context> {
266    let mut propctx = match ctx.check(|ctx| { PropContext::new(cr, ctx.path().clone(), interface_name, property_name) }) {
267        Ok(p) => p,
268        Err(_) => return Some(ctx),
269    };
270    let ann = cr.registry()
271        .find_annotation(propctx.iface_token, EMITS_CHANGED, Some(&propctx.name));
272    propctx.emits_changed = match ann {
273        Some("const") => Some("const"),
274        Some("false") => Some("false"),
275        Some("invalidates") => Some("invalidates"),
276        _ => Some("true"),
277    };
278    propctx.context = Some(ctx);
279    propctx.call_prop(cr, true).map(|propctx| { propctx.context.unwrap() })
280}
281
282pub fn properties(cr: &mut Crossroads) -> IfaceToken<()> {
283    cr.register("org.freedesktop.DBus.Properties", |b| {
284        b.method_with_cr_custom::<_, (Variant<u8>,), _, _>("Get", ("interface_name", "property_name"), ("value",), get);
285        b.method_with_cr_custom::<_, (PropMap,), _, _>("GetAll", ("interface_name",), ("properties",), getall);
286        b.method_with_cr_custom::<_, (), _, _>("Set", ("interface_name", "property_name", "value"), (), set);
287        b.signal::<(String, PropMap, Vec<String>), _>("PropertiesChanged",
288            ("interface_name", "changed_properties", "invalidated_properties"));
289    })
290}
291
292#[derive(Debug, Default)]
293struct IfaceContext {
294    remaining: usize,
295    ifaces: IfacePropMap,
296    donefn: Option<Dbg<Box<dyn FnOnce(&mut IfaceContext, &mut Option<Context>) + Send + 'static>>>,
297}
298
299type IfacePropMap = HashMap<String, PropMap>;
300type PathPropMap = HashMap<dbus::Path<'static>, IfacePropMap>;
301
302fn get_all_for_path<F>(path: &dbus::Path<'static>, cr: &mut Crossroads, octx: Option<Context>, f: F) -> Option<Context>
303where F: FnOnce(&mut IfaceContext, &mut Option<Context>) + Send + 'static {
304    let (_reg, ifaces) = cr.registry_and_ifaces(&path);
305    let all: Vec<usize> = ifaces.into_iter().map(|token| *token).collect();
306    for_each_interface_with_properties(path, all, cr, octx, f)
307}
308
309fn for_each_interface_with_properties<F: FnOnce(&mut IfaceContext, &mut Option<Context>) + Send + 'static, I: IntoIterator<Item = usize>>
310(path: &dbus::Path<'static>, ifaces: I, cr: &mut Crossroads, mut octx: Option<Context>, f: F) -> Option<Context> {
311    let ictx: Arc<Mutex<IfaceContext>> = Default::default();
312    let (reg, _all_ifaces) = cr.registry_and_ifaces(&path);
313    let all: Vec<_> = ifaces.into_iter().filter_map(|token| {
314        let iface_name = reg.get_intf_name(token)?;
315        Some(PropContext {
316            context: None,
317            emits_changed: None,
318            get_all: None,
319            iface_token: token,
320            interface: iface_name.clone(),
321            path: path.clone(),
322            name: "".into(),
323        })
324    }).collect();
325
326    if all.len() == 0 {
327        f(&mut *ictx.lock().unwrap(), &mut octx);
328        return octx;
329    }
330
331    let mut ic = ictx.lock().unwrap();
332    ic.remaining = all.len();
333    ic.donefn = Some(Dbg(Box::new(f)));
334    drop(ic);
335    let amoctx = Arc::new(Mutex::new(octx));
336    for mut pctx in all.into_iter() {
337        let iclone = ictx.clone();
338        let amoclone = amoctx.clone();
339        pctx.context = amoctx.lock().unwrap().take();
340        pctx.call_all_props(cr, move |pactx| {
341            let mut ic = iclone.lock().unwrap();
342            let mut amoctx = amoclone.lock().unwrap();
343            if amoctx.is_none() {
344                *amoctx = pactx.propctx.as_mut().unwrap().context.take();
345            }
346            let answers = std::mem::replace(&mut pactx.answers, HashMap::new());
347            //dbg!(amoctx.is_some(), &answers);
348            ic.ifaces.insert(pactx.propctx.as_ref().unwrap().interface.to_string(), answers);
349            ic.remaining -= 1;
350            if ic.remaining == 0 {
351                let donefn = ic.donefn.take().unwrap().0;
352                (donefn)(&mut *ic, &mut amoctx)
353            }
354        }).map(|mut pctx| {
355            let mut amoctx = amoctx.lock().unwrap();
356            if amoctx.is_none() {
357                *amoctx = pctx.context.take();
358            }
359        });
360    }
361    let mut amoctx = amoctx.lock().unwrap();
362    amoctx.take()
363}
364
365//
366fn get_managed_objects(mut ctx: Context, cr: &mut Crossroads, _: ()) -> Option<Context> {
367    // HashMap<dbus::Path<'static>, IfacePropMap>
368    let parent = ctx.path();
369    let children: Vec<dbus::Path<'static>> =
370        cr.get_children(ctx.path(), false).into_iter().map(|child_path| {
371            let mut x = String::from(&**parent);
372            if !x.ends_with('/') {
373                x.push_str("/");
374            }
375            x.push_str(child_path);
376            dbus::Path::from(x).into_static()
377        }).collect();
378
379    if children.len() == 0 {
380        ctx.do_reply(|msg| {
381            let x: PathPropMap = Default::default();
382            msg.append_all((x,));
383        });
384        return Some(ctx);
385    }
386
387    #[derive(Debug)]
388    struct Temp {
389        remaining: usize,
390        temp_map: PathPropMap,
391        octx: Option<Context>,
392    }
393    let r = Arc::new(Mutex::new(Temp {
394        remaining: children.len(),
395        temp_map: HashMap::new(),
396        octx: Some(ctx),
397    }));
398    for subpath in children {
399        let rclone = r.clone();
400        let subpath_clone = subpath.clone();
401        let octx = r.lock().unwrap().octx.take();
402        get_all_for_path(&subpath, cr, octx, move |ictx, octx| {
403            let mut rr = rclone.lock().unwrap();
404            if rr.octx.is_none() { rr.octx = octx.take(); }
405            let ifaces = std::mem::replace(&mut ictx.ifaces, HashMap::new());
406            rr.temp_map.insert(subpath_clone, ifaces);
407            rr.remaining -= 1;
408            // dbg!(&rr);
409            if rr.remaining > 0 { return; }
410            let mut octx = rr.octx.take().unwrap();
411            octx.do_reply(|msg| {
412                msg.append_all((&rr.temp_map,));
413            });
414            rr.octx = Some(octx);
415        }).map(|octx| {
416            let mut rr = r.lock().unwrap();
417            if rr.octx.is_none() { rr.octx = Some(octx); }
418        });
419    }
420    let mut rr = r.lock().unwrap();
421    rr.octx.take()
422}
423
424pub fn object_manager(cr: &mut Crossroads) -> IfaceToken<()> {
425    cr.register("org.freedesktop.DBus.ObjectManager", |b| {
426        b.method_with_cr_custom::<(), (PathPropMap,), _, _>
427            ("GetManagedObjects", (), ("objpath_interfaces_and_properties",), get_managed_objects);
428        b.signal::<(dbus::Path<'static>, IfacePropMap), _>("InterfacesAdded",
429            ("object_path", "interfaces_and_properties"));
430        b.signal::<(dbus::Path<'static>, Vec<String>), _>("InterfacesRemoved",
431            ("object_path", "interfaces"));
432    })
433}
434
435fn object_manager_parents<F: FnMut(dbus::Path<'static>, &mut Crossroads)>(name: &dbus::Path<'static>, cr: &mut Crossroads, mut f: F) {
436    for idx in 0..name.len()-1 {
437        if name.as_bytes()[idx] != b'/' { continue; }
438        let parent = dbus::Path::from(&name[0..(if idx == 0 { idx + 1 } else {idx})]).into_static();
439        if !cr.has_interface(&parent, cr.object_manager::<()>()) { continue; }
440        f(parent, cr)
441    }
442}
443
444pub fn object_manager_path_added(sender: Arc<dyn Sender + Send + Sync>, name: &dbus::Path<'static>, cr: &mut Crossroads) {
445    object_manager_parents(name, cr, |parent, cr| {
446        let n = name.clone();
447        let s = sender.clone();
448        get_all_for_path(&name, cr, None, move |ictx, _| {
449            let x = dbus::blocking::stdintf::org_freedesktop_dbus::ObjectManagerInterfacesAdded {
450                object: n,
451                interfaces: std::mem::replace(&mut ictx.ifaces, HashMap::new()),
452            };
453            let _ = s.send(dbus::message::SignalArgs::to_emit_message(&x, &parent));
454        });
455    });
456}
457
458pub fn object_manager_path_removed(sender: Arc<dyn Sender + Send + Sync>, name: &dbus::Path<'static>, cr: &mut Crossroads) {
459    object_manager_parents(name, cr, |parent, cr| {
460        let (reg, ifaces) = cr.registry_and_ifaces(&name);
461
462        let x = dbus::blocking::stdintf::org_freedesktop_dbus::ObjectManagerInterfacesRemoved {
463            object: name.clone(),
464            interfaces: ifaces.into_iter()
465                .filter_map(|iface| reg.get_intf_name(*iface))
466                .map(|iface| String::from(&**iface))
467                .collect(),
468        };
469        let _ = sender.send(dbus::message::SignalArgs::to_emit_message(&x, &parent));
470    });
471}
472
473pub fn object_manager_interface_added(sender: Arc<dyn Sender + Send + Sync>, name: &dbus::Path<'static>, itoken: usize, cr: &mut Crossroads) {
474    object_manager_parents(name, cr, |parent, cr| {
475        let n = name.clone();
476        let s = sender.clone();
477
478        for_each_interface_with_properties(&name, vec![itoken], cr, None, move |ictx, _| {
479            let x = dbus::blocking::stdintf::org_freedesktop_dbus::ObjectManagerInterfacesAdded {
480                object: n,
481                interfaces: std::mem::replace(&mut ictx.ifaces, HashMap::new()),
482            };
483            let _ = s.send(dbus::message::SignalArgs::to_emit_message(&x, &parent));
484        });
485    });
486}
487
488pub fn object_manager_interface_removed(sender: Arc<dyn Sender + Send + Sync>, name: &dbus::Path<'static>, itoken: usize, cr: &mut Crossroads) {
489    object_manager_parents(name, cr, |parent, cr| {
490        let (reg, _ifaces) = cr.registry_and_ifaces(&name);
491
492        if let Some(iface) = reg.get_intf_name(itoken) {
493            let x = dbus::blocking::stdintf::org_freedesktop_dbus::ObjectManagerInterfacesRemoved {
494                object: name.clone(),
495                interfaces: vec![iface.to_string()],
496            };
497            let _ = sender.send(dbus::message::SignalArgs::to_emit_message(&x, &parent));
498        }
499    });
500}