1 //! Macros to make working with dbus-rs easier.
2 //!
3 //! This crate provides several macros to make it easier to project Rust types
4 //! and traits onto D-Bus.
5 extern crate proc_macro;
6
7 use quote::{format_ident, quote, ToTokens};
8
9 use std::fs::File;
10 use std::io::Write;
11 use std::path::Path;
12
13 use syn::parse::Parser;
14 use syn::punctuated::Punctuated;
15 use syn::token::Comma;
16 use syn::{Expr, FnArg, ImplItem, ItemImpl, ItemStruct, Meta, NestedMeta, Pat, ReturnType, Type};
17
18 use crate::proc_macro::TokenStream;
19
20 const OUTPUT_DEBUG: bool = false;
21
debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String)22 fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) {
23 if !OUTPUT_DEBUG {
24 return;
25 }
26
27 let filepath = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
28 .join(filename)
29 .to_str()
30 .unwrap()
31 .to_string();
32
33 let path = Path::new(&filepath);
34 let mut file = File::create(path).unwrap();
35 file.write_all(gen.to_string().as_bytes()).unwrap();
36 }
37
38 /// Marks a method to be projected to a D-Bus method and specifies the D-Bus method name.
39 ///
40 /// Example:
41 /// `#[dbus_method("GetAdapterFoo")]`
42 /// `#[dbus_method("RegisterFoo", DBusLog::Enable(DBusLogOptions::LogAll, DBusLogVerbosity::Info))`
43 ///
44 /// # Args
45 ///
46 /// `dbus_method_name`: String. The D-Bus method name.
47 /// `dbus_logging`: enum DBusLog, optional. Whether to enable logging and the log verbosity.
48 /// Note that log is disabled by default for outgoing dbus messages, but enabled
49 /// by default with verbose level for incoming messages.
50 #[proc_macro_attribute]
dbus_method(_attr: TokenStream, item: TokenStream) -> TokenStream51 pub fn dbus_method(_attr: TokenStream, item: TokenStream) -> TokenStream {
52 let ori_item: proc_macro2::TokenStream = item.clone().into();
53 let gen = quote! {
54 #[allow(unused_variables)]
55 #ori_item
56 };
57 gen.into()
58 }
59
60 /// Generates a function to export a Rust object to D-Bus. The result will provide an IFaceToken
61 /// that must then be registered to an object.
62 ///
63 /// Example:
64 /// `#[generate_dbus_exporter(export_foo_dbus_intf, "org.example.FooInterface")]`
65 /// `#[generate_dbus_exporter(export_foo_dbus_intf, "org.example.FooInterface", FooMixin, foo)]`
66 ///
67 /// This generates a method called `export_foo_dbus_intf` that will export a Rust object type into a
68 /// interface token for `org.example.FooInterface`. This interface must then be inserted to an
69 /// object in order to be exported.
70 ///
71 /// If the mixin parameter is provided, you must provide the mixin class when registering with
72 /// crossroads (and that's the one that should be Arc<Mutex<...>>.
73 ///
74 /// In order to use the interface via D-Bus calls, the Rust object needs to declare its methods with
75 /// the attribute #[dbus_method()].
76 ///
77 /// # Args
78 ///
79 /// `exporter`: Function name for outputted interface exporter.
80 /// `interface`: Name of the interface where this object should be exported.
81 /// `mixin_type`: The name of the Mixin struct. Mixins should be used when
82 /// exporting multiple interfaces and objects under a single object
83 /// path.
84 /// `mixin_name`: Name of this object in the mixin where it's implemented.
85 #[proc_macro_attribute]
generate_dbus_exporter(attr: TokenStream, item: TokenStream) -> TokenStream86 pub fn generate_dbus_exporter(attr: TokenStream, item: TokenStream) -> TokenStream {
87 let ori_item: proc_macro2::TokenStream = item.clone().into();
88
89 let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
90
91 let fn_ident = if let Expr::Path(p) = &args[0] {
92 p.path.get_ident().unwrap()
93 } else {
94 panic!("function name must be specified");
95 };
96
97 let dbus_iface_name = if let Expr::Lit(lit) = &args[1] {
98 lit
99 } else {
100 panic!("D-Bus interface name must be specified");
101 };
102
103 // Must provide both a mixin type and name.
104 let (mixin_type, mixin_name) = if args.len() > 3 {
105 match (&args[2], &args[3]) {
106 (Expr::Path(t), Expr::Path(n)) => (Some(t), Some(n)),
107 (_, _) => (None, None),
108 }
109 } else {
110 (None, None)
111 };
112
113 let ast: ItemImpl = syn::parse(item.clone()).unwrap();
114 let api_iface_ident = ast.trait_.unwrap().1.to_token_stream();
115
116 let mut register_methods = quote! {};
117
118 // If the object isn't expected to be part of a mixin, expect the object
119 // type to be Arc<Mutex<Box<T>>>. Otherwise, we accept any type T and depend
120 // on the field name lookup to throw an error.
121 let obj_type = match mixin_type {
122 None => quote! { std::sync::Arc<std::sync::Mutex<Box<T>>> },
123 Some(t) => quote! { Box<#t> },
124 };
125
126 for item in ast.items {
127 if let ImplItem::Method(method) = item {
128 if method.attrs.len() != 1 {
129 continue;
130 }
131
132 let attr = &method.attrs[0];
133 if !attr.path.get_ident().unwrap().to_string().eq("dbus_method") {
134 continue;
135 }
136
137 let meta_list = match attr.parse_meta().unwrap() {
138 Meta::List(meta_list) => meta_list,
139 _ => continue,
140 };
141
142 let dbus_method_name = meta_list.nested[0].clone();
143
144 // logging is default to verbose if not specified
145 let dbus_logging = if meta_list.nested.len() > 1 {
146 meta_list.nested[1].clone()
147 } else {
148 let token =
149 quote! { DBusLog::Enable(DBusLogOptions::LogAll, DBusLogVerbosity::Verbose) };
150 syn::parse2::<NestedMeta>(token).unwrap()
151 };
152
153 let method_name = method.sig.ident;
154
155 let mut arg_names = quote! {};
156 let mut method_args = quote! {};
157 let mut make_args = quote! {};
158 let mut dbus_input_vars = quote! {};
159 let mut dbus_input_types = quote! {};
160 let mut args_debug = quote! {};
161 let mut args_debug_format = String::new();
162
163 for input in method.sig.inputs {
164 if let FnArg::Typed(ref typed) = input {
165 let arg_type = &typed.ty;
166 if let Pat::Ident(pat_ident) = &*typed.pat {
167 let ident = pat_ident.ident.clone();
168 let mut dbus_input_ident = ident.to_string();
169 dbus_input_ident.push('_');
170 let dbus_input_arg = format_ident!("{}", dbus_input_ident);
171 let ident_string = ident.to_string();
172
173 arg_names = quote! {
174 #arg_names #ident_string,
175 };
176
177 method_args = quote! {
178 #method_args #ident,
179 };
180
181 dbus_input_vars = quote! {
182 #dbus_input_vars #dbus_input_arg,
183 };
184
185 dbus_input_types = quote! {
186 #dbus_input_types
187 <#arg_type as DBusArg>::DBusType,
188 };
189
190 make_args = quote! {
191 #make_args
192 let #ident = <#arg_type as DBusArg>::from_dbus(
193 #dbus_input_arg,
194 Some(conn_clone.clone()),
195 Some(ctx.message().sender().unwrap().into_static()),
196 Some(dc_watcher_clone.clone()),
197 );
198
199 if let Result::Err(e) = #ident {
200 return Err(dbus_crossroads::MethodErr::invalid_arg(
201 e.to_string().as_str()
202 ));
203 }
204
205 let #ident = #ident.unwrap();
206 };
207
208 args_debug = quote! {
209 #args_debug
210 <#arg_type as DBusArg>::log(&#ident),
211 };
212
213 if !args_debug_format.is_empty() {
214 args_debug_format.push_str(", ");
215 }
216 args_debug_format.push_str("|{}|");
217 }
218 }
219 }
220
221 let dbus_input_args = quote! {
222 (#dbus_input_vars): (#dbus_input_types)
223 };
224
225 let mut output_names = quote! {};
226 let mut output_type = quote! {};
227 let mut ret = quote! {Ok(())};
228 if let ReturnType::Type(_, t) = method.sig.output {
229 output_type = quote! {<#t as DBusArg>::DBusType,};
230 ret = quote! {Ok((<#t as DBusArg>::to_dbus(ret).unwrap(),))};
231 output_names = quote! { "out", };
232 }
233
234 let debug = quote! {
235 let args_formatted = format!(#args_debug_format, #args_debug);
236 DBusLog::log(#dbus_logging, "dbus in", #dbus_iface_name, #dbus_method_name, args_formatted.as_str());
237 };
238
239 let method_call = match mixin_name {
240 Some(name) => {
241 quote! {
242 let ret = obj.#name.lock().unwrap().#method_name(#method_args);
243 }
244 }
245 None => {
246 quote! {
247 let ret = obj.lock().unwrap().#method_name(#method_args);
248 }
249 }
250 };
251
252 register_methods = quote! {
253 #register_methods
254
255 let conn_clone = conn.clone();
256 let dc_watcher_clone = disconnect_watcher.clone();
257 let handle_method = move |ctx: &mut dbus_crossroads::Context,
258 obj: &mut #obj_type,
259 #dbus_input_args |
260 -> Result<(#output_type), dbus_crossroads::MethodErr> {
261 #make_args
262 #debug
263 #method_call
264 #ret
265 };
266 ibuilder.method(
267 #dbus_method_name,
268 (#arg_names),
269 (#output_names),
270 handle_method,
271 );
272 };
273 }
274 }
275
276 // If mixin is not given, we enforce the API trait is implemented when exporting.
277 let type_t = match mixin_type {
278 None => quote! { <T: 'static + #api_iface_ident + Send + ?Sized> },
279 Some(_) => quote! {},
280 };
281
282 let gen = quote! {
283 #ori_item
284
285 pub fn #fn_ident #type_t(
286 conn: std::sync::Arc<dbus::nonblock::SyncConnection>,
287 cr: &mut dbus_crossroads::Crossroads,
288 disconnect_watcher: std::sync::Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
289 ) -> dbus_crossroads::IfaceToken<#obj_type> {
290 cr.register(#dbus_iface_name, |ibuilder| {
291 #register_methods
292 })
293 }
294 };
295
296 debug_output_to_file(&gen, format!("out-{}.rs", fn_ident));
297
298 gen.into()
299 }
300
301 /// Generates a client implementation of a D-Bus interface.
302 ///
303 /// Example:
304 /// #[generate_dbus_interface_client]
305 ///
306 /// The impl containing #[dbus_method()] will contain a generated code to call the method via D-Bus.
307 ///
308 /// Example:
309 /// #[generate_dbus_interface_client(SomeRPC)]
310 ///
311 /// When the RPC wrapper struct name is specified, it also generates the more RPC-friendly struct:
312 /// * All methods are async, allowing clients to await (yield) without blocking. Even methods that
313 /// are sync at the server side requires clients to "wait" for the return.
314 /// * All method returns are wrapped with `Result`, allowing clients to detect D-Bus level errors in
315 /// addition to API-level errors.
316 #[proc_macro_attribute]
generate_dbus_interface_client(attr: TokenStream, item: TokenStream) -> TokenStream317 pub fn generate_dbus_interface_client(attr: TokenStream, item: TokenStream) -> TokenStream {
318 let rpc_struct_name = attr.to_string();
319
320 let ast: ItemImpl = syn::parse(item.clone()).unwrap();
321 let trait_path = ast.trait_.unwrap().1;
322 let struct_path = match *ast.self_ty {
323 Type::Path(path) => path,
324 _ => panic!("Struct path not available"),
325 };
326
327 // Generated methods
328 let mut methods = quote! {};
329
330 // Generated RPC-friendly methods (async and Result-wrapped).
331 let mut rpc_methods = quote! {};
332
333 // Iterate on every methods of a trait impl
334 for item in ast.items {
335 if let ImplItem::Method(method) = item {
336 // If the method is not marked with #[dbus_method], just copy the
337 // original method body.
338 if method.attrs.len() != 1 {
339 methods = quote! {
340 #methods
341
342 #method
343 };
344 continue;
345 }
346
347 let attr = &method.attrs[0];
348 if !attr.path.get_ident().unwrap().to_string().eq("dbus_method") {
349 continue;
350 }
351
352 let sig = &method.sig;
353
354 // For RPC-friendly method, copy the original signature but add public, async, and wrap
355 // the return with Result.
356 let mut rpc_sig = sig.clone();
357 rpc_sig.asyncness = Some(<syn::Token![async]>::default());
358 rpc_sig.output = match rpc_sig.output {
359 syn::ReturnType::Default => {
360 syn::parse(quote! {-> Result<(), dbus::Error>}.into()).unwrap()
361 }
362 syn::ReturnType::Type(_arrow, path) => {
363 syn::parse(quote! {-> Result<#path, dbus::Error>}.into()).unwrap()
364 }
365 };
366 let rpc_sig = quote! {
367 pub #rpc_sig
368 };
369
370 let dbus_method_name = if let Meta::List(meta_list) = attr.parse_meta().unwrap() {
371 Some(meta_list.nested[0].clone())
372 } else {
373 None
374 };
375
376 if dbus_method_name.is_none() {
377 continue;
378 }
379
380 let mut input_list = quote! {};
381
382 let mut object_conversions = quote! {};
383
384 // Iterate on every parameter of a method to build a tuple, e.g.
385 // `(param1, param2, param3)`
386 for input in &method.sig.inputs {
387 if let FnArg::Typed(ref typed) = input {
388 let arg_type = &typed.ty;
389 if let Pat::Ident(pat_ident) = &*typed.pat {
390 let ident = pat_ident.ident.clone();
391
392 let is_box = if let Type::Path(type_path) = &**arg_type {
393 type_path.path.segments[0].ident.to_string().eq("Box")
394 } else {
395 false
396 };
397
398 if is_box {
399 // A Box<dyn> parameter means this is an object that should be exported
400 // on D-Bus.
401 object_conversions = quote! {
402 #object_conversions
403 let #ident = {
404 let path = dbus::Path::new(#ident.get_object_id()).unwrap();
405 #ident.export_for_rpc();
406 path
407 };
408 };
409 } else {
410 // Convert every parameter to its corresponding type recognized by
411 // the D-Bus library.
412 object_conversions = quote! {
413 #object_conversions
414 let #ident = <#arg_type as DBusArg>::to_dbus(#ident).unwrap();
415 };
416 }
417 input_list = quote! {
418 #input_list
419 #ident,
420 };
421 }
422 }
423 }
424
425 let mut output_as_dbus_arg = quote! {};
426 if let ReturnType::Type(_, t) = &method.sig.output {
427 output_as_dbus_arg = quote! {<#t as DBusArg>};
428 }
429
430 let input_tuple = quote! {
431 (#input_list)
432 };
433
434 let body = match &method.sig.output {
435 // Build the method call to `self.client_proxy`. `method` or `method_noreturn`
436 // depends on whether there is a return from the function.
437 ReturnType::Default => {
438 quote! {
439 self.client_proxy.method_noreturn(#dbus_method_name, #input_tuple)
440 }
441 }
442 _ => {
443 quote! {
444 let ret: #output_as_dbus_arg::DBusType = self.client_proxy.method(
445 #dbus_method_name,
446 #input_tuple,
447 );
448 #output_as_dbus_arg::from_dbus(ret, None, None, None).unwrap()
449 }
450 }
451 };
452 let rpc_body = match &method.sig.output {
453 // Build the async method call to `self.client_proxy`.
454 ReturnType::Default => {
455 quote! {
456 self.client_proxy
457 .async_method_noreturn(#dbus_method_name, #input_tuple)
458 .await
459 }
460 }
461 _ => {
462 quote! {
463 self.client_proxy
464 .async_method(#dbus_method_name, #input_tuple)
465 .await
466 .map(|(x,)| {
467 #output_as_dbus_arg::from_dbus(x, None, None, None).unwrap()
468 })
469 }
470 }
471 };
472
473 // Assemble the method body. May have object conversions if there is a param that is
474 // a proxy object (`Box<dyn>` type).
475 let body = quote! {
476 #object_conversions
477
478 #body
479 };
480 let rpc_body = quote! {
481 #object_conversions
482
483 #rpc_body
484 };
485
486 // The method definition is its signature and the body.
487 let generated_method = quote! {
488 #sig {
489 #body
490 }
491 };
492 let generated_rpc_method = quote! {
493 #rpc_sig {
494 #rpc_body
495 }
496 };
497
498 // Assemble all the method definitions.
499 methods = quote! {
500 #methods
501
502 #generated_method
503 };
504 rpc_methods = quote! {
505 #rpc_methods
506
507 #generated_rpc_method
508 };
509 }
510 }
511
512 // Generated code for the RPC wrapper struct.
513 let rpc_gen = if rpc_struct_name.is_empty() {
514 quote! {}
515 } else {
516 let rpc_struct = format_ident!("{}", rpc_struct_name);
517 quote! {
518 impl #rpc_struct {
519 #rpc_methods
520 }
521 }
522 };
523
524 // The final generated code.
525 let gen = quote! {
526 impl #trait_path for #struct_path {
527 #methods
528 }
529
530 #rpc_gen
531 };
532
533 debug_output_to_file(
534 &gen,
535 std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
536 .join(format!("out-{}.rs", struct_path.path.get_ident().unwrap()))
537 .to_str()
538 .unwrap()
539 .to_string(),
540 );
541
542 gen.into()
543 }
544
copy_without_attributes(item: &TokenStream) -> TokenStream545 fn copy_without_attributes(item: &TokenStream) -> TokenStream {
546 let mut ast: ItemStruct = syn::parse(item.clone()).unwrap();
547 for field in &mut ast.fields {
548 field.attrs.clear();
549 }
550
551 let gen = quote! {
552 #ast
553 };
554
555 gen.into()
556 }
557
558 /// Generates a DBusArg implementation to transform Rust plain structs to a D-Bus data structure.
559 ///
560 /// The D-Bus structure constructed by this macro has the signature `a{sv}`.
561 ///
562 /// # Examples
563 ///
564 /// Assume you have a struct as follows:
565 /// ```
566 /// struct FooBar {
567 /// foo: i32,
568 /// bar: u8,
569 /// }
570 /// ```
571 ///
572 /// In order to serialize this into D-Bus (and deserialize it), you must re-declare this struct
573 /// as follows. Note that the field names must match but the struct name does not.
574 /// ```ignore
575 /// #[dbus_propmap(FooBar)]
576 /// struct AnyNameIsFineHere {
577 /// foo: i32,
578 /// bar: u8
579 /// }
580 /// ```
581 ///
582 /// The resulting serialized D-Bus data will look like the following:
583 ///
584 /// ```text
585 /// array [
586 /// dict {
587 /// key: "foo",
588 /// value: Variant(Int32(0))
589 /// }
590 /// dict {
591 /// key: "bar",
592 /// value: Variant(Byte(0))
593 /// }
594 /// ]
595 /// ```
596 // TODO: Support more data types of struct fields (currently only supports integers and enums).
597 #[proc_macro_attribute]
dbus_propmap(attr: TokenStream, item: TokenStream) -> TokenStream598 pub fn dbus_propmap(attr: TokenStream, item: TokenStream) -> TokenStream {
599 let ori_item: proc_macro2::TokenStream = copy_without_attributes(&item).into();
600
601 let ast: ItemStruct = syn::parse(item.clone()).unwrap();
602
603 let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
604 let struct_ident =
605 if let Expr::Path(p) = &args[0] { p.path.get_ident().unwrap().clone() } else { ast.ident };
606
607 let struct_str = struct_ident.to_string();
608
609 let mut make_fields = quote! {};
610 let mut field_idents = quote! {};
611
612 let mut insert_map_fields = quote! {};
613
614 let mut log_format = String::new();
615 let mut log_args = quote! {};
616
617 for field in ast.fields {
618 let Some(field_ident) = field.ident else { continue };
619
620 let field_str = field_ident.to_string();
621
622 let field_type = if let Type::Path(t) = field.ty {
623 t
624 } else {
625 continue;
626 };
627
628 field_idents = quote! {
629 #field_idents #field_ident,
630 };
631
632 let field_type_name = format_ident! {"{}_type_", field_str};
633 let make_field = quote! {
634 match #field_ident.arg_type() {
635 dbus::arg::ArgType::Variant => {}
636 _ => {
637 return Err(Box::new(DBusArgError::new(format!(
638 "{}.{} must be a variant",
639 #struct_str, #field_str
640 ))));
641 }
642 };
643 let #field_ident = <<#field_type as DBusArg>::DBusType as RefArgToRust>::ref_arg_to_rust(
644 #field_ident.as_static_inner(0).unwrap(),
645 format!("{}.{}", #struct_str, #field_str),
646 )?;
647 type #field_type_name = #field_type;
648 let #field_ident = #field_type_name::from_dbus(
649 #field_ident,
650 conn__.clone(),
651 remote__.clone(),
652 disconnect_watcher__.clone(),
653 )?;
654 };
655
656 make_fields = quote! {
657 #make_fields
658
659 let #field_ident = match data__.get(#field_str) {
660 Some(data) => data,
661 None => {
662 return Err(Box::new(DBusArgError::new(format!(
663 "{}.{} is required",
664 #struct_str, #field_str
665 ))));
666 }
667 };
668 #make_field
669 };
670
671 insert_map_fields = quote! {
672 #insert_map_fields
673 let field_data__ = DBusArg::to_dbus(data__.#field_ident)?;
674 map__.insert(String::from(#field_str), dbus::arg::Variant(Box::new(field_data__)));
675 };
676
677 if !log_format.is_empty() {
678 log_format.push_str(", ");
679 }
680 log_format.push_str(field_str.as_str());
681 log_format.push_str(": {}");
682
683 log_args = quote! {
684 #log_args
685 <#field_type as DBusArg>::log(&data__.#field_ident),
686 };
687 }
688
689 // Give an example type: struct BluetoothDevice { address: RawAddress, name: String }
690 // At this point the |log_format| would be: "address: {}, name: {}"
691 // Now, wrap it with curly braces and prepend the structure name so it becomes:
692 // "BluetoothDevice { address: {}, name: {} }"
693 log_format.insert_str(0, " {{ ");
694 log_format.push_str(" }}");
695 log_format.insert_str(0, struct_ident.to_string().as_str());
696
697 let gen = quote! {
698 #[allow(dead_code)]
699 #ori_item
700
701 impl DBusArg for #struct_ident {
702 type DBusType = dbus::arg::PropMap;
703
704 fn from_dbus(
705 data__: dbus::arg::PropMap,
706 conn__: Option<std::sync::Arc<dbus::nonblock::SyncConnection>>,
707 remote__: Option<dbus::strings::BusName<'static>>,
708 disconnect_watcher__: Option<std::sync::Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>>,
709 ) -> Result<#struct_ident, Box<dyn std::error::Error>> {
710 #make_fields
711
712 return Ok(#struct_ident {
713 #field_idents
714 });
715 }
716
717 fn to_dbus(data__: #struct_ident) -> Result<dbus::arg::PropMap, Box<dyn std::error::Error>> {
718 let mut map__: dbus::arg::PropMap = std::collections::HashMap::new();
719 #insert_map_fields
720 return Ok(map__);
721 }
722
723 fn log(data__: &#struct_ident) -> String {
724 format!(#log_format, #log_args)
725 }
726 }
727 };
728
729 debug_output_to_file(&gen, format!("out-{}.rs", struct_ident));
730
731 gen.into()
732 }
733
734 /// Generates a DBusArg implementation of a Remote RPC proxy object.
735 ///
736 /// Example:
737 /// `#[dbus_proxy_obj(FooCallback, "org.example.FooCallbackInterface")]`
738 ///
739 /// In order to call the remote methods, declare them with the attribute #[dbus_method()].
740 ///
741 /// # Args
742 ///
743 /// `struct_ident`: A freeform name used to identify the object struct.
744 /// `dbus_iface_name`: Name of the interface where this object should be exported.
745 #[proc_macro_attribute]
dbus_proxy_obj(attr: TokenStream, item: TokenStream) -> TokenStream746 pub fn dbus_proxy_obj(attr: TokenStream, item: TokenStream) -> TokenStream {
747 let ori_item: proc_macro2::TokenStream = item.clone().into();
748
749 let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
750
751 let struct_ident = if let Expr::Path(p) = &args[0] {
752 p.path.get_ident().unwrap()
753 } else {
754 panic!("struct name must be specified");
755 };
756
757 let dbus_iface_name = if let Expr::Lit(lit) = &args[1] {
758 lit
759 } else {
760 panic!("D-Bus interface name must be specified");
761 };
762
763 let mut method_impls = quote! {};
764
765 let ast: ItemImpl = syn::parse(item.clone()).unwrap();
766 let self_ty = ast.self_ty;
767 let trait_ = ast.trait_.unwrap().1;
768
769 for item in ast.items {
770 if let ImplItem::Method(method) = item {
771 // If the method is not marked with #[dbus_method], just copy the
772 // original method body.
773 if method.attrs.len() != 1 {
774 method_impls = quote! {
775 #method_impls
776 #method
777 };
778 continue;
779 }
780
781 let attr = &method.attrs[0];
782 if !attr.path.get_ident().unwrap().to_string().eq("dbus_method") {
783 continue;
784 }
785
786 let meta_list = match attr.parse_meta().unwrap() {
787 Meta::List(meta_list) => meta_list,
788 _ => continue,
789 };
790
791 let dbus_method_name = meta_list.nested[0].clone();
792
793 // logging is default to disabled if not specified
794 let dbus_logging = if meta_list.nested.len() > 1 {
795 meta_list.nested[1].clone()
796 } else {
797 let token = quote! { DBusLog::Disable };
798 syn::parse2::<NestedMeta>(token).unwrap()
799 };
800
801 let method_sig = method.sig.clone();
802
803 let mut method_args = quote! {};
804 let mut args_debug = quote! {};
805 let mut args_debug_format = String::new();
806
807 for input in method.sig.inputs {
808 if let FnArg::Typed(ref typed) = input {
809 let arg_type = &typed.ty;
810 if let Pat::Ident(pat_ident) = &*typed.pat {
811 let ident = pat_ident.ident.clone();
812
813 method_args = quote! {
814 #method_args DBusArg::to_dbus(#ident).unwrap(),
815 };
816
817 args_debug = quote! {
818 #args_debug
819 <#arg_type as DBusArg>::log(&#ident),
820 };
821
822 if !args_debug_format.is_empty() {
823 args_debug_format.push_str(", ");
824 }
825 args_debug_format.push_str("|{}|");
826 }
827 }
828 }
829
830 let debug = quote! {
831 let args_formatted = format!(#args_debug_format, #args_debug);
832 DBusLog::log(#dbus_logging, "dbus out", #dbus_iface_name, #dbus_method_name, args_formatted.as_str());
833 };
834
835 method_impls = quote! {
836 #method_impls
837 #[allow(unused_variables)]
838 #method_sig {
839 let remote__ = self.remote.clone();
840 let objpath__ = self.objpath.clone();
841 let conn__ = self.conn.clone();
842
843 #debug
844
845 let proxy = dbus::nonblock::Proxy::new(
846 remote__,
847 objpath__,
848 std::time::Duration::from_secs(2),
849 conn__,
850 );
851 let future: dbus::nonblock::MethodReply<()> = proxy.method_call(
852 #dbus_iface_name,
853 #dbus_method_name,
854 (#method_args),
855 );
856
857 // Acquire await lock before pushing task.
858 let has_await_block = {
859 let await_guard = self.futures_awaiting.lock().unwrap();
860 self.cb_futures.lock().unwrap().push_back(future);
861 *await_guard
862 };
863
864 // Only insert async task if there isn't already one.
865 if !has_await_block {
866 // Callbacks will await in the order they were called.
867 let futures = self.cb_futures.clone();
868 let already_awaiting = self.futures_awaiting.clone();
869 tokio::spawn(async move {
870 // Check for another await block.
871 {
872 let mut await_guard = already_awaiting.lock().unwrap();
873 if *await_guard {
874 return;
875 }
876
877 // We are now the only awaiting block. Mark and
878 // drop the lock.
879 *await_guard = true;
880 }
881
882 loop {
883 // Go through all pending futures and await them.
884 while futures.lock().unwrap().len() > 0 {
885 let future = {
886 let mut guard = futures.lock().unwrap();
887 match guard.pop_front() {
888 Some(f) => f,
889 None => {break;}
890 }
891 };
892 let _result = future.await;
893 }
894
895 // Acquire await block and make final check on
896 // futures list to avoid racing against
897 // insertion. Must acquire in-order to avoid a
898 // deadlock.
899 {
900 let mut await_guard = already_awaiting.lock().unwrap();
901 let futures_guard = futures.lock().unwrap();
902 if (*futures_guard).len() > 0 {
903 continue;
904 }
905
906 *await_guard = false;
907 break;
908 }
909 }
910 });
911 }
912 }
913 };
914 }
915 }
916
917 let gen = quote! {
918 #ori_item
919
920 impl RPCProxy for #self_ty {}
921
922 struct #struct_ident {
923 conn: std::sync::Arc<dbus::nonblock::SyncConnection>,
924 remote: dbus::strings::BusName<'static>,
925 objpath: Path<'static>,
926 disconnect_watcher: std::sync::Arc<std::sync::Mutex<DisconnectWatcher>>,
927
928 /// Callback futures to await. If accessing with |futures_awaiting|,
929 /// always acquire |futures_awaiting| first to avoid deadlock.
930 cb_futures: std::sync::Arc<std::sync::Mutex<std::collections::VecDeque<dbus::nonblock::MethodReply<()>>>>,
931
932 /// Is there a task already awaiting on |cb_futures|? If acquiring
933 /// with |cb_futures|, always acquire this lock first to avoid deadlocks.
934 futures_awaiting: std::sync::Arc<std::sync::Mutex<bool>>,
935 }
936
937 impl #struct_ident {
938 fn new(
939 conn: std::sync::Arc<dbus::nonblock::SyncConnection>,
940 remote: dbus::strings::BusName<'static>,
941 objpath: Path<'static>,
942 disconnect_watcher: std::sync::Arc<std::sync::Mutex<DisconnectWatcher>>) -> Self {
943 Self {
944 conn,
945 remote,
946 objpath,
947 disconnect_watcher,
948 cb_futures: std::sync::Arc::new(std::sync::Mutex::new(std::collections::VecDeque::new())),
949 futures_awaiting: std::sync::Arc::new(std::sync::Mutex::new(false)),
950 }
951 }
952 }
953
954 impl #trait_ for #struct_ident {
955 #method_impls
956 }
957
958 impl RPCProxy for #struct_ident {
959 fn register_disconnect(&mut self, disconnect_callback: Box<dyn Fn(u32) + Send>) -> u32 {
960 return self.disconnect_watcher.lock().unwrap().add(self.remote.clone(), disconnect_callback);
961 }
962
963 fn get_object_id(&self) -> String {
964 self.objpath.to_string().clone()
965 }
966
967 fn unregister(&mut self, id: u32) -> bool {
968 self.disconnect_watcher.lock().unwrap().remove(self.remote.clone(), id)
969 }
970 }
971
972 impl DBusArg for Box<dyn #trait_ + Send> {
973 type DBusType = Path<'static>;
974
975 fn from_dbus(
976 objpath__: Path<'static>,
977 conn__: Option<std::sync::Arc<dbus::nonblock::SyncConnection>>,
978 remote__: Option<dbus::strings::BusName<'static>>,
979 disconnect_watcher__: Option<std::sync::Arc<std::sync::Mutex<DisconnectWatcher>>>,
980 ) -> Result<Box<dyn #trait_ + Send>, Box<dyn std::error::Error>> {
981 Ok(Box::new(#struct_ident::new(
982 conn__.unwrap(),
983 remote__.unwrap(),
984 objpath__,
985 disconnect_watcher__.unwrap(),
986 )))
987 }
988
989 fn to_dbus(_data: Box<dyn #trait_ + Send>) -> Result<Path<'static>, Box<dyn std::error::Error>> {
990 // This impl represents a remote DBus object, so `to_dbus` does not make sense.
991 panic!("not implemented");
992 }
993
994 fn log(_data: &Box<dyn #trait_ + Send>) -> String {
995 format!("Box<dyn>")
996 }
997 }
998 };
999
1000 debug_output_to_file(&gen, format!("out-{}.rs", struct_ident));
1001
1002 gen.into()
1003 }
1004
1005 /// Generates the definition of `DBusArg` trait required for D-Bus projection.
1006 ///
1007 /// Due to Rust orphan rule, `DBusArg` trait needs to be defined locally in the crate that wants to
1008 /// use D-Bus projection. Providing `DBusArg` as a public trait won't let other crates implement
1009 /// it for structs defined in foreign crates. As a workaround, this macro is provided to generate
1010 /// `DBusArg` trait definition.
1011 #[proc_macro]
generate_dbus_arg(_item: TokenStream) -> TokenStream1012 pub fn generate_dbus_arg(_item: TokenStream) -> TokenStream {
1013 let gen = quote! {
1014 use dbus::arg::RefArg;
1015 use dbus::nonblock::SyncConnection;
1016 use dbus::strings::BusName;
1017 use dbus_projection::DisconnectWatcher;
1018 use dbus_projection::impl_dbus_arg_from_into;
1019
1020 use std::convert::{TryFrom, TryInto};
1021 use std::error::Error;
1022 use std::fmt;
1023 use std::hash::Hash;
1024 use std::sync::{Arc, Mutex};
1025
1026 // Key for serialized Option<T> in propmap
1027 const OPTION_KEY: &'static str = "optional_value";
1028
1029 #[derive(Debug)]
1030 pub(crate) struct DBusArgError {
1031 message: String,
1032 }
1033
1034 impl DBusArgError {
1035 pub fn new(message: String) -> DBusArgError {
1036 DBusArgError { message }
1037 }
1038 }
1039
1040 impl fmt::Display for DBusArgError {
1041 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1042 write!(f, "{}", self.message)
1043 }
1044 }
1045
1046 impl Error for DBusArgError {}
1047
1048 /// Trait for converting `dbus::arg::RefArg` to a Rust type.
1049 ///
1050 /// This trait needs to be implemented for all types that need to be
1051 /// converted from the D-Bus representation (`dbus::arg::RefArg`) to
1052 /// a Rust representation.
1053 ///
1054 /// These implementations should be provided as part of this macros
1055 /// library since the reference types are defined by the D-Bus specification
1056 /// (look under Basic Types, Container Types, etc) in
1057 /// https://dbus.freedesktop.org/doc/dbus-specification.html.
1058 pub(crate) trait RefArgToRust {
1059 type RustType;
1060 fn ref_arg_to_rust(
1061 arg: &(dyn dbus::arg::RefArg + 'static),
1062 name: String,
1063 ) -> Result<Self::RustType, Box<dyn Error>>;
1064 }
1065
1066 impl<T: 'static + DirectDBus> RefArgToRust for T {
1067 type RustType = T;
1068 fn ref_arg_to_rust(
1069 arg: &(dyn dbus::arg::RefArg + 'static),
1070 name: String,
1071 ) -> Result<Self::RustType, Box<dyn Error>> {
1072 let any = arg.as_any();
1073 if !any.is::<<Self as DBusArg>::DBusType>() {
1074 return Err(Box::new(DBusArgError::new(format!(
1075 "{} type does not match: expected {}, found {}",
1076 name,
1077 std::any::type_name::<<Self as DBusArg>::DBusType>(),
1078 arg.arg_type().as_str(),
1079 ))));
1080 }
1081 let arg = (*any.downcast_ref::<<Self as DBusArg>::DBusType>().unwrap()).clone();
1082 return Ok(arg);
1083 }
1084 }
1085
1086 impl RefArgToRust for std::fs::File {
1087 type RustType = std::fs::File;
1088
1089 fn ref_arg_to_rust(
1090 arg: &(dyn dbus::arg::RefArg + 'static),
1091 name: String,
1092 ) -> Result<Self::RustType, Box<dyn Error>> {
1093 let any = arg.as_any();
1094 if !any.is::<<Self as DBusArg>::DBusType>() {
1095 return Err(Box::new(DBusArgError::new(format!(
1096 "{} type does not match: expected {}, found {}",
1097 name,
1098 std::any::type_name::<<Self as DBusArg>::DBusType>(),
1099 arg.arg_type().as_str(),
1100 ))));
1101 }
1102 let arg = match (*any.downcast_ref::<<Self as DBusArg>::DBusType>().unwrap()).try_clone() {
1103 Ok(foo) => foo,
1104 Err(_) => return Err(Box::new(DBusArgError::new(format!("{} cannot clone file.", name)))),
1105 };
1106
1107 return Ok(arg);
1108 }
1109 }
1110
1111 impl RefArgToRust for dbus::arg::PropMap {
1112 type RustType = dbus::arg::PropMap;
1113 fn ref_arg_to_rust(
1114 arg: &(dyn dbus::arg::RefArg + 'static),
1115 name: String,
1116 ) -> Result<Self::RustType, Box<dyn Error>> {
1117 let mut map: dbus::arg::PropMap = std::collections::HashMap::new();
1118 let mut iter = match arg.as_iter() {
1119 None => {
1120 return Err(Box::new(DBusArgError::new(format!(
1121 "{} is not iterable",
1122 name,
1123 ))))
1124 }
1125 Some(item) => item,
1126 };
1127 let mut key = iter.next();
1128 let mut val = iter.next();
1129 while !key.is_none() && !val.is_none() {
1130 let k = key.unwrap().as_str().unwrap().to_string();
1131 let val_clone = val.unwrap().box_clone();
1132 let v = dbus::arg::Variant(
1133 val_clone
1134 .as_static_inner(0)
1135 .ok_or(Box::new(DBusArgError::new(format!(
1136 "{}.{} is not a variant",
1137 name, k
1138 ))))?
1139 .box_clone(),
1140 );
1141 map.insert(k, v);
1142 key = iter.next();
1143 val = iter.next();
1144 }
1145 return Ok(map);
1146 }
1147 }
1148
1149 // A vector is convertible from DBus' dynamic type RefArg to Rust's Vec, if the elements
1150 // of the vector are also convertible themselves recursively.
1151 impl<T: 'static + RefArgToRust<RustType = T>> RefArgToRust for Vec<T> {
1152 type RustType = Vec<T>;
1153 fn ref_arg_to_rust(
1154 arg: &(dyn dbus::arg::RefArg + 'static),
1155 _name: String,
1156 ) -> Result<Self::RustType, Box<dyn Error>> {
1157 let mut vec: Vec<T> = vec![];
1158 let mut iter = arg.as_iter().ok_or(Box::new(DBusArgError::new(format!(
1159 "Failed parsing array for `{}`",
1160 _name
1161 ))))?;
1162 let mut val = iter.next();
1163 while !val.is_none() {
1164 let arg = val.unwrap().box_clone();
1165 let arg = <T as RefArgToRust>::ref_arg_to_rust(&arg, _name.clone() + " element")?;
1166 vec.push(arg);
1167 val = iter.next();
1168 }
1169 return Ok(vec);
1170 }
1171 }
1172
1173 impl<
1174 K: 'static + Eq + Hash + RefArgToRust<RustType = K>,
1175 V: 'static + RefArgToRust<RustType = V>
1176 > RefArgToRust for std::collections::HashMap<K, V>
1177 {
1178 type RustType = std::collections::HashMap<K, V>;
1179
1180 fn ref_arg_to_rust(
1181 arg: &(dyn dbus::arg::RefArg + 'static),
1182 name: String,
1183 ) -> Result<Self::RustType, Box<dyn Error>> {
1184 let mut map: std::collections::HashMap<K, V> = std::collections::HashMap::new();
1185 let mut iter = arg.as_iter().unwrap();
1186 let mut key = iter.next();
1187 let mut val = iter.next();
1188 while !key.is_none() && !val.is_none() {
1189 let k = key.unwrap().box_clone();
1190 let k = <K as RefArgToRust>::ref_arg_to_rust(&k, name.clone() + " key")?;
1191 let v = val.unwrap().box_clone();
1192 let v = <V as RefArgToRust>::ref_arg_to_rust(&v, name.clone() + " value")?;
1193 map.insert(k, v);
1194 key = iter.next();
1195 val = iter.next();
1196 }
1197 Ok(map)
1198 }
1199 }
1200
1201 /// Trait describing how to convert to and from a D-Bus type, and to log D-Bus transaction.
1202 ///
1203 /// All Rust structs that need to be serialized to and from D-Bus need
1204 /// to implement this trait. Basic and container types will have their
1205 /// implementation provided by this macros crate.
1206 ///
1207 /// For Rust objects, implement the std::convert::TryFrom and std::convert::TryInto
1208 /// traits into the relevant basic or container types for serialization. A
1209 /// helper macro is provided in the `dbus_projection` macro (impl_dbus_arg_from_into).
1210 /// For enums, use `impl_dbus_arg_enum`.
1211 ///
1212 /// When implementing this trait for Rust container types (i.e. Option<T>),
1213 /// you must first select the D-Bus container type used (i.e. array, property map, etc) and
1214 /// then implement the `from_dbus`, `to_dbus`, and `log` functions.
1215 ///
1216 /// Note that when implementing `log` function for a container type, avoid using the "{:?}"
1217 /// Debug format because the `log` function could be recursively called and generate many
1218 /// backslashes.
1219 pub(crate) trait DBusArg {
1220 type DBusType;
1221
1222 fn from_dbus(
1223 x: Self::DBusType,
1224 conn: Option<Arc<dbus::nonblock::SyncConnection>>,
1225 remote: Option<BusName<'static>>,
1226 disconnect_watcher: Option<Arc<Mutex<DisconnectWatcher>>>,
1227 ) -> Result<Self, Box<dyn Error>>
1228 where
1229 Self: Sized;
1230
1231 fn to_dbus(x: Self) -> Result<Self::DBusType, Box<dyn Error>>;
1232
1233 fn log(x: &Self) -> String;
1234 }
1235
1236 // Types that implement dbus::arg::Append do not need any conversion.
1237 pub(crate) trait DirectDBus: Clone + std::fmt::Debug {}
1238 impl DirectDBus for bool {}
1239 impl DirectDBus for i32 {}
1240 impl DirectDBus for u32 {}
1241 impl DirectDBus for i64 {}
1242 impl DirectDBus for u64 {}
1243 impl DirectDBus for f64 {}
1244 impl DirectDBus for i16 {}
1245 impl DirectDBus for u16 {}
1246 impl DirectDBus for u8 {}
1247 impl DirectDBus for String {}
1248 impl<T: DirectDBus> DBusArg for T {
1249 type DBusType = T;
1250
1251 fn from_dbus(
1252 data: T,
1253 _conn: Option<Arc<dbus::nonblock::SyncConnection>>,
1254 _remote: Option<BusName<'static>>,
1255 _disconnect_watcher: Option<Arc<Mutex<DisconnectWatcher>>>,
1256 ) -> Result<T, Box<dyn Error>> {
1257 return Ok(data);
1258 }
1259
1260 fn to_dbus(data: T) -> Result<T, Box<dyn Error>> {
1261 return Ok(data);
1262 }
1263
1264 fn log(data: &T) -> String {
1265 format!("{:?}", data)
1266 }
1267 }
1268
1269 // Represent i8 as D-Bus's i16, since D-Bus only has unsigned type for BYTE.
1270 impl_dbus_arg_from_into!(i8, i16);
1271
1272 impl DBusArg for std::fs::File {
1273 type DBusType = std::fs::File;
1274
1275 fn from_dbus(
1276 data: std::fs::File,
1277 _conn: Option<Arc<dbus::nonblock::SyncConnection>>,
1278 _remote: Option<BusName<'static>>,
1279 _disconnect_watcher: Option<Arc<Mutex<DisconnectWatcher>>>,
1280 ) -> Result<std::fs::File, Box<dyn Error>> {
1281 return Ok(data);
1282 }
1283
1284 fn to_dbus(data: std::fs::File) -> Result<std::fs::File, Box<dyn Error>> {
1285 return Ok(data);
1286 }
1287
1288 fn log(data: &std::fs::File) -> String {
1289 format!("{:?}", data)
1290 }
1291 }
1292
1293 impl<T: DBusArg> DBusArg for Vec<T> {
1294 type DBusType = Vec<T::DBusType>;
1295
1296 fn from_dbus(
1297 data: Vec<T::DBusType>,
1298 conn: Option<Arc<dbus::nonblock::SyncConnection>>,
1299 remote: Option<BusName<'static>>,
1300 disconnect_watcher: Option<Arc<Mutex<DisconnectWatcher>>>,
1301 ) -> Result<Vec<T>, Box<dyn Error>> {
1302 let mut list: Vec<T> = vec![];
1303 for prop in data {
1304 let t = T::from_dbus(
1305 prop,
1306 conn.clone(),
1307 remote.clone(),
1308 disconnect_watcher.clone(),
1309 )?;
1310 list.push(t);
1311 }
1312 Ok(list)
1313 }
1314
1315 fn to_dbus(data: Vec<T>) -> Result<Vec<T::DBusType>, Box<dyn Error>> {
1316 let mut list: Vec<T::DBusType> = vec![];
1317 for item in data {
1318 let t = T::to_dbus(item)?;
1319 list.push(t);
1320 }
1321 Ok(list)
1322 }
1323
1324 fn log(data: &Vec<T>) -> String {
1325 format!(
1326 "[{}]",
1327 data
1328 .iter()
1329 .map(|d| <T as DBusArg>::log(d))
1330 .collect::<Vec<String>>()
1331 .join(", ")
1332 )
1333 }
1334 }
1335
1336 impl<T: DBusArg> DBusArg for Option<T>
1337 where
1338 <T as DBusArg>::DBusType: dbus::arg::RefArg
1339 + 'static
1340 + RefArgToRust<RustType = <T as DBusArg>::DBusType>,
1341 {
1342 type DBusType = dbus::arg::PropMap;
1343
1344 fn from_dbus(
1345 data: dbus::arg::PropMap,
1346 conn: Option<Arc<dbus::nonblock::SyncConnection>>,
1347 remote: Option<BusName<'static>>,
1348 disconnect_watcher: Option<Arc<Mutex<DisconnectWatcher>>>)
1349 -> Result<Option<T>, Box<dyn Error>> {
1350
1351 // It's Ok if the key doesn't exist. That just means we have an empty option (i.e.
1352 // None).
1353 let prop_value = match data.get(OPTION_KEY) {
1354 Some(data) => data,
1355 None => {
1356 return Ok(None);
1357 }
1358 };
1359
1360 // Make sure the option type was encoded correctly. If the key exists but the value
1361 // is not right, we return an Err type.
1362 match prop_value.arg_type() {
1363 dbus::arg::ArgType::Variant => (),
1364 _ => {
1365 return Err(Box::new(DBusArgError::new(format!("{} must be a variant", OPTION_KEY))));
1366 }
1367 };
1368
1369 // Convert the Variant into the target type and return an Err if that fails.
1370 let ref_value: <T as DBusArg>::DBusType = match <<T as DBusArg>::DBusType as RefArgToRust>::ref_arg_to_rust(
1371 prop_value.as_static_inner(0).unwrap(),
1372 OPTION_KEY.to_string()) {
1373 Ok(v) => v,
1374 Err(e) => return Err(e),
1375 };
1376
1377 let value = match T::from_dbus(ref_value, conn, remote, disconnect_watcher) {
1378 Ok(v) => Some(v),
1379 Err(e) => return Err(e),
1380 };
1381
1382 Ok(value)
1383 }
1384
1385 fn to_dbus(data: Option<T>) -> Result<dbus::arg::PropMap, Box<dyn Error>> {
1386 let mut props = dbus::arg::PropMap::new();
1387
1388 if let Some(d) = data {
1389 let b = T::to_dbus(d)?;
1390 props.insert(OPTION_KEY.to_string(), dbus::arg::Variant(Box::new(b)));
1391 }
1392
1393 Ok(props)
1394 }
1395
1396 fn log(data: &Option<T>) -> String {
1397 if let Some(d) = data.as_ref() {
1398 format!("Some({})", <T as DBusArg>::log(d))
1399 } else {
1400 String::from("None")
1401 }
1402 }
1403 }
1404
1405 impl<K: Eq + Hash + DBusArg, V: DBusArg> DBusArg for std::collections::HashMap<K, V>
1406 where
1407 <K as DBusArg>::DBusType: 'static
1408 + Eq
1409 + Hash
1410 + dbus::arg::RefArg
1411 + RefArgToRust<RustType = <K as DBusArg>::DBusType>,
1412 {
1413 type DBusType = std::collections::HashMap<K::DBusType, V::DBusType>;
1414
1415 fn from_dbus(
1416 data: std::collections::HashMap<K::DBusType, V::DBusType>,
1417 conn: Option<std::sync::Arc<dbus::nonblock::SyncConnection>>,
1418 remote: Option<dbus::strings::BusName<'static>>,
1419 disconnect_watcher: Option<
1420 std::sync::Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>>,
1421 ) -> Result<std::collections::HashMap<K, V>, Box<dyn std::error::Error>> {
1422 let mut map = std::collections::HashMap::new();
1423 for (key, val) in data {
1424 let k = K::from_dbus(
1425 key,
1426 conn.clone(),
1427 remote.clone(),
1428 disconnect_watcher.clone()
1429 )?;
1430 let v = V::from_dbus(
1431 val,
1432 conn.clone(),
1433 remote.clone(),
1434 disconnect_watcher.clone()
1435 )?;
1436 map.insert(k, v);
1437 }
1438 Ok(map)
1439 }
1440
1441 fn to_dbus(
1442 data: std::collections::HashMap<K, V>,
1443 ) -> Result<std::collections::HashMap<K::DBusType, V::DBusType>, Box<dyn std::error::Error>>
1444 {
1445 let mut map = std::collections::HashMap::new();
1446 for (key, val) in data {
1447 let k = K::to_dbus(key)?;
1448 let v = V::to_dbus(val)?;
1449 map.insert(k, v);
1450 }
1451 Ok(map)
1452 }
1453
1454 fn log(data: &std::collections::HashMap<K, V>) -> String {
1455 format!(
1456 "{{{}}}",
1457 data.iter()
1458 .map(|(k, v)| format!(
1459 "{}: {}",
1460 <K as DBusArg>::log(k),
1461 <V as DBusArg>::log(v),
1462 ))
1463 .collect::<Vec<String>>()
1464 .join(", ")
1465 )
1466 }
1467 }
1468 };
1469
1470 debug_output_to_file(&gen, "out-generate_dbus_arg.rs".into());
1471
1472 gen.into()
1473 }
1474