1 extern crate proc_macro;
2
3 use quote::{format_ident, quote, ToTokens};
4
5 use std::fs::File;
6 use std::io::Write;
7 use std::path::Path;
8
9 use syn::parse::Parser;
10 use syn::punctuated::Punctuated;
11 use syn::token::Comma;
12 use syn::{Expr, FnArg, ImplItem, ItemImpl, ItemStruct, Meta, Pat, ReturnType, Type};
13
14 use crate::proc_macro::TokenStream;
15
debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String)16 fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) {
17 let path = Path::new(filename.as_str());
18 let mut file = File::create(&path).unwrap();
19 file.write_all(gen.to_string().as_bytes()).unwrap();
20 }
21
22 /// Marks a method to be projected to a D-Bus method and specifies the D-Bus method name.
23 #[proc_macro_attribute]
dbus_method(_attr: TokenStream, item: TokenStream) -> TokenStream24 pub fn dbus_method(_attr: TokenStream, item: TokenStream) -> TokenStream {
25 let ori_item: proc_macro2::TokenStream = item.clone().into();
26 let gen = quote! {
27 #[allow(unused_variables)]
28 #ori_item
29 };
30 gen.into()
31 }
32
33 /// Generates a function to export a Rust object to D-Bus.
34 #[proc_macro_attribute]
generate_dbus_exporter(attr: TokenStream, item: TokenStream) -> TokenStream35 pub fn generate_dbus_exporter(attr: TokenStream, item: TokenStream) -> TokenStream {
36 let ori_item: proc_macro2::TokenStream = item.clone().into();
37
38 let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
39
40 let fn_ident = if let Expr::Path(p) = &args[0] {
41 p.path.get_ident().unwrap()
42 } else {
43 panic!("function name must be specified");
44 };
45
46 let dbus_iface_name = if let Expr::Lit(lit) = &args[1] {
47 lit
48 } else {
49 panic!("D-Bus interface name must be specified");
50 };
51
52 let ast: ItemImpl = syn::parse(item.clone()).unwrap();
53 let api_iface_ident = ast.trait_.unwrap().1.to_token_stream();
54
55 let mut register_methods = quote! {};
56
57 for item in ast.items {
58 if let ImplItem::Method(method) = item {
59 if method.attrs.len() != 1 {
60 continue;
61 }
62
63 let attr = &method.attrs[0];
64 if !attr.path.get_ident().unwrap().to_string().eq("dbus_method") {
65 continue;
66 }
67
68 let attr_args = attr.parse_meta().unwrap();
69 let dbus_method_name = if let Meta::List(meta_list) = attr_args {
70 Some(meta_list.nested[0].clone())
71 } else {
72 None
73 };
74
75 if dbus_method_name.is_none() {
76 continue;
77 }
78
79 let method_name = method.sig.ident;
80
81 let mut arg_names = quote! {};
82 let mut method_args = quote! {};
83 let mut make_args = quote! {};
84 let mut dbus_input_vars = quote! {};
85 let mut dbus_input_types = quote! {};
86
87 for input in method.sig.inputs {
88 if let FnArg::Typed(ref typed) = input {
89 let arg_type = &typed.ty;
90 if let Pat::Ident(pat_ident) = &*typed.pat {
91 let ident = pat_ident.ident.clone();
92 let mut dbus_input_ident = ident.to_string();
93 dbus_input_ident.push_str("_");
94 let dbus_input_arg = format_ident!("{}", dbus_input_ident);
95 let ident_string = ident.to_string();
96
97 arg_names = quote! {
98 #arg_names #ident_string,
99 };
100
101 method_args = quote! {
102 #method_args #ident,
103 };
104
105 dbus_input_vars = quote! {
106 #dbus_input_vars #dbus_input_arg,
107 };
108
109 dbus_input_types = quote! {
110 #dbus_input_types
111 <#arg_type as DBusArg>::DBusType,
112 };
113
114 make_args = quote! {
115 #make_args
116 let #ident = <#arg_type as DBusArg>::from_dbus(
117 #dbus_input_arg,
118 conn_clone.clone(),
119 ctx.message().sender().unwrap().into_static(),
120 dc_watcher_clone.clone(),
121 );
122
123 if let Result::Err(e) = #ident {
124 return Err(dbus_crossroads::MethodErr::invalid_arg(
125 e.to_string().as_str()
126 ));
127 }
128
129 let #ident = #ident.unwrap();
130 };
131 }
132 }
133 }
134
135 let dbus_input_args = quote! {
136 (#dbus_input_vars): (#dbus_input_types)
137 };
138
139 let mut output_names = quote! {};
140 let mut output_type = quote! {};
141 let mut ret = quote! {Ok(())};
142 if let ReturnType::Type(_, t) = method.sig.output {
143 output_type = quote! {#t,};
144 ret = quote! {Ok((ret,))};
145 output_names = quote! { "out", };
146 }
147
148 register_methods = quote! {
149 #register_methods
150
151 let conn_clone = conn.clone();
152 let dc_watcher_clone = disconnect_watcher.clone();
153 let handle_method = move |ctx: &mut dbus_crossroads::Context,
154 obj: &mut ObjType,
155 #dbus_input_args |
156 -> Result<(#output_type), dbus_crossroads::MethodErr> {
157 #make_args
158 let ret = obj.lock().unwrap().#method_name(#method_args);
159 #ret
160 };
161 ibuilder.method(
162 #dbus_method_name,
163 (#arg_names),
164 (#output_names),
165 handle_method,
166 );
167 };
168 }
169 }
170
171 let gen = quote! {
172 #ori_item
173
174 type ObjType = std::sync::Arc<std::sync::Mutex<dyn #api_iface_ident + Send>>;
175
176 pub fn #fn_ident(
177 path: &'static str,
178 conn: std::sync::Arc<SyncConnection>,
179 cr: &mut dbus_crossroads::Crossroads,
180 obj: ObjType,
181 disconnect_watcher: Arc<Mutex<dbus_projection::DisconnectWatcher>>,
182 ) {
183 fn get_iface_token(
184 conn: Arc<SyncConnection>,
185 cr: &mut dbus_crossroads::Crossroads,
186 disconnect_watcher: std::sync::Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
187 ) -> dbus_crossroads::IfaceToken<ObjType> {
188 cr.register(#dbus_iface_name, |ibuilder| {
189 #register_methods
190 })
191 }
192
193 let iface_token = get_iface_token(conn, cr, disconnect_watcher);
194 cr.insert(path, &[iface_token], obj);
195 }
196 };
197
198 // TODO: Have a switch to turn on/off this debug.
199 debug_output_to_file(&gen, format!("/tmp/out-{}.rs", fn_ident.to_string()));
200
201 gen.into()
202 }
203
copy_without_attributes(item: &TokenStream) -> TokenStream204 fn copy_without_attributes(item: &TokenStream) -> TokenStream {
205 let mut ast: ItemStruct = syn::parse(item.clone()).unwrap();
206 for field in &mut ast.fields {
207 field.attrs.clear();
208 }
209
210 let gen = quote! {
211 #ast
212 };
213
214 gen.into()
215 }
216
217 /// Generates a DBusArg implementation to transform Rust plain structs to a D-Bus data structure.
218 // TODO: Support more data types of struct fields (currently only supports integers and enums).
219 #[proc_macro_attribute]
dbus_propmap(attr: TokenStream, item: TokenStream) -> TokenStream220 pub fn dbus_propmap(attr: TokenStream, item: TokenStream) -> TokenStream {
221 let ori_item: proc_macro2::TokenStream = copy_without_attributes(&item).into();
222
223 let ast: ItemStruct = syn::parse(item.clone()).unwrap();
224
225 let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
226 let struct_ident =
227 if let Expr::Path(p) = &args[0] { p.path.get_ident().unwrap().clone() } else { ast.ident };
228
229 let struct_str = struct_ident.to_string();
230
231 let mut make_fields = quote! {};
232 let mut field_idents = quote! {};
233
234 let mut insert_map_fields = quote! {};
235 for field in ast.fields {
236 let field_ident = field.ident;
237
238 if field_ident.is_none() {
239 continue;
240 }
241
242 let field_str = field_ident.as_ref().unwrap().clone().to_string();
243
244 let propmap_attr = field.attrs.clone().into_iter().find(|x| {
245 let ident = x.path.get_ident();
246
247 if ident.is_none() {
248 return false;
249 }
250
251 ident.unwrap().to_string().eq("dbus_propmap_field_propmap")
252 });
253
254 let field_type_str = if let Type::Path(t) = field.ty {
255 t.path.get_ident().unwrap().to_string()
256 } else {
257 String::from("")
258 };
259
260 let field_type_ident = format_ident!("{}", field_type_str);
261
262 field_idents = quote! {
263 #field_idents #field_ident,
264 };
265
266 let make_field = if !propmap_attr.is_none() {
267 quote! {
268 let mut map: dbus::arg::PropMap = HashMap::new();
269
270 let mut iter = #field_ident.as_iter().unwrap();
271 let mut iter = iter.next().unwrap().as_iter().unwrap();
272
273 let mut i1 = iter.next();
274 let mut i2 = iter.next();
275 while !i1.is_none() && !i2.is_none() {
276 let k = i1.unwrap().as_str().unwrap().to_string();
277 let v = dbus::arg::Variant(i2.unwrap().box_clone());
278 map.insert(k, v);
279 i1 = iter.next();
280 i2 = iter.next();
281 }
282
283 let #field_ident = #field_type_ident::from_dbus(
284 map,
285 conn.clone(),
286 remote.clone(),
287 disconnect_watcher.clone(),
288 )?;
289 }
290 } else {
291 quote! {
292 match #field_ident.arg_type() {
293 dbus::arg::ArgType::Variant => {}
294 _ => {
295 return Err(Box::new(DBusArgError::new(String::from(format!(
296 "{}.{} must be a variant",
297 #struct_str, #field_str
298 )))));
299 }
300 };
301 let #field_ident = #field_ident.as_static_inner(0).unwrap();
302 let any = #field_ident.as_any();
303 if !any.is::<<#field_type_ident as DBusArg>::DBusType>() {
304 return Err(Box::new(DBusArgError::new(String::from(format!(
305 "{}.{} type does not match: expected {}, found {}",
306 #struct_str,
307 #field_str,
308 std::any::type_name::<<#field_type_ident as DBusArg>::DBusType>(),
309 #field_ident.arg_type().as_str(),
310 )))));
311 }
312 let #field_ident = *any.downcast_ref::<<#field_type_ident as DBusArg>::DBusType>().unwrap();
313 let #field_ident = #field_type_ident::from_dbus(
314 #field_ident,
315 conn.clone(),
316 remote.clone(),
317 disconnect_watcher.clone(),
318 )?;
319 }
320 };
321
322 make_fields = quote! {
323 #make_fields
324
325 let #field_ident = match data.get(#field_str) {
326 Some(data) => data,
327 None => {
328 return Err(Box::new(DBusArgError::new(String::from(format!(
329 "{}.{} is required",
330 #struct_str, #field_str
331 )))));
332 }
333 };
334 #make_field
335 };
336
337 insert_map_fields = quote! {
338 #insert_map_fields
339 let field_data = DBusArg::to_dbus(data.#field_ident)?;
340 map.insert(String::from(#field_str), dbus::arg::Variant(Box::new(field_data)));
341 };
342 }
343
344 let gen = quote! {
345 #[allow(dead_code)]
346 #ori_item
347
348 impl DBusArg for #struct_ident {
349 type DBusType = dbus::arg::PropMap;
350
351 fn from_dbus(
352 data: dbus::arg::PropMap,
353 conn: Arc<SyncConnection>,
354 remote: BusName<'static>,
355 disconnect_watcher: Arc<Mutex<dbus_projection::DisconnectWatcher>>,
356 ) -> Result<#struct_ident, Box<dyn Error>> {
357 #make_fields
358
359 return Ok(#struct_ident {
360 #field_idents
361 ..Default::default()
362 });
363 }
364
365 fn to_dbus(data: #struct_ident) -> Result<dbus::arg::PropMap, Box<dyn Error>> {
366 let mut map: dbus::arg::PropMap = HashMap::new();
367 #insert_map_fields
368 return Ok(map);
369 }
370 }
371 };
372
373 // TODO: Have a switch to turn this debug off/on.
374 debug_output_to_file(&gen, format!("/tmp/out-{}.rs", struct_ident.to_string()));
375
376 gen.into()
377 }
378
379 /// Generates a DBusArg implementation of a Remote RPC proxy object.
380 #[proc_macro_attribute]
dbus_proxy_obj(attr: TokenStream, item: TokenStream) -> TokenStream381 pub fn dbus_proxy_obj(attr: TokenStream, item: TokenStream) -> TokenStream {
382 let ori_item: proc_macro2::TokenStream = item.clone().into();
383
384 let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
385
386 let struct_ident = if let Expr::Path(p) = &args[0] {
387 p.path.get_ident().unwrap()
388 } else {
389 panic!("struct name must be specified");
390 };
391
392 let dbus_iface_name = if let Expr::Lit(lit) = &args[1] {
393 lit
394 } else {
395 panic!("D-Bus interface name must be specified");
396 };
397
398 let mut method_impls = quote! {};
399
400 let ast: ItemImpl = syn::parse(item.clone()).unwrap();
401 let self_ty = ast.self_ty;
402 let trait_ = ast.trait_.unwrap().1;
403
404 for item in ast.items {
405 if let ImplItem::Method(method) = item {
406 if method.attrs.len() != 1 {
407 continue;
408 }
409
410 let attr = &method.attrs[0];
411 if !attr.path.get_ident().unwrap().to_string().eq("dbus_method") {
412 continue;
413 }
414
415 let attr_args = attr.parse_meta().unwrap();
416 let dbus_method_name = if let Meta::List(meta_list) = attr_args {
417 Some(meta_list.nested[0].clone())
418 } else {
419 None
420 };
421
422 if dbus_method_name.is_none() {
423 continue;
424 }
425
426 let method_sig = method.sig.clone();
427
428 let mut method_args = quote! {};
429
430 for input in method.sig.inputs {
431 if let FnArg::Typed(ref typed) = input {
432 if let Pat::Ident(pat_ident) = &*typed.pat {
433 let ident = pat_ident.ident.clone();
434
435 method_args = quote! {
436 #method_args DBusArg::to_dbus(#ident).unwrap(),
437 };
438 }
439 }
440 }
441
442 method_impls = quote! {
443 #method_impls
444 #[allow(unused_variables)]
445 #method_sig {
446 let remote = self.remote.clone();
447 let objpath = self.objpath.clone();
448 let conn = self.conn.clone();
449 bt_topshim::topstack::get_runtime().spawn(async move {
450 let proxy = dbus::nonblock::Proxy::new(
451 remote,
452 objpath,
453 std::time::Duration::from_secs(2),
454 conn,
455 );
456 let future: dbus::nonblock::MethodReply<()> = proxy.method_call(
457 #dbus_iface_name,
458 #dbus_method_name,
459 (#method_args),
460 );
461 let _result = future.await;
462 });
463 }
464 };
465 }
466 }
467
468 let gen = quote! {
469 #ori_item
470
471 impl RPCProxy for #self_ty {
472 fn register_disconnect(&mut self, _disconnect_callback: Box<dyn Fn() + Send>) {}
473 }
474
475 struct #struct_ident {
476 conn: Arc<SyncConnection>,
477 remote: BusName<'static>,
478 objpath: Path<'static>,
479 disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
480 }
481
482 impl #trait_ for #struct_ident {
483 #method_impls
484 }
485
486 impl RPCProxy for #struct_ident {
487 fn register_disconnect(&mut self, disconnect_callback: Box<dyn Fn() + Send>) {
488 self.disconnect_watcher.lock().unwrap().add(self.remote.clone(), disconnect_callback);
489 }
490 }
491
492 impl DBusArg for Box<dyn #trait_ + Send> {
493 type DBusType = Path<'static>;
494
495 fn from_dbus(
496 objpath: Path<'static>,
497 conn: Arc<SyncConnection>,
498 remote: BusName<'static>,
499 disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
500 ) -> Result<Box<dyn #trait_ + Send>, Box<dyn Error>> {
501 Ok(Box::new(#struct_ident { conn, remote, objpath, disconnect_watcher }))
502 }
503
504 fn to_dbus(_data: Box<dyn #trait_ + Send>) -> Result<Path<'static>, Box<dyn Error>> {
505 // This impl represents a remote DBus object, so `to_dbus` does not make sense.
506 panic!("not implemented");
507 }
508 }
509 };
510
511 // TODO: Have a switch to turn this debug off/on.
512 debug_output_to_file(&gen, format!("/tmp/out-{}.rs", struct_ident.to_string()));
513
514 gen.into()
515 }
516
517 /// Generates the definition of `DBusArg` trait required for D-Bus projection.
518 ///
519 /// Due to Rust orphan rule, `DBusArg` trait needs to be defined locally in the crate that wants to
520 /// use D-Bus projection. Providing `DBusArg` as a public trait won't let other crates implement
521 /// it for structs defined in foreign crates. As a workaround, this macro is provided to generate
522 /// `DBusArg` trait definition.
523 #[proc_macro]
generate_dbus_arg(_item: TokenStream) -> TokenStream524 pub fn generate_dbus_arg(_item: TokenStream) -> TokenStream {
525 let gen = quote! {
526 use dbus::arg::PropMap;
527 use dbus::nonblock::SyncConnection;
528 use dbus::strings::BusName;
529 use dbus_projection::DisconnectWatcher;
530
531 use std::error::Error;
532 use std::fmt;
533 use std::sync::{Arc, Mutex};
534
535 #[derive(Debug)]
536 pub(crate) struct DBusArgError {
537 message: String,
538 }
539
540 impl DBusArgError {
541 pub fn new(message: String) -> DBusArgError {
542 DBusArgError { message }
543 }
544 }
545
546 impl fmt::Display for DBusArgError {
547 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
548 write!(f, "{}", self.message)
549 }
550 }
551
552 impl Error for DBusArgError {}
553
554 pub(crate) trait DBusArg {
555 type DBusType;
556
557 fn from_dbus(
558 x: Self::DBusType,
559 conn: Arc<SyncConnection>,
560 remote: BusName<'static>,
561 disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
562 ) -> Result<Self, Box<dyn Error>>
563 where
564 Self: Sized;
565
566 fn to_dbus(x: Self) -> Result<Self::DBusType, Box<dyn Error>>;
567 }
568
569 // Types that implement dbus::arg::Append do not need any conversion.
570 pub(crate) trait DirectDBus {}
571 impl DirectDBus for i32 {}
572 impl DirectDBus for u32 {}
573 impl DirectDBus for String {}
574 impl<T: DirectDBus> DBusArg for T {
575 type DBusType = T;
576
577 fn from_dbus(
578 data: T,
579 _conn: Arc<SyncConnection>,
580 _remote: BusName<'static>,
581 _disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
582 ) -> Result<T, Box<dyn Error>> {
583 return Ok(data);
584 }
585
586 fn to_dbus(data: T) -> Result<T, Box<dyn Error>> {
587 return Ok(data);
588 }
589 }
590
591 impl<T: DBusArg> DBusArg for Vec<T> {
592 type DBusType = Vec<T::DBusType>;
593
594 fn from_dbus(
595 data: Vec<T::DBusType>,
596 conn: Arc<SyncConnection>,
597 remote: BusName<'static>,
598 disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
599 ) -> Result<Vec<T>, Box<dyn Error>> {
600 let mut list: Vec<T> = vec![];
601 for prop in data {
602 let t = T::from_dbus(
603 prop,
604 conn.clone(),
605 remote.clone(),
606 disconnect_watcher.clone(),
607 )?;
608 list.push(t);
609 }
610 Ok(list)
611 }
612
613 fn to_dbus(data: Vec<T>) -> Result<Vec<T::DBusType>, Box<dyn Error>> {
614 let mut list: Vec<T::DBusType> = vec![];
615 for item in data {
616 let t = T::to_dbus(item)?;
617 list.push(t);
618 }
619 Ok(list)
620 }
621 }
622 };
623
624 // TODO: Have a switch to turn this debug off/on.
625 debug_output_to_file(&gen, format!("/tmp/out-generate_dbus_arg.rs"));
626
627 gen.into()
628 }
629