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