1 | #![deny (rust_2018_idioms)] |
2 | #![doc ( |
3 | html_logo_url = "https://storage.googleapis.com/fdo-gitlab-uploads/project/avatar/3213/zbus-logomark.png" |
4 | )] |
5 | #![doc = include_str!("../README.md" )] |
6 | #![doc (test(attr( |
7 | warn(unused), |
8 | deny(warnings), |
9 | // W/o this, we seem to get some bogus warning about `extern crate zbus`. |
10 | allow(unused_extern_crates), |
11 | )))] |
12 | |
13 | use proc_macro::TokenStream; |
14 | use syn::{parse_macro_input, AttributeArgs, DeriveInput, ItemImpl, ItemTrait}; |
15 | |
16 | mod error; |
17 | mod iface; |
18 | mod proxy; |
19 | mod utils; |
20 | |
21 | /// Attribute macro for defining D-Bus proxies (using [`zbus::Proxy`] and |
22 | /// [`zbus::blocking::Proxy`]). |
23 | /// |
24 | /// The macro must be applied on a `trait T`. Two matching `impl T` will provide an asynchronous |
25 | /// Proxy implementation, named `TraitNameProxy` and a blocking one, named `TraitNameProxyBlocking`. |
26 | /// The proxy instances can be created with the associated `new()` or `builder()` methods. The |
27 | /// former doesn't take any argument and uses the default service name and path. The later allows |
28 | /// you to specify non-default proxy arguments. |
29 | /// |
30 | /// The following attributes are supported: |
31 | /// |
32 | /// * `interface` - the name of the D-Bus interface this proxy is for. |
33 | /// |
34 | /// * `default_service` - the default service this proxy should connect to. |
35 | /// |
36 | /// * `default_path` - The default object path the method calls will be sent on and signals will be |
37 | /// sent for by the target service. |
38 | /// |
39 | /// * `gen_async` - Whether or not to generate the asynchronous Proxy type. |
40 | /// |
41 | /// * `gen_blocking` - Whether or not to generate the blocking Proxy type. If set to `false`, the |
42 | /// asynchronous proxy type will take the name `TraitNameProxy` (i-e no `Async` prefix). |
43 | /// |
44 | /// * `async_name` - Specify the exact name of the asynchronous proxy type. |
45 | /// |
46 | /// * `blocking_name` - Specify the exact name of the blocking proxy type. |
47 | /// |
48 | /// * `assume_defaults` - whether to auto-generate values for `default_path` and `default_service` |
49 | /// if none are specified (default: `true`). `dbus_proxy` currently generates a warning if neither |
50 | /// this attribute nor one of the default values are specified. A future release will change the |
51 | /// default to `false`. Please make sure to explicitly set either this attribute or the default |
52 | /// values, according to your needs. |
53 | /// |
54 | /// Each trait method will be expanded to call to the associated D-Bus remote interface. |
55 | /// |
56 | /// Trait methods accept `dbus_proxy` attributes: |
57 | /// |
58 | /// * `name` - override the D-Bus name (pascal case form by default) |
59 | /// |
60 | /// * `property` - expose the method as a property. If the method takes an argument, it must be a |
61 | /// setter, with a `set_` prefix. Otherwise, it's a getter. Additional sub-attributes exists to |
62 | /// control specific property behaviors: |
63 | /// * `emits_changed_signal` - specifies how property changes are signaled. Valid values are those |
64 | /// documented in [DBus specifications][dbus_emits_changed_signal]: |
65 | /// * `"true"` - (default) change signal is always emitted with the value included. This uses |
66 | /// the default caching behavior of the proxy, and generates a listener method for the change |
67 | /// signal. |
68 | /// * `"invalidates"` - change signal is emitted, but the value is not included in the signal. |
69 | /// This has the same behavior as `"true"`. |
70 | /// * `"const"` - property never changes, thus no signal is ever emitted for it. This uses the |
71 | /// default caching behavior of the proxy, but does not generate a listener method for the |
72 | /// change signal. |
73 | /// * `"false"` - change signal is not (guaranteed to be) emitted if the property changes. This |
74 | /// disables property value caching, and does not generate a listener method for the change |
75 | /// signal. |
76 | /// |
77 | /// * `signal` - declare a signal just like a D-Bus method. Read the [Signals](#signals) section |
78 | /// below for details. |
79 | /// |
80 | /// * `no_reply` - declare a method call that does not wait for a reply. |
81 | /// |
82 | /// * `no_autostart` - declare a method call that will not trigger the bus to automatically launch |
83 | /// the destination service if it is not already running. |
84 | /// |
85 | /// * `allow_interactive_auth` - declare a method call that is allowed to trigger an interactive |
86 | /// prompt for authorization or confirmation from the receiver. |
87 | /// |
88 | /// * `object` - methods that returns an [`ObjectPath`] can be annotated with the `object` attribute |
89 | /// to specify the proxy object to be constructed from the returned [`ObjectPath`]. |
90 | /// |
91 | /// * `async_object` - if the assumptions made by `object` attribute about naming of the |
92 | /// asynchronous proxy type, don't fit your bill, you can use this to specify its exact name. |
93 | /// |
94 | /// * `blocking_object` - if the assumptions made by `object` attribute about naming of the blocking |
95 | /// proxy type, don't fit your bill, you can use this to specify its exact name. |
96 | /// |
97 | /// NB: Any doc comments provided shall be appended to the ones added by the macro. |
98 | /// |
99 | /// # Signals |
100 | /// |
101 | /// For each signal method declared, this macro will provide a method, named `receive_<method_name>` |
102 | /// to create a [`zbus::SignalStream`] ([`zbus::blocking::SignalIterator`] for the blocking proxy) |
103 | /// wrapper, named `<SignalName>Stream` (`<SignalName>Iterator` for the blocking proxy) that yield |
104 | /// a [`zbus::Message`] wrapper, named `<SignalName>`. This wrapper provides type safe access to the |
105 | /// signal arguments. It also implements `Deref<Target = Message>` to allow easy access to the |
106 | /// underlying [`zbus::Message`]. |
107 | /// |
108 | /// # Example |
109 | /// |
110 | /// ```no_run |
111 | /// # use std::error::Error; |
112 | /// use zbus_macros::dbus_proxy; |
113 | /// use zbus::{blocking::Connection, Result, fdo, zvariant::Value}; |
114 | /// use futures_util::stream::StreamExt; |
115 | /// use async_io::block_on; |
116 | /// |
117 | /// #[dbus_proxy( |
118 | /// interface = "org.test.SomeIface" , |
119 | /// default_service = "org.test.SomeService" , |
120 | /// default_path = "/org/test/SomeObject" |
121 | /// )] |
122 | /// trait SomeIface { |
123 | /// fn do_this(&self, with: &str, some: u32, arg: &Value<'_>) -> Result<bool>; |
124 | /// |
125 | /// #[dbus_proxy(property)] |
126 | /// fn a_property(&self) -> fdo::Result<String>; |
127 | /// |
128 | /// #[dbus_proxy(property)] |
129 | /// fn set_a_property(&self, a_property: &str) -> fdo::Result<()>; |
130 | /// |
131 | /// #[dbus_proxy(signal)] |
132 | /// fn some_signal(&self, arg1: &str, arg2: u32) -> fdo::Result<()>; |
133 | /// |
134 | /// #[dbus_proxy(object = "SomeOtherIface" , blocking_object = "SomeOtherInterfaceBlock" )] |
135 | /// // The method will return a `SomeOtherIfaceProxy` or `SomeOtherIfaceProxyBlock`, depending |
136 | /// // on whether it is called on `SomeIfaceProxy` or `SomeIfaceProxyBlocking`, respectively. |
137 | /// // |
138 | /// // NB: We explicitly specified the exact name of the blocking proxy type. If we hadn't, |
139 | /// // `SomeOtherIfaceProxyBlock` would have been assumed and expected. We could also specify |
140 | /// // the specific name of the asynchronous proxy types, using the `async_object` attribute. |
141 | /// fn some_method(&self, arg1: &str); |
142 | /// } |
143 | /// |
144 | /// #[dbus_proxy( |
145 | /// interface = "org.test.SomeOtherIface" , |
146 | /// default_service = "org.test.SomeOtherService" , |
147 | /// blocking_name = "SomeOtherInterfaceBlock" , |
148 | /// )] |
149 | /// trait SomeOtherIface {} |
150 | /// |
151 | /// let connection = Connection::session()?; |
152 | /// // Use `builder` to override the default arguments, `new` otherwise. |
153 | /// let proxy = SomeIfaceProxyBlocking::builder(&connection) |
154 | /// .destination("org.another.Service" )? |
155 | /// .cache_properties(zbus::CacheProperties::No) |
156 | /// .build()?; |
157 | /// let _ = proxy.do_this("foo" , 32, &Value::new(true)); |
158 | /// let _ = proxy.set_a_property("val" ); |
159 | /// |
160 | /// let signal = proxy.receive_some_signal()?.next().unwrap(); |
161 | /// let args = signal.args()?; |
162 | /// println!("arg1: {}, arg2: {}" , args.arg1(), args.arg2()); |
163 | /// |
164 | /// // Now the same again, but asynchronous. |
165 | /// block_on(async move { |
166 | /// let proxy = SomeIfaceProxy::builder(&connection.into()) |
167 | /// .cache_properties(zbus::CacheProperties::No) |
168 | /// .build() |
169 | /// .await |
170 | /// .unwrap(); |
171 | /// let _ = proxy.do_this("foo" , 32, &Value::new(true)).await; |
172 | /// let _ = proxy.set_a_property("val" ).await; |
173 | /// |
174 | /// let signal = proxy.receive_some_signal().await?.next().await.unwrap(); |
175 | /// let args = signal.args()?; |
176 | /// println!("arg1: {}, arg2: {}" , args.arg1(), args.arg2()); |
177 | /// |
178 | /// Ok::<(), zbus::Error>(()) |
179 | /// })?; |
180 | /// |
181 | /// # Ok::<_, Box<dyn Error + Send + Sync>>(()) |
182 | /// ``` |
183 | /// |
184 | /// [`zbus_polkit`] is a good example of how to bind a real D-Bus API. |
185 | /// |
186 | /// [`zbus_polkit`]: https://docs.rs/zbus_polkit/1.0.0/zbus_polkit/policykit1/index.html |
187 | /// [`zbus::Proxy`]: https://docs.rs/zbus/3.0.0/zbus/struct.Proxy.html |
188 | /// [`zbus::Message`]: https://docs.rs/zbus/3.0.0/zbus/struct.Message.html |
189 | /// [`zbus::blocking::Proxy`]: https://docs.rs/zbus/3.0.0/zbus/blocking/struct.Proxy.html |
190 | /// [`zbus::SignalStream`]: https://docs.rs/zbus/3.0.0/zbus/struct.SignalStream.html |
191 | /// [`zbus::blocking::SignalIterator`]: https://docs.rs/zbus/3.0.0/zbus/blocking/struct.SignalIterator.html |
192 | /// [`zbus::SignalReceiver::receive_for`]: |
193 | /// https://docs.rs/zbus/3.0.0/zbus/struct.SignalReceiver.html#method.receive_for |
194 | /// [`ObjectPath`]: https://docs.rs/zvariant/2.10.0/zvariant/struct.ObjectPath.html |
195 | /// [dbus_emits_changed_signal]: https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format |
196 | #[proc_macro_attribute ] |
197 | pub fn dbus_proxy (attr: TokenStream, item: TokenStream) -> TokenStream { |
198 | let args: Vec = parse_macro_input!(attr as AttributeArgs); |
199 | let input: ItemTrait = parse_macro_input!(item as ItemTrait); |
200 | proxyTokenStream::expand(args, input) |
201 | .unwrap_or_else(|err: Error| err.to_compile_error()) |
202 | .into() |
203 | } |
204 | |
205 | /// Attribute macro for implementing a D-Bus interface. |
206 | /// |
207 | /// The macro must be applied on an `impl T`. All methods will be exported, either as methods, |
208 | /// properties or signal depending on the item attributes. It will implement the [`Interface`] trait |
209 | /// `for T` on your behalf, to handle the message dispatching and introspection support. |
210 | /// |
211 | /// The methods accepts the `dbus_interface` attributes: |
212 | /// |
213 | /// * `name` - override the D-Bus name (pascal case form of the method by default) |
214 | /// |
215 | /// * `property` - expose the method as a property. If the method takes an argument, it must be a |
216 | /// setter, with a `set_` prefix. Otherwise, it's a getter. If it may fail, a property method must |
217 | /// return `zbus::fdo::Result`. |
218 | /// |
219 | /// * `signal` - the method is a "signal". It must be a method declaration (without body). Its code |
220 | /// block will be expanded to emit the signal from the object path associated with the interface |
221 | /// instance. |
222 | /// |
223 | /// You can call a signal method from a an interface method, or from an [`ObjectServer::with`] |
224 | /// function. |
225 | /// |
226 | /// * `out_args` - When returning multiple values from a method, naming the out arguments become |
227 | /// important. You can use `out_args` to specify their names. |
228 | /// |
229 | /// In such case, your method must return a tuple containing |
230 | /// your out arguments, in the same order as passed to `out_args`. |
231 | /// |
232 | /// The `struct_return` attribute (from zbus 1.x) is no longer supported. If you want to return a |
233 | /// single structure from a method, declare it to return a tuple containing either a named structure |
234 | /// or a nested tuple. |
235 | /// |
236 | /// Note: a `<property_name_in_snake_case>_changed` method is generated for each property: this |
237 | /// method emits the "PropertiesChanged" signal for the associated property. The setter (if it |
238 | /// exists) will automatically call this method. For instance, a property setter named `set_foo` |
239 | /// will be called to set the property "Foo", and will emit the "PropertiesChanged" signal with the |
240 | /// new value for "Foo". Other changes to the "Foo" property can be signaled manually with the |
241 | /// generated `foo_changed` method. In addition, a `<property_name_in_snake_case>_invalidated` |
242 | /// method is also generated that much like `_changed` method, emits a "PropertyChanged" signal |
243 | /// but does not send over the new value of the property along with it. It is usually best to avoid |
244 | /// using this since it will force all interested peers to fetch the new value and hence result in |
245 | /// excess traffic on the bus. |
246 | /// |
247 | /// The method arguments support the following `zbus` attributes: |
248 | /// |
249 | /// * `object_server` - This marks the method argument to receive a reference to the |
250 | /// [`ObjectServer`] this method was called by. |
251 | /// * `connection` - This marks the method argument to receive a reference to the [`Connection`] on |
252 | /// which the method call was received. |
253 | /// * `header` - This marks the method argument to receive the message header associated with the |
254 | /// D-Bus method call being handled. |
255 | /// * `signal_context` - This marks the method argument to receive a [`SignalContext`] instance, |
256 | /// which is needed for emitting signals the easy way. |
257 | /// |
258 | /// # Example |
259 | /// |
260 | /// ``` |
261 | /// # use std::error::Error; |
262 | /// use zbus_macros::dbus_interface; |
263 | /// use zbus::{ObjectServer, SignalContext, MessageHeader}; |
264 | /// |
265 | /// struct Example { |
266 | /// _some_data: String, |
267 | /// } |
268 | /// |
269 | /// #[dbus_interface(name = "org.myservice.Example" )] |
270 | /// impl Example { |
271 | /// // "Quit" method. A method may throw errors. |
272 | /// async fn quit( |
273 | /// &self, |
274 | /// #[zbus(header)] |
275 | /// hdr: MessageHeader<'_>, |
276 | /// #[zbus(signal_context)] |
277 | /// ctxt: SignalContext<'_>, |
278 | /// #[zbus(object_server)] |
279 | /// _server: &ObjectServer, |
280 | /// ) -> zbus::fdo::Result<()> { |
281 | /// let path = hdr.path()?.unwrap(); |
282 | /// let msg = format!("You are leaving me on the {} path?" , path); |
283 | /// Example::bye(&ctxt, &msg).await?; |
284 | /// |
285 | /// // Do some asynchronous tasks before quitting.. |
286 | /// |
287 | /// Ok(()) |
288 | /// } |
289 | /// |
290 | /// // "TheAnswer" property (note: the "name" attribute), with its associated getter. |
291 | /// // A `the_answer_changed` method has also been generated to emit the |
292 | /// // "PropertiesChanged" signal for this property. |
293 | /// #[dbus_interface(property, name = "TheAnswer" )] |
294 | /// fn answer(&self) -> u32 { |
295 | /// 2 * 3 * 7 |
296 | /// } |
297 | /// |
298 | /// // "IFail" property with its associated getter. |
299 | /// // An `i_fail_changed` method has also been generated to emit the |
300 | /// // "PropertiesChanged" signal for this property. |
301 | /// #[dbus_interface(property)] |
302 | /// fn i_fail(&self) -> zbus::fdo::Result<i32> { |
303 | /// Err(zbus::fdo::Error::UnknownProperty("IFail" .into())) |
304 | /// } |
305 | /// |
306 | /// // "Bye" signal (note: no implementation body). |
307 | /// #[dbus_interface(signal)] |
308 | /// async fn bye(signal_ctxt: &SignalContext<'_>, message: &str) -> zbus::Result<()>; |
309 | /// |
310 | /// #[dbus_interface(out_args("answer" , "question" ))] |
311 | /// fn meaning_of_life(&self) -> zbus::fdo::Result<(i32, String)> { |
312 | /// Ok((42, String::from("Meaning of life" ))) |
313 | /// } |
314 | /// } |
315 | /// |
316 | /// # Ok::<_, Box<dyn Error + Send + Sync>>(()) |
317 | /// ``` |
318 | /// |
319 | /// See also [`ObjectServer`] documentation to learn how to export an interface over a `Connection`. |
320 | /// |
321 | /// [`ObjectServer`]: https://docs.rs/zbus/3.0.0/zbus/struct.ObjectServer.html |
322 | /// [`ObjectServer::with`]: https://docs.rs/zbus/3.0.0/zbus/struct.ObjectServer.html#method.with |
323 | /// [`Connection`]: https://docs.rs/zbus/3.0.0/zbus/struct.Connection.html |
324 | /// [`Connection::emit_signal()`]: https://docs.rs/zbus/3.0.0/zbus/struct.Connection.html#method.emit_signal |
325 | /// [`SignalContext`]: https://docs.rs/zbus/3.0.0/zbus/struct.SignalContext.html |
326 | /// [`Interface`]: https://docs.rs/zbus/3.0.0/zbus/trait.Interface.html |
327 | #[proc_macro_attribute ] |
328 | pub fn dbus_interface (attr: TokenStream, item: TokenStream) -> TokenStream { |
329 | let args: Vec = parse_macro_input!(attr as AttributeArgs); |
330 | let input: ItemImpl = syn::parse_macro_input!(item as ItemImpl); |
331 | ifaceTokenStream::expand(args, input) |
332 | .unwrap_or_else(|err: Error| err.to_compile_error()) |
333 | .into() |
334 | } |
335 | |
336 | /// Derive macro for implementing [`zbus::DBusError`] trait. |
337 | /// |
338 | /// This macro makes it easy to implement the [`zbus::DBusError`] trait for your custom error type |
339 | /// (currently only enums are supported). |
340 | /// |
341 | /// If a special variant marked with the `dbus_error` attribute is present, `From<zbus::Error>` is |
342 | /// also implemented for your type. This variant can only have a single unnamed field of type |
343 | /// [`zbus::Error`]. This implementation makes it possible for you to declare proxy methods to |
344 | /// directly return this type, rather than [`zbus::Error`]. |
345 | /// |
346 | /// Each variant (except for the special `dbus_error` one) can optionally have a (named or unnamed) |
347 | /// `String` field (which is used as the human-readable error description). |
348 | /// |
349 | /// # Example |
350 | /// |
351 | /// ``` |
352 | /// use zbus_macros::DBusError; |
353 | /// |
354 | /// #[derive(DBusError, Debug)] |
355 | /// #[dbus_error(prefix = "org.myservice.App" )] |
356 | /// enum Error { |
357 | /// #[dbus_error(zbus_error)] |
358 | /// ZBus(zbus::Error), |
359 | /// FileNotFound(String), |
360 | /// OutOfMemory, |
361 | /// } |
362 | /// ``` |
363 | /// |
364 | /// [`zbus::DBusError`]: https://docs.rs/zbus/3.0.0/zbus/trait.DBusError.html |
365 | /// [`zbus::Error`]: https://docs.rs/zbus/3.0.0/zbus/enum.Error.html |
366 | /// [`zvariant::Type`]: https://docs.rs/zvariant/3.0.0/zvariant/trait.Type.html |
367 | /// [`serde::Serialize`]: https://docs.rs/serde/1.0.132/serde/trait.Serialize.html |
368 | #[proc_macro_derive (DBusError, attributes(dbus_error))] |
369 | pub fn derive_dbus_error(input: TokenStream) -> TokenStream { |
370 | let input: DeriveInput = parse_macro_input!(input as DeriveInput); |
371 | errorTokenStream::expand_derive(input) |
372 | .unwrap_or_else(|err: Error| err.to_compile_error()) |
373 | .into() |
374 | } |
375 | |