zbus_macros-1.9.2/.cargo_vcs_info.json0000644000000001120000000000100133660ustar { "git": { "sha1": "59fa38ebdf0be014c4edfbb3145db7f8600af807" } } zbus_macros-1.9.2/Cargo.toml0000644000000025010000000000100113700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "zbus_macros" version = "1.9.2" authors = ["Marc-André Lureau "] description = "proc-macros for zbus" documentation = "http://docs.rs/zbus_macros/" readme = "../README.md" keywords = ["D-Bus", "DBus", "IPC"] categories = ["data-structures", "encoding", "parsing"] license = "MIT" repository = "https://gitlab.freedesktop.org/dbus/zbus/" [lib] proc-macro = true [dependencies.proc-macro-crate] version = "0.1.4" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0.3" [dependencies.syn] version = "1.0.18" features = ["extra-traits", "full"] [dev-dependencies.rustversion] version = "1.0.4" [dev-dependencies.serde] version = "1.0" features = ["derive"] [dev-dependencies.trybuild] version = "1.0.31" [dev-dependencies.zbus] version = "1" [dev-dependencies.zvariant] version = "2" zbus_macros-1.9.2/Cargo.toml.orig000064400000000000000000000014620072674642500151060ustar 00000000000000[package] name = "zbus_macros" # Keep version in sync with zbus crate version = "1.9.2" authors = ["Marc-André Lureau "] edition = "2018" description = "proc-macros for zbus" repository = "https://gitlab.freedesktop.org/dbus/zbus/" documentation = "http://docs.rs/zbus_macros/" keywords = ["D-Bus", "DBus", "IPC"] license = "MIT" categories = ["data-structures", "encoding", "parsing"] readme = "../README.md" [lib] proc-macro = true [dependencies] proc-macro2 = "1.0" syn = { version = "1.0.18", features = ["extra-traits", "full"] } quote = "1.0.3" proc-macro-crate = "0.1.4" [dev-dependencies] zvariant = { path = "../zvariant", version = "2" } zbus = { path = "../zbus", version = "1" } serde = { version = "1.0", features = ["derive"] } trybuild = "1.0.31" rustversion = "1.0.4" zbus_macros-1.9.2/LICENSE000064400000000000000000000017770072674642500132350ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. zbus_macros-1.9.2/src/error.rs000064400000000000000000000140310072674642500145010ustar 00000000000000use proc_macro2::TokenStream; use quote::quote; use syn::{ Attribute, Data, DeriveInput, Fields, Lit, Meta::{List, NameValue}, NestedMeta, NestedMeta::Meta, }; use crate::utils::*; pub fn get_dbus_error_meta_items(attr: &Attribute) -> Result, ()> { if !attr.path.is_ident("dbus_error") { return Ok(Vec::new()); } match attr.parse_meta() { Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), _ => panic!("unsupported attribute"), } } pub fn expand_derive(input: DeriveInput) -> TokenStream { let mut prefix = "org.freedesktop.DBus".to_string(); for meta_item in input .attrs .iter() .flat_map(get_dbus_error_meta_items) .flatten() { match &meta_item { // Parse `#[dbus_error(prefix = "foo")]` Meta(NameValue(m)) if m.path.is_ident("prefix") => { if let Lit::Str(s) = &m.lit { prefix = s.value(); } } _ => panic!("unsupported attribute"), } } let (vis, name, generics, data) = match input.data { Data::Enum(data) => (input.vis, input.ident, input.generics, data), _ => panic!("Only works with DBus error enums"), }; let zbus = get_zbus_crate_ident(); let mut replies = quote! {}; let mut error_names = quote! {}; let mut error_descriptions = quote! {}; let mut error_converts = quote! {}; for variant in data.variants { let attrs = error_parse_item_attributes(&variant.attrs).unwrap(); let ident = variant.ident; let name = attrs .iter() .find_map(|x| match x { ItemAttribute::Name(n) => Some(n.to_string()), _ => None, }) .unwrap_or_else(|| ident.to_string()); if name == "ZBus" { continue; } let fqn = format!("{}.{}", prefix, name); let e = match variant.fields { Fields::Unit => quote! { Self::#ident => #fqn, }, Fields::Unnamed(_) => quote! { Self::#ident(..) => #fqn, }, Fields::Named(_) => quote! { Self::#ident { .. } => #fqn, }, }; error_names.extend(e); // FIXME: this will error if the first field is not a string as per the dbus spec, but we // may support other cases? let e = match &variant.fields { Fields::Unit => quote! { Self::#ident => &"", }, Fields::Unnamed(_) => quote! { Self::#ident(desc, ..) => &desc, }, Fields::Named(n) => { let f = &n.named.first().unwrap().ident; quote! { Self::#ident { #f, } => #f, } } }; error_descriptions.extend(e); // FIXME: deserialize msg to error field instead, to support variable args let e = match variant.fields { Fields::Unit => quote! { #fqn => Self::#ident, }, Fields::Unnamed(_) => quote! { #fqn => Self::#ident(desc), }, Fields::Named(_) => quote! { #fqn => Self::#ident { desc }, }, }; error_converts.extend(e); let r = match variant.fields { Fields::Unit => { quote! { Self::#ident => c.reply_error(call, name, &()), } } Fields::Unnamed(f) => { let fields = (0..f.unnamed.len()) .map(|n| format!("f{}", n)) .map(|v| syn::Ident::new(&v, ident.span())) .collect::>(); quote! { Self::#ident(#(#fields),*) => c.reply_error(call, name, &(#(#fields),*)), } } Fields::Named(f) => { let fields = f.named.iter().map(|v| v.ident.as_ref()).collect::>(); quote! { Self::#ident { #(#fields),* } => c.reply_error(call, name, &(#(#fields),*)), } } }; replies.extend(r); } let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { impl #impl_generics #name #ty_generics #where_clause { #vis fn name(&self) -> &str { match self { #error_names Self::ZBus(_) => "Unknown", } } #vis fn description(&self) -> &str { match self { #error_descriptions Self::ZBus(_) => "Unknown", } } #vis fn reply( &self, c: &::#zbus::Connection, call: &::#zbus::Message, ) -> std::result::Result { let name = self.name(); match self { #replies Self::ZBus(_) => panic!("Can not reply with ZBus error type"), } } } impl std::fmt::Display for #name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}: {}", self.name(), self.description()) } } impl std::error::Error for #name {} impl From<::#zbus::Error> for #name { fn from(value: ::#zbus::Error) -> #name { if let ::#zbus::Error::MethodError(name, desc, _) = &value { // FIXME: 100% sure this String cloning is not needed. let desc = desc.as_ref().map(String::from).unwrap_or_else(|| String::from("")); match name.as_ref() { #error_converts _ => Self::ZBus(value), } } else { Self::ZBus(value) } } } } } zbus_macros-1.9.2/src/iface.rs000064400000000000000000000456320072674642500144320ustar 00000000000000use proc_macro2::TokenStream; use quote::quote; use std::collections::HashMap; use syn::{ self, parse_quote, punctuated::Punctuated, AngleBracketedGenericArguments, AttributeArgs, FnArg, Ident, ImplItem, ItemImpl, Lit::Str, Meta, Meta::NameValue, MetaList, MetaNameValue, NestedMeta, PatType, PathArguments, ReturnType, Signature, Token, Type, TypePath, }; use crate::utils::*; #[derive(Debug)] struct Property<'a> { read: bool, write: bool, ty: Option<&'a Type>, doc_comments: TokenStream, } impl<'a> Property<'a> { fn new() -> Self { Self { read: false, write: false, ty: None, doc_comments: quote!(), } } } pub fn expand(args: AttributeArgs, mut input: ItemImpl) -> syn::Result { let zbus = get_zbus_crate_ident(); let mut properties = HashMap::new(); let mut set_dispatch = quote!(); let mut get_dispatch = quote!(); let mut get_all = quote!(); let mut call_dispatch = quote!(); let mut call_mut_dispatch = quote!(); let mut introspect = quote!(); // the impl Type let ty = match input.self_ty.as_ref() { Type::Path(p) => { &p.path .segments .last() .expect("Unsupported 'impl' type") .ident } _ => panic!("Invalid type"), }; let mut iface_name = None; for arg in args { match arg { NestedMeta::Meta(NameValue(nv)) => { if nv.path.is_ident("interface") || nv.path.is_ident("name") { if let Str(lit) = nv.lit { iface_name = Some(lit.value()); } else { panic!("Invalid interface argument") } } else { panic!("Unsupported argument"); } } _ => panic!("Unknown attribute"), } } let iface_name = iface_name.unwrap_or(format!("org.freedesktop.{}", ty)); for method in input.items.iter_mut().filter_map(|i| { if let ImplItem::Method(m) = i { Some(m) } else { None } }) { let Signature { ident, inputs, output, .. } = &mut method.sig; let attrs = parse_item_attributes(&method.attrs, "dbus_interface") .expect("bad dbus_interface attributes"); method .attrs .retain(|attr| !attr.path.is_ident("dbus_interface")); let docs = get_doc_attrs(&method.attrs) .iter() .filter_map(|attr| { if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() { Some(s.value()) } else { // non #[doc = "..."] attributes are not our concern // we leave them for rustc to handle None } }) .collect(); let doc_comments = to_xml_docs(docs); let is_property = attrs.iter().any(|x| x.is_property()); let is_signal = attrs.iter().any(|x| x.is_signal()); let struct_ret = attrs.iter().any(|x| x.is_struct_return()); assert_eq!(is_property && is_signal && struct_ret, false); let has_inputs = inputs.len() > 1; let is_mut = if let FnArg::Receiver(r) = inputs.first().expect("not &self method") { r.mutability.is_some() } else { panic!("The method is missing a self receiver"); }; let typed_inputs = inputs .iter() .skip(1) .filter_map(|i| { if let FnArg::Typed(t) = i { Some(t) } else { None } }) .collect::>(); let mut intro_args = quote!(); introspect_add_input_args(&mut intro_args, &typed_inputs, is_signal); let is_result_output = introspect_add_output_args(&mut intro_args, &output)?; let (args_from_msg, args) = get_args_from_inputs(&typed_inputs, &zbus)?; clean_input_args(inputs); let reply = if is_result_output { let ret = if struct_ret { quote!((r,)) } else { quote!(r) }; quote!(match reply { Ok(r) => c.reply(m, &#ret), Err(e) => ::#zbus::fdo::Error::from(e).reply(c, m), }) } else if struct_ret { quote!(c.reply(m, &(reply,))) } else { quote!(c.reply(m, &reply)) }; let member_name = attrs .iter() .find_map(|x| match x { ItemAttribute::Name(n) => Some(n.to_string()), _ => None, }) .unwrap_or_else(|| { let mut name = ident.to_string(); if is_property && has_inputs { assert!(name.starts_with("set_")); name = name[4..].to_string(); } pascal_case(&name) }); if is_signal { introspect.extend(doc_comments); introspect_add_signal(&mut introspect, &member_name, &intro_args); method.block = parse_quote!({ ::#zbus::ObjectServer::local_node_emit_signal( None, #iface_name, #member_name, &(#args), ) }); } else if is_property { let p = properties .entry(member_name.to_string()) .or_insert_with(Property::new); p.doc_comments.extend(doc_comments); if has_inputs { p.write = true; let set_call = if is_result_output { quote!(self.#ident(val)) } else { quote!(Ok(self.#ident(val))) }; let q = quote!( #member_name => { let val = match value.try_into() { Ok(val) => val, Err(e) => return Some(Err(::#zbus::MessageError::Variant(e).into())), }; Some(#set_call) } ); set_dispatch.extend(q); } else { p.ty = Some(get_property_type(output)?); p.read = true; let q = quote!( #member_name => { Some(Ok(::#zbus::export::zvariant::Value::from(self.#ident()).into())) }, ); get_dispatch.extend(q); let q = quote!( props.insert( #member_name.to_string(), ::#zbus::export::zvariant::Value::from(self.#ident()).into(), ); ); get_all.extend(q) } } else { introspect.extend(doc_comments); introspect_add_method(&mut introspect, &member_name, &intro_args); let m = quote!( #member_name => { #args_from_msg let reply = self.#ident(#args); Some(#reply) }, ); if is_mut { call_mut_dispatch.extend(m); } else { call_dispatch.extend(m); } } } introspect_add_properties(&mut introspect, properties); let self_ty = &input.self_ty; let generics = &input.generics; let where_clause = &generics.where_clause; Ok(quote! { #input impl #generics ::#zbus::Interface for #self_ty #where_clause { fn name() -> &'static str { #iface_name } fn get( &self, property_name: &str, ) -> Option<::#zbus::fdo::Result<::#zbus::export::zvariant::OwnedValue>> { match property_name { #get_dispatch _ => None, } } fn get_all( &self, ) -> std::collections::HashMap { let mut props: std::collections::HashMap< String, ::#zbus::export::zvariant::OwnedValue, > = std::collections::HashMap::new(); #get_all props } fn set( &mut self, property_name: &str, value: &::#zbus::export::zvariant::Value, ) -> Option<::#zbus::fdo::Result<()>> { use std::convert::TryInto; match property_name { #set_dispatch _ => None, } } fn call( &self, c: &::#zbus::Connection, m: &::#zbus::Message, name: &str, ) -> std::option::Option<::#zbus::Result> { match name { #call_dispatch _ => None, } } fn call_mut( &mut self, c: &::#zbus::Connection, m: &::#zbus::Message, name: &str, ) -> std::option::Option<::#zbus::Result> { match name { #call_mut_dispatch _ => None, } } fn introspect_to_writer(&self, writer: &mut dyn std::fmt::Write, level: usize) { writeln!( writer, r#"{:indent$}"#, "", Self::name(), indent = level ).unwrap(); { use ::#zbus::export::zvariant::Type; let level = level + 2; #introspect } writeln!(writer, r#"{:indent$}"#, "", indent = level).unwrap(); } } }) } fn get_args_from_inputs( inputs: &[&PatType], zbus: &Ident, ) -> syn::Result<(TokenStream, TokenStream)> { if inputs.is_empty() { Ok((quote!(), quote!())) } else { let mut header_arg_decl = None; let mut args = Vec::new(); let mut tys = Vec::new(); for input in inputs { let mut is_header = false; for attr in &input.attrs { if !attr.path.is_ident("zbus") { continue; } let nested = match attr.parse_meta()? { Meta::List(MetaList { nested, .. }) => nested, meta => { return Err(syn::Error::new_spanned( meta, "Unsupported syntax\n Did you mean `#[zbus(...)]`?", )); } }; for item in nested { match item { NestedMeta::Meta(Meta::Path(p)) if p.is_ident("header") => { is_header = true; } NestedMeta::Meta(_) => { return Err(syn::Error::new_spanned( item, "Unrecognized zbus attribute", )); } NestedMeta::Lit(l) => { return Err(syn::Error::new_spanned(l, "Unexpected literal")) } } } } if is_header { if header_arg_decl.is_some() { return Err(syn::Error::new_spanned( input, "There can only be one header argument", )); } let header_arg = &input.pat; header_arg_decl = Some(quote! { let #header_arg = match m.header() { Ok(r) => r, Err(e) => return Some(::#zbus::fdo::Error::from(e).reply(c, m)), }; }); } else { args.push(&input.pat); tys.push(&input.ty); } } let args_from_msg = quote! { #header_arg_decl let (#(#args),*): (#(#tys),*) = match m.body() { Ok(r) => r, Err(e) => return Some(::#zbus::fdo::Error::from(e).reply(c, m)), }; }; let all_args = inputs.iter().map(|t| &t.pat); let all_args = quote! { #(#all_args,)* }; Ok((args_from_msg, all_args)) } } fn clean_input_args(inputs: &mut Punctuated) { for input in inputs { if let FnArg::Typed(t) = input { t.attrs.retain(|attr| !attr.path.is_ident("zbus")); } } } fn introspect_add_signal(introspect: &mut TokenStream, name: &str, args: &TokenStream) { let intro = quote!( writeln!(writer, "{:indent$}", "", #name, indent = level).unwrap(); { let level = level + 2; #args } writeln!(writer, "{:indent$}", "", indent = level).unwrap(); ); introspect.extend(intro); } fn introspect_add_method(introspect: &mut TokenStream, name: &str, args: &TokenStream) { let intro = quote!( writeln!(writer, "{:indent$}", "", #name, indent = level).unwrap(); { let level = level + 2; #args } writeln!(writer, "{:indent$}", "", indent = level).unwrap(); ); introspect.extend(intro); } fn introspect_add_input_args(args: &mut TokenStream, inputs: &[&PatType], is_signal: bool) { for PatType { pat, ty, attrs, .. } in inputs { let is_header_arg = attrs.iter().any(|attr| { if !attr.path.is_ident("zbus") { return false; } let meta = match attr.parse_meta() { Ok(meta) => meta, Err(_) => return false, }; let nested = match meta { Meta::List(MetaList { nested, .. }) => nested, _ => return false, }; let res = nested.iter().any(|nested_meta| { matches!( nested_meta, NestedMeta::Meta(Meta::Path(path)) if path.is_ident("header") ) }); res }); if is_header_arg { continue; } let arg_name = quote!(#pat).to_string(); let dir = if is_signal { "" } else { " direction=\"in\"" }; let arg = quote!( writeln!(writer, "{:indent$}", "", #arg_name, <#ty>::signature(), #dir, indent = level).unwrap(); ); args.extend(arg); } } fn introspect_add_output_arg(args: &mut TokenStream, ty: &Type) { let arg = quote!( writeln!(writer, "{:indent$}", "", <#ty>::signature(), indent = level).unwrap(); ); args.extend(arg); } fn get_result_type(p: &TypePath) -> syn::Result<&Type> { if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &p .path .segments .last() .expect("unsupported result type") .arguments { if let Some(syn::GenericArgument::Type(ty)) = args.first() { return Ok(&ty); } } Err(syn::Error::new_spanned(p, "unhandled Result return")) } fn introspect_add_output_args(args: &mut TokenStream, output: &ReturnType) -> syn::Result { let mut is_result_output = false; if let ReturnType::Type(_, ty) = output { let mut ty = ty.as_ref(); if let Type::Path(p) = ty { is_result_output = p .path .segments .last() .expect("unsupported output type") .ident == "Result"; if is_result_output { ty = get_result_type(p)?; } } if let Type::Tuple(t) = ty { for ty in &t.elems { introspect_add_output_arg(args, ty); } } else { introspect_add_output_arg(args, ty); } } Ok(is_result_output) } fn get_property_type(output: &ReturnType) -> syn::Result<&Type> { if let ReturnType::Type(_, ty) = output { let ty = ty.as_ref(); if let Type::Path(p) = ty { let is_result_output = p .path .segments .last() .expect("unsupported property type") .ident == "Result"; if is_result_output { return get_result_type(p); } } Ok(ty) } else { Err(syn::Error::new_spanned(output, "Invalid property getter")) } } fn introspect_add_properties( introspect: &mut TokenStream, properties: HashMap>, ) { for (name, prop) in properties { let access = if prop.read && prop.write { "readwrite" } else if prop.read { "read" } else if prop.write { "write" } else { eprintln!("Property '{}' is not readable nor writable!", name); continue; }; let ty = prop .ty .expect("Write-only properties aren't supported yet."); introspect.extend(prop.doc_comments); let intro = quote!( writeln!( writer, "{:indent$}", "", #name, <#ty>::signature(), #access, indent = level, ).unwrap(); ); introspect.extend(intro); } } pub fn to_xml_docs(lines: Vec) -> TokenStream { let mut docs = quote!(); let mut lines: Vec<&str> = lines .iter() .skip_while(|s| is_blank(s)) .flat_map(|s| s.split('\n')) .collect(); while let Some(true) = lines.last().map(|s| is_blank(s)) { lines.pop(); } if lines.is_empty() { return docs; } docs.extend(quote!(writeln!(writer, "{:indent$}", "", indent = level).unwrap();)); docs } zbus_macros-1.9.2/src/lib.rs000064400000000000000000000207110072674642500141200ustar 00000000000000#![deny(rust_2018_idioms)] #![doc( html_logo_url = "https://storage.googleapis.com/fdo-gitlab-uploads/project/avatar/3213/zbus-logomark.png" )] //! This crate provides derive macros helpers for zbus. use proc_macro::TokenStream; use syn::{parse_macro_input, AttributeArgs, DeriveInput, ItemImpl, ItemTrait}; mod error; mod iface; mod proxy; mod utils; /// Attribute macro for defining a D-Bus proxy (using zbus [`Proxy`]). /// /// The macro must be applied on a `trait T`. A matching `impl T` will provide the proxy. The proxy /// instance can be created with the associated `new()` or `new_for()` methods. The former doesn't take /// any argument and uses the default service name and path. The later allows you to specify both. /// /// Each trait method will be expanded to call to the associated D-Bus remote interface. /// /// Trait methods accept `dbus_proxy` attributes: /// /// * `name` - override the D-Bus name (pascal case form by default) /// /// * `property` - expose the method as a property. If the method takes an argument, it must be a /// setter, with a `set_` prefix. Otherwise, it's a getter. /// /// * `signal` - declare a signal just like a D-Bus method. The macro will provide a method to /// register and deregister a handler for the signal, whose signature must match that of the /// signature declaration. /// /// NB: Any doc comments provided shall be appended to the ones added by the macro. /// /// (the expanded `impl` also provides an `introspect()` method, for convenience) /// /// # Example /// /// ``` ///# use std::error::Error; /// use zbus_macros::dbus_proxy; /// use zbus::{Connection, Result, fdo}; /// use zvariant::Value; /// /// #[dbus_proxy( /// interface = "org.test.SomeIface", /// default_service = "org.test.SomeService", /// default_path = "/org/test/SomeObject" /// )] /// trait SomeIface { /// fn do_this(&self, with: &str, some: u32, arg: &Value) -> Result; /// /// #[dbus_proxy(property)] /// fn a_property(&self) -> fdo::Result; /// /// #[dbus_proxy(property)] /// fn set_a_property(&self, a_property: &str) -> fdo::Result<()>; /// /// #[dbus_proxy(signal)] /// fn some_signal(&self, arg1: &str, arg2: u32) -> fdo::Result<()>; /// }; /// /// let connection = Connection::new_session()?; /// let proxy = SomeIfaceProxy::new(&connection)?; /// let _ = proxy.do_this("foo", 32, &Value::new(true)); /// let _ = proxy.set_a_property("val"); /// /// proxy.connect_some_signal(|s, u| { /// println!("arg1: {}, arg2: {}", s, u); /// /// Ok(()) /// })?; /// /// // You'll want to make at least a call to `handle_next_signal` before disconnecting the signal. /// assert!(proxy.disconnect_some_signal()?); /// assert!(!proxy.disconnect_some_signal()?); /// ///# Ok::<_, Box>(()) /// ``` /// /// [`zbus_polkit`] is a good example of how to bind a real D-Bus API. /// /// [`zbus_polkit`]: https://docs.rs/zbus_polkit/1.0.0/zbus_polkit/policykit1/index.html /// [`Proxy`]: https://docs.rs/zbus/1.0.0/zbus/struct.Proxy.html /// [`zbus::SignalReceiver::receive_for`]: /// https://docs.rs/zbus/1.5.0/zbus/struct.SignalReceiver.html#method.receive_for #[proc_macro_attribute] pub fn dbus_proxy(attr: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attr as AttributeArgs); let input = parse_macro_input!(item as ItemTrait); proxy::expand(args, input).into() } /// Attribute macro for implementing a D-Bus interface. /// /// The macro must be applied on an `impl T`. All methods will be exported, either as methods, /// properties or signal depending on the item attributes. It will implement the [`Interface`] trait /// `for T` on your behalf, to handle the message dispatching and introspection support. /// /// The methods accepts the `dbus_interface` attributes: /// /// * `name` - override the D-Bus name (pascal case form of the method by default) /// /// * `property` - expose the method as a property. If the method takes an argument, it must be a /// setter, with a `set_` prefix. Otherwise, it's a getter. /// /// * `signal` - the method is a "signal". It must be a method declaration (without body). Its code /// block will be expanded to emit the signal from the object path associated with the interface /// instance. /// /// You can call a signal method from a an interface method, or from an [`ObjectServer::with`] /// function. /// /// * `struct_return` - the method returns a structure. Although it is very rare for a D-Bus method /// to return a single structure, it does happen. Since it is not possible for zbus to /// differentiate this case from multiple out arguments put in a structure, you must either /// explicitly mark the case of single structure return with this attribute or wrap the structure /// in another structure or tuple. The latter can be achieve by using `(,)` syntax, for example /// instead of `MyStruct`, write `(MyStruct,)`. /// /// The method arguments offers some the following `zbus` attributes: /// /// * `header` - This marks the method argument to receive the message header associated with the /// D-Bus method call being handled. /// /// # Example /// /// ``` ///# use std::error::Error; /// use zbus_macros::dbus_interface; /// use zbus::MessageHeader; /// /// struct Example { /// some_data: String, /// } /// /// #[dbus_interface(name = "org.myservice.Example")] /// impl Example { /// // "Quit" method. A method may throw errors. /// fn quit(&self, #[zbus(header)] hdr: MessageHeader<'_>) -> zbus::fdo::Result<()> { /// let path = hdr.path()?.unwrap(); /// let msg = format!("You are leaving me on the {} path?", path); /// /// Err(zbus::fdo::Error::Failed(msg)) /// } /// /// // "TheAnswer" property (note: the "name" attribute), with its associated getter. /// #[dbus_interface(property, name = "TheAnswer")] /// fn answer(&self) -> u32 { /// 2 * 3 * 7 /// } /// /// // "Notify" signal (note: no implementation body). /// #[dbus_interface(signal)] /// fn notify(&self, message: &str) -> zbus::Result<()>; /// } /// ///# Ok::<_, Box>(()) /// ``` /// /// See also [`ObjectServer`] documentation to learn how to export an interface over a `Connection`. /// /// [`ObjectServer`]: https://docs.rs/zbus/1.0.0/zbus/struct.ObjectServer.html /// [`ObjectServer::with`]: https://docs.rs/zbus/1.2.0/zbus/struct.ObjectServer.html#method.with /// [`Connection::emit_signal()`]: https://docs.rs/zbus/1.0.0/zbus/struct.Connection.html#method.emit_signal /// [`Interface`]: https://docs.rs/zbus/1.0.0/zbus/trait.Interface.html #[proc_macro_attribute] pub fn dbus_interface(attr: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attr as AttributeArgs); let input = syn::parse_macro_input!(item as ItemImpl); iface::expand(args, input) .unwrap_or_else(|err| err.to_compile_error()) .into() } /// Derive macro for defining a D-Bus error. /// /// This macro helps to implement an [`Error`] suitable for D-Bus handling with zbus. It will expand /// an `enum E` with [`Error`] traits implementation, and `From`. The latter makes it /// possible for you to declare proxy methods to directly return this type, rather than /// [`zbus::Error`]. However, for this to work, we require a variant by the name `ZBus` that /// contains an unnamed field of type [`zbus::Error`]. /// /// Additionnally, the derived `impl E` will provide the following convenience methods: /// /// * `name(&self)` - get the associated D-Bus error name. /// /// * `description(&self)` - get the associated error description (the first argument of an error /// message) /// /// * `reply(&self, &zbus::Connection, &zbus::Message)` - send this error as reply to the message. /// /// Note: it is recommended that errors take a single argument `String` which describes it in /// a human-friendly fashion (support for other arguments is limited or TODO currently). /// /// # Example /// /// ``` /// use zbus_macros::DBusError; /// /// #[derive(DBusError, Debug)] /// #[dbus_error(prefix = "org.myservice.App")] /// enum Error { /// ZBus(zbus::Error), /// FileNotFound(String), /// OutOfMemory, /// } /// ``` /// /// [`Error`]: http://doc.rust-lang.org/std/error/trait.Error.html /// [`zbus::Error`]: https://docs.rs/zbus/1.0.0/zbus/enum.Error.html #[proc_macro_derive(DBusError, attributes(dbus_error))] pub fn derive_dbus_error(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); error::expand_derive(input).into() } zbus_macros-1.9.2/src/proxy.rs000064400000000000000000000234470072674642500145440ustar 00000000000000use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{self, AttributeArgs, FnArg, Ident, ItemTrait, NestedMeta, TraitItemMethod, Type}; use crate::utils::*; pub fn expand(args: AttributeArgs, input: ItemTrait) -> TokenStream { let mut iface_name = None; let mut default_path = None; let mut default_service = None; let mut has_introspect_method = false; let zbus = get_zbus_crate_ident(); for arg in args { match arg { NestedMeta::Meta(syn::Meta::NameValue(nv)) => { if nv.path.is_ident("interface") || nv.path.is_ident("name") { if let syn::Lit::Str(lit) = nv.lit { iface_name = Some(lit.value()); } else { panic!("Invalid interface argument") } } else if nv.path.is_ident("default_path") { if let syn::Lit::Str(lit) = nv.lit { default_path = Some(lit.value()); } else { panic!("Invalid path argument") } } else if nv.path.is_ident("default_service") { if let syn::Lit::Str(lit) = nv.lit { default_service = Some(lit.value()); } else { panic!("Invalid service argument") } } else { panic!("Unsupported argument"); } } _ => panic!("Unknown attribute"), } } let doc = get_doc_attrs(&input.attrs); let proxy_name = Ident::new(&format!("{}Proxy", input.ident), Span::call_site()); let ident = input.ident.to_string(); let name = iface_name.unwrap_or(format!("org.freedesktop.{}", ident)); let default_path = default_path.unwrap_or(format!("/org/freedesktop/{}", ident)); let default_service = default_service.unwrap_or_else(|| name.clone()); let mut methods = TokenStream::new(); for i in input.items.iter() { if let syn::TraitItem::Method(m) = i { let method_name = m.sig.ident.to_string(); if method_name == "introspect" { has_introspect_method = true; } let attrs = parse_item_attributes(&m.attrs, "dbus_proxy").unwrap(); let is_property = attrs.iter().any(|x| x.is_property()); let is_signal = attrs.iter().any(|x| x.is_signal()); let has_inputs = m.sig.inputs.len() > 1; let name = attrs .iter() .find_map(|x| match x { ItemAttribute::Name(n) => Some(n.to_string()), _ => None, }) .unwrap_or_else(|| { pascal_case(if is_property && has_inputs { assert!(method_name.starts_with("set_")); &method_name[4..] } else { &method_name }) }); let m = if is_property { gen_proxy_property(&name, &m) } else if is_signal { gen_proxy_signal(&name, &method_name, &m) } else { gen_proxy_method_call(&name, &m) }; methods.extend(m); } } if !has_introspect_method { methods.extend(quote! { pub fn introspect(&self) -> ::#zbus::fdo::Result { self.0.introspect() } }); }; quote! { #(#doc)* pub struct #proxy_name<'c>(::#zbus::Proxy<'c>); impl<'c> #proxy_name<'c> { /// Creates a new proxy with the default service & path. pub fn new(conn: &::#zbus::Connection) -> ::#zbus::Result { Ok(Self(::#zbus::Proxy::new( conn, #default_service, #default_path, #name, )?)) } /// Creates a new proxy for the given `destination` and `path`. pub fn new_for( conn: &::#zbus::Connection, destination: &'c str, path: &'c str, ) -> ::#zbus::Result { Ok(Self(::#zbus::Proxy::new( conn, destination, path, #name, )?)) } /// Same as `new_for` but takes ownership of the passed arguments. pub fn new_for_owned( conn: ::#zbus::Connection, destination: String, path: String, ) -> ::#zbus::Result { Ok(Self(::#zbus::Proxy::new_owned( conn, destination, path, #name.to_owned(), )?)) } /// Creates a new proxy for the given `path`. pub fn new_for_path( conn: &::#zbus::Connection, path: &'c str, ) -> ::#zbus::Result { Ok(Self(::#zbus::Proxy::new( conn, #default_service, path, #name, )?)) } /// Same as `new_for_path` but takes ownership of the passed arguments. pub fn new_for_owned_path( conn: ::#zbus::Connection, path: String, ) -> ::#zbus::Result { Ok(Self(::#zbus::Proxy::new_owned( conn, #default_service.to_owned(), path, #name.to_owned(), )?)) } /// Consumes `self`, returning the underlying `zbus::Proxy`. pub fn into_inner(self) -> ::#zbus::Proxy<'c> { self.0 } /// The reference to the underlying `zbus::Proxy`. pub fn inner(&self) -> &::#zbus::Proxy { &self.0 } #methods } impl<'c> std::ops::Deref for #proxy_name<'c> { type Target = ::#zbus::Proxy<'c>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'c> std::ops::DerefMut for #proxy_name<'c> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl<'c> std::convert::AsRef<::#zbus::Proxy<'c>> for #proxy_name<'c> { fn as_ref(&self) -> &::#zbus::Proxy<'c> { &*self } } impl<'c> std::convert::AsMut<::#zbus::Proxy<'c>> for #proxy_name<'c> { fn as_mut(&mut self) -> &mut ::#zbus::Proxy<'c> { &mut *self } } } } fn gen_proxy_method_call(method_name: &str, m: &TraitItemMethod) -> TokenStream { let doc = get_doc_attrs(&m.attrs); let args = m.sig.inputs.iter().filter_map(|arg| arg_ident(arg)); let signature = &m.sig; quote! { #(#doc)* pub #signature { let reply = self.0.call(#method_name, &(#(#args),*))?; Ok(reply) } } } fn gen_proxy_property(property_name: &str, m: &TraitItemMethod) -> TokenStream { let doc = get_doc_attrs(&m.attrs); let signature = &m.sig; if signature.inputs.len() > 1 { let value = arg_ident(signature.inputs.last().unwrap()).unwrap(); quote! { #(#doc)* #[allow(clippy::needless_question_mark)] pub #signature { Ok(self.0.set_property(#property_name, #value)?) } } } else { quote! { #(#doc)* #[allow(clippy::needless_question_mark)] pub #signature { Ok(self.0.get_property(#property_name)?) } } } } fn gen_proxy_signal(signal_name: &str, snake_case_name: &str, m: &TraitItemMethod) -> TokenStream { let zbus = get_zbus_crate_ident(); let doc = get_doc_attrs(&m.attrs); let connect_method = format_ident!("connect_{}", snake_case_name); let disconnect_method = Ident::new( &format!("disconnect_{}", snake_case_name), Span::call_site(), ); let input_types: Vec> = m .sig .inputs .iter() .filter_map(|arg| match arg { FnArg::Typed(p) => Some(p.ty.clone()), _ => None, }) .collect(); let args: Vec = m .sig .inputs .iter() .filter_map(|arg| arg_ident(arg).cloned()) .collect(); let connect_gen_doc = format!( "Connect the handler for the `{}` signal. This is a convenient wrapper around \ [`zbus::Proxy::connect_signal`](https://docs.rs/zbus/latest/zbus/struct.Proxy.html\ #method.connect_signal). ", signal_name, ); let disconnect_gen_doc = format!( "Disconnected the handler (if any) for the `{}` signal. This is a convenient wrapper \ around [`zbus::Proxy::disconnect_signal`]\ (https://docs.rs/zbus/latest/zbus/struct.Proxy.html#method.disconnect_signal). ", signal_name, ); quote! { #[doc = #connect_gen_doc] #(#doc)* pub fn #connect_method(&self, mut handler: H) -> ::#zbus::fdo::Result<()> where H: FnMut(#(#input_types),*) -> ::zbus::Result<()> + Send + 'static, { self.0.connect_signal(#signal_name, move |m| { let (#(#args),*) = m.body().expect("Incorrect signal signature"); handler(#(#args),*) }) } #[doc = #disconnect_gen_doc] #(#doc)* pub fn #disconnect_method(&self) -> ::#zbus::fdo::Result { self.0.disconnect_signal(#signal_name) } } } zbus_macros-1.9.2/src/utils.rs000064400000000000000000000102430072674642500145110ustar 00000000000000use proc_macro2::Span; use proc_macro_crate::crate_name; use syn::{ Attribute, FnArg, Ident, Lit, Meta, MetaList, NestedMeta, Pat, PatIdent, PatType, Result, }; pub fn get_zbus_crate_ident() -> Ident { Ident::new( crate_name("zbus") .as_ref() .map(String::as_str) .unwrap_or("zbus"), Span::call_site(), ) } pub fn arg_ident(arg: &FnArg) -> Option<&Ident> { match arg { FnArg::Typed(PatType { pat, .. }) => { if let Pat::Ident(PatIdent { ident, .. }) = &**pat { return Some(ident); } None } _ => None, } } pub fn get_doc_attrs(attrs: &[Attribute]) -> Vec<&Attribute> { attrs.iter().filter(|x| x.path.is_ident("doc")).collect() } pub fn pascal_case(s: &str) -> String { let mut pascal = String::new(); let mut capitalize = true; for ch in s.chars() { if ch == '_' { capitalize = true; } else if capitalize { pascal.push(ch.to_ascii_uppercase()); capitalize = false; } else { pascal.push(ch); } } pascal } #[derive(Debug, PartialEq)] pub enum ItemAttribute { Property, Signal, StructReturn, Name(String), } impl ItemAttribute { pub fn is_property(&self) -> bool { self == &Self::Property } pub fn is_signal(&self) -> bool { self == &Self::Signal } pub fn is_struct_return(&self) -> bool { self == &Self::StructReturn } } // find the #[@attr_name] attribute in @attrs pub fn find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result> { let meta = match attrs.iter().find(|a| a.path.is_ident(attr_name)) { Some(a) => a.parse_meta(), _ => return Ok(None), }; match meta? { Meta::List(n) => Ok(Some(n)), _ => panic!("wrong meta type"), } } // parse a single meta like: ident = "value" fn parse_attribute(meta: &NestedMeta) -> (String, String) { let meta = match &meta { NestedMeta::Meta(m) => m, _ => panic!("wrong meta type"), }; let meta = match meta { Meta::Path(p) => return (p.get_ident().unwrap().to_string(), "".to_string()), Meta::NameValue(n) => n, _ => panic!("wrong meta type"), }; let value = match &meta.lit { Lit::Str(s) => s.value(), _ => panic!("wrong meta type"), }; let ident = match meta.path.get_ident() { None => panic!("missing ident"), Some(ident) => ident, }; (ident.to_string(), value) } fn proxy_parse_item_attribute(meta: &NestedMeta) -> Result { let (ident, v) = parse_attribute(meta); match ident.as_ref() { "name" => Ok(ItemAttribute::Name(v)), "property" => Ok(ItemAttribute::Property), "signal" => Ok(ItemAttribute::Signal), "struct_return" => Ok(ItemAttribute::StructReturn), s => panic!("Unknown item meta {}", s), } } // Parse optional item attributes such as: // #[dbus_proxy(name = "MyName", property)] pub fn parse_item_attributes(attrs: &[Attribute], attr_name: &str) -> Result> { let meta = find_attribute_meta(attrs, attr_name)?; let v = match meta { Some(meta) => meta .nested .iter() .map(|m| proxy_parse_item_attribute(&m).unwrap()) .collect(), None => Vec::new(), }; Ok(v) } fn error_parse_item_attribute(meta: &NestedMeta) -> Result { let (ident, v) = parse_attribute(meta); match ident.as_ref() { "name" => Ok(ItemAttribute::Name(v)), s => panic!("Unknown item meta {}", s), } } // Parse optional item attributes such as: // #[dbus_error(name = "MyName")] pub fn error_parse_item_attributes(attrs: &[Attribute]) -> Result> { let meta = find_attribute_meta(attrs, "dbus_error")?; let v = match meta { Some(meta) => meta .nested .iter() .map(|m| error_parse_item_attribute(&m).unwrap()) .collect(), None => Vec::new(), }; Ok(v) } pub fn is_blank(s: &str) -> bool { s.trim().is_empty() } zbus_macros-1.9.2/tests/compiletest.rs000064400000000000000000000002170072674642500162540ustar 00000000000000#[rustversion::attr(before(1.48), ignore)] #[test] fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/**/*.rs"); } zbus_macros-1.9.2/tests/tests.rs000064400000000000000000000075750072674642500151040ustar 00000000000000use zbus::{self, fdo}; use zbus_macros::{dbus_interface, dbus_proxy, DBusError}; #[test] fn test_proxy() { #[dbus_proxy( interface = "org.freedesktop.zbus.Test", default_service = "org.freedesktop.zbus", default_path = "/org/freedesktop/zbus/test" )] trait Test { /// comment for a_test() fn a_test(&self, val: &str) -> zbus::Result; #[dbus_proxy(name = "CheckRENAMING")] fn check_renaming(&self) -> zbus::Result>; #[dbus_proxy(property)] fn property(&self) -> fdo::Result>; #[dbus_proxy(property)] fn set_property(&self, val: u16) -> fdo::Result<()>; } } #[test] fn test_derive_error() { #[derive(Debug, DBusError)] #[dbus_error(prefix = "org.freedesktop.zbus")] enum Test { ZBus(zbus::Error), SomeExcuse, #[dbus_error(name = "I.Am.Sorry.Dave")] IAmSorryDave(String), LetItBe { desc: String, }, } } #[test] fn test_interface() { use zbus::Interface; struct Test<'a, T> { something: &'a str, generic: T, } #[dbus_interface(name = "org.freedesktop.zbus.Test")] impl Test<'static, T> where T: serde::ser::Serialize + zvariant::Type, { /// Testing `no_arg` documentation is reflected in XML. fn no_arg(&self) { unimplemented!() } fn str_u32(&self, val: &str) -> zbus::fdo::Result { val.parse() .map_err(|e| zbus::fdo::Error::Failed(format!("Invalid val: {}", e))) } // TODO: naming output arguments after "RFC: Structural Records #2584" fn many_output(&self) -> zbus::fdo::Result<(&T, String)> { Ok((&self.generic, self.something.to_string())) } fn pair_output(&self) -> zbus::fdo::Result<((u32, String),)> { unimplemented!() } #[dbus_interface(name = "CheckVEC")] fn check_vec(&self) -> Vec { unimplemented!() } /// Testing my_prop documentation is reflected in XML. /// /// And that too. #[dbus_interface(property)] fn my_prop(&self) -> u16 { unimplemented!() } #[dbus_interface(property)] fn set_my_prop(&self, _val: u16) { unimplemented!() } /// Emit a signal. #[dbus_interface(signal)] fn signal(&self, arg: u8, other: &str) -> zbus::Result<()>; } const EXPECTED_XML: &str = r#" "#; let t = Test { something: &"somewhere", generic: 42u32, }; let mut xml = String::new(); t.introspect_to_writer(&mut xml, 0); assert_eq!(xml, EXPECTED_XML); assert_eq!(Test::::name(), "org.freedesktop.zbus.Test"); if false { // check compilation let c = zbus::Connection::new_session().unwrap(); let m = zbus::Message::method(None, None, "/", None, "StrU32", &(42,)).unwrap(); let _ = t.call(&c, &m, "StrU32").unwrap(); t.signal(23, "ergo sum").unwrap(); } } zbus_macros-1.9.2/tests/ui/proxy/no_zvariant_type_impl.rs000064400000000000000000000007700072674642500221420ustar 00000000000000use serde::{Deserialize, Serialize}; use zbus::fdo; use zbus_macros::dbus_proxy; #[derive(Deserialize, Serialize)] struct Foo; #[dbus_proxy( interface = "org.freedesktop.zbus.Test", default_service = "org.freedesktop.zbus", default_path = "/org/freedesktop/zbus/test" )] trait Test { fn invalid_arg(&self, arg: Foo) -> zbus::Result<()>; fn invalid_result(&self) -> zbus::Result; #[dbus_proxy(property)] fn invalid_property(&self) -> fdo::Result; } fn main() {} zbus_macros-1.9.2/tests/ui/proxy/no_zvariant_type_impl.stderr000064400000000000000000000033410072674642500230160ustar 00000000000000warning: use of deprecated function `nb_connect::unix`: This crate is now deprecated in favor of [socket2](https://crates.io/crates/socket2). --> $WORKSPACE/zbus/src/address.rs | | use nb_connect::unix; | ^^^^^^^^^^^^^^^^ | = note: `#[warn(deprecated)]` on by default warning: use of deprecated function `nb_connect::unix`: This crate is now deprecated in favor of [socket2](https://crates.io/crates/socket2). --> $WORKSPACE/zbus/src/address.rs | | let stream = unix(p)?; | ^^^^ warning: 2 warnings emitted error[E0277]: the trait bound `Foo: Type` is not satisfied --> tests/ui/proxy/no_zvariant_type_impl.rs:8:1 | 8 | / #[dbus_proxy( 9 | | interface = "org.freedesktop.zbus.Test", 10 | | default_service = "org.freedesktop.zbus", 11 | | default_path = "/org/freedesktop/zbus/test" 12 | | )] | |__^ the trait `Type` is not implemented for `Foo` | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Foo: From` is not satisfied --> tests/ui/proxy/no_zvariant_type_impl.rs:8:1 | 8 | / #[dbus_proxy( 9 | | interface = "org.freedesktop.zbus.Test", 10 | | default_service = "org.freedesktop.zbus", 11 | | default_path = "/org/freedesktop/zbus/test" 12 | | )] | |__^ the trait `From` is not implemented for `Foo` | = note: required because of the requirements on the impl of `Into` for `OwnedValue` = note: required because of the requirements on the impl of `TryFrom` for `Foo` = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)