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